Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
304 changes: 7 additions & 297 deletions htdocs/kernel/session.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,302 +20,12 @@
defined('XOOPS_ROOT_PATH') || exit('Restricted access');

/**
* Handler for a session
* @package kernel
*
* @author Kazumi Ono <[email protected]>
* @author Taiwen Jiang <[email protected]>
* @copyright (c) 2000-2025 XOOPS Project (https://xoops.org)
* Loader shim: include the correct handler for this PHP version.
* - PHP < 8.0: untyped handler (no unions), 7.4-safe
* - PHP >= 8.0: fully typed handler with union returns 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)) {
if ([$sess_data, $sess_ip] = $this->db->fetchRow($result)) {
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