49269952c8
This is to clear the runway for getting rid of GlobalNameValueTableWrapper. It moves aside these three items that were in there for no particular reason other than convenience. I moved them aside into another struct that I arena-allocate and initialize at the same time as the global VarEnv (which initializes the GlobalNVTW). I called the struct where these live "EnvConstants" since they look like constants to PHP but their values are determined at startup time (by the environment, like whether we're in server mode). lvalProxy doesn't fit that mold, but oh well.
1653 linhas
46 KiB
C++
1653 linhas
46 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
| Copyright (c) 1997-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/ext/ext_session.h"
|
|
#include "hphp/runtime/ext/ext_options.h"
|
|
#include "hphp/runtime/ext/ext_hash.h"
|
|
#include "hphp/runtime/ext/ext_function.h"
|
|
#include "hphp/runtime/base/builtin_functions.h"
|
|
#include "hphp/runtime/base/zend/zend_math.h"
|
|
#include "hphp/runtime/base/util/string_buffer.h"
|
|
#include "hphp/runtime/base/util/request_local.h"
|
|
#include "hphp/runtime/base/ini_setting.h"
|
|
#include "hphp/runtime/base/time/datetime.h"
|
|
#include "hphp/runtime/base/variable_unserializer.h"
|
|
#include "hphp/runtime/base/array/array_iterator.h"
|
|
#include "hphp/util/lock.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/compatibility.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
|
|
namespace HPHP {
|
|
IMPLEMENT_DEFAULT_EXTENSION(session);
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool ini_on_update_save_handler(CStrRef value, void *p);
|
|
bool ini_on_update_serializer(CStrRef value, void *p);
|
|
bool ini_on_update_trans_sid(CStrRef value, void *p);
|
|
bool ini_on_update_save_dir(CStrRef value, void *p);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// global data
|
|
|
|
class SessionModule;
|
|
class SessionSerializer;
|
|
class Session {
|
|
public:
|
|
enum Status {
|
|
Disabled,
|
|
None,
|
|
Active
|
|
};
|
|
|
|
std::string m_save_path;
|
|
std::string m_session_name;
|
|
std::string m_extern_referer_chk;
|
|
std::string m_entropy_file;
|
|
int64_t m_entropy_length;
|
|
std::string m_cache_limiter;
|
|
int64_t m_cookie_lifetime;
|
|
std::string m_cookie_path;
|
|
std::string m_cookie_domain;
|
|
bool m_cookie_secure;
|
|
bool m_cookie_httponly;
|
|
|
|
SessionModule *m_mod;
|
|
|
|
Status m_session_status;
|
|
int64_t m_gc_probability;
|
|
int64_t m_gc_divisor;
|
|
int64_t m_gc_maxlifetime;
|
|
int m_module_number;
|
|
int64_t m_cache_expire;
|
|
|
|
std::string m_ps_open;
|
|
std::string m_ps_close;
|
|
std::string m_ps_read;
|
|
std::string m_ps_write;
|
|
std::string m_ps_destroy;
|
|
std::string m_ps_gc;
|
|
|
|
SessionSerializer *m_serializer;
|
|
|
|
bool m_auto_start;
|
|
bool m_use_cookies;
|
|
bool m_use_only_cookies;
|
|
bool m_use_trans_sid; // contains the INI value of whether to use trans-sid
|
|
bool m_apply_trans_sid; // whether to enable trans-sid for current request
|
|
|
|
std::string m_hash_func;
|
|
int64_t m_hash_bits_per_character;
|
|
|
|
int m_send_cookie;
|
|
int m_define_sid;
|
|
bool m_invalid_session_id; /* allows the driver to report about an invalid
|
|
session id and request id regeneration */
|
|
|
|
Session()
|
|
: m_entropy_length(0), m_cookie_lifetime(0), m_cookie_secure(false),
|
|
m_cookie_httponly(false), m_mod(NULL), m_session_status(None),
|
|
m_gc_probability(0), m_gc_divisor(0), m_gc_maxlifetime(0),
|
|
m_module_number(0), m_cache_expire(0), m_serializer(NULL),
|
|
m_auto_start(false), m_use_cookies(false), m_use_only_cookies(false),
|
|
m_use_trans_sid(false), m_apply_trans_sid(false),
|
|
m_hash_bits_per_character(0), m_send_cookie(0), m_define_sid(0),
|
|
m_invalid_session_id(false) {
|
|
}
|
|
};
|
|
|
|
class SessionRequestData : public RequestEventHandler, public Session {
|
|
public:
|
|
SessionRequestData() : m_threadInited(false) {}
|
|
|
|
virtual void requestInit() {
|
|
if (!m_threadInited) {
|
|
m_threadInited = true;
|
|
threadInit();
|
|
}
|
|
m_id.reset();
|
|
m_session_status = Session::None;
|
|
}
|
|
|
|
void requestShutdownImpl();
|
|
virtual void requestShutdown() {
|
|
f_session_write_close();
|
|
requestShutdownImpl();
|
|
}
|
|
|
|
public:
|
|
bool m_threadInited;
|
|
String m_id;
|
|
|
|
void threadInit() {
|
|
IniSetting::Bind("session.save_path", "",
|
|
ini_on_update_save_dir, &m_save_path);
|
|
IniSetting::Bind("session.name", "PHPSESSID",
|
|
ini_on_update_string, &m_session_name);
|
|
IniSetting::Bind("session.save_handler", "files",
|
|
ini_on_update_save_handler);
|
|
IniSetting::Bind("session.auto_start", "0",
|
|
ini_on_update_bool, &m_auto_start);
|
|
IniSetting::Bind("session.gc_probability", "1",
|
|
ini_on_update_long, &m_gc_probability);
|
|
IniSetting::Bind("session.gc_divisor", "100",
|
|
ini_on_update_long, &m_gc_divisor);
|
|
IniSetting::Bind("session.gc_maxlifetime", "1440",
|
|
ini_on_update_long, &m_gc_maxlifetime);
|
|
IniSetting::Bind("session.serialize_handler", "php",
|
|
ini_on_update_serializer);
|
|
IniSetting::Bind("session.cookie_lifetime", "0",
|
|
ini_on_update_long, &m_cookie_lifetime);
|
|
IniSetting::Bind("session.cookie_path", "/",
|
|
ini_on_update_string, &m_cookie_path);
|
|
IniSetting::Bind("session.cookie_domain", "",
|
|
ini_on_update_string, &m_cookie_domain);
|
|
IniSetting::Bind("session.cookie_secure", "",
|
|
ini_on_update_bool, &m_cookie_secure);
|
|
IniSetting::Bind("session.cookie_httponly", "",
|
|
ini_on_update_bool, &m_cookie_httponly);
|
|
IniSetting::Bind("session.use_cookies", "1",
|
|
ini_on_update_bool, &m_use_cookies);
|
|
IniSetting::Bind("session.use_only_cookies", "1",
|
|
ini_on_update_bool, &m_use_only_cookies);
|
|
IniSetting::Bind("session.referer_check", "",
|
|
ini_on_update_string, &m_extern_referer_chk);
|
|
IniSetting::Bind("session.entropy_file", "",
|
|
ini_on_update_string, &m_entropy_file);
|
|
IniSetting::Bind("session.entropy_length", "0",
|
|
ini_on_update_long, &m_entropy_length);
|
|
IniSetting::Bind("session.cache_limiter", "nocache",
|
|
ini_on_update_string, &m_cache_limiter);
|
|
IniSetting::Bind("session.cache_expire", "180",
|
|
ini_on_update_long, &m_cache_expire);
|
|
IniSetting::Bind("session.use_trans_sid", "0",
|
|
ini_on_update_trans_sid);
|
|
IniSetting::Bind("session.hash_function", "0",
|
|
ini_on_update_string, &m_hash_func);
|
|
IniSetting::Bind("session.hash_bits_per_character", "4",
|
|
ini_on_update_long, &m_hash_bits_per_character);
|
|
}
|
|
};
|
|
IMPLEMENT_STATIC_REQUEST_LOCAL(SessionRequestData, s_session);
|
|
#define PS(name) s_session->m_ ## name
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// SessionModule
|
|
|
|
class SessionModule {
|
|
public:
|
|
enum {
|
|
md5,
|
|
sha1,
|
|
};
|
|
|
|
public:
|
|
SessionModule(const char *name) : m_name(name) {
|
|
RegisteredModules.push_back(this);
|
|
}
|
|
virtual ~SessionModule() {}
|
|
|
|
const char *getName() const { return m_name;}
|
|
|
|
virtual bool open(const char *save_path, const char *session_name) = 0;
|
|
virtual bool close() = 0;
|
|
virtual bool read(const char *key, String &value) = 0;
|
|
virtual bool write(const char *key, CStrRef value) = 0;
|
|
virtual bool destroy(const char *key) = 0;
|
|
virtual bool gc(int maxlifetime, int *nrdels) = 0;
|
|
virtual String create_sid();
|
|
|
|
public:
|
|
static SessionModule *Find(const char *name) {
|
|
for (unsigned int i = 0; i < RegisteredModules.size(); i++) {
|
|
SessionModule *mod = RegisteredModules[i];
|
|
if (mod && strcasecmp(name, mod->m_name) == 0) {
|
|
return mod;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
static std::vector<SessionModule*> RegisteredModules;
|
|
|
|
const char *m_name;
|
|
};
|
|
std::vector<SessionModule*> SessionModule::RegisteredModules;
|
|
|
|
void SessionRequestData::requestShutdownImpl() {
|
|
if (m_mod) {
|
|
try {
|
|
m_mod->close();
|
|
} catch (...) {}
|
|
}
|
|
m_id.reset();
|
|
}
|
|
|
|
/*
|
|
* Note that we cannot use the BASE64 alphabet here, because
|
|
* it contains "/" and "+": both are unacceptable for simple inclusion
|
|
* into URLs.
|
|
*/
|
|
static char hexconvtab[] =
|
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
|
|
|
|
/* returns a pointer to the byte after the last valid character in out */
|
|
static void bin_to_readable(CStrRef in, StringBuffer &out, char nbits) {
|
|
unsigned char *p = (unsigned char *)in.data();
|
|
unsigned char *q = (unsigned char *)in.data() + in.size();
|
|
unsigned short w = 0;
|
|
int have = 0;
|
|
int mask = (1 << nbits) - 1;
|
|
|
|
while (true) {
|
|
if (have < nbits) {
|
|
if (p < q) {
|
|
w |= *p++ << have;
|
|
have += 8;
|
|
} else {
|
|
/* consumed everything? */
|
|
if (have == 0) break;
|
|
/* No? We need a final round */
|
|
have = nbits;
|
|
}
|
|
}
|
|
|
|
/* consume nbits */
|
|
out.append(hexconvtab[w & mask]);
|
|
w >>= nbits;
|
|
have -= nbits;
|
|
}
|
|
}
|
|
|
|
static const StaticString
|
|
s_REMOTE_ADDR("REMOTE_ADDR"),
|
|
s__SERVER("_SERVER"),
|
|
s__SESSION("_SESSION"),
|
|
s__COOKIE("_COOKIE"),
|
|
s__GET("_GET"),
|
|
s__POST("_POST");
|
|
|
|
String SessionModule::create_sid() {
|
|
GlobalVariables *g = get_global_variables();
|
|
String remote_addr = g->get(s__SERVER)[s_REMOTE_ADDR].toString();
|
|
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
|
|
StringBuffer buf;
|
|
buf.printf("%.15s%ld%ld%0.8F", remote_addr.data(),
|
|
tv.tv_sec, (long int)tv.tv_usec, math_combined_lcg() * 10);
|
|
|
|
if (String(PS(hash_func)).isNumeric()) {
|
|
switch (String(PS(hash_func)).toInt64()) {
|
|
case md5: PS(hash_func) = "md5"; break;
|
|
case sha1: PS(hash_func) = "sha1"; break;
|
|
}
|
|
}
|
|
|
|
Variant context = f_hash_init(PS(hash_func));
|
|
if (same(context, false)) {
|
|
Logger::Error("Invalid session hash function: %s", PS(hash_func).c_str());
|
|
return String();
|
|
}
|
|
if (!f_hash_update(context, buf.detach())) {
|
|
Logger::Error("hash_update() failed");
|
|
return String();
|
|
}
|
|
|
|
if (PS(entropy_length) > 0) {
|
|
int fd = open(PS(entropy_file).c_str(), O_RDONLY);
|
|
if (fd >= 0) {
|
|
unsigned char rbuf[2048];
|
|
int n;
|
|
int to_read = PS(entropy_length);
|
|
while (to_read > 0) {
|
|
n = ::read(fd, rbuf, (to_read < (int)sizeof(rbuf) ?
|
|
to_read : (int)sizeof(buf)));
|
|
if (n <= 0) break;
|
|
if (!f_hash_update(context,
|
|
String((const char *)rbuf, n, AttachLiteral))) {
|
|
Logger::Error("hash_update() failed");
|
|
::close(fd);
|
|
return String();
|
|
}
|
|
to_read -= n;
|
|
}
|
|
::close(fd);
|
|
}
|
|
}
|
|
|
|
String hashed = f_hash_final(context);
|
|
|
|
if (PS(hash_bits_per_character) < 4 || PS(hash_bits_per_character) > 6) {
|
|
PS(hash_bits_per_character) = 4;
|
|
raise_warning("The ini setting hash_bits_per_character is out of range "
|
|
"(should be 4, 5, or 6) - using 4 for now");
|
|
}
|
|
|
|
StringBuffer readable;
|
|
bin_to_readable(hashed, readable, PS(hash_bits_per_character));
|
|
return readable.detach();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// FileSessionModule
|
|
|
|
class FileSessionData {
|
|
public:
|
|
FileSessionData() : m_fd(-1), m_dirdepth(0), m_st_size(0), m_filemode(0600) {
|
|
}
|
|
|
|
bool open(const char *save_path, const char *session_name) {
|
|
String tmpdir;
|
|
if (*save_path == '\0') {
|
|
tmpdir = f_sys_get_temp_dir();
|
|
save_path = tmpdir.data();
|
|
}
|
|
|
|
/* split up input parameter */
|
|
const char *argv[3];
|
|
int argc = 0;
|
|
const char *last = save_path;
|
|
const char *p = strchr(save_path, ';');
|
|
while (p) {
|
|
argv[argc++] = last; last = ++p; p = strchr(p, ';');
|
|
if (argc > 1) break;
|
|
}
|
|
argv[argc++] = last;
|
|
|
|
if (argc > 1) {
|
|
errno = 0;
|
|
m_dirdepth = (size_t) strtol(argv[0], NULL, 10);
|
|
if (errno == ERANGE) {
|
|
raise_warning("The first parameter in session.save_path is invalid");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (argc > 2) {
|
|
errno = 0;
|
|
m_filemode = strtol(argv[1], NULL, 8);
|
|
if (errno == ERANGE || m_filemode < 0 || m_filemode > 07777) {
|
|
raise_warning("The second parameter in session.save_path is invalid");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
save_path = argv[argc - 1];
|
|
if (File::TranslatePath(save_path).empty()) {
|
|
raise_warning("Unable to open save_path %s", save_path);
|
|
return false;
|
|
}
|
|
|
|
m_fd = -1;
|
|
m_basedir = save_path;
|
|
return true;
|
|
}
|
|
|
|
bool close() {
|
|
closeImpl();
|
|
m_lastkey.clear();
|
|
m_basedir.clear();
|
|
return true;
|
|
}
|
|
|
|
bool read(const char *key, String &value) {
|
|
openImpl(key);
|
|
if (m_fd < 0) {
|
|
return false;
|
|
}
|
|
|
|
struct stat sbuf;
|
|
if (fstat(m_fd, &sbuf)) {
|
|
return false;
|
|
}
|
|
m_st_size = sbuf.st_size;
|
|
if (m_st_size == 0) {
|
|
value = "";
|
|
return true;
|
|
}
|
|
|
|
String s = String(m_st_size, ReserveString);
|
|
char *val = s.mutableSlice().ptr;
|
|
|
|
#if defined(HAVE_PREAD)
|
|
long n = pread(m_fd, val, m_st_size, 0);
|
|
#else
|
|
lseek(m_fd, 0, SEEK_SET);
|
|
long n = ::read(m_fd, val, m_st_size);
|
|
#endif
|
|
|
|
if (n != (int)m_st_size) {
|
|
if (n == -1) {
|
|
raise_warning("read failed: %s (%d)", strerror(errno), errno);
|
|
} else {
|
|
raise_warning("read returned less bytes than requested");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
value = s.setSize(m_st_size);
|
|
return true;
|
|
}
|
|
|
|
bool write(const char *key, CStrRef value) {
|
|
openImpl(key);
|
|
if (m_fd < 0) {
|
|
return false;
|
|
}
|
|
|
|
struct stat sbuf;
|
|
if (fstat(m_fd, &sbuf)) {
|
|
return false;
|
|
}
|
|
m_st_size = sbuf.st_size;
|
|
|
|
/*
|
|
* truncate file, if the amount of new data is smaller than
|
|
* the existing data set.
|
|
*/
|
|
if (value.size() < (int)m_st_size) {
|
|
if (ftruncate(m_fd, 0) < 0) {
|
|
raise_warning("truncate failed: %s (%d)", strerror(errno), errno);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_PWRITE)
|
|
long n = pwrite(m_fd, value.data(), value.size(), 0);
|
|
#else
|
|
lseek(m_fd, 0, SEEK_SET);
|
|
long n = ::write(m_fd, value.data(), value.size());
|
|
#endif
|
|
|
|
if (n != value.size()) {
|
|
if (n == -1) {
|
|
raise_warning("write failed: %s (%d)", strerror(errno), errno);
|
|
} else {
|
|
raise_warning("write wrote less bytes than requested");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool destroy(const char *key) {
|
|
char buf[PATH_MAX];
|
|
if (!createPath(buf, sizeof(buf), key)) {
|
|
return false;
|
|
}
|
|
|
|
if (m_fd != -1) {
|
|
closeImpl();
|
|
if (unlink(buf) == -1) {
|
|
/* This is a little safety check for instances when we are dealing
|
|
with a regenerated session that was not yet written to disk */
|
|
if (!access(buf, F_OK)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gc(int maxlifetime, int *nrdels) {
|
|
/* we don't perform any cleanup, if dirdepth is larger than 0.
|
|
we return true, since all cleanup should be handled by
|
|
an external entity (i.e. find -ctime x | xargs rm) */
|
|
if (m_dirdepth == 0) {
|
|
*nrdels = CleanupDir(m_basedir.c_str(), maxlifetime);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
int m_fd;
|
|
string m_lastkey;
|
|
string m_basedir;
|
|
size_t m_dirdepth;
|
|
size_t m_st_size;
|
|
int m_filemode;
|
|
|
|
/* If you change the logic here, please also update the error message in
|
|
* ps_files_open() appropriately */
|
|
static bool IsValid(const char *key) {
|
|
const char *p; char c;
|
|
bool ret = true;
|
|
for (p = key; (c = *p); p++) {
|
|
/* valid characters are a..z,A..Z,0..9 */
|
|
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
|
|| (c >= '0' && c <= '9') || c == ',' || c == '-')) {
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
size_t len = p - key;
|
|
if (len == 0) {
|
|
ret = false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define FILE_PREFIX "sess_"
|
|
|
|
bool createPath(char *buf, size_t buflen, const char *key) {
|
|
size_t key_len = strlen(key);
|
|
if (key_len <= m_dirdepth ||
|
|
buflen < (m_basedir.size() + 2 * m_dirdepth + key_len +
|
|
5 + sizeof(FILE_PREFIX))) {
|
|
return false;
|
|
}
|
|
|
|
const char *p = key;
|
|
int n = m_basedir.size();
|
|
memcpy(buf, m_basedir.c_str(), n);
|
|
buf[n++] = PHP_DIR_SEPARATOR;
|
|
for (int i = 0; i < (int)m_dirdepth; i++) {
|
|
buf[n++] = *p++;
|
|
buf[n++] = PHP_DIR_SEPARATOR;
|
|
}
|
|
memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
|
|
n += sizeof(FILE_PREFIX) - 1;
|
|
memcpy(buf + n, key, key_len);
|
|
n += key_len;
|
|
buf[n] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
void closeImpl() {
|
|
if (m_fd != -1) {
|
|
#ifdef PHP_WIN32
|
|
/* On Win32 locked files that are closed without being explicitly
|
|
unlocked will be unlocked only when "system resources become
|
|
available". */
|
|
flock(m_fd, LOCK_UN);
|
|
#endif
|
|
::close(m_fd);
|
|
m_fd = -1;
|
|
}
|
|
}
|
|
|
|
void openImpl(const char *key) {
|
|
if (m_fd < 0 || !m_lastkey.empty() || m_lastkey != key) {
|
|
m_lastkey.clear();
|
|
closeImpl();
|
|
|
|
if (!IsValid(key)) {
|
|
raise_warning("The session id contains illegal characters, "
|
|
"valid characters are a-z, A-Z, 0-9 and '-,'");
|
|
PS(invalid_session_id) = true;
|
|
return;
|
|
}
|
|
|
|
char buf[PATH_MAX];
|
|
if (!createPath(buf, sizeof(buf), key)) {
|
|
return;
|
|
}
|
|
|
|
m_lastkey = key;
|
|
m_fd = ::open(buf, O_CREAT | O_RDWR | O_BINARY, m_filemode);
|
|
|
|
if (m_fd != -1) {
|
|
#ifdef PHP_WIN32
|
|
flock(m_fd, LOCK_EX);
|
|
#endif
|
|
|
|
#ifdef F_SETFD
|
|
# ifndef FD_CLOEXEC
|
|
# define FD_CLOEXEC 1
|
|
# endif
|
|
if (fcntl(m_fd, F_SETFD, FD_CLOEXEC)) {
|
|
raise_warning("fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)",
|
|
m_fd, strerror(errno), errno);
|
|
}
|
|
#endif
|
|
} else {
|
|
raise_warning("open(%s, O_RDWR) failed: %s (%d)", buf,
|
|
strerror(errno), errno);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int CleanupDir(const char *dirname, int maxlifetime) {
|
|
DIR *dir = opendir(dirname);
|
|
if (!dir) {
|
|
raise_notice("ps_files_cleanup_dir: opendir(%s) failed: %s (%d)",
|
|
dirname, strerror(errno), errno);
|
|
return 0;
|
|
}
|
|
|
|
time_t now;
|
|
time(&now);
|
|
|
|
size_t dirname_len = strlen(dirname);
|
|
char dentry[sizeof(struct dirent) + PATH_MAX];
|
|
struct dirent *entry = (struct dirent *) &dentry;
|
|
struct stat sbuf;
|
|
int nrdels = 0;
|
|
|
|
/* Prepare buffer (dirname never changes) */
|
|
char buf[PATH_MAX];
|
|
memcpy(buf, dirname, dirname_len);
|
|
buf[dirname_len] = PHP_DIR_SEPARATOR;
|
|
|
|
while (readdir_r(dir, (struct dirent *)dentry, &entry) == 0 && entry) {
|
|
/* does the file start with our prefix? */
|
|
if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
|
|
size_t entry_len = strlen(entry->d_name);
|
|
|
|
/* does it fit into our buffer? */
|
|
if (entry_len + dirname_len + 2 < PATH_MAX) {
|
|
/* create the full path.. */
|
|
memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
|
|
|
|
/* NUL terminate it and */
|
|
buf[dirname_len + entry_len + 1] = '\0';
|
|
|
|
/* check whether its last access was more than maxlifet ago */
|
|
if (stat(buf, &sbuf) == 0 && (now - sbuf.st_mtime) > maxlifetime) {
|
|
unlink(buf);
|
|
nrdels++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
return nrdels;
|
|
}
|
|
};
|
|
IMPLEMENT_THREAD_LOCAL(FileSessionData, s_file_session_data);
|
|
|
|
class FileSessionModule : public SessionModule {
|
|
public:
|
|
FileSessionModule() : SessionModule("files") {
|
|
}
|
|
virtual bool open(const char *save_path, const char *session_name) {
|
|
return s_file_session_data->open(save_path, session_name);
|
|
}
|
|
virtual bool close() {
|
|
return s_file_session_data->close();
|
|
}
|
|
virtual bool read(const char *key, String &value) {
|
|
return s_file_session_data->read(key, value);
|
|
}
|
|
virtual bool write(const char *key, CStrRef value) {
|
|
return s_file_session_data->write(key, value);
|
|
}
|
|
virtual bool destroy(const char *key) {
|
|
return s_file_session_data->destroy(key);
|
|
}
|
|
virtual bool gc(int maxlifetime, int *nrdels) {
|
|
return s_file_session_data->gc(maxlifetime, nrdels);
|
|
}
|
|
};
|
|
static FileSessionModule s_file_session_module;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// UserSessionModule
|
|
|
|
class UserSessionModule : public SessionModule {
|
|
public:
|
|
UserSessionModule() : SessionModule("user") {}
|
|
|
|
virtual bool open(const char *save_path, const char *session_name) {
|
|
return vm_call_user_func
|
|
(String(PS(ps_open)),
|
|
CREATE_VECTOR2(String(save_path, CopyString),
|
|
String(session_name, CopyString)));
|
|
}
|
|
|
|
virtual bool close() {
|
|
return vm_call_user_func
|
|
(String(PS(ps_close)),
|
|
Array::Create());
|
|
}
|
|
|
|
virtual bool read(const char *key, String &value) {
|
|
Variant ret = vm_call_user_func
|
|
(String(PS(ps_read)),
|
|
CREATE_VECTOR1(String(key, CopyString)));
|
|
if (ret.isString()) {
|
|
value = ret.toString();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual bool write(const char *key, CStrRef value) {
|
|
return vm_call_user_func
|
|
(String(PS(ps_write)),
|
|
CREATE_VECTOR2(String(key, CopyString), value));
|
|
}
|
|
|
|
virtual bool destroy(const char *key) {
|
|
return vm_call_user_func
|
|
(String(PS(ps_destroy)),
|
|
CREATE_VECTOR1(String(key, CopyString)));
|
|
}
|
|
|
|
virtual bool gc(int maxlifetime, int *nrdels) {
|
|
return vm_call_user_func
|
|
(String(PS(ps_gc)),
|
|
CREATE_VECTOR1((int64_t)maxlifetime));
|
|
}
|
|
};
|
|
static UserSessionModule s_user_session_module;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// session serializers
|
|
|
|
class SessionSerializer {
|
|
public:
|
|
explicit SessionSerializer(const char *name) : m_name(name) {
|
|
RegisteredSerializers.push_back(this);
|
|
}
|
|
virtual ~SessionSerializer() {}
|
|
|
|
virtual String encode() = 0;
|
|
virtual bool decode(CStrRef value) = 0;
|
|
|
|
static SessionSerializer *Find(const char *name) {
|
|
for (unsigned int i = 0; i < RegisteredSerializers.size(); i++) {
|
|
SessionSerializer *ss = RegisteredSerializers[i];
|
|
if (ss && strcasecmp(name, ss->m_name) == 0) {
|
|
return ss;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
static std::vector<SessionSerializer*> RegisteredSerializers;
|
|
|
|
const char *m_name;
|
|
};
|
|
std::vector<SessionSerializer*> SessionSerializer::RegisteredSerializers;
|
|
|
|
#define PS_BIN_NR_OF_BITS 8
|
|
#define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
|
|
#define PS_BIN_MAX (PS_BIN_UNDEF-1)
|
|
|
|
class BinarySessionSerializer : public SessionSerializer {
|
|
public:
|
|
BinarySessionSerializer() : SessionSerializer("php_binary") {}
|
|
|
|
virtual String encode() {
|
|
StringBuffer buf;
|
|
GlobalVariables *g = get_global_variables();
|
|
for (ArrayIter iter(g->get(s__SESSION)); iter; ++iter) {
|
|
Variant key = iter.first();
|
|
if (key.isString()) {
|
|
String skey = key.toString();
|
|
if (skey.size() <= PS_BIN_MAX) {
|
|
buf.append((unsigned char)skey.size());
|
|
buf.append(skey);
|
|
buf.append(f_serialize(iter.second()));
|
|
}
|
|
} else {
|
|
raise_notice("Skipping numeric key %" PRId64, key.toInt64());
|
|
}
|
|
}
|
|
return buf.detach();
|
|
}
|
|
|
|
virtual bool decode(CStrRef value) {
|
|
const char *endptr = value.data() + value.size();
|
|
GlobalVariables *g = get_global_variables();
|
|
for (const char *p = value.data(); p < endptr; ) {
|
|
int namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
|
|
if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
|
|
return false;
|
|
}
|
|
|
|
int has_value = *p & PS_BIN_UNDEF ? 0 : 1;
|
|
String key(p + 1, namelen, CopyString);
|
|
p += namelen + 1;
|
|
if (has_value) {
|
|
VariableUnserializer vu(p, endptr, VariableUnserializer::Serialize);
|
|
try {
|
|
g->getRef(s__SESSION).set(key, vu.unserialize());
|
|
p = vu.head();
|
|
} catch (Exception &e) {
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
static BinarySessionSerializer s_binary_session_serializer;
|
|
|
|
#define PS_DELIMITER '|'
|
|
#define PS_UNDEF_MARKER '!'
|
|
|
|
class PhpSessionSerializer : public SessionSerializer {
|
|
public:
|
|
PhpSessionSerializer() : SessionSerializer("php") {}
|
|
|
|
virtual String encode() {
|
|
StringBuffer buf;
|
|
GlobalVariables *g = get_global_variables();
|
|
for (ArrayIter iter(g->get(s__SESSION)); iter; ++iter) {
|
|
Variant key = iter.first();
|
|
if (key.isString()) {
|
|
String skey = key.toString();
|
|
buf.append(skey);
|
|
if (skey.find(PS_DELIMITER) >= 0) {
|
|
return String();
|
|
}
|
|
buf.append(PS_DELIMITER);
|
|
buf.append(f_serialize(iter.second()));
|
|
} else {
|
|
raise_notice("Skipping numeric key %" PRId64, key.toInt64());
|
|
}
|
|
}
|
|
return buf.detach();
|
|
}
|
|
|
|
virtual bool decode(CStrRef value) {
|
|
const char *p = value.data();
|
|
const char *endptr = value.data() + value.size();
|
|
GlobalVariables *g = get_global_variables();
|
|
while (p < endptr) {
|
|
const char *q = p;
|
|
while (*q != PS_DELIMITER) {
|
|
if (++q >= endptr) return true;
|
|
}
|
|
int has_value;
|
|
if (p[0] == PS_UNDEF_MARKER) {
|
|
p++;
|
|
has_value = 0;
|
|
} else {
|
|
has_value = 1;
|
|
}
|
|
|
|
String key(p, q - p, CopyString);
|
|
q++;
|
|
if (has_value) {
|
|
VariableUnserializer vu(q, endptr, VariableUnserializer::Serialize);
|
|
try {
|
|
g->getRef(s__SESSION).set(key, vu.unserialize());
|
|
q = vu.head();
|
|
} catch (Exception &e) {
|
|
}
|
|
}
|
|
p = q;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
static PhpSessionSerializer s_php_session_serializer;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define SESSION_CHECK_ACTIVE_STATE \
|
|
if (PS(session_status) == Session::Active) { \
|
|
raise_warning("A session is active. You cannot change the session" \
|
|
" module's ini settings at this time"); \
|
|
return false; \
|
|
}
|
|
|
|
bool ini_on_update_save_handler(CStrRef value, void *p) {
|
|
SESSION_CHECK_ACTIVE_STATE;
|
|
PS(mod) = SessionModule::Find(value.data());
|
|
return true;
|
|
}
|
|
|
|
bool ini_on_update_serializer(CStrRef value, void *p) {
|
|
SESSION_CHECK_ACTIVE_STATE;
|
|
PS(serializer) = SessionSerializer::Find(value.data());
|
|
return true;
|
|
}
|
|
|
|
bool ini_on_update_trans_sid(CStrRef value, void *p) {
|
|
SESSION_CHECK_ACTIVE_STATE;
|
|
if (!strncasecmp(value.data(), "on", sizeof("on"))) {
|
|
PS(use_trans_sid) = true;
|
|
} else {
|
|
PS(use_trans_sid) = value.toBoolean();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ini_on_update_save_dir(CStrRef value, void *p) {
|
|
if (value.find('\0') >= 0) {
|
|
return false;
|
|
}
|
|
const char *path = value.data() + value.rfind(';') + 1;
|
|
if (File::TranslatePath(path).empty()) {
|
|
return false;
|
|
}
|
|
return ini_on_update_string(value, p);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int php_session_destroy() {
|
|
int retval = true;
|
|
|
|
if (PS(session_status) != Session::Active) {
|
|
raise_warning("Trying to destroy uninitialized session");
|
|
return false;
|
|
}
|
|
|
|
if (PS(mod)->destroy(PS(id).data()) == false) {
|
|
retval = false;
|
|
raise_warning("Session object destruction failed");
|
|
}
|
|
|
|
s_session->requestShutdownImpl();
|
|
s_session->requestInit();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static String php_session_encode() {
|
|
if (!PS(serializer)) {
|
|
raise_warning("Unknown session.serialize_handler. "
|
|
"Failed to encode session object");
|
|
return String();
|
|
}
|
|
return PS(serializer)->encode();
|
|
}
|
|
|
|
static void php_session_decode(CStrRef value) {
|
|
if (!PS(serializer)) {
|
|
raise_warning("Unknown session.serialize_handler. "
|
|
"Failed to decode session object");
|
|
return;
|
|
}
|
|
if (!PS(serializer)->decode(value)) {
|
|
php_session_destroy();
|
|
raise_warning("Failed to decode session object. "
|
|
"Session has been destroyed");
|
|
}
|
|
}
|
|
|
|
static void php_session_initialize() {
|
|
/* check session name for invalid characters */
|
|
if (strpbrk(PS(id).data(), "\r\n\t <>'\"\\")) {
|
|
PS(id).reset();
|
|
}
|
|
|
|
if (!PS(mod)) {
|
|
raise_error("No storage module chosen - failed to initialize session");
|
|
return;
|
|
}
|
|
|
|
/* Open session handler first */
|
|
if (!PS(mod)->open(PS(save_path).c_str(), PS(session_name).c_str())) {
|
|
raise_error("Failed to initialize storage module: %s (path: %s)",
|
|
PS(mod)->getName(), PS(save_path).c_str());
|
|
return;
|
|
}
|
|
|
|
/* If there is no ID, use session module to create one */
|
|
int attempts = 3;
|
|
if (PS(id).empty()) {
|
|
new_session:
|
|
PS(id) = PS(mod)->create_sid();
|
|
if (PS(id).empty()) {
|
|
raise_error("Failed to create session id: %s", PS(mod)->getName());
|
|
return;
|
|
}
|
|
if (PS(use_cookies)) {
|
|
PS(send_cookie) = 1;
|
|
}
|
|
}
|
|
|
|
/* Read data */
|
|
/* Question: if you create a SID here, should you also try to read data?
|
|
* I'm not sure, but while not doing so will remove one session operation
|
|
* it could prove usefull for those sites which wish to have "default"
|
|
* session information
|
|
*/
|
|
|
|
/* Unconditionally destroy existing arrays -- possible dirty data */
|
|
GlobalVariables *g = get_global_variables();
|
|
g->getRef(s__SESSION) = Array::Create();
|
|
|
|
PS(invalid_session_id) = false;
|
|
String value;
|
|
if (PS(mod)->read(PS(id).data(), value)) {
|
|
php_session_decode(value);
|
|
} else if (PS(invalid_session_id)) {
|
|
/* address instances where the session read fails due to an invalid id */
|
|
PS(invalid_session_id) = false;
|
|
PS(id).reset();
|
|
if (--attempts > 0) {
|
|
goto new_session;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void php_session_save_current_state() {
|
|
bool ret = false;
|
|
if (PS(mod)) {
|
|
String value = php_session_encode();
|
|
if (!value.isNull()) {
|
|
ret = PS(mod)->write(PS(id).data(), value);
|
|
}
|
|
}
|
|
if (!ret) {
|
|
raise_warning("Failed to write session data (%s). Please verify that the "
|
|
"current setting of session.save_path is correct (%s)",
|
|
PS(mod)->getName(), PS(save_path).c_str());
|
|
}
|
|
if (PS(mod)) {
|
|
PS(mod)->close();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Cookie Management
|
|
|
|
#define COOKIE_SET_COOKIE "Set-Cookie: "
|
|
#define COOKIE_EXPIRES "; expires="
|
|
#define COOKIE_PATH "; path="
|
|
#define COOKIE_DOMAIN "; domain="
|
|
#define COOKIE_SECURE "; secure"
|
|
#define COOKIE_HTTPONLY "; HttpOnly"
|
|
|
|
static void php_session_send_cookie() {
|
|
Transport *transport = g_context->getTransport();
|
|
if (!transport) return;
|
|
|
|
if (transport->headersSent()) {
|
|
raise_warning("Cannot send session cookie - headers already sent");
|
|
return;
|
|
}
|
|
|
|
/* URL encode session_name and id because they might be user supplied */
|
|
String session_name =
|
|
StringUtil::UrlEncode(String(PS(session_name).c_str()));
|
|
String id = StringUtil::UrlEncode(PS(id));
|
|
|
|
StringBuffer ncookie;
|
|
ncookie.append(COOKIE_SET_COOKIE);
|
|
ncookie.append(session_name);
|
|
ncookie.append('=');
|
|
ncookie.append(id);
|
|
|
|
if (PS(cookie_lifetime) > 0) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
time_t t = tv.tv_sec + PS(cookie_lifetime);
|
|
if (t > 0) {
|
|
ncookie.append(COOKIE_EXPIRES);
|
|
ncookie.append(DateTime(t).toString(DateTime::Cookie));
|
|
}
|
|
}
|
|
if (!PS(cookie_path).empty()) {
|
|
ncookie.append(COOKIE_PATH);
|
|
ncookie.append(PS(cookie_path));
|
|
}
|
|
if (!PS(cookie_domain).empty()) {
|
|
ncookie.append(COOKIE_DOMAIN);
|
|
ncookie.append(PS(cookie_domain));
|
|
}
|
|
if (PS(cookie_secure)) {
|
|
ncookie.append(COOKIE_SECURE);
|
|
}
|
|
if (PS(cookie_httponly)) {
|
|
ncookie.append(COOKIE_HTTPONLY);
|
|
}
|
|
|
|
transport->addHeader(ncookie.detach());
|
|
}
|
|
|
|
static void php_session_reset_id() {
|
|
if (PS(use_cookies) && PS(send_cookie)) {
|
|
php_session_send_cookie();
|
|
PS(send_cookie) = 0;
|
|
}
|
|
|
|
EnvConstants *g = get_env_constants();
|
|
if (PS(define_sid)) {
|
|
StringBuffer var;
|
|
var.append(String(PS(session_name)));
|
|
var.append('=');
|
|
var.append(PS(id));
|
|
g->k_SID = var.detach();
|
|
} else {
|
|
g->k_SID = empty_string;
|
|
}
|
|
|
|
// hzhao: not sure how to support this yet
|
|
#if 0
|
|
if (PS(apply_trans_sid)) {
|
|
php_url_scanner_reset_vars();
|
|
php_url_scanner_add_var(PS(session_name), strlen(PS(session_name)),
|
|
PS(id), strlen(PS(id)), 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Cache Limiters
|
|
|
|
typedef struct {
|
|
char *name;
|
|
void (*func)();
|
|
} php_session_cache_limiter_t;
|
|
|
|
#define CACHE_LIMITER(name) _php_cache_limiter_##name
|
|
#define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)()
|
|
#define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
|
|
#define ADD_HEADER(hdr) g_context->getTransport()->addHeader(hdr)
|
|
|
|
#define LAST_MODIFIED "Last-Modified: "
|
|
#define EXPIRES "Expires: "
|
|
#define MAX_STR 512
|
|
|
|
static char *month_names[] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
static char *week_days[] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
|
|
};
|
|
|
|
static inline void strcpy_gmt(char *ubuf, time_t *when) {
|
|
char buf[MAX_STR];
|
|
struct tm tm, *res;
|
|
int n;
|
|
|
|
res = gmtime_r(when, &tm);
|
|
|
|
if (!res) {
|
|
buf[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
n = snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", // SAFE
|
|
week_days[tm.tm_wday], tm.tm_mday,
|
|
month_names[tm.tm_mon], tm.tm_year + 1900,
|
|
tm.tm_hour, tm.tm_min,
|
|
tm.tm_sec);
|
|
memcpy(ubuf, buf, n);
|
|
ubuf[n] = '\0';
|
|
}
|
|
|
|
static const StaticString s_PATH_TRANSLATED("PATH_TRANSLATED");
|
|
|
|
static inline void last_modified() {
|
|
GlobalVariables *g = get_global_variables();
|
|
String path = g->get(s__SERVER)[s_PATH_TRANSLATED].toString();
|
|
if (!path.empty()) {
|
|
struct stat sb;
|
|
if (stat(path.data(), &sb) == -1) {
|
|
return;
|
|
}
|
|
|
|
char buf[MAX_STR + 1];
|
|
memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
|
|
strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
|
|
ADD_HEADER(buf);
|
|
}
|
|
}
|
|
|
|
CACHE_LIMITER_FUNC(public) {
|
|
char buf[MAX_STR + 1];
|
|
struct timeval tv;
|
|
time_t now;
|
|
|
|
gettimeofday(&tv, NULL);
|
|
now = tv.tv_sec + PS(cache_expire) * 60;
|
|
memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
|
|
strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
|
|
ADD_HEADER(buf);
|
|
|
|
snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%" PRId64,
|
|
PS(cache_expire) * 60); /* SAFE */
|
|
ADD_HEADER(buf);
|
|
|
|
last_modified();
|
|
}
|
|
|
|
CACHE_LIMITER_FUNC(private_no_expire) {
|
|
char buf[MAX_STR + 1];
|
|
|
|
snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%" PRId64 ", "
|
|
"pre-check=%" PRId64, PS(cache_expire) * 60,
|
|
PS(cache_expire) * 60); /* SAFE */
|
|
ADD_HEADER(buf);
|
|
|
|
last_modified();
|
|
}
|
|
|
|
CACHE_LIMITER_FUNC(private) {
|
|
ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
|
|
CACHE_LIMITER(private_no_expire)();
|
|
}
|
|
|
|
CACHE_LIMITER_FUNC(nocache) {
|
|
ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
|
|
|
|
/* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
|
|
ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, "
|
|
"post-check=0, pre-check=0");
|
|
|
|
/* For HTTP/1.0 conforming clients */
|
|
ADD_HEADER("Pragma: no-cache");
|
|
}
|
|
|
|
static php_session_cache_limiter_t php_session_cache_limiters[] = {
|
|
CACHE_LIMITER_ENTRY(public)
|
|
CACHE_LIMITER_ENTRY(private)
|
|
CACHE_LIMITER_ENTRY(private_no_expire)
|
|
CACHE_LIMITER_ENTRY(nocache)
|
|
{0}
|
|
};
|
|
|
|
static int php_session_cache_limiter() {
|
|
if (PS(cache_limiter)[0] == '\0') return 0;
|
|
|
|
Transport *transport = g_context->getTransport();
|
|
if (transport) {
|
|
if (transport->headersSent()) {
|
|
raise_warning("Cannot send session cache limiter - "
|
|
"headers already sent");
|
|
return -2;
|
|
}
|
|
|
|
php_session_cache_limiter_t *lim;
|
|
for (lim = php_session_cache_limiters; lim->name; lim++) {
|
|
if (!strcasecmp(lim->name, PS(cache_limiter).c_str())) {
|
|
lim->func();
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void f_session_set_cookie_params(int64_t lifetime,
|
|
CStrRef path /* = null_string */,
|
|
CStrRef domain /* = null_string */,
|
|
CVarRef secure /* = null */,
|
|
CVarRef httponly /* = null */) {
|
|
if (PS(use_cookies)) {
|
|
IniSetting::Set("session.cookie_lifetime", lifetime);
|
|
if (!path.isNull()) {
|
|
IniSetting::Set("session.cookie_path", path);
|
|
}
|
|
if (!domain.isNull()) {
|
|
IniSetting::Set("session.cookie_domain", domain);
|
|
}
|
|
if (!secure.isNull()) {
|
|
IniSetting::Set("session.cookie_secure", secure.toBoolean());
|
|
}
|
|
if (!httponly.isNull()) {
|
|
IniSetting::Set("session.cookie_httponly", httponly.toBoolean());
|
|
}
|
|
}
|
|
}
|
|
|
|
static const StaticString s_lifetime("lifetime");
|
|
static const StaticString s_path("path");
|
|
static const StaticString s_domain("domain");
|
|
static const StaticString s_secure("secure");
|
|
static const StaticString s_httponly("httponly");
|
|
|
|
Array f_session_get_cookie_params() {
|
|
ArrayInit ret(5);
|
|
ret.set(s_lifetime, PS(cookie_lifetime));
|
|
ret.set(s_path, String(PS(cookie_path)));
|
|
ret.set(s_domain, String(PS(cookie_domain)));
|
|
ret.set(s_secure, PS(cookie_secure));
|
|
ret.set(s_httponly, PS(cookie_httponly));
|
|
return ret.create();
|
|
}
|
|
|
|
String f_session_name(CStrRef newname /* = null_string */) {
|
|
String oldname = String(PS(session_name));
|
|
if (!newname.isNull()) {
|
|
IniSetting::Set("session.name", newname);
|
|
}
|
|
return oldname;
|
|
}
|
|
|
|
Variant f_session_module_name(CStrRef newname /* = null_string */) {
|
|
String oldname;
|
|
if (PS(mod) && PS(mod)->getName()) {
|
|
oldname = String(PS(mod)->getName(), CopyString);
|
|
}
|
|
|
|
if (!newname.isNull()) {
|
|
if (!SessionModule::Find(newname.data())) {
|
|
raise_warning("Cannot find named PHP session module (%s)",
|
|
newname.data());
|
|
return false;
|
|
}
|
|
if (PS(mod)) {
|
|
PS(mod)->close();
|
|
}
|
|
PS(mod) = NULL;
|
|
|
|
IniSetting::Set("session.save_handler", newname);
|
|
}
|
|
|
|
return oldname;
|
|
}
|
|
|
|
static bool check_handler(const char *name, std::string &member) {
|
|
if (!f_is_callable(name)) {
|
|
raise_warning("Argument '%s' is not a valid callback", name);
|
|
return false;
|
|
}
|
|
member = name;
|
|
return true;
|
|
}
|
|
|
|
bool f_session_set_save_handler(CStrRef open, CStrRef close, CStrRef read,
|
|
CStrRef write, CStrRef destroy, CStrRef gc) {
|
|
if (PS(session_status) != Session::None) {
|
|
return false;
|
|
}
|
|
|
|
if (!check_handler(open.data(), PS(ps_open))) return false;
|
|
if (!check_handler(close.data(), PS(ps_close))) return false;
|
|
if (!check_handler(read.data(), PS(ps_read))) return false;
|
|
if (!check_handler(write.data(), PS(ps_write))) return false;
|
|
if (!check_handler(destroy.data(), PS(ps_destroy))) return false;
|
|
if (!check_handler(gc.data(), PS(ps_gc))) return false;
|
|
|
|
IniSetting::Set("session.save_handler", "user");
|
|
return true;
|
|
}
|
|
|
|
String f_session_save_path(CStrRef newname /* = null_string */) {
|
|
if (!newname.isNull()) {
|
|
if (memchr(newname.data(), '\0', newname.size()) != NULL) {
|
|
raise_warning("The save_path cannot contain NULL characters");
|
|
return false;
|
|
}
|
|
IniSetting::Set("session.save_path", newname);
|
|
}
|
|
return String(PS(save_path));
|
|
}
|
|
|
|
String f_session_id(CStrRef newid /* = null_string */) {
|
|
String ret = PS(id);
|
|
if (!newid.isNull()) {
|
|
PS(id) = newid;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool f_session_regenerate_id(bool delete_old_session /* = false */) {
|
|
Transport *transport = g_context->getTransport();
|
|
if (transport && transport->headersSent()) {
|
|
raise_warning("Cannot regenerate session id - headers already sent");
|
|
return false;
|
|
}
|
|
|
|
if (PS(session_status) == Session::Active) {
|
|
if (!PS(id).empty()) {
|
|
if (delete_old_session && !PS(mod)->destroy(PS(id).data())) {
|
|
raise_warning("Session object destruction failed");
|
|
return false;
|
|
}
|
|
PS(id).reset();
|
|
}
|
|
|
|
PS(id) = PS(mod)->create_sid();
|
|
PS(send_cookie) = 1;
|
|
php_session_reset_id();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String f_session_cache_limiter(CStrRef new_cache_limiter /* = null_string */) {
|
|
String ret(PS(cache_limiter));
|
|
if (!new_cache_limiter.isNull()) {
|
|
IniSetting::Set("session.cache_limiter", new_cache_limiter);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int64_t f_session_cache_expire(CStrRef new_cache_expire /* = null_string */) {
|
|
int64_t ret = PS(cache_expire);
|
|
if (!new_cache_expire.isNull()) {
|
|
IniSetting::Set("session.cache_expire", new_cache_expire.toInt64());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant f_session_encode() {
|
|
String ret = php_session_encode();
|
|
if (ret.isNull()) {
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool f_session_decode(CStrRef data) {
|
|
if (PS(session_status) != Session::None) {
|
|
php_session_decode(data);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const StaticString
|
|
s_REQUEST_URI("REQUEST_URI"),
|
|
s_HTTP_REFERER("HTTP_REFERER");
|
|
|
|
bool f_session_start() {
|
|
PS(apply_trans_sid) = PS(use_trans_sid);
|
|
|
|
String value;
|
|
switch (PS(session_status)) {
|
|
case Session::Active:
|
|
raise_notice("A session had already been started - "
|
|
"ignoring session_start()");
|
|
return false;
|
|
case Session::Disabled:
|
|
{
|
|
if (!PS(mod) && IniSetting::Get("session.save_handler", value)) {
|
|
PS(mod) = SessionModule::Find(value.data());
|
|
if (!PS(mod)) {
|
|
raise_warning("Cannot find save handler '%s' - "
|
|
"session startup failed", value.data());
|
|
return false;
|
|
}
|
|
}
|
|
if (!PS(serializer) &&
|
|
IniSetting::Get("session.serialize_handler", value)) {
|
|
PS(serializer) = SessionSerializer::Find(value.data());
|
|
if (!PS(serializer)) {
|
|
raise_warning("Cannot find serialization handler '%s' - "
|
|
"session startup failed", value.data());
|
|
return false;
|
|
}
|
|
}
|
|
PS(session_status) = Session::None;
|
|
/* fallthrough */
|
|
}
|
|
default:
|
|
assert(PS(session_status) == Session::None);
|
|
PS(define_sid) = 1;
|
|
PS(send_cookie) = 1;
|
|
}
|
|
|
|
/*
|
|
* Cookies are preferred, because initially
|
|
* cookie and get variables will be available.
|
|
*/
|
|
GlobalVariables *g = get_global_variables();
|
|
if (PS(id).empty()) {
|
|
if (PS(use_cookies) &&
|
|
g->get(s__COOKIE).toArray().exists(String(PS(session_name)))) {
|
|
PS(id) = g->get(s__COOKIE)[String(PS(session_name))].toString();
|
|
PS(apply_trans_sid) = 0;
|
|
PS(send_cookie) = 0;
|
|
PS(define_sid) = 0;
|
|
}
|
|
|
|
if (!PS(use_only_cookies) && !PS(id) &&
|
|
g->get(s__GET).toArray().exists(String(PS(session_name)))) {
|
|
PS(id) = g->get(s__GET)[String(PS(session_name))].toString();
|
|
PS(send_cookie) = 0;
|
|
}
|
|
|
|
if (!PS(use_only_cookies) && !PS(id) &&
|
|
g->get(s__POST).toArray().exists(String(PS(session_name)))) {
|
|
PS(id) = g->get(s__POST)[String(PS(session_name))].toString();
|
|
PS(send_cookie) = 0;
|
|
}
|
|
}
|
|
|
|
int lensess = PS(session_name).size();
|
|
|
|
/* check the REQUEST_URI symbol for a string of the form
|
|
'<session-name>=<session-id>' to allow URLs of the form
|
|
http://yoursite/<session-name>=<session-id>/script.php */
|
|
if (!PS(use_only_cookies) && PS(id).empty()) {
|
|
value = g->get(s__SERVER)[s_REQUEST_URI].toString();
|
|
const char *p = strstr(value.data(), PS(session_name).c_str());
|
|
if (p && p[lensess] == '=') {
|
|
p += lensess + 1;
|
|
const char *q;
|
|
if ((q = strpbrk(p, "/?\\"))) {
|
|
PS(id) = String(p, q - p, CopyString);
|
|
PS(send_cookie) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check whether the current request was referred to by
|
|
an external site which invalidates the previously found id */
|
|
if (!PS(id).empty() && PS(extern_referer_chk)[0] != '\0') {
|
|
value = g->get(s__SERVER)[s_HTTP_REFERER].toString();
|
|
if (strstr(value.data(), PS(extern_referer_chk).c_str()) == NULL) {
|
|
PS(id).reset();
|
|
PS(send_cookie) = 1;
|
|
if (PS(use_trans_sid)) {
|
|
PS(apply_trans_sid) = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
php_session_initialize();
|
|
|
|
if (!PS(use_cookies) && PS(send_cookie)) {
|
|
if (PS(use_trans_sid)) {
|
|
PS(apply_trans_sid) = 1;
|
|
}
|
|
PS(send_cookie) = 0;
|
|
}
|
|
|
|
php_session_reset_id();
|
|
|
|
PS(session_status) = Session::Active;
|
|
|
|
php_session_cache_limiter();
|
|
|
|
if (PS(mod) && PS(gc_probability) > 0) {
|
|
int nrdels = -1;
|
|
|
|
int nrand = (int) ((float) PS(gc_divisor) * math_combined_lcg());
|
|
if (nrand < PS(gc_probability)) {
|
|
PS(mod)->gc(PS(gc_maxlifetime), &nrdels);
|
|
}
|
|
}
|
|
|
|
if (PS(session_status) != Session::Active) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool f_session_destroy() {
|
|
bool retval = true;
|
|
|
|
if (PS(session_status) != Session::Active) {
|
|
raise_warning("Trying to destroy uninitialized session");
|
|
return false;
|
|
}
|
|
|
|
if (!PS(mod)->destroy(PS(id).data())) {
|
|
retval = false;
|
|
raise_warning("Session object destruction failed");
|
|
}
|
|
|
|
s_session->requestShutdownImpl();
|
|
s_session->requestInit();
|
|
|
|
return retval;
|
|
}
|
|
|
|
Variant f_session_unset() {
|
|
if (PS(session_status) == Session::None) {
|
|
return false;
|
|
}
|
|
GlobalVariables *g = get_global_variables();
|
|
g->getRef(s__SESSION).reset();
|
|
return uninit_null();
|
|
}
|
|
|
|
void f_session_write_close() {
|
|
if (PS(session_status) == Session::Active) {
|
|
PS(session_status) = Session::None;
|
|
php_session_save_current_state();
|
|
}
|
|
}
|
|
|
|
void f_session_commit() {
|
|
f_session_write_close();
|
|
}
|
|
|
|
bool f_session_register(int _argc, CVarRef var_names,
|
|
CArrRef _argv /* = null_array */) {
|
|
throw NotSupportedException
|
|
(__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
|
|
"Relying on this feature is highly discouraged.");
|
|
}
|
|
|
|
bool f_session_unregister(CStrRef varname) {
|
|
throw NotSupportedException
|
|
(__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
|
|
"Relying on this feature is highly discouraged.");
|
|
}
|
|
|
|
bool f_session_is_registered(CStrRef varname) {
|
|
throw NotSupportedException
|
|
(__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
|
|
"Relying on this feature is highly discouraged.");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|