Arquivos
hhvm/hphp/runtime/ext/ext_session.cpp
T
Drew Paroski 84b9d9a3a2 Separate resources from objects, part 1
In HHVM (and HPHPc before it) we've been piggybacking resources on the
KindOfObject machinery. At the language level, resource is considered to
be a different type than object, and there are a number of differences
in behavior between objects and resources (ex. resources don't allow for
dynamic properties, resources don't work with the clone operator, the
"(object)" cast behaves differently for resources vs. objects, etc).

Piggybacking resources on the KindOfObject machinery has some downsides.
Code that deals with KindOfObject values often needs to check if the value
is a resource and go down a different code path. This makes things harder
to maintain and harder to keep parity with Zend. Also, these extra branches
hurt performance a little, and they make it harder for the JIT to do a good
job in some cases when its generating machine code that operates on objects.

This diff prepares the code base for a new KindOfResource type by adding a
new "Resource" smart pointer type (currently a typedef for the Object smart
pointer type) and it updates the C++ code and the idl files appropriately.
This diff is essentially a cosmetic change and should not impact run time
behavior. In the next diff (part 2) we'll actually add a new KindOfResource
type, detach ResourceData from the ObjectData inheritence hierarchy, and
provide a real implementation for the Resource smart pointer type (instead
of just aliasing the Object smart pointer type).
2013-07-10 11:16:33 -07:00

1640 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 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
void SessionRequestData::requestShutdownImpl() {
if (m_mod) {
try {
m_mod->close();
} catch (...) {}
}
m_id.reset();
}
std::vector<SessionModule*> SessionModule::RegisteredModules;
/*
* 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.toResource(), 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.toResource(),
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.toResource());
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))).toBoolean();
}
virtual bool close() {
return vm_call_user_func(
CREATE_VECTOR2(PS(ps_session_handler),
String("close")),
Array::Create()).toBoolean();
}
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)).toBoolean();
}
virtual bool destroy(const char *key) {
return vm_call_user_func(
CREATE_VECTOR2(PS(ps_session_handler),
String("destroy")),
CREATE_VECTOR1(String(key, CopyString))).toBoolean();
}
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)).toBoolean();
}
};
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).toArray()); 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).toArray()); 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);
}
///////////////////////////////////////////////////////////////////////////////
}