I have run into a very interesting problem trying to debug my custom PHP session handler. For some reason unknown to me I can set cookies all the way through the session handler right up until the very start of the write function.
As far as I know session handler calls go in this order.
open -> read -> write -> close
The open function sets a cookie just fine.
function open($save_path,$session_name)
{
require_once('database.php');
require_once('websiteinfo.php');
mysql_connect($sqllocation,$sql_session_user,$sql_session_pass);
#mysql_select_db($sql_default_db);
date_default_timezone_set('America/Los_Angeles');
setcookie("test","test");
return TRUE;
}
The read function can set a cookie right up until the very moment it returns a value.
function read($session_id)
{
$time = time();
$query = "SELECT * FROM 'sessions' WHERE 'expires' > '$time'";
$query_result = mysql_query($query);
$data = '';
/* fetch the array and start sifting through it */
while($session_array = mysql_fetch_array($query_result))
{
/* strip the slashes from the session array */
$session_array = $this->strip($session_array);
/* authenticate the user and if so return the session data */
if($this->auth_check($session_array,$session_id))
{
$data = $session_array['data'];
}
}
setcookie("testcookie1","value1",time()+1000,'/');
return $data;
}
The very first line of the write function is setting another cookie and it cannot because the headers are already sent.
From the manual for session_set_save_handler():
Note: The "write" handler is not
executed until after the output stream
is closed. Thus, output from debugging
statements in the "write" handler will
never be seen in the browser. If
debugging output is necessary, it is
suggested that the debug output be
written to a file instead.
Essentially, writing changes to session data (calling the registered session write function) does not happen until PHP is almost completely done with its execution cycle. By this time, all your output has already been sent and it is not possible to modify the header information, so setcookie() won't work.
You can have it write data earlier by using session_write_close().
Related
In this chunk of code it was previously designed to use the session_id. I am trying to convert from using the session_id to using a User ID that is retrieved from the database. I'm not sure what I did wrong but the function is not returning the variable. Any suggestions would be appreciated.
protected function get_user_id() {
//previous code used the session id
//#session_start();
//return session_id();
// New code to use User ID instead of session_id
// Connecting to the database
include ("../../../admin/includes/connect.php");
// Let's get the user ID from the database for use with the widget
$user_id_query = "SELECT nonadmin_user_id FROM `nonadmin_user_login` WHERE email = '$_SESSION[email]'";
$run_query = mysqli_query($conn, $user_id_query);
while($row=mysqli_fetch_array($run_query)){
// Create variable for the user's id
$nonadmin_user_id = $row['nonadmin_user_id']; }
return $nonadmin_user_id;
}
// This function needs to use the variable $nonadmin_user_id
protected function get_user_path() {
if ($this->options['user_dirs']) {
return $this->get_user_id().'/';
}
return '';
}
"Fred you're the man! It was the session. I removed the comment out from in front of the session start and now it works perfect. What baffles me on this is I was under the impression that if you start a session in a file and then include other files the included files did not require the session to be started."
The session needs to be started in order for the session array to be recognized and passed successfully in your query.
Plus, session_start(); is required to be resident inside all files using sessions.
http://php.net/manual/en/function.session-start.php
Add error reporting to the top of your file(s) which will help find errors.
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// rest of your code
Sidenote: Error reporting should only be done in staging, and never production.
I have a very intersting problem, and I wonder if there's someone who has a good solution for me:
I'm logging every possible error in a log-file.
Including exceptions, fatal errors, warnings, notices, JS-errors (sent via Ajax),...
Now, for debugging-purposes, I wanted to display all these errors on the HTML-page too (so I don't need to check the log-file).
For this reason, I'm storing every new error inside the Session-Variable. And when the User saw it, it's beeing removed from the Session.
My Problem:
The php-backtrace of an error / exceptions contains lots of information. Including global Variables like $_SESSION.
And since I store all the information in the session, the session-size doubles after each error. (Each error-message in the session contains the whole content of the session before this error)
For example:
No error:
$_SESSION = array();
First error:
$_SESSION = array(error1=>array("msg"="foo", session=array()));
Second Error:
$_SESSION = array(error1=>array("msg"="foo", session=array()), error2 => array("msg"="foo2", session = array(error1=>array("msg"="foo", session=array()))));
(this is only a simplified example)
After 5 Errors, the Session is already so big, that I can't load it from my local server anymore (timeout).
I thought about cutting of every element from an array after the depth of 5 or sth. like that.
Or is it possible to prevent php from storing global variables in the backtrace?
In the moment, I don't store the trace in the Session, but maybe someone knows a better solution, where I can still see basic trace-info
If I may understand you correctly, what you're trying to do is to have some sort of per-session error log, which can be read later on.
My suggestion is to create separate log file per session, you can identify the log file by using the session id, which is unique per session.
<?php
define('PER_SESSION_LOG_PATH', '/tmp/logs/'); //or whatever path
/* return log handle */
class SessionLogger {
static $fh;
static function log_path() {
return PER_SESSION_LOG_PATH . session_id() . '.log';
}
static function log_open() {
if (!self::$fh) {
self::$fh = fopen(self::log_path(), 'a+');
}
return self::$fh;
}
static function add_log($errors) {
if (self::log_open()) {
fwrite(self::$fh, serialize($errors) . PHP_EOL);
}
}
static function log_close() {
if (self::$fh) {
fclose(self::$fh);
}
}
//note that if the size of the log file is larger than memory_limit, you
//may run into problems.
//returns all the logs
static function get_logs($truncate = TRUE) {
$logs = array();
if (self::log_open()) {
while (!feof(self::$fh)) {
$logs[] = unserialize(fgets(self::$fh));
}
//if the logs should be clear, then...
if ($truncate) {
fclose(self::$fh); //close and nullify handle
self::$fh = null;
unlink(self::log_path()); //then delete log file.
}
}
return $logs;
}
}
Then you can add errors by doing
SessionLogger::add_log($error);
You can also read the logs by doing
SessionLogger::get_logs(true); //or false if you don't want to delete file.
Essentially I have a page which prints a form, form is filled out - then user is redirected to a 3rd party payment page (and data sent to 3rd party payment page via xml request). Here is the function which should be working:
function redirect_form()
{
global $pxpay;
$request = new PxPayRequest();
$http_host = getenv("HTTP_HOST");
$request_uri = getenv("SCRIPT_NAME");
$server_url = "http://$http_host";
#$script_url = "$server_url/$request_uri"; //using this code before PHP version 4.3.4
$script_url = "$server_url$request_uri"; //Using this code after PHP version 4.3.4
#$script_url = (version_compare(PHP_VERSION, "4.3.4", ">=")) ?"$server_url$request_uri" : "$server_url/$request_uri";
$MerchantReference = $_REQUEST["Reference"];
$StudentName = $_REQUEST["x_name"];
$Venue = $_REQUEST["x_venue"];
$Course = $_REQUEST["x_course"];
$AmountInput = $_REQUEST["x_amount"];
$Currency = $_REQUEST["x_currency"];
$Email = $_REQUEST["x_email"];
#Generate a unique identifier for the transaction
$TxnId = uniqid("ID");
#Set PxPay properties
$request->setMerchantReference($MerchantReference);
$request->setAmountInput($AmountInput);
$request->setTxnData1($StudentName);
$request->setTxnData2($Venue);
$request->setTxnData3($Course);
$request->setTxnType("Purchase");
$request->setCurrencyInput($Currency);
$request->setEmailAddress($Email);
$request->setUrlFail($script_url); # can be a dedicated failure page
$request->setUrlSuccess($script_url); # can be a dedicated success page
$request->setTxnId($TxnId);
#The following properties are not used in this case
# $request->setEnableAddBillCard($EnableAddBillCard);
# $request->setBillingId($BillingId);
# $request->setOpt($Opt);
#Call makeRequest function to obtain input XML
$request_string = $pxpay->makeRequest($request);
#Obtain output XML
$response = new MifMessage($request_string);
#Parse output XML
$url = $response->get_element_text("URI");
$valid = $response->get_attribute("valid");
#Redirect to payment page
header("Location: ".$url);
}
This form works perfectly in the sample code when its using $_REQUEST and the form sends perfectly if I comment out the part where each variable is defined - each variable is also a string.
I have done testing with defining the variables as plain text and it still works.
This function is being called by:
if (isset($_POST['StudentName'])) { redirect_form(); }
The issue is that the page is simply going to a blank page - where is should be going to the payment page.
Any suggestions would be appreciated.
EDIT:-
PHP Error reporting shows:
Warning: Cannot modify header information - headers already sent by (output started at /home/becky/public_html/paymentconfirmation.php:244) in /home/becky/public_html/paymentconfirmation.php on line 288
Line 244 is:
$MerchantReference = $_REQUEST["Reference"];
Line 288 is the header call:
header("Location: ".$url);
The thing I dont understand is that this issue only arises if there is already a session started filling the form (WHICH I NEED). If you start your session on this page there is no problem.
Please forgive me if this is a simple problem - I tried to find a similar issue that's been solved but struggled
It appears that the script which calls your function is part of a page where the headers and output have already been sent to the browser.
You can only modify the headers after that if you are using output buffering. The error that you are receiving tells me that you're not.
<?php
/*
* Establish output buffer, build output string
*/
ob_start();
/* (YOUR ENTIRE SCRIPT / PAGE GOES HERE) */
/*
* Send output and flush the buffer
*/
ob_end_flush();
?>
I heard the best method to share session across multiple domains on same server is to use custom php session handler. (ie, domain name different like abc.com, xyz.com but single application.)
But after i tried it, even custom php session handler that using SAME DATABASE ON 1 SERVER can't share session, when i tried to read cookie value from different domain.
Here's my custom session handler, Please kindly check or fix if something missing here. because i've tried it for a week now. can't get it to work
P.S. To get previous session id, i use link such as: newdomain.com/?ssid=[SESSION_ID]
SESSION_INCLUDE.PHP
<?php
// config
$m_host = "localhost"; //MySQL Host
$m_user = "db_user"; //MySQL User
$m_pass = "db_pass"; //MySQL Pass
$m_db = "db_name"; //MySQL Database
$table = "sess_data";
$session_expire = 600; // Session expire time, in seconds (minutes * 60 = seconds)
$gc_probability = 100; // Probability that the garbage collection function will be called. 50% chance by default
ini_set("session.gc_probability",$gc_probability);
/* Open function; Opens/starts session
Opens a connection to the database and stays open until specifically closed
This function is called first and with each page load */
function open ($s,$n) // do not modify function parameters
{
global $session_connection, $m_host, $m_user, $m_pass, $m_db;
$session_connection = mysql_pconnect($m_host,$m_user,$m_pass);
mysql_select_db($m_db,$session_connection);
return true;
}
/* Read function; downloads data from repository to current session
Queries the mysql database, unencrypts data, and returns it.
This function is called after 'open' with each page load. */
function read ($id) // do not modify function parameters
{
global $session_connection,$session_read,$table;
$query = "SELECT data FROM `$table` WHERE id=\"{$id}\"";
$res = mysql_query($query,$session_connection);
if(mysql_num_rows($res) != 1) return ""; // must return string, not 'false'
else
{
$session_read = mysql_fetch_assoc($res);
$session_read["data"] = base64_decode($session_read["data"]);
return $session_read["data"];
}
}
function write ($id,$data) // do not modify function parameters
{
if(!$data) { return false; }
global $session_connection, $session_read, $session_expire, $table;
$expire = time() + $session_expire;
$data = mysql_real_escape_string(base64_encode($data));
if($session_read) $query = "UPDATE `$table` SET data=\"{$data}\", expire=\"{$expire}\" WHERE id=\"{$id}\"";
else $query = "INSERT INTO sess_data SET id=\"{$id}\", data=\"{$data}\", expire=\"{$expire}\"";
mysql_query($query,$session_connection);
return true;
}
function close ()
{
global $session_connection;
mysql_close($session_connection);
return true;
}
function destroy ($id) // do not modify function parameters
{
global $session_connection,$table;
$query = "DELETE FROM `$table` WHERE id=\"{$id}\"";
mysql_query($query,$session_connection);
return true;
}
function gc ($expire)
{
global $session_connection,$table;
$query = "DELETE FROM `$table` WHERE expire < ".time();
mysql_query($query,$session_connection);
}
// Set custom handlers
session_set_save_handler ("open", "close", "read", "write", "destroy", "gc");
// Start session
session_start();
?>
MySQL Database Description
create table sess_data (
id2 int not null auto_increment,
id text not null,
data text,
expire int not null,
primary key(id2)
);
You can't read cookies from one domain in another domain. That's a security thing implemented in the browser. Using a database for sessions allows you to have multiple servers share sessions on the same domain, but does not allow for multiple domains on the same server to share sessions.
If you want to share sessions between domains, you would need to implement some sort of session transfer method when you switch domains. The simplest way to do this would involve passing the session id as a GET parameter from a page on one domain to a page on the other. Then, on the other domain, you would pick up the session id and create a new session using that ID.
While that is a simple way to do it, it isn't very secure and allows for session hijacking. A better way would be to use the database to create a record with the session id in it, set a short timeout on it, and pass the ID of that record to the other domain. The other domain would then pick up the record from the database and create a session with it. If the record in the database is past it's expiration, it wouldn't pick up the session. This would provide better protection against session hijacking.
This is the purpose of session_name(). Assign a different name to each application's session to avoid collisions between $_SESSION keys. The name will be used as the session cookie's name so although both session cookies will be passed to both applications, only the one matching the application's session_name() will be used to populate $_SESSION.
// App 1
session_name('app1');
session_start();
// App 2
session_name('app2');
session_start();
You really should look into SSO (single sign-on). One option for SSO is to use OpenID (as used on SO), and using it will make your life a lot easier.
Here's an article on it : http://devzone.zend.com/article/3581
the cookies and their visibility is a problem. The browser accessing the new site would not send the session id of the old site to the server.
I think your read() does not use the ssid parameter you provide as session id but as the browser has no session with this domain the system generates one with new id as $id. Have a look if $_REQUEST['ssid'] exist in the database.
Custom session handler might a bit big for this job. You could just check if $_REQUEST['ssid'] exist in the session database and rewrite $_SESSION with it.
I was wondering if anyone could give some suggestions on my method for sharing sessions between domains on same server (same cookie storage folder).
In each pages HEAD tag on all my sites, I call the following PHP code
if(!isset($_SESSION['sso'])) {
require_once('database.php');
$sites = array('http://site1', 'http://site2');
session_regenerate_id(); //Make new session id that will be shared
$session_id = session_id();
foreach($sites as $site) {
if($site != CURRENT_SITE) {
$sesh_key = md5(SALT.$site.$session_id);
$database->insertSessionId($sesh_key, $session_id);
$url = sprintf('%s/sso_set.php?k=%s', $site, $sesh_key);
echo('<link type="text/css" rel="stylesheet" href="'.$url.'" />');
}
}
$_SESSION['sso'] = 'SET';
}
Then on each site I have a file called 'sso_set.php' which contains
<?php
session_start();
if(!isset($_SESSION['sso'])) {
require_once('database.php');
$key = $_GET['k'];
$session_id = $database->getSessionId($key);
if($session_id) {
session_destroy();
session_id($session_id);
session_start();
$database->deleteSessionId($key);
$_SESSION['sso'] = 'SET';
}
}
Is using a text/css link a good idea?
I figured this is always called even if Javascript or Images are disabled?
This code basically makes the first site out of all my sites that gets opened by the user sets the Session ID, and then passes it on to the other sites.
Seems to work pretty well.
You get a slight delay the very first time any of the sites opened and the ID is passed to the sites. But, you could do this via AJAX so the page loads fast. But, then you rely on Javascript being enabled.
Thoughts?
This question already has answers here:
Check if PHP session has already started
(27 answers)
Closed 1 year ago.
Per request, there are a few different ways that you can tell whether or not a session has been started, such as:
$isSessionActive = (session_id() != "");
Or:
$isSessionActive = defined('SID');
However, these both fail if you start a session, then close it; session_id() will return the prior session's ID, while SID will be defined. Likewise, calling session_start() at this point will generate an E_NOTICE if you already have a session active. Is there a sane way to check if a session is currently active, without having to resort to output buffering, the shut-up operator (#session_start()), or something else equally as hacky?
EDIT: I wrote a patch to try to get this functionality included in PHP: http://bugs.php.net/bug.php?id=52982
EDIT 8/29/2011: New function added to PHP 5.4 to fix this: "Expose session status via new function, session_status"
// as of 8/29/2011
$isSessionActive = (session_status() == PHP_SESSION_ACTIVE);
EDIT 12/5/11: session_status() on the PHP manual.
See edits to the original question; basically, PHP 5.4 and above now has a function called session_status() to solve this problem!
"Expose session status via new function, session_status" (SVN Revision 315745)
If you need this functionality in a pre-PHP 5.4 version, see hakre's answer.
I worked around this by adding a couple wrapper functions around the various session creation/closing/destroying functions. Basically:
function open_session() {
session_start();
$_SESSION['is_open'] = TRUE;
}
function close_session() {
session_write_close();
$_SESSION['is_open'] = FALSE;
}
function destroy_session() {
session_destroy();
$_SESSION['is_open'] = FALSE;
}
function session_is_open() {
return($_SESSION['is_open']);
}
Hackish, but accomplished what I needed.
I'm running into this as well, and a setting in $_SESSION is not an option for me. For PHP 5.3.8:
If any session has been started with the request, define('SID') will return FALSE, as well as $_SESSION is unset.
This is independent whether or not session_id() has been used to set a session id or not.
After the first session_start(), SID is defined and $_SESSION is set to an empty array.
session_destroy() does unset the session_id(), it's an empty string then. SID will remain defined (and set to it's previous value which might be an empty string). $_SESSION is left unchanged. It will get reset/populated next time session_start is called.
With these states, especially as session_id() can be called in between to set the id for the next session, it's not possible to safely determine the session status with SID, $_SESSION and session_id().
"Trying" with session_start() (e.g. with #) might not be really helpful, as this will change the session status and changing the contents of $_SESSION (and adding a set-cookie header if the cookie was not part of the request). It was not fitting in my case.
While I was running tests, I came across the behaviour, that you can not try to change the ini setting of session.serialize_handler when the session is active, not even when you set it to the same value. Same is true for session.use_trans_sidDocs which is more lightweight. This lead me to the following function:
/**
* #return bool
*/
function session_is_active()
{
$setting = 'session.use_trans_sid';
$current = ini_get($setting);
if (FALSE === $current)
{
throw new UnexpectedValueException(sprintf('Setting %s does not exists.', $setting));
}
$result = #ini_set($setting, $current);
return $result !== $current;
}
As far as I can see, the error is checking for active session status only (not disabled), so this should not return a false positive when sessions are disabled.
To get this function compatible with PHP 5.2, it needs a little modification:
/**
* #return bool
*/
function session_is_active()
{
$setting = 'session.use_trans_sid';
$current = ini_get($setting);
if (FALSE === $current)
{
throw new UnexpectedValueException(sprintf('Setting %s does not exists.', $setting));
}
$testate = "mix$current$current";
$old = #ini_set($setting, $testate);
$peek = #ini_set($setting, $current);
$result = $peek === $current || $peek === FALSE;
return $result;
}
Some sandbox.
The following code only dumps one session_id() for me, not two
session_start();
echo session_id();
session_destroy();
echo session_id();
If you're having difficulties with this still you can try creating a variable to check, that you destroy when you destroy the session.
session_start();
$_SESSION['intialized'] = 'This will not print';
$_SESSION = array(); // Unset all variables
session_destroy();
echo $_SESSION['initialized']; // No output
Here's a good drop in replacement that won't break stuff when you move to 5.4:
if(!function_exists('session_status')){
function session_active(){
return defined('SID');
}
}else{
function session_active(){
return (session_status() == PHP_SESSION_ACTIVE);
}
}
Ken, if the session is destroyed, the $_SESSION array should not be available...or at the very least, your values should be unset. So, in theory (untested) you should be able to check the array for a value to know if anything is currently set.
There are multiple places you need to check to verify the session is active:
1- cookie exists and is not expired
2- underlying session storage mechanism (file system or database) has a record matching the cookie.