Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions htdocs/include/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -840,13 +840,13 @@ function redirect_header($url, $time = 3, $message = '', $addredirect = true, $a
$url .= '&xoops_redirect=' . urlencode($_SERVER['REQUEST_URI']);
}
}
if (defined('SID') && SID && (!isset($_COOKIE[session_name()]) || ($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '' && !isset($_COOKIE[$xoopsConfig['session_name']])))) {
if (false === strpos($url, '?')) {
$url .= '?' . SID;
} else {
$url .= '&' . SID;
}
}
// if (defined('SID') && SID && (!isset($_COOKIE[session_name()]) || ($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '' && !isset($_COOKIE[$xoopsConfig['session_name']])))) {
// if (false === strpos($url, '?')) {
// $url .= '?' . SID;
// } else {
// $url .= '&' . SID;
// }
// }
$url = preg_replace('/&/i', '&', htmlspecialchars($url, ENT_QUOTES | ENT_HTML5));
$xoopsTpl->assign('url', $url);
$message = trim($message) != '' ? $message : _TAKINGBACK;
Expand Down
306 changes: 7 additions & 299 deletions htdocs/kernel/session.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,304 +22,12 @@
}

/**
* Handler for a session
* @package kernel
*
* @author Kazumi Ono <onokazu@xoops.org>
* @author Taiwen Jiang <phppp@users.sourceforge.net>
* @copyright (c) 2000-2025 XOOPS Project (https://xoops.org)
* Loader shim: include the correct handler for this PHP version.
* - PHP < 8.0: PHP 7.4-compatible handler (no union types, no SessionUpdateTimestampHandlerInterface)
* - PHP >= 8.0: handler using union types and lazy timestamp updates
*/
class XoopsSessionHandler implements SessionHandlerInterface
{
/**
* Database connection
*
* @var object
* @access private
*/
public $db;

/**
* Security checking level
*
* Possible value:
* 0 - no check;
* 1 - check browser characteristics (HTTP_USER_AGENT/HTTP_ACCEPT_LANGUAGE), to be implemented in the future now;
* 2 - check browser and IP A.B;
* 3 - check browser and IP A.B.C, recommended;
* 4 - check browser and IP A.B.C.D;
*
* @var int
* @access public
*/
public $securityLevel = 3;

protected $bitMasks = [
2 => ['v4' => 16, 'v6' => 64],
3 => ['v4' => 24, 'v6' => 56],
4 => ['v4' => 32, 'v6' => 128],
];

/**
* Enable regenerate_id
*
* @var bool
* @access public
*/
public $enableRegenerateId = true;

/**
* Constructor
*
* @param XoopsDatabase $db reference to the {@link XoopsDatabase} object
*
*/
public function __construct(XoopsDatabase $db)
{
global $xoopsConfig;

$this->db = $db;
// after php 7.3 we just let php handle the session cookie
$lifetime = ($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
? $xoopsConfig['session_expire'] * 60
: ini_get('session.cookie_lifetime');
$secure = (XOOPS_PROT === 'https://');
// --- START: New Domain Validation Logic ---
$host = parse_url(XOOPS_URL, PHP_URL_HOST);
if (!is_string($host)) {
$host = ''; // Fallback in case of invalid XOOPS_URL
}
$cookieDomain = XOOPS_COOKIE_DOMAIN;
if (class_exists('\Xoops\RegDom\RegisteredDomain')) {
if (!\Xoops\RegDom\RegisteredDomain::domainMatches($host, $cookieDomain)) {
$cookieDomain = ''; // The corrected, safe domain
}
}
// --- END: New Domain Validation Logic ---

if (PHP_VERSION_ID >= 70300) {
$options = [
'lifetime' => $lifetime,
'path' => '/',
'domain' => $cookieDomain,
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
];
session_set_cookie_params($options);
} else {
session_set_cookie_params($lifetime, '/', $cookieDomain, $secure, true);
}
}

/**
* Open a session
*
* @param string $savePath
* @param string $sessionName
*
* @return bool
*/
public function open($savePath, $sessionName): bool
{
return true;
}

/**
* Close a session
*
* @return bool
*/
public function close(): bool
{
$this->gc_force();
return true;
}

/**
* Read a session from the database
*
* @param string $sessionId ID of the session
*
* @return string Session data (empty string if no data or failure)
*/
public function read($sessionId): string
{
$ip = \Xmf\IPAddress::fromRequest();
$sql = sprintf(
'SELECT sess_data, sess_ip FROM %s WHERE sess_id = %s',
$this->db->prefix('session'),
$this->db->quote($sessionId)
);

$result = $this->db->queryF($sql);
if ($this->db->isResultSet($result)) {
$row = $this->db->fetchRow($result);
if (false !== $row) {
[$sess_data, $sess_ip] = $row;
if ($this->securityLevel > 1) {
if (false === $ip->sameSubnet(
$sess_ip,
$this->bitMasks[$this->securityLevel]['v4'],
$this->bitMasks[$this->securityLevel]['v6']
)) {
$sess_data = '';
}
}
return $sess_data;
}
}
return '';
}

/**
* Write a session to the database
*
* @param string $sessionId
* @param string $data
*
* @return bool
*/
public function write($sessionId, $data): bool
{
$myReturn = true;
$remoteAddress = \Xmf\IPAddress::fromRequest()->asReadable();
$sessionId = $this->db->quote($sessionId);

$sql= sprintf('INSERT INTO %s (sess_id, sess_updated, sess_ip, sess_data)
VALUES (%s, %u, %s, %s)
ON DUPLICATE KEY UPDATE
sess_updated = %u,
sess_data = %s
',
$this->db->prefix('session'),
$sessionId,
time(),
$this->db->quote($remoteAddress),
$this->db->quote($data),
time(),
$this->db->quote($data),
);
$myReturn = $this->db->exec($sql);
$this->update_cookie();
return $myReturn;
}

/**
* Destroy a session
*
* @param string $sessionId
*
* @return bool
*/
public function destroy($sessionId): bool
{
$sql = sprintf(
'DELETE FROM %s WHERE sess_id = %s',
$this->db->prefix('session'),
$this->db->quote($sessionId)
);
if (!$result = $this->db->exec($sql)) {
return false;
}
return true;
}

/**
* Garbage Collector
*
* @param int $expire Time in seconds until a session expires
* @return int|bool The number of deleted sessions on success, or false on failure
*/
#[\ReturnTypeWillChange]
public function gc($expire)
{
if (empty($expire)) {
return 0;
}

$mintime = time() - (int)$expire;
$sql = sprintf('DELETE FROM %s WHERE sess_updated < %u', $this->db->prefix('session'), $mintime);

if ($this->db->exec($sql)) {
return $this->db->getAffectedRows();
}
return false;
}

/**
* Force gc for situations where gc is registered but not executed
**/
public function gc_force()
{
if (mt_rand(1, 100) < 11) {
$expire = @ini_get('session.gc_maxlifetime');
$expire = ($expire > 0) ? $expire : 900;
$this->gc($expire);
}
}

/**
* Update the current session id with a newly generated one
*
* To be refactored
*
* @param bool $delete_old_session
* @return bool
**/
public function regenerate_id($delete_old_session = false)
{
if (!$this->enableRegenerateId) {
$success = true;
} else {
$success = session_regenerate_id($delete_old_session);
}

// Force updating cookie for session cookie
if ($success) {
$this->update_cookie();
}

return $success;
}

/**
* Update cookie status for current session
*
* To be refactored
* FIXME: how about $xoopsConfig['use_ssl'] is enabled?
*
* @param string $sess_id session ID
* @param int $expire Time in seconds until a session expires
* @return bool
**/
public function update_cookie($sess_id = null, $expire = null)
{
if (PHP_VERSION_ID < 70300) {
global $xoopsConfig;
$session_name = session_name();
$session_expire = null !== $expire
? (int)$expire
: (
($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
? $xoopsConfig['session_expire'] * 60
: ini_get('session.cookie_lifetime')
);
$session_id = empty($sess_id) ? session_id() : $sess_id;
$cookieDomain = XOOPS_COOKIE_DOMAIN;
if (2 > substr_count($cookieDomain, '.')) {
$cookieDomain = '.' . $cookieDomain ;
}

xoops_setcookie(
$session_name,
$session_id,
$session_expire ? time() + $session_expire : 0,
'/',
$cookieDomain,
(XOOPS_PROT === 'https://'),
true,
);
}
}
if (PHP_VERSION_ID < 80000) {
require_once __DIR__ . '/session74.php';
} else {
require_once __DIR__ . '/session80.php';
}
Loading