3d0c614b9a
C++11 cleanup (clean up easy enums) This is for runtime/base/... and ended up touching a lot of files because it turns out we have a lot of reasonably behaved enums.
1683 linhas
47 KiB
C++
1683 linhas
47 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;
|
|
|
|
Object m_ps_session_handler;
|
|
|
|
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(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("open")),
|
|
CREATE_VECTOR2(String(save_path, CopyString),
|
|
String(session_name, CopyString)));
|
|
}
|
|
|
|
virtual bool close() {
|
|
return vm_call_user_func(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("close")),
|
|
Array::Create());
|
|
}
|
|
|
|
virtual bool read(const char *key, String &value) {
|
|
Variant ret = vm_call_user_func(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("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(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("write")),
|
|
CREATE_VECTOR2(String(key, CopyString), value));
|
|
}
|
|
|
|
virtual bool destroy(const char *key) {
|
|
return vm_call_user_func(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("destroy")),
|
|
CREATE_VECTOR1(String(key, CopyString)));
|
|
}
|
|
|
|
virtual bool gc(int maxlifetime, int *nrdels) {
|
|
return vm_call_user_func(
|
|
CREATE_VECTOR2(PS(ps_session_handler),
|
|
String("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::Type::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::Type::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::DateFormat::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;
|
|
}
|
|
|
|
bool f_hphp_session_set_save_handler(CObjRef sessionhandler,
|
|
bool register_shutdown /* = true */) {
|
|
|
|
if (PS(session_status) != Session::None) {
|
|
return false;
|
|
}
|
|
|
|
PS(ps_session_handler) = sessionhandler;
|
|
if (register_shutdown) {
|
|
f_register_shutdown_function(1, String("session_write_close"));
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
void c_SessionHandler::t___construct() { }
|
|
c_SessionHandler::c_SessionHandler(Class* cb) : ExtObjectData(cb) {
|
|
m_mod = PS(mod);
|
|
}
|
|
c_SessionHandler::~c_SessionHandler() { }
|
|
|
|
bool c_SessionHandler::t_open(CStrRef save_path, CStrRef session_id) {
|
|
return m_mod->open(save_path->data(), session_id->data());
|
|
}
|
|
|
|
bool c_SessionHandler::t_close() {
|
|
return m_mod->close();
|
|
}
|
|
|
|
String c_SessionHandler::t_read(CStrRef session_id) {
|
|
String value;
|
|
if (m_mod->read(PS(id).data(), value)) {
|
|
php_session_decode(value);
|
|
return value;
|
|
}
|
|
return uninit_null();
|
|
}
|
|
|
|
bool c_SessionHandler::t_write(CStrRef session_id, CStrRef session_data) {
|
|
return m_mod->write(session_id->data(), session_data->data());
|
|
}
|
|
|
|
bool c_SessionHandler::t_destroy(CStrRef session_id) {
|
|
return m_mod->destroy(session_id->data());
|
|
}
|
|
|
|
bool c_SessionHandler::t_gc(int maxlifetime) {
|
|
int nrdels = -1;
|
|
return m_mod->gc(maxlifetime, &nrdels);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|