I'm using the spotify api and for some reason their session class blocks to store own data in $_SESSION. As a workaround I wrote a class 'SystemHelper':
namespace App;
class SystemHelper
{
/**
* if session_id is empty string, then session_id has not been initialized
* then set session_id named 'session1'
* start session needs 2 parameters, name and value
* always close session after writing
*
* #param string $name any given name
* #param [type] $value any given value
*
*/
public static function customSessionStore($name, $value)
{
if (session_id() == '') {
session_id('session1');
}
session_start();
$_SESSION[$name] = $value;
session_write_close();
}
It is possible now to store data in $_SESSION but the problem is that as long as I'm logged in with my account (own login form, not spotfiy account), everybody else is logged in, no matter which browser, ip, etc...
I don't know how this can be solved. Shouldn't session_id generate a random id? Anybody can help please?
Leaving out
if (session_id() == '') {
session_id('session1');
}
doesn't solve it because I need to read and delete the data stored in session as well. So, additionally I have in this workaround:
public static function customSessionRead($name)
{
if (session_id() == '') {
session_id('session1');
}
session_start();
session_write_close();
return $_SESSION[$name];
}
and...
public static function customSessionDestroy()
{
session_start();
session_destroy();
}
If i get this right.. all your users get the same session_id().
So they technically share one session. As much as i know, if you start a session, the session_id() will be automattically generated. So you dont need to set the session_id() by yourself.
So your code should look like this:
class SystemHelper
{
/**
* if session_id is empty string, then session_id has not been initialized
* then set session_id named 'session1'
* start session needs 2 parameters, name and value
* always close session after writing
*
* #param string $name any given name
* #param [type] $value any given value
*
*/
public static function customSessionStore($name, $value)
{
session_start();
$_SESSION[$name] = $value;
session_write_close();
}
}
Solved, actually pretty simple. The problem first:
If written like this:
session_id('session1');
in both, customStore and customRead simply means resuming the session. Of course you will always get the same data, no matter which browser, ip, ... that's the point of resuming the session.
What is solved:
session_create_id($name);
So, the full again:
public static function customSessionStore($name, $value)
{
// if (session_id() == '') {
// session_id('session1');
// }
session_create_id($name);
session_start();
$_SESSION[$name] = $value;
session_write_close();
}
and,
public static function customSessionRead($name)
{
// if (session_id() == '') {
// session_id('session1');
// }
session_start();
session_write_close();
return $_SESSION[$name];
}
Related
I've written a custom session handler class which works for the most part, until I try to log my users out and destroy the session, then I keep getting the following error:
ErrorException: 0
session_write_close(): Failed to write session data using user defined save handler. (session.save_path: D:\xampp\tmp)
I am running PHP Version 7.3.1 on XAMPP.
This is my custom class
class CornerstoneSessionHandler implements SessionHandlerInterface {
/**
* Construct the Session
* No parameters required, nothing will be returned
*/
public function __construct() {
// Set the handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
// Set the shutdown function
register_shutdown_function('session_write_close');
/** Define and initialise the Session Handler */
session_start();
}
/**
* Set the open callback
*
* #param string $savePath
* #param string $sessionName
*
* #return bool return value should be true for success or false for failure
*/
public function open($savePath, $sessionName) {
// Make the $csdb global accessible
global $csdb;
// Check that the DB connection is set
return ((!empty($csdb)) && $csdb->isConnected() != 1) ? FALSE : TRUE ;
}
/**
* Set the close callback
*
* #return bool return value can only be true for success
*/
public function close() {
return TRUE;
}
/**
* Set the read callback
*
* #param string $sessionID
*
* #return string return value should be the session data or an empty string
*/
public function read($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Get the session from the database
$csdb->query_prepared("SELECT session_data FROM cs_session WHERE session_id=?", [$sessionID]);
// If results returned, continue
if($csdb->getNum_Rows() > 0) {
// Get the data
$result = $csdb->get_row(NULL);
return $result->session_data;
} else { // Else return an empty string
return '';
}
}
/**
* Set the write callback
*
* #param string $sessionID
* #param string $data
*
* #return bool return value should be true for success or false for failure
*/
public function write($sessionID, $data) {
// Make the $csdb global accessible
global $csdb;
// Set the time stamp
$access_dtm = new \DateTime();
// Replace the data
$csdb->query_prepared('REPLACE INTO cs_session(session_id, session_ip_address, session_data , session_access_dtm) VALUES (?, ?, ?, ?)', [$sessionID, $_SERVER['REMOTE_ADDR'], $data, $access_dtm->format('Y-m-d H:i:s')]);
// If modified a success, return true
if($csdb->getNum_Rows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the destroy callback
*
* #param string $sessionID
*
* #return bool return value should be true for success or false for failure
*/
public function destroy($sessionID) {
// Make the $csdb global accessible
global $csdb;
// Delete the session from the database
$csdb->delete('cs_session', where(eq('session_id', $sessionID)));
// If results returned, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
/**
* Set the garbage collector callback
*
* #param string $lifetime
*
* #return bool return value should be true for success or false for failure
*/
public function gc($lifetime) {
// Make the $csdb global accessible
global $csdb;
// Set the date calculation
$expiredTime = new \DateTime();
$expiredTime->modify('-' . $lifetime . ' seconds');
// Get the session from the database
$csdb->delete('cs_session', where(lt('session_access_dtm', $expiredTime->format('Y-m-d H:i:s'))));
// If results deleted, return true
if($csdb->affectedRows() > 0) {
return TRUE;
} else { // Else, return false
return FALSE;
}
}
}
I am using ezSQL as my database handler.
This is the code in the top of all my pages
/**
* Set session data so that logins work properly over all browsers
* This should fix login errors some users can face
* More info can be found at {#link https://www.php.net/manual/en/session.security.ini.php the php user manual}
*/
# PREVENTING SESSION HIJACKING
# Prevents javascript XSS attacks aimed to steal the session ID
ini_set('session.cookie_httponly', 1);
# Make sure the cookie lifetime is set to '0'
ini_set('session.cookie_lifetime', 0);
# Adds entropy into the randomization of the session ID, as PHP's random number
# generator has some known flaws
ini_set('session.entropy_file', '/dev/urandom');
# Uses a strong hash
ini_set('session.hash_function', 'whirlpool');
# Set the session save location (best for shared servers)
# Uncomment out the next line if you would like to set a custom path and haven't already set the value in your `php.ini` file.
# ini_set('session.save_path',realpath(ABSPATH . 'tmp' . DIRECTORY_SEPARATOR));
# Note: The folder referenced above must exist for it to work.
# Set the session garbage collection lifetime to custom defined minutes (PHP default is 24 minutes)
ini_set('session.gc_maxlifetime', (int)get_option("session_expire") * MINUTE_IN_SECONDS);
# Enable session garbage collection with a 1% chance of
# running on each session_start()
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);
# Uses a secure connection (HTTPS) if possible
# Allow cookies to be sent over insecure connections if not an HTTPS site
(SITE_HTTPS) ? ini_set('session.cookie_secure', 1) : ini_set('session.cookie_secure', false);
# PREVENTING SESSION FIXATION
# Session ID cannot be passed through URLs
# so only use cookies to store the session id on the client side
ini_set('session.use_only_cookies', 1);
# Set a custom session name
session_name('CSSESSID');
# Load the session class
new CornerstoneSessionHandler();
# Start the session if it's not already started
if (session_status() == PHP_SESSION_NONE) {session_start();}
The weird thing is, even though it's giving me that error, if I check the database the data is being updated no worries.
This is my logout code
/**
* Logout the user
*
* #return bool
*/
public static function logoutUser() {
// Make the $fear global accessible
global $fear; // Direct access check
// Check if session set (just in case) and start if it isn't
if(session_id() == '') {session_start();}
// Delete the $_SESSION data set in `authenticateUser()`
unset($_SESSION['HTTP_USER_AGENT']);
unset($_SESSION['_cs-uid']);
unset($_SESSION['_cs-ue']);
unset($_SESSION['_cs-ul']);
unset($_SESSION['_cs-un']);
/**
* Get the "ext.auth.php" file and run `clearCustomAuth()` function
* to clear any custom set $_SESSION items
*/
require_once( get_lib_path('ext.auth.php') );
clearCustomAuth();
// Regenerate a new session ID just to be sure
session_regenerate_id();
// Destroy the session
session_destroy();
// Check if the $_COOKIE data if set
if(isset( $_COOKIE['_cs-ti'] ) && !empty( $_COOKIE['_cs-ti'] )) {
// Delete the cookie token from the database
if(!self::deleteAuthCookie($_COOKIE['_cs-ti'])) {
// Return false if cookie couldn't be deleted
return false;
}
}
// Return true if run all the way
return true;
}
I've tried shifting around the session_regenerate_id() but if I put it after the session_destroy() it says there's no session to regenerate, but if I put it before the session_destroy() or don't have it at all, my session is deleted from my database but I can still see it in my storage in my inspector on Firefox.
It only gives me an error when trying to delete a session from the database. Everything else works no worries! I've manually tried to delete a session from the database in testing and I still get that error.
Is anyone able to see what I might be doing wrong or how I can fix it? I've only just learnt about this so pretty new to it. If I need to supply more information, just let me know. I've been searching over the web for nearly 2 days with no luck.
I don't see a reason why you need to set a shutdown handler. Sessions are automatically saved when php shuts down, even when you get a fatal error.
Also the write, destroy and gc should always return true, even if no rows were updated / deleted. This seems counter intuitive, but php considers deleting a non existing session also a success.
I've looked at existing answers for my problem.
I've echo'd the value right through the process and right up until the "header('Location" instruction the values remain intact.
I don't think it's a serialization problem as suggested for similar problems...
Here are the relevant bits of the class:
class clsSetUser {
protected $UserID = 0;
public function initUser($id) {
// get user details from database
$this->setUserID($id);
// etc...
}
private function setUserID($value) { $this->UserID = $value; }
public function getUserID() { return $this->UserID; }
}
common.php:
if(unset($clsUser)) $clsUser = new clsSetUser;
login-exec.php:
$clsUser->initUser($id);
header("Location: somewhere.php");
somewhere.php:
echo $clsUser->getUserID();
// here it equals 0
any ideas? does "header" serialize everything?
This is because PHP is actually starting from a clean slate in somewhere.php.
header("Location: somewhere.php"); sends a command the browser to connect to a different page. In this page non of variables of the previous page are available in PHP.
You need to set the userId in the $_SESSION so that you can reload the user from the database when he visits somewhere.php.
login-exec.php
$clsUser->initUser($id);
$_SESSION['user_id'] = $id;
header("Location: somewhere.php");
somewhere.php
$clsUser->initUser($_SESSION['user_id']);
I've been working on the security of my site (PHP) and there's a ton of information to ingest. I've tried to implement security I've researched on OWASP, but one thing I'm a little nervous about, among other things, is how to handle SESSIONS when the user logs out.
Currently all I'm using is:
session_destroy();
But, I've read that I should change the XRSF token and start another SESSION so it forces the user to resubmit login credentials in-turn explicitly ending the users SESSION.
Is session_destroy() enough?
EDIT
I've downloaded michael-the-messenger, which I believe was created by Michael Brooks (Rook) which should be VERY secure, and I saw some code that I might want to use. Is this something that could safely replace the session_destroy() I'm using?
CODE
if($_SESSION['user']->isAuth())
{
/* if they have clicked log out */
/* this will kill the session */
if($_POST['LogMeOut'] == 'true')
{
//When the user logs out the xsrf token changes.
$tmp_xsrf = $_SESSION['user']->getXsrfToken();
$_SESSION['user']->logout();
$loginMessage = str_replace($tmp_xsrf, $_SESSION['user']->getXsrfToken(), $loginMessage);
print layout('Authorization Required', $loginMessage);
}
else
{
header("Location: inbox.php");
//user is allowed access.
}
}
else
{
// code goes on ....
LOGOUT
public function logout()
{
$_SESSION['user'] = new auth();
}
Obviously $_SESSION['user'] = new auth(); reinstantiates the object which sets a private variable $auth to false.
but one thing I'm a little nervous about, among other things, is how
to handle SESSIONS when the user logs out.
According to manual:
In order to kill the session altogether, like to log the user out, the
session id must also be unset. If a cookie is used to propagate the
session id (default behavior), then the session cookie must be
deleted. setcookie() may be used for that.
So, in order to safely destroy a session, we'd also erase it on the client-machine.
session_destroy() along with setcookie(session_name(), null, time() - 86400) will do that.
Apart from that,
What you are doing wrong and why:
Session storage merely uses data serialization internally. By storing
an object in the $_SESSION superglobal you just do
serialize/unserialize that object on demand without even knowing it.
1) By storing an object in $_SESSION you do introduce global state. $_SESSION is a superglobal array, thus can be accessed from anywhere.
2) Even by storing an object that keeps an information about logged user, you do waste system memory. The length of object representation is always greater than a length of the strings.
But why on earth should you even care about wrapping session functionality? Well,
It makes a code easy to read, maintain and test
It adheres Single-Responsibility Principle
It avoids global state (if properly used), you'll access session not as $_SESSION['foo'], but $session->read['foo']
You can easily change its behaivor (say, if you decide to use DB as session storage) without even affecting another parts of your application.
Code reuse-ability. You can use this class for another applications (or parts of it)
If you wrap all session-related functionality into a signle class, then it will turn into attractive:
$session = new SessionStorage();
$session->write( array('foo' => 'bar') );
if ( $session->isValid() === TRUE ) {
echo $session->read('foo'); // bar
} else {
// Session hijack. Handle here
}
// To totally destroy a session:
$session->destroy();
// if some part of your application requires a session, then just inject an instance of `SessionStorage`
// like this:
$user = new Profile($session);
// Take this implementation as example:
final class SessionStorage
{
public function __construct()
{
// Don't start again if session is started:
if ( session_id() != '' ) {
session_start();
}
// Keep initial values
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
}
/**
* You can prevent majority of hijacks using this method
*
* #return boolean TRUE if session is valid
*/
public function isValid()
{
return $_SESSION['HTTP_USER_AGENT'] === $_SERVER['HTTP_USER_AGENT'] && $_SESSION['REMOTE_ADDR'] === $_SERVER['REMOTE_ADDR'] ;
}
public function __destruct()
{
session_write_close();
}
/**
* Fixed session_destroy()
*
* #return boolean
*/
public function destroy()
{
// Erase the session name on client side
setcookie(session_name(), null, time() - 86400);
// Erase on the server
return session_destroy();
}
public function write(array $data)
{
foreach($data as $key => $value) {
$_SESSION[$key] = $value;
}
}
public function exists()
{
foreach(func_get_args() as $arg){
if ( ! array_key_exists($arg, $_SESSION) ){
return false;
}
}
return true;
}
public function read($key)
{
if ( $this->exists($key) ){
return $_SESSION[$key];
} else {
throw new RuntimeException('Cannot access non-existing var ' .$key);
}
}
}
Maybe session_unset() is what you are looking for.
I'm trying to integrate a forum (created in Codeigniter) into a website (simple php >>> no framework used).
In order to automatically login to the forum, when I login in my website, I need to use a function of the forum which expects 2 parameters $username and $password.
I already have this informations (username and password) from my website, in $_SESSION.
How can I read the $_SESSION from the forum(as I say before Codeigniter based), because, I have no acces to it.
Is there a posibility to define 2 constants, somewhere in the core / config of the forum, to hold these details from $_SESSION, in order to have acces from anywhere inside the forum ?
I know that the sessions from CI are different from $_SESSION, so please help me with something more practical, in order to solve my problem.
Thanks.
Read this url;-
http://codeigniter.com/forums/viewthread/158923/#766011
http://codeigniter.com/forums/viewthread/188648/#892137
In case for those who want to do native session with 2.0.2
Just copy the native_session.php file to your application/libraries/ and rename it as Session.php
Then change the class name and constructor name to CI_Session
Also add the following then it should work fine.
function sess_destroy()
{
$this->destroy();
}
or
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
Native / Database hybrid
Code Igniter
Citrusmedia - Matthew Lymer
*/
class CI_Session
{
var $sess_table_name = '';
var $sess_expiration = 7200;
var $sess_match_ip = FALSE;
var $sess_match_useragent = TRUE;
var $sess_time_to_update = 300;
var $encryption_key = '';
var $flashdata_key = 'flash';
var $time_reference = 'time';
var $gc_probability = 5;
var $userdata = array();
var $CI;
var $now;
/**
* Session Constructor
*
* The constructor runs the session routines automatically
* whenever the class is instantiated.
*/
function CI_Session($params = array())
{
log_message('debug', "Session Class Initialized");
// Set the super object to a local variable for use throughout the class
$this->CI =& get_instance();
// Set all the session preferences, which can either be set
// manually via the $params array above or via the config file
foreach (array('sess_table_name', 'sess_expiration', 'sess_match_ip', 'sess_match_useragent', 'sess_time_to_update', 'time_reference', 'encryption_key') as $key)
{
$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
}
// Sessions, start your engines!
ini_set("session.gc_maxlifetime", $this->sess_expiration);
session_start();
// Load the string helper so we can use the strip_slashes() function
$this->CI->load->helper('string');
// Are we using a database? If so, load it
if( !$this->sess_table_name ) {
die('Session class database table name not configured');
}
$this->CI->load->database();
// Set the "now" time. Can either be GMT or server time, based on the
// config prefs. We use this to set the "last activity" time
$this->now = $this->_get_time();
// Set the session length. If the session expiration is
// set to zero we'll set the expiration two years from now.
if ($this->sess_expiration == 0)
{
$this->sess_expiration = (60*60*24*365*2);
}
// Run the Session routine. If a session doesn't exist we'll
// create a new one. If it does, we'll update it.
if ( ! $this->sess_read())
{
$this->sess_create();
}
else
{
$this->sess_update();
}
// Delete 'old' flashdata (from last request)
$this->_flashdata_sweep();
// Mark all new flashdata as old (data will be deleted before next request)
$this->_flashdata_mark();
// Delete expired sessions if necessary
$this->_sess_gc();
log_message('debug', "Session routines successfully run");
}
// --------------------------------------------------------------------
/**
* Fetch the current session data if it exists
*
* #access public
* #return bool
*/
function sess_read()
{
// Unserialize the session array
// $session = $this->_unserialize($session);
$session = array();
foreach( array('session_id', 'ip_address', 'user_agent', 'last_activity') as $key )
{
if( !isset($_SESSION[$key]) ) {
$this->sess_destroy();
return FALSE;
}
$session[$key] = $_SESSION[$key];
}
// Is the session current?
if (($session['last_activity'] + $this->sess_expiration) < $this->now)
{
$this->sess_destroy();
return FALSE;
}
// Does the IP Match?
if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
{
$this->sess_destroy();
return FALSE;
}
// Does the User Agent Match?
if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
{
$this->sess_destroy();
return FALSE;
}
$this->CI->db->where('session_id', $session['session_id']);
if ($this->sess_match_ip == TRUE)
{
$this->CI->db->where('ip_address', $session['ip_address']);
}
if ($this->sess_match_useragent == TRUE)
{
$this->CI->db->where('user_agent', $session['user_agent']);
}
$query = $this->CI->db->get($this->sess_table_name);
By default, PHP's session handling mechanisms set a session cookie header and store a session even if there is no data in the session. If no data is set in the session then I don't want a Set-Cookie header sent to the client in the response and I don't want an empty session record stored on the server. If data is added to $_SESSION, then the normal behavior should continue.
My goal is to implement lazy session creation behavior of the sort that Drupal 7 and Pressflow where no session is stored (or session cookie header sent) unless data is added to the $_SESSION array during application execution. The point of this behavior is to allow reverse proxies such as Varnish to cache and serve anonymous traffic while letting authenticated requests pass through to Apache/PHP. Varnish (or another proxy-server) is configured to pass through any requests without cookies, assuming correctly that if a cookie exists then the request is for a particular client.
I have ported the session handling code from Pressflow that uses session_set_save_handler() and overrides the implementation of session_write() to check for data in the $_SESSION array before saving and will write this up as library and add an answer here if this is the best/only route to take.
My Question: While I can implement a fully custom session_set_save_handler() system, is there an easier way to get this lazy session creation behavior in a relatively generic way that would be transparent to most applications?
Well, one option would be to use a session class to start/stop/store data in the session. So, you could do something like:
class Session implements ArrayAccess {
protected $closed = false;
protected $data = array();
protected $name = 'mySessionName';
protected $started = false;
protected function __construct() {
if (isset($_COOKIE[$this->name])) $this->start();
$this->data = $_SESSION;
}
public static function initialize() {
if (is_object($_SESSION)) return $_SESSION;
$_SESSION = new Session();
register_shutdown_function(array($_SESSION, 'close'));
return $_SESSION;
}
public function close() {
if ($this->closed) return false;
if (!$this->started) {
$_SESSION = array();
} else {
$_SESSION = $this->data;
}
session_write_close();
$this->started = false;
$this->closed = true;
}
public function offsetExists($offset) {
return isset($this->data[$offset]);
}
public function offsetGet($offset) {
if (!isset($this->data[$offset])) {
throw new OutOfBoundsException('Key does not exist');
}
return $this->data[$offset];
}
public function offsetSet($offset, $value) {
$this->set($offset, $value);
}
public function offsetUnset($offset) {
if (isset($this->data[$offset])) unset($this->data[$offset]);
}
public function set($key, $value) {
if (!$this->started) $this->start();
$this->data[$key] = $value;
}
public function start() {
session_name($this->name);
session_start();
$this->started = true;
}
}
To use, at the start of your script call Session::initialize(). It will replace $_SESSION with the object, and setup the lazy loading. Afterward, you can just do
$_SESSION['user_id'] = 1;
If the session isn't started, it will be, and the user_id key would be set to 1. If at any point you wanted to close (commit) the session, just call $_SESSION->close().
You'll probably want to add some more session management functions (such as destroy, regenerate_id, the ability to change the name of the session, etc), but this should implement the basic functionality you're after...
It's not a save_handler, it's just a class to manage your sessions. If you really wanted to, you could implement ArrayAccess in the class, and on construct replace $_SESSION with that class (The benefit of doing that, is that way legacy code can still use session as they used to without calling $session->setData()). The only downside is that I'm not sure if the serialization routine that PHP uses would work properly (You'd need to put back the array into $_SESSION at some point... Probably with a register_shutdown_function()...
I have developed a working solution to this problem that uses session_set_save_handler() and a set of custom session storage methods that check for content in the $_SESSION array before writing out session data. If there is no data to write for the session, then header('Set-Cookie:', true); is used to prevent PHP's session-cookie from being sent in the response.
The latest version of this code as well as documentation and examples are available on GitHub. In the code below, the important functions that make this work are lazysess_read($id) and lazysess_write($id, $sess_data).
<?php
/**
* This file registers session save handlers so that sessions are not created if no data
* has been added to the $_SESSION array.
*
* This code is based on the session handling code in Pressflow (a backport of
* Drupal 7 performance features to Drupal 6) as well as the example code described
* the PHP.net documentation for session_set_save_handler(). The actual session data
* storage in the file-system is directly from the PHP.net example while the switching
* based on session data presence is merged in from Pressflow's includes/session.inc
*
* Links:
* http://www.php.net/manual/en/function.session-set-save-handler.php
* http://bazaar.launchpad.net/~pressflow/pressflow/6/annotate/head:/includes/session.inc
*
* Caveats:
* - Requires output buffering before session_write_close(). If content is
* sent before shutdown or session_write_close() is called manually, then
* the check for an empty session won't happen and Set-Cookie headers will
* get sent.
*
* Work-around: Call session_write_close() before using flush();
*
* - The current implementation blows away all Set-Cookie headers if the
* session is empty. This basic implementation will prevent any additional
* cookie use and should be improved if using non-session cookies.
*
* #copyright Copyright © 2010, Middlebury College
* #license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL), Version 3 or later.
*/
/*********************************************************
* Storage Callbacks
*********************************************************/
function lazysess_open($save_path, $session_name)
{
global $sess_save_path;
$sess_save_path = $save_path;
return(true);
}
function lazysess_close()
{
return(true);
}
function lazysess_read($id)
{
// Write and Close handlers are called after destructing objects
// since PHP 5.0.5.
// Thus destructors can use sessions but session handler can't use objects.
// So we are moving session closure before destructing objects.
register_shutdown_function('session_write_close');
// Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers).
if (!isset($_COOKIE[session_name()])) {
return '';
}
// Continue with reading.
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
return (string) #file_get_contents($sess_file);
}
function lazysess_write($id, $sess_data)
{
// If saving of session data is disabled, or if a new empty anonymous session
// has been started, do nothing. This keeps anonymous users, including
// crawlers, out of the session table, unless they actually have something
// stored in $_SESSION.
if (empty($_COOKIE[session_name()]) && empty($sess_data)) {
// Ensure that the client doesn't store the session cookie as it is worthless
lazysess_remove_session_cookie_header();
return TRUE;
}
// Continue with storage
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
if ($fp = #fopen($sess_file, "w")) {
$return = fwrite($fp, $sess_data);
fclose($fp);
return $return;
} else {
return(false);
}
}
function lazysess_destroy($id)
{
// If the session ID being destroyed is the one of the current user,
// clean-up his/her session data and cookie.
if ($id == session_id()) {
global $user;
// Reset $_SESSION and $user to prevent a new session from being started
// in drupal_session_commit()
$_SESSION = array();
// Unset the session cookie.
lazysess_set_delete_cookie_header();
if (isset($_COOKIE[session_name()])) {
unset($_COOKIE[session_name()]);
}
}
// Continue with destruction
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id";
return(#unlink($sess_file));
}
function lazysess_gc($maxlifetime)
{
global $sess_save_path;
foreach (glob("$sess_save_path/sess_*") as $filename) {
if (filemtime($filename) + $maxlifetime < time()) {
#unlink($filename);
}
}
return true;
}
/*********************************************************
* Helper functions
*********************************************************/
function lazysess_set_delete_cookie_header() {
$params = session_get_cookie_params();
if (version_compare(PHP_VERSION, '5.2.0') === 1) {
setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
}
else {
setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure']);
}
}
function lazysess_remove_session_cookie_header () {
// Note: this implementation will blow away all Set-Cookie headers, not just
// those for the session cookie. If your app uses other cookies, reimplement
// this function.
header('Set-Cookie:', true);
}
/*********************************************************
* Register the save handlers
*********************************************************/
session_set_save_handler('lazysess_open', 'lazysess_close', 'lazysess_read', 'lazysess_write', 'lazysess_destroy', 'lazysess_gc');
While this solution works and is mostly transparent to applications including it, it requires rewriting the entire session-storage mechanism rather than relying on the built-in storage mechanisms with a switch to save or not.
I created a lazy session proof of concept here:
it uses the native php session handler and _SESSION array
it only starts the session if a cookie has been sent or
it starts the session if something has been added the $_SESSION array
it removes the session if a session is started and $_SESSION is empty
Will extend it in the next days:
https://github.com/s0enke/php-lazy-session
this topic is under discussion for a future php version
https://wiki.php.net/rfc/session-read_only-lazy_write