remove operator-> from String
[hiphop-php.git] / hphp / runtime / ext / ext_session.cpp
blobfe20fe3f9c2122d65d6a9737fb3dbefd697ef664
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/ext_session.h"
19 #include <string>
21 #include <sys/types.h>
22 #include <sys/time.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <dirent.h>
26 #include <vector>
28 #include "folly/String.h"
30 #include "hphp/util/lock.h"
31 #include "hphp/util/logger.h"
32 #include "hphp/util/compatibility.h"
34 #include "hphp/runtime/base/array-iterator.h"
35 #include "hphp/runtime/base/builtin-functions.h"
36 #include "hphp/runtime/base/datetime.h"
37 #include "hphp/runtime/base/ini-setting.h"
38 #include "hphp/runtime/base/object-data.h"
39 #include "hphp/runtime/base/request-local.h"
40 #include "hphp/runtime/base/string-buffer.h"
41 #include "hphp/runtime/base/variable-unserializer.h"
42 #include "hphp/runtime/base/zend-math.h"
43 #include "hphp/runtime/ext/ext_function.h"
44 #include "hphp/runtime/ext/ext_hash.h"
45 #include "hphp/runtime/ext/ext_options.h"
46 #include "hphp/runtime/vm/jit/translator-inline.h"
47 #include "hphp/runtime/base/request-event-handler.h"
49 namespace HPHP {
51 ///////////////////////////////////////////////////////////////////////////////
53 using std::string;
55 static bool ini_on_update_save_handler(const std::string& value);
56 static std::string ini_get_save_handler();
57 static bool ini_on_update_serializer(const std::string& value);
58 static std::string ini_get_serializer();
59 static bool ini_on_update_trans_sid(const bool& value);
60 static bool ini_on_update_save_dir(const std::string& value);
61 static bool mod_is_open();
63 ///////////////////////////////////////////////////////////////////////////////
64 // global data
66 class SessionSerializer;
67 class Session {
68 public:
69 enum Status {
70 Disabled,
71 None,
72 Active
75 std::string m_save_path;
76 std::string m_session_name;
77 std::string m_extern_referer_chk;
78 std::string m_entropy_file;
79 int64_t m_entropy_length;
80 std::string m_cache_limiter;
81 int64_t m_cookie_lifetime;
82 std::string m_cookie_path;
83 std::string m_cookie_domain;
84 bool m_cookie_secure;
85 bool m_cookie_httponly;
86 bool m_mod_data = false;
87 bool m_mod_user_implemented = false;
89 SessionModule *m_mod;
90 SessionModule *m_default_mod;
92 Status m_session_status;
93 int64_t m_gc_probability;
94 int64_t m_gc_divisor;
95 int64_t m_gc_maxlifetime;
96 int m_module_number;
97 int64_t m_cache_expire;
99 ObjectData *m_ps_session_handler;
101 SessionSerializer *m_serializer;
103 bool m_auto_start;
104 bool m_use_cookies;
105 bool m_use_only_cookies;
106 bool m_use_trans_sid; // contains the INI value of whether to use trans-sid
107 bool m_apply_trans_sid; // whether to enable trans-sid for current request
109 std::string m_hash_func;
110 int64_t m_hash_bits_per_character;
112 int m_send_cookie;
113 int m_define_sid;
114 bool m_invalid_session_id; /* allows the driver to report about an invalid
115 session id and request id regeneration */
117 Session()
118 : m_entropy_length(0), m_cookie_lifetime(0), m_cookie_secure(false),
119 m_cookie_httponly(false), m_mod(nullptr), m_default_mod(nullptr),
120 m_session_status(None), m_gc_probability(0), m_gc_divisor(0),
121 m_gc_maxlifetime(0), m_module_number(0), m_cache_expire(0),
122 m_ps_session_handler(nullptr),
123 m_serializer(nullptr), m_auto_start(false), m_use_cookies(false),
124 m_use_only_cookies(false), m_use_trans_sid(false),
125 m_apply_trans_sid(false), m_hash_bits_per_character(0), m_send_cookie(0),
126 m_define_sid(0), m_invalid_session_id(false) {
130 const int64_t k_PHP_SESSION_DISABLED = Session::Disabled;
131 const int64_t k_PHP_SESSION_NONE = Session::None;
132 const int64_t k_PHP_SESSION_ACTIVE = Session::Active;
133 const StaticString s_session_ext_name("session");
135 struct SessionRequestData final : RequestEventHandler, Session {
136 SessionRequestData() {}
138 void destroy() {
139 m_id.reset();
140 m_session_status = Session::None;
141 m_ps_session_handler = nullptr;
144 void requestInit() override {
145 destroy();
148 void requestShutdown() override {
149 // We don't actually want to do our requestShutdownImpl here---it
150 // is run explicitly from the execution context, because it could
151 // run user code.
154 void requestShutdownImpl();
156 public:
157 String m_id;
160 IMPLEMENT_STATIC_REQUEST_LOCAL(SessionRequestData, s_session);
161 #define PS(name) s_session->m_ ## name
163 void SessionRequestData::requestShutdownImpl() {
164 if (mod_is_open()) {
165 try {
166 m_mod->close();
167 } catch (...) {}
169 if (ObjectData* obj = m_ps_session_handler) {
170 m_ps_session_handler = nullptr;
171 decRefObj(obj);
173 m_id.reset();
176 void ext_session_request_shutdown() {
177 f_session_write_close();
178 s_session->requestShutdownImpl();
181 std::vector<SessionModule*> SessionModule::RegisteredModules;
184 * Note that we cannot use the BASE64 alphabet here, because
185 * it contains "/" and "+": both are unacceptable for simple inclusion
186 * into URLs.
188 static char hexconvtab[] =
189 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
191 /* returns a pointer to the byte after the last valid character in out */
192 static void bin_to_readable(const String& in, StringBuffer &out, char nbits) {
193 unsigned char *p = (unsigned char *)in.data();
194 unsigned char *q = (unsigned char *)in.data() + in.size();
195 unsigned short w = 0;
196 int have = 0;
197 int mask = (1 << nbits) - 1;
199 while (true) {
200 if (have < nbits) {
201 if (p < q) {
202 w |= *p++ << have;
203 have += 8;
204 } else {
205 /* consumed everything? */
206 if (have == 0) break;
207 /* No? We need a final round */
208 have = nbits;
212 /* consume nbits */
213 out.append(hexconvtab[w & mask]);
214 w >>= nbits;
215 have -= nbits;
219 const StaticString
220 s_REMOTE_ADDR("REMOTE_ADDR"),
221 s__SERVER("_SERVER"),
222 s__SESSION("_SESSION"),
223 s__COOKIE("_COOKIE"),
224 s__GET("_GET"),
225 s__POST("_POST");
227 String SessionModule::create_sid() {
228 GlobalVariables *g = get_global_variables();
229 String remote_addr = g->get(s__SERVER)[s_REMOTE_ADDR].toString();
231 struct timeval tv;
232 gettimeofday(&tv, NULL);
234 StringBuffer buf;
235 buf.printf("%.15s%ld%ld%0.8F", remote_addr.data(),
236 tv.tv_sec, (long int)tv.tv_usec, math_combined_lcg() * 10);
238 if (String(PS(hash_func)).isNumeric()) {
239 switch (String(PS(hash_func)).toInt64()) {
240 case md5: PS(hash_func) = "md5"; break;
241 case sha1: PS(hash_func) = "sha1"; break;
245 Variant context = HHVM_FN(hash_init)(PS(hash_func));
246 if (same(context, false)) {
247 Logger::Error("Invalid session hash function: %s", PS(hash_func).c_str());
248 return String();
250 if (!HHVM_FN(hash_update)(context.toResource(), buf.detach())) {
251 Logger::Error("hash_update() failed");
252 return String();
255 if (PS(entropy_length) > 0) {
256 int fd = ::open(PS(entropy_file).c_str(), O_RDONLY);
257 if (fd >= 0) {
258 unsigned char rbuf[2048];
259 int n;
260 int to_read = PS(entropy_length);
261 while (to_read > 0) {
262 n = ::read(fd, rbuf, (to_read < (int)sizeof(rbuf) ?
263 to_read : (int)sizeof(buf)));
264 if (n <= 0) break;
265 if (!HHVM_FN(hash_update)(context.toResource(),
266 String((const char *)rbuf, n, CopyString))) {
267 Logger::Error("hash_update() failed");
268 ::close(fd);
269 return String();
271 to_read -= n;
273 ::close(fd);
277 String hashed = HHVM_FN(hash_final)(context.toResource());
279 if (PS(hash_bits_per_character) < 4 || PS(hash_bits_per_character) > 6) {
280 PS(hash_bits_per_character) = 4;
281 raise_warning("The ini setting hash_bits_per_character is out of range "
282 "(should be 4, 5, or 6) - using 4 for now");
285 StringBuffer readable;
286 bin_to_readable(hashed, readable, PS(hash_bits_per_character));
287 return readable.detach();
290 ///////////////////////////////////////////////////////////////////////////////
291 // SystemlibSessionModule
293 static StaticString s_SessionHandlerInterface("SessionHandlerInterface");
295 static StaticString s_open("open");
296 static StaticString s_close("close");
297 static StaticString s_read("read");
298 static StaticString s_write("write");
299 static StaticString s_gc("gc");
300 static StaticString s_destroy("destroy");
302 Class *SystemlibSessionModule::s_SHIClass = nullptr;
305 * Relies on the fact that only one SessionModule will be active
306 * in a given thread at any one moment.
308 IMPLEMENT_REQUEST_LOCAL(SystemlibSessionInstance, SystemlibSessionModule::s_obj);
310 Func* SystemlibSessionModule::lookupFunc(Class *cls, StringData *fname) {
311 Func *f = cls->lookupMethod(fname);
312 if (!f) {
313 throw InvalidArgumentException(0, "class %s must declare method %s()",
314 m_classname, fname->data());
317 if (f->attrs() & AttrStatic) {
318 throw InvalidArgumentException(0, "%s::%s() must not be declared static",
319 m_classname, fname->data());
322 if (f->attrs() & (AttrPrivate|AttrProtected|AttrAbstract)) {
323 throw InvalidArgumentException(0, "%s::%s() must be declared public",
324 m_classname, fname->data());
327 return f;
330 void SystemlibSessionModule::lookupClass() {
331 Class *cls;
332 if (!(cls = Unit::loadClass(String(m_classname, CopyString).get()))) {
333 throw InvalidArgumentException(0, "Unable to locate systemlib class '%s'",
334 m_classname);
337 if (cls->attrs() & (AttrTrait|AttrInterface)) {
338 throw InvalidArgumentException(0, "'%s' must be a real class, "
339 "not an interface or trait", m_classname);
342 if (!s_SHIClass) {
343 s_SHIClass = Unit::lookupClass(s_SessionHandlerInterface.get());
344 if (!s_SHIClass) {
345 throw InvalidArgumentException(0, "Unable to locate '%s' interface",
346 s_SessionHandlerInterface.data());
350 if (!cls->classof(s_SHIClass)) {
351 throw InvalidArgumentException(0, "SystemLib session module '%s' "
352 "must implement '%s'",
353 m_classname,
354 s_SessionHandlerInterface.data());
357 if (LookupResult::MethodFoundWithThis !=
358 g_context->lookupCtorMethod(m_ctor, cls)) {
359 throw InvalidArgumentException(0, "Unable to call %s's constructor",
360 m_classname);
363 m_open = lookupFunc(cls, s_open.get());
364 m_close = lookupFunc(cls, s_close.get());
365 m_read = lookupFunc(cls, s_read.get());
366 m_write = lookupFunc(cls, s_write.get());
367 m_gc = lookupFunc(cls, s_gc.get());
368 m_destroy = lookupFunc(cls, s_destroy.get());
369 m_cls = cls;
372 ObjectData* SystemlibSessionModule::getObject() {
373 if (auto o = s_obj->getObject()) {
374 return o;
377 JIT::VMRegAnchor _;
378 Variant ret;
380 if (!m_cls) {
381 lookupClass();
383 s_obj->setObject(ObjectData::newInstance(m_cls));
384 ObjectData *obj = s_obj->getObject();
385 g_context->invokeFuncFew(ret.asTypedValue(), m_ctor, obj);
387 return obj;
390 bool SystemlibSessionModule::open(const char *save_path,
391 const char *session_name) {
392 ObjectData *obj = getObject();
394 Variant savePath = String(save_path, CopyString);
395 Variant sessionName = String(session_name, CopyString);
396 Variant ret;
397 TypedValue args[2] = { *savePath.asCell(), *sessionName.asCell() };
398 g_context->invokeFuncFew(ret.asTypedValue(), m_open, obj,
399 nullptr, 2, args);
401 if (ret.isBoolean() && ret.toBoolean()) {
402 return true;
405 raise_warning("Failed calling %s::open()", m_classname);
406 return false;
409 bool SystemlibSessionModule::close() {
410 auto obj = s_obj->getObject();
411 if (!obj) {
412 // close() can be called twice in some circumstances
413 return true;
416 Variant ret;
417 g_context->invokeFuncFew(ret.asTypedValue(), m_close, obj);
418 s_obj->destroy();
420 if (ret.isBoolean() && ret.toBoolean()) {
421 return true;
424 raise_warning("Failed calling %s::close()", m_classname);
425 return false;
428 bool SystemlibSessionModule::read(const char *key, String &value) {
429 ObjectData *obj = getObject();
431 Variant sessionKey = String(key, CopyString);
432 Variant ret;
433 g_context->invokeFuncFew(ret.asTypedValue(), m_read, obj,
434 nullptr, 1, sessionKey.asCell());
436 if (ret.isString()) {
437 value = ret.toString();
438 return true;
441 raise_warning("Failed calling %s::read()", m_classname);
442 return false;
445 bool SystemlibSessionModule::write(const char *key, const String& value) {
446 ObjectData *obj = getObject();
448 Variant sessionKey = String(key, CopyString);
449 Variant sessionVal = value;
450 Variant ret;
451 TypedValue args[2] = { *sessionKey.asCell(), *sessionVal.asCell() };
452 g_context->invokeFuncFew(ret.asTypedValue(), m_write, obj,
453 nullptr, 2, args);
455 if (ret.isBoolean() && ret.toBoolean()) {
456 return true;
459 raise_warning("Failed calling %s::write()", m_classname);
460 return false;
463 bool SystemlibSessionModule::destroy(const char *key) {
464 ObjectData *obj = getObject();
466 Variant sessionKey = String(key, CopyString);
467 Variant ret;
468 g_context->invokeFuncFew(ret.asTypedValue(), m_destroy, obj,
469 nullptr, 1, sessionKey.asCell());
471 if (ret.isBoolean() && ret.toBoolean()) {
472 return true;
475 raise_warning("Failed calling %s::destroy()", m_classname);
476 return false;
479 bool SystemlibSessionModule::gc(int maxlifetime, int *nrdels) {
480 ObjectData *obj = getObject();
482 Variant maxLifeTime = maxlifetime;
483 Variant ret;
484 g_context->invokeFuncFew(ret.asTypedValue(), m_gc, obj,
485 nullptr, 1, maxLifeTime.asCell());
487 if (ret.isInteger()) {
488 if (nrdels) {
489 *nrdels = ret.toInt64();
491 return true;
494 raise_warning("Failed calling %s::gc()", m_classname);
495 return false;
498 //////////////////////////////////////////////////////////////////////////////
499 // SystemlibSessionModule implementations
501 static class RedisSessionModule : public SystemlibSessionModule {
502 public:
503 RedisSessionModule() :
504 SystemlibSessionModule("redis", "RedisSessionModule") { }
505 } s_redis_session_module;
507 //////////////////////////////////////////////////////////////////////////////
508 // FileSessionModule
510 class FileSessionData {
511 public:
512 FileSessionData() : m_fd(-1), m_dirdepth(0), m_st_size(0), m_filemode(0600) {
515 bool open(const char *save_path, const char *session_name) {
516 String tmpdir;
517 if (*save_path == '\0') {
518 tmpdir = f_sys_get_temp_dir();
519 save_path = tmpdir.data();
522 /* split up input parameter */
523 const char *argv[3];
524 int argc = 0;
525 const char *last = save_path;
526 const char *p = strchr(save_path, ';');
527 while (p) {
528 argv[argc++] = last; last = ++p; p = strchr(p, ';');
529 if (argc > 1) break;
531 argv[argc++] = last;
533 if (argc > 1) {
534 errno = 0;
535 m_dirdepth = (size_t) strtol(argv[0], NULL, 10);
536 if (errno == ERANGE) {
537 raise_warning("The first parameter in session.save_path is invalid");
538 return false;
542 if (argc > 2) {
543 errno = 0;
544 m_filemode = strtol(argv[1], NULL, 8);
545 if (errno == ERANGE || m_filemode < 0 || m_filemode > 07777) {
546 raise_warning("The second parameter in session.save_path is invalid");
547 return false;
551 save_path = argv[argc - 1];
552 if (File::TranslatePath(save_path).empty()) {
553 raise_warning("Unable to open save_path %s", save_path);
554 return false;
557 m_fd = -1;
558 m_basedir = save_path;
559 PS(mod_data) = true;
560 return true;
563 bool close() {
564 closeImpl();
565 m_lastkey.clear();
566 m_basedir.clear();
567 PS(mod_data) = false;
568 return true;
571 bool read(const char *key, String &value) {
572 openImpl(key);
573 if (m_fd < 0) {
574 return false;
577 struct stat sbuf;
578 if (fstat(m_fd, &sbuf)) {
579 return false;
581 m_st_size = sbuf.st_size;
582 if (m_st_size == 0) {
583 value = "";
584 return true;
587 String s = String(m_st_size, ReserveString);
588 char *val = s.bufferSlice().ptr;
590 #if defined(HAVE_PREAD)
591 long n = pread(m_fd, val, m_st_size, 0);
592 #else
593 lseek(m_fd, 0, SEEK_SET);
594 long n = ::read(m_fd, val, m_st_size);
595 #endif
597 if (n != (int)m_st_size) {
598 if (n == -1) {
599 raise_warning("read failed: %s (%d)", folly::errnoStr(errno).c_str(),
600 errno);
601 } else {
602 raise_warning("read returned less bytes than requested");
604 return false;
607 value = s.setSize(m_st_size);
608 return true;
611 bool write(const char *key, const String& value) {
612 openImpl(key);
613 if (m_fd < 0) {
614 return false;
617 struct stat sbuf;
618 if (fstat(m_fd, &sbuf)) {
619 return false;
621 m_st_size = sbuf.st_size;
624 * truncate file, if the amount of new data is smaller than
625 * the existing data set.
627 if (value.size() < (int)m_st_size) {
628 if (ftruncate(m_fd, 0) < 0) {
629 raise_warning("truncate failed: %s (%d)",
630 folly::errnoStr(errno).c_str(), errno);
631 return false;
635 #if defined(HAVE_PWRITE)
636 long n = pwrite(m_fd, value.data(), value.size(), 0);
637 #else
638 lseek(m_fd, 0, SEEK_SET);
639 long n = ::write(m_fd, value.data(), value.size());
640 #endif
642 if (n != value.size()) {
643 if (n == -1) {
644 raise_warning("write failed: %s (%d)",
645 folly::errnoStr(errno).c_str(), errno);
646 } else {
647 raise_warning("write wrote less bytes than requested");
649 return false;
652 return true;
655 bool destroy(const char *key) {
656 char buf[PATH_MAX];
657 if (!createPath(buf, sizeof(buf), key)) {
658 return false;
661 if (m_fd != -1) {
662 closeImpl();
663 if (unlink(buf) == -1) {
664 /* This is a little safety check for instances when we are dealing
665 with a regenerated session that was not yet written to disk */
666 if (!access(buf, F_OK)) {
667 return false;
672 return true;
675 bool gc(int maxlifetime, int *nrdels) {
676 /* we don't perform any cleanup, if dirdepth is larger than 0.
677 we return true, since all cleanup should be handled by
678 an external entity (i.e. find -ctime x | xargs rm) */
679 if (m_dirdepth == 0) {
680 *nrdels = CleanupDir(m_basedir.c_str(), maxlifetime);
682 return true;
685 private:
686 int m_fd;
687 std::string m_lastkey;
688 std::string m_basedir;
689 size_t m_dirdepth;
690 size_t m_st_size;
691 int m_filemode;
693 /* If you change the logic here, please also update the error message in
694 * ps_files_open() appropriately */
695 static bool IsValid(const char *key) {
696 const char *p; char c;
697 bool ret = true;
698 for (p = key; (c = *p); p++) {
699 /* valid characters are a..z,A..Z,0..9 */
700 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
701 || (c >= '0' && c <= '9') || c == ',' || c == '-')) {
702 ret = false;
703 break;
706 size_t len = p - key;
707 if (len == 0) {
708 ret = false;
710 return ret;
713 #define FILE_PREFIX "sess_"
715 bool createPath(char *buf, size_t buflen, const char *key) {
716 size_t key_len = strlen(key);
717 if (key_len <= m_dirdepth ||
718 buflen < (m_basedir.size() + 2 * m_dirdepth + key_len +
719 5 + sizeof(FILE_PREFIX))) {
720 return false;
723 const char *p = key;
724 int n = m_basedir.size();
725 memcpy(buf, m_basedir.c_str(), n);
726 buf[n++] = PHP_DIR_SEPARATOR;
727 for (int i = 0; i < (int)m_dirdepth; i++) {
728 buf[n++] = *p++;
729 buf[n++] = PHP_DIR_SEPARATOR;
731 memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
732 n += sizeof(FILE_PREFIX) - 1;
733 memcpy(buf + n, key, key_len);
734 n += key_len;
735 buf[n] = '\0';
737 return true;
740 #ifndef O_BINARY
741 #define O_BINARY 0
742 #endif
744 void closeImpl() {
745 if (m_fd != -1) {
746 #ifdef PHP_WIN32
747 /* On Win32 locked files that are closed without being explicitly
748 unlocked will be unlocked only when "system resources become
749 available". */
750 flock(m_fd, LOCK_UN);
751 #endif
752 ::close(m_fd);
753 m_fd = -1;
757 void openImpl(const char *key) {
758 if (m_fd < 0 || !m_lastkey.empty() || m_lastkey != key) {
759 m_lastkey.clear();
760 closeImpl();
762 if (!IsValid(key)) {
763 raise_warning("The session id contains illegal characters, "
764 "valid characters are a-z, A-Z, 0-9 and '-,'");
765 PS(invalid_session_id) = true;
766 return;
769 char buf[PATH_MAX];
770 if (!createPath(buf, sizeof(buf), key)) {
771 return;
774 m_lastkey = key;
775 m_fd = ::open(buf, O_CREAT | O_RDWR | O_BINARY, m_filemode);
777 if (m_fd != -1) {
778 #ifdef PHP_WIN32
779 flock(m_fd, LOCK_EX);
780 #endif
782 #ifdef F_SETFD
783 # ifndef FD_CLOEXEC
784 # define FD_CLOEXEC 1
785 # endif
786 if (fcntl(m_fd, F_SETFD, FD_CLOEXEC)) {
787 raise_warning("fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)",
788 m_fd, folly::errnoStr(errno).c_str(), errno);
790 #endif
791 } else {
792 raise_warning("open(%s, O_RDWR) failed: %s (%d)", buf,
793 folly::errnoStr(errno).c_str(), errno);
798 static int CleanupDir(const char *dirname, int maxlifetime) {
799 DIR *dir = opendir(dirname);
800 if (!dir) {
801 raise_notice("ps_files_cleanup_dir: opendir(%s) failed: %s (%d)",
802 dirname, folly::errnoStr(errno).c_str(), errno);
803 return 0;
806 time_t now;
807 time(&now);
809 size_t dirname_len = strlen(dirname);
810 char dentry[sizeof(struct dirent) + PATH_MAX];
811 struct dirent *entry = (struct dirent *) &dentry;
812 struct stat sbuf;
813 int nrdels = 0;
815 /* Prepare buffer (dirname never changes) */
816 char buf[PATH_MAX];
817 memcpy(buf, dirname, dirname_len);
818 buf[dirname_len] = PHP_DIR_SEPARATOR;
820 while (readdir_r(dir, (struct dirent *)dentry, &entry) == 0 && entry) {
821 /* does the file start with our prefix? */
822 if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
823 size_t entry_len = strlen(entry->d_name);
825 /* does it fit into our buffer? */
826 if (entry_len + dirname_len + 2 < PATH_MAX) {
827 /* create the full path.. */
828 memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
830 /* NUL terminate it and */
831 buf[dirname_len + entry_len + 1] = '\0';
833 /* check whether its last access was more than maxlifet ago */
834 if (stat(buf, &sbuf) == 0 && (now - sbuf.st_mtime) > maxlifetime) {
835 unlink(buf);
836 nrdels++;
842 closedir(dir);
843 return nrdels;
846 IMPLEMENT_THREAD_LOCAL(FileSessionData, s_file_session_data);
848 class FileSessionModule : public SessionModule {
849 public:
850 FileSessionModule() : SessionModule("files") {
852 virtual bool open(const char *save_path, const char *session_name) {
853 return s_file_session_data->open(save_path, session_name);
855 virtual bool close() {
856 return s_file_session_data->close();
858 virtual bool read(const char *key, String &value) {
859 return s_file_session_data->read(key, value);
861 virtual bool write(const char *key, const String& value) {
862 return s_file_session_data->write(key, value);
864 virtual bool destroy(const char *key) {
865 return s_file_session_data->destroy(key);
867 virtual bool gc(int maxlifetime, int *nrdels) {
868 return s_file_session_data->gc(maxlifetime, nrdels);
871 static FileSessionModule s_file_session_module;
873 ///////////////////////////////////////////////////////////////////////////////
874 // UserSessionModule
876 class UserSessionModule : public SessionModule {
877 public:
878 UserSessionModule() : SessionModule("user") {}
880 virtual bool open(const char *save_path, const char *session_name) {
881 auto func = make_packed_array(Object(PS(ps_session_handler)), s_open);
882 auto args = make_packed_array(String(save_path), String(session_name));
884 auto res = vm_call_user_func(func, args);
885 PS(mod_user_implemented) = true;
886 return res.toBoolean();
889 virtual bool close() {
890 auto func = make_packed_array(Object(PS(ps_session_handler)), s_close);
891 auto args = Array::Create();
893 auto res = vm_call_user_func(func, args);
894 PS(mod_user_implemented) = false;
895 return res.toBoolean();
898 virtual bool read(const char *key, String &value) {
899 Variant ret = vm_call_user_func(
900 make_packed_array(Object(PS(ps_session_handler)), s_read),
901 make_packed_array(String(key))
903 if (ret.isString()) {
904 value = ret.toString();
905 return true;
907 return false;
910 virtual bool write(const char *key, const String& value) {
911 return vm_call_user_func(
912 make_packed_array(Object(PS(ps_session_handler)), s_write),
913 make_packed_array(String(key, CopyString), value)
914 ).toBoolean();
917 virtual bool destroy(const char *key) {
918 return vm_call_user_func(
919 make_packed_array(Object(PS(ps_session_handler)), s_destroy),
920 make_packed_array(String(key))
921 ).toBoolean();
924 virtual bool gc(int maxlifetime, int *nrdels) {
925 return vm_call_user_func(
926 make_packed_array(Object(PS(ps_session_handler)), s_gc),
927 make_packed_array((int64_t)maxlifetime)
928 ).toBoolean();
931 static UserSessionModule s_user_session_module;
933 ///////////////////////////////////////////////////////////////////////////////
934 // session serializers
936 class SessionSerializer {
937 public:
938 explicit SessionSerializer(const char *name) : m_name(name) {
939 RegisteredSerializers.push_back(this);
941 virtual ~SessionSerializer() {}
943 const char *getName() const { return m_name; }
945 virtual String encode() = 0;
946 virtual bool decode(const String& value) = 0;
948 static SessionSerializer *Find(const char *name) {
949 for (unsigned int i = 0; i < RegisteredSerializers.size(); i++) {
950 SessionSerializer *ss = RegisteredSerializers[i];
951 if (ss && strcasecmp(name, ss->m_name) == 0) {
952 return ss;
955 return NULL;
958 private:
959 static std::vector<SessionSerializer*> RegisteredSerializers;
961 const char *m_name;
963 std::vector<SessionSerializer*> SessionSerializer::RegisteredSerializers;
965 #define PS_BIN_NR_OF_BITS 8
966 #define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
967 #define PS_BIN_MAX (PS_BIN_UNDEF-1)
969 class BinarySessionSerializer : public SessionSerializer {
970 public:
971 BinarySessionSerializer() : SessionSerializer("php_binary") {}
973 virtual String encode() {
974 StringBuffer buf;
975 GlobalVariables *g = get_global_variables();
976 for (ArrayIter iter(g->get(s__SESSION).toArray()); iter; ++iter) {
977 Variant key = iter.first();
978 if (key.isString()) {
979 String skey = key.toString();
980 if (skey.size() <= PS_BIN_MAX) {
981 buf.append((unsigned char)skey.size());
982 buf.append(skey);
983 buf.append(f_serialize(iter.second()));
985 } else {
986 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
989 return buf.detach();
992 virtual bool decode(const String& value) {
993 const char *endptr = value.data() + value.size();
994 GlobalVariables *g = get_global_variables();
995 for (const char *p = value.data(); p < endptr; ) {
996 int namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
997 if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
998 return false;
1001 int has_value = *p & PS_BIN_UNDEF ? 0 : 1;
1002 String key(p + 1, namelen, CopyString);
1003 p += namelen + 1;
1004 if (has_value) {
1005 VariableUnserializer vu(p, endptr,
1006 VariableUnserializer::Type::Serialize);
1007 try {
1008 g->getRef(s__SESSION).set(key, vu.unserialize());
1009 p = vu.head();
1010 } catch (Exception &e) {
1014 return true;
1017 static BinarySessionSerializer s_binary_session_serializer;
1019 #define PS_DELIMITER '|'
1020 #define PS_UNDEF_MARKER '!'
1022 class PhpSessionSerializer : public SessionSerializer {
1023 public:
1024 PhpSessionSerializer() : SessionSerializer("php") {}
1026 virtual String encode() {
1027 StringBuffer buf;
1028 GlobalVariables *g = get_global_variables();
1029 for (ArrayIter iter(g->get(s__SESSION).toArray()); iter; ++iter) {
1030 Variant key = iter.first();
1031 if (key.isString()) {
1032 String skey = key.toString();
1033 buf.append(skey);
1034 if (skey.find(PS_DELIMITER) >= 0) {
1035 return String();
1037 buf.append(PS_DELIMITER);
1038 buf.append(f_serialize(iter.second()));
1039 } else {
1040 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1043 return buf.detach();
1046 virtual bool decode(const String& value) {
1047 const char *p = value.data();
1048 const char *endptr = value.data() + value.size();
1049 GlobalVariables *g = get_global_variables();
1050 while (p < endptr) {
1051 const char *q = p;
1052 while (*q != PS_DELIMITER) {
1053 if (++q >= endptr) return true;
1055 int has_value;
1056 if (p[0] == PS_UNDEF_MARKER) {
1057 p++;
1058 has_value = 0;
1059 } else {
1060 has_value = 1;
1063 String key(p, q - p, CopyString);
1064 q++;
1065 if (has_value) {
1066 VariableUnserializer vu(q, endptr,
1067 VariableUnserializer::Type::Serialize);
1068 try {
1069 g->getRef(s__SESSION).set(key, vu.unserialize());
1070 q = vu.head();
1071 } catch (Exception &e) {
1074 p = q;
1076 return true;
1079 static PhpSessionSerializer s_php_session_serializer;
1081 ///////////////////////////////////////////////////////////////////////////////
1083 #define SESSION_CHECK_ACTIVE_STATE \
1084 if (PS(session_status) == Session::Active) { \
1085 raise_warning("A session is active. You cannot change the session" \
1086 " module's ini settings at this time"); \
1087 return false; \
1090 static bool mod_is_open() {
1091 return PS(mod_data) || PS(mod_user_implemented);
1094 static bool ini_on_update_save_handler(const std::string& value) {
1095 SESSION_CHECK_ACTIVE_STATE;
1096 PS(mod) = SessionModule::Find(value.c_str());
1097 return true;
1100 static std::string ini_get_save_handler() {
1101 auto &mod = PS(mod);
1102 if (mod == nullptr) {
1103 return "";
1105 return mod->getName();
1108 static bool ini_on_update_serializer(const std::string& value) {
1109 SESSION_CHECK_ACTIVE_STATE;
1110 PS(serializer) = SessionSerializer::Find(value.data());
1111 return true;
1114 static std::string ini_get_serializer() {
1115 auto &serializer = PS(serializer);
1116 if (serializer == nullptr) {
1117 return "";
1119 return serializer->getName();
1122 static bool ini_on_update_trans_sid(const bool& value) {
1123 SESSION_CHECK_ACTIVE_STATE;
1124 return true;
1127 static bool ini_on_update_save_dir(const std::string& value) {
1128 if (value.find('\0') >= 0) {
1129 return false;
1131 const char *path = value.data() + value.rfind(';') + 1;
1132 if (File::TranslatePath(path).empty()) {
1133 return false;
1135 return true;
1138 ///////////////////////////////////////////////////////////////////////////////
1140 static int php_session_destroy() {
1141 int retval = true;
1143 if (PS(session_status) != Session::Active) {
1144 raise_warning("Trying to destroy uninitialized session");
1145 return false;
1148 if (PS(mod)->destroy(PS(id).data()) == false) {
1149 retval = false;
1150 raise_warning("Session object destruction failed");
1153 s_session->requestShutdownImpl();
1154 s_session->destroy();
1156 return retval;
1159 static String php_session_encode() {
1160 if (!PS(serializer)) {
1161 raise_warning("Unknown session.serialize_handler. "
1162 "Failed to encode session object");
1163 return String();
1165 return PS(serializer)->encode();
1168 static void php_session_decode(const String& value) {
1169 if (!PS(serializer)) {
1170 raise_warning("Unknown session.serialize_handler. "
1171 "Failed to decode session object");
1172 return;
1174 if (!PS(serializer)->decode(value)) {
1175 php_session_destroy();
1176 raise_warning("Failed to decode session object. "
1177 "Session has been destroyed");
1181 static void php_session_initialize() {
1182 /* check session name for invalid characters */
1183 if (strpbrk(PS(id).data(), "\r\n\t <>'\"\\")) {
1184 PS(id).reset();
1187 if (!PS(mod)) {
1188 raise_error("No storage module chosen - failed to initialize session");
1189 return;
1192 /* Open session handler first */
1193 if (!PS(mod)->open(PS(save_path).c_str(), PS(session_name).c_str())) {
1194 raise_error("Failed to initialize storage module: %s (path: %s)",
1195 PS(mod)->getName(), PS(save_path).c_str());
1196 return;
1199 /* If there is no ID, use session module to create one */
1200 int attempts = 3;
1201 if (PS(id).empty()) {
1202 new_session:
1203 PS(id) = PS(mod)->create_sid();
1204 if (PS(id).empty()) {
1205 raise_error("Failed to create session id: %s", PS(mod)->getName());
1206 return;
1208 if (PS(use_cookies)) {
1209 PS(send_cookie) = 1;
1213 /* Read data */
1214 /* Question: if you create a SID here, should you also try to read data?
1215 * I'm not sure, but while not doing so will remove one session operation
1216 * it could prove usefull for those sites which wish to have "default"
1217 * session information
1220 /* Unconditionally destroy existing arrays -- possible dirty data */
1221 GlobalVariables *g = get_global_variables();
1222 g->set(s__SESSION, Array::Create(), false);
1224 PS(invalid_session_id) = false;
1225 String value;
1226 if (PS(mod)->read(PS(id).data(), value)) {
1227 php_session_decode(value);
1228 } else if (PS(invalid_session_id)) {
1229 /* address instances where the session read fails due to an invalid id */
1230 PS(invalid_session_id) = false;
1231 PS(id).reset();
1232 if (--attempts > 0) {
1233 goto new_session;
1238 static void php_session_save_current_state() {
1239 bool ret = false;
1240 if (mod_is_open()) {
1241 String value = php_session_encode();
1242 if (!value.isNull()) {
1243 ret = PS(mod)->write(PS(id).data(), value);
1246 if (!ret) {
1247 raise_warning("Failed to write session data (%s). Please verify that the "
1248 "current setting of session.save_path is correct (%s)",
1249 PS(mod)->getName(), PS(save_path).c_str());
1251 if (mod_is_open()) {
1252 PS(mod)->close();
1256 ///////////////////////////////////////////////////////////////////////////////
1257 // Cookie Management
1259 static void php_session_send_cookie() {
1260 Transport *transport = g_context->getTransport();
1261 if (!transport) return;
1263 if (transport->headersSent()) {
1264 raise_warning("Cannot send session cookie - headers already sent");
1265 return;
1268 int64_t expire = 0;
1269 if (PS(cookie_lifetime) > 0) {
1270 struct timeval tv;
1271 gettimeofday(&tv, NULL);
1272 expire = tv.tv_sec + PS(cookie_lifetime);
1274 transport->setCookie(PS(session_name), PS(id), expire, PS(cookie_path),
1275 PS(cookie_domain), PS(cookie_secure),
1276 PS(cookie_httponly), true);
1279 static void php_session_reset_id() {
1280 if (PS(use_cookies) && PS(send_cookie)) {
1281 php_session_send_cookie();
1282 PS(send_cookie) = 0;
1285 EnvConstants *g = get_env_constants();
1286 if (PS(define_sid)) {
1287 StringBuffer var;
1288 var.append(String(PS(session_name)));
1289 var.append('=');
1290 var.append(PS(id));
1291 g->k_SID = var.detach();
1292 } else {
1293 g->k_SID = empty_string;
1296 // hzhao: not sure how to support this yet
1297 #if 0
1298 if (PS(apply_trans_sid)) {
1299 php_url_scanner_reset_vars();
1300 php_url_scanner_add_var(PS(session_name), strlen(PS(session_name)),
1301 PS(id), strlen(PS(id)), 1);
1303 #endif
1306 ///////////////////////////////////////////////////////////////////////////////
1307 // Cache Limiters
1309 typedef struct {
1310 char *name;
1311 void (*func)();
1312 } php_session_cache_limiter_t;
1314 #define CACHE_LIMITER(name) _php_cache_limiter_##name
1315 #define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)()
1316 #define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
1317 #define ADD_HEADER(hdr) g_context->getTransport()->addHeader(hdr)
1319 #define LAST_MODIFIED "Last-Modified: "
1320 #define EXPIRES "Expires: "
1321 #define MAX_STR 512
1323 static char *month_names[] = {
1324 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1325 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1328 static char *week_days[] = {
1329 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
1332 static inline void strcpy_gmt(char *ubuf, time_t *when) {
1333 char buf[MAX_STR];
1334 struct tm tm, *res;
1335 int n;
1337 res = gmtime_r(when, &tm);
1339 if (!res) {
1340 buf[0] = '\0';
1341 return;
1344 n = snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", // SAFE
1345 week_days[tm.tm_wday], tm.tm_mday,
1346 month_names[tm.tm_mon], tm.tm_year + 1900,
1347 tm.tm_hour, tm.tm_min,
1348 tm.tm_sec);
1349 memcpy(ubuf, buf, n);
1350 ubuf[n] = '\0';
1353 const StaticString s_PATH_TRANSLATED("PATH_TRANSLATED");
1355 static inline void last_modified() {
1356 GlobalVariables *g = get_global_variables();
1357 String path = g->get(s__SERVER)[s_PATH_TRANSLATED].toString();
1358 if (!path.empty()) {
1359 struct stat sb;
1360 if (stat(path.data(), &sb) == -1) {
1361 return;
1364 char buf[MAX_STR + 1];
1365 memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
1366 strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
1367 ADD_HEADER(buf);
1371 CACHE_LIMITER_FUNC(public) {
1372 char buf[MAX_STR + 1];
1373 struct timeval tv;
1374 time_t now;
1376 gettimeofday(&tv, NULL);
1377 now = tv.tv_sec + PS(cache_expire) * 60;
1378 memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
1379 strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
1380 ADD_HEADER(buf);
1382 snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%" PRId64,
1383 PS(cache_expire) * 60); /* SAFE */
1384 ADD_HEADER(buf);
1386 last_modified();
1389 CACHE_LIMITER_FUNC(private_no_expire) {
1390 char buf[MAX_STR + 1];
1392 snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%" PRId64 ", "
1393 "pre-check=%" PRId64, PS(cache_expire) * 60,
1394 PS(cache_expire) * 60); /* SAFE */
1395 ADD_HEADER(buf);
1397 last_modified();
1400 CACHE_LIMITER_FUNC(private) {
1401 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1402 CACHE_LIMITER(private_no_expire)();
1405 CACHE_LIMITER_FUNC(nocache) {
1406 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1408 /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
1409 ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, "
1410 "post-check=0, pre-check=0");
1412 /* For HTTP/1.0 conforming clients */
1413 ADD_HEADER("Pragma: no-cache");
1416 static php_session_cache_limiter_t php_session_cache_limiters[] = {
1417 CACHE_LIMITER_ENTRY(public)
1418 CACHE_LIMITER_ENTRY(private)
1419 CACHE_LIMITER_ENTRY(private_no_expire)
1420 CACHE_LIMITER_ENTRY(nocache)
1424 static int php_session_cache_limiter() {
1425 if (PS(cache_limiter)[0] == '\0') return 0;
1427 Transport *transport = g_context->getTransport();
1428 if (transport) {
1429 if (transport->headersSent()) {
1430 raise_warning("Cannot send session cache limiter - "
1431 "headers already sent");
1432 return -2;
1435 php_session_cache_limiter_t *lim;
1436 for (lim = php_session_cache_limiters; lim->name; lim++) {
1437 if (!strcasecmp(lim->name, PS(cache_limiter).c_str())) {
1438 lim->func();
1439 return 0;
1444 return -1;
1447 ///////////////////////////////////////////////////////////////////////////////
1449 int64_t f_session_status() {
1450 return PS(session_status);
1453 void f_session_set_cookie_params(int64_t lifetime,
1454 const String& path /* = null_string */,
1455 const String& domain /* = null_string */,
1456 const Variant& secure /* = null */,
1457 const Variant& httponly /* = null */) {
1458 if (PS(use_cookies)) {
1459 f_ini_set("session.cookie_lifetime", lifetime);
1460 if (!path.isNull()) {
1461 f_ini_set("session.cookie_path", path);
1463 if (!domain.isNull()) {
1464 f_ini_set("session.cookie_domain", domain);
1466 if (!secure.isNull()) {
1467 f_ini_set("session.cookie_secure", secure.toBoolean());
1469 if (!httponly.isNull()) {
1470 f_ini_set("session.cookie_httponly", httponly.toBoolean());
1475 const StaticString
1476 s_lifetime("lifetime"),
1477 s_path("path"),
1478 s_domain("domain"),
1479 s_secure("secure"),
1480 s_httponly("httponly");
1482 Array f_session_get_cookie_params() {
1483 ArrayInit ret(5);
1484 ret.set(s_lifetime, PS(cookie_lifetime));
1485 ret.set(s_path, String(PS(cookie_path)));
1486 ret.set(s_domain, String(PS(cookie_domain)));
1487 ret.set(s_secure, PS(cookie_secure));
1488 ret.set(s_httponly, PS(cookie_httponly));
1489 return ret.create();
1492 String f_session_name(const String& newname /* = null_string */) {
1493 String oldname = String(PS(session_name));
1494 if (!newname.isNull()) {
1495 f_ini_set("session.name", newname);
1497 return oldname;
1500 Variant f_session_module_name(const String& newname /* = null_string */) {
1501 String oldname;
1502 if (PS(mod) && PS(mod)->getName()) {
1503 oldname = String(PS(mod)->getName(), CopyString);
1506 if (!newname.isNull()) {
1507 if (!SessionModule::Find(newname.data())) {
1508 raise_warning("Cannot find named PHP session module (%s)",
1509 newname.data());
1510 return false;
1512 if (mod_is_open()) {
1513 PS(mod)->close();
1515 PS(mod_data) = false;
1517 f_ini_set("session.save_handler", newname);
1520 return oldname;
1523 bool f_hphp_session_set_save_handler(const Object& sessionhandler,
1524 bool register_shutdown /* = true */) {
1526 if (PS(mod) &&
1527 PS(session_status) != Session::None &&
1528 PS(mod) != &s_user_session_module) {
1529 return false;
1532 if (PS(default_mod) == nullptr) {
1533 PS(default_mod) = PS(mod);
1536 if (ObjectData* obj = PS(ps_session_handler)) {
1537 PS(ps_session_handler) = nullptr;
1538 decRefObj(obj);
1540 PS(ps_session_handler) = sessionhandler.get();
1541 PS(ps_session_handler)->incRefCount();
1543 // remove previous shutdown function
1544 g_context->popShutdownFunction(ExecutionContext::ShutDown);
1545 if (register_shutdown) {
1546 f_register_shutdown_function(1, String("session_write_close"));
1549 f_ini_set("session.save_handler", "user");
1550 return true;
1553 String f_session_save_path(const String& newname /* = null_string */) {
1554 if (!newname.isNull()) {
1555 if (memchr(newname.data(), '\0', newname.size()) != NULL) {
1556 raise_warning("The save_path cannot contain NULL characters");
1557 return false;
1559 f_ini_set("session.save_path", newname);
1561 return String(PS(save_path));
1564 String f_session_id(const String& newid /* = null_string */) {
1565 String ret = PS(id);
1566 if (!newid.isNull()) {
1567 PS(id) = newid;
1569 return ret;
1572 bool f_session_regenerate_id(bool delete_old_session /* = false */) {
1573 Transport *transport = g_context->getTransport();
1574 if (transport && transport->headersSent()) {
1575 raise_warning("Cannot regenerate session id - headers already sent");
1576 return false;
1579 if (PS(session_status) == Session::Active) {
1580 if (!PS(id).empty()) {
1581 if (delete_old_session && !PS(mod)->destroy(PS(id).data())) {
1582 raise_warning("Session object destruction failed");
1583 return false;
1585 PS(id).reset();
1588 PS(id) = PS(mod)->create_sid();
1589 PS(send_cookie) = 1;
1590 php_session_reset_id();
1591 return true;
1593 return false;
1596 String f_session_cache_limiter(const String& new_cache_limiter /* = null_string */) {
1597 String ret(PS(cache_limiter));
1598 if (!new_cache_limiter.isNull()) {
1599 f_ini_set("session.cache_limiter", new_cache_limiter);
1601 return ret;
1604 int64_t f_session_cache_expire(const String& new_cache_expire /* = null_string */) {
1605 int64_t ret = PS(cache_expire);
1606 if (!new_cache_expire.isNull()) {
1607 f_ini_set("session.cache_expire", new_cache_expire.toInt64());
1609 return ret;
1612 Variant f_session_encode() {
1613 String ret = php_session_encode();
1614 if (ret.isNull()) {
1615 return false;
1617 return ret;
1620 bool f_session_decode(const String& data) {
1621 if (PS(session_status) != Session::None) {
1622 php_session_decode(data);
1623 return true;
1625 return false;
1628 const StaticString
1629 s_REQUEST_URI("REQUEST_URI"),
1630 s_HTTP_REFERER("HTTP_REFERER");
1632 bool f_session_start() {
1633 PS(apply_trans_sid) = PS(use_trans_sid);
1635 String value;
1636 switch (PS(session_status)) {
1637 case Session::Active:
1638 raise_notice("A session had already been started - "
1639 "ignoring session_start()");
1640 return false;
1641 case Session::Disabled:
1643 if (!PS(mod) && IniSetting::Get("session.save_handler", value)) {
1644 PS(mod) = SessionModule::Find(value.data());
1645 if (!PS(mod)) {
1646 raise_warning("Cannot find save handler '%s' - "
1647 "session startup failed", value.data());
1648 return false;
1651 if (!PS(serializer) &&
1652 IniSetting::Get("session.serialize_handler", value)) {
1653 PS(serializer) = SessionSerializer::Find(value.data());
1654 if (!PS(serializer)) {
1655 raise_warning("Cannot find serialization handler '%s' - "
1656 "session startup failed", value.data());
1657 return false;
1660 PS(session_status) = Session::None;
1661 /* fallthrough */
1663 default:
1664 assert(PS(session_status) == Session::None);
1665 PS(define_sid) = 1;
1666 PS(send_cookie) = 1;
1670 * Cookies are preferred, because initially
1671 * cookie and get variables will be available.
1673 GlobalVariables *g = get_global_variables();
1674 if (PS(id).empty()) {
1675 if (PS(use_cookies) &&
1676 g->get(s__COOKIE).toArray().exists(String(PS(session_name)))) {
1677 PS(id) = g->get(s__COOKIE)[String(PS(session_name))].toString();
1678 PS(apply_trans_sid) = 0;
1679 PS(send_cookie) = 0;
1680 PS(define_sid) = 0;
1683 if (!PS(use_only_cookies) && !PS(id) &&
1684 g->get(s__GET).toArray().exists(String(PS(session_name)))) {
1685 PS(id) = g->get(s__GET)[String(PS(session_name))].toString();
1686 PS(send_cookie) = 0;
1689 if (!PS(use_only_cookies) && !PS(id) &&
1690 g->get(s__POST).toArray().exists(String(PS(session_name)))) {
1691 PS(id) = g->get(s__POST)[String(PS(session_name))].toString();
1692 PS(send_cookie) = 0;
1696 int lensess = PS(session_name).size();
1698 /* check the REQUEST_URI symbol for a string of the form
1699 '<session-name>=<session-id>' to allow URLs of the form
1700 http://yoursite/<session-name>=<session-id>/script.php */
1701 if (!PS(use_only_cookies) && PS(id).empty()) {
1702 value = g->get(s__SERVER)[s_REQUEST_URI].toString();
1703 const char *p = strstr(value.data(), PS(session_name).c_str());
1704 if (p && p[lensess] == '=') {
1705 p += lensess + 1;
1706 const char *q;
1707 if ((q = strpbrk(p, "/?\\"))) {
1708 PS(id) = String(p, q - p, CopyString);
1709 PS(send_cookie) = 0;
1714 /* check whether the current request was referred to by
1715 an external site which invalidates the previously found id */
1716 if (!PS(id).empty() && PS(extern_referer_chk)[0] != '\0') {
1717 value = g->get(s__SERVER)[s_HTTP_REFERER].toString();
1718 if (strstr(value.data(), PS(extern_referer_chk).c_str()) == NULL) {
1719 PS(id).reset();
1720 PS(send_cookie) = 1;
1721 if (PS(use_trans_sid)) {
1722 PS(apply_trans_sid) = 1;
1727 php_session_initialize();
1729 if (!PS(use_cookies) && PS(send_cookie)) {
1730 if (PS(use_trans_sid)) {
1731 PS(apply_trans_sid) = 1;
1733 PS(send_cookie) = 0;
1736 php_session_reset_id();
1738 PS(session_status) = Session::Active;
1740 php_session_cache_limiter();
1742 if (mod_is_open() && PS(gc_probability) > 1) {
1743 int nrdels = -1;
1745 int nrand = (int) ((float) PS(gc_divisor) * math_combined_lcg());
1746 if (nrand < PS(gc_probability)) {
1747 PS(mod)->gc(PS(gc_maxlifetime), &nrdels);
1751 if (PS(session_status) != Session::Active) {
1752 return false;
1754 return true;
1757 bool f_session_destroy() {
1758 bool retval = true;
1760 if (PS(session_status) != Session::Active) {
1761 raise_warning("Trying to destroy uninitialized session");
1762 return false;
1765 if (!PS(mod)->destroy(PS(id).data())) {
1766 retval = false;
1767 raise_warning("Session object destruction failed");
1770 s_session->requestShutdownImpl();
1771 s_session->destroy();
1773 return retval;
1776 Variant f_session_unset() {
1777 if (PS(session_status) == Session::None) {
1778 return false;
1780 GlobalVariables *g = get_global_variables();
1781 g->getRef(s__SESSION).reset();
1782 return uninit_null();
1785 void f_session_write_close() {
1786 if (PS(session_status) == Session::Active) {
1787 PS(session_status) = Session::None;
1788 php_session_save_current_state();
1792 void f_session_commit() {
1793 f_session_write_close();
1796 bool f_session_register(int _argc, const Variant& var_names,
1797 const Array& _argv /* = null_array */) {
1798 throw NotSupportedException
1799 (__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
1800 "Relying on this feature is highly discouraged.");
1803 bool f_session_unregister(const String& varname) {
1804 throw NotSupportedException
1805 (__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
1806 "Relying on this feature is highly discouraged.");
1809 bool f_session_is_registered(const String& varname) {
1810 throw NotSupportedException
1811 (__func__, "Deprecated as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. "
1812 "Relying on this feature is highly discouraged.");
1815 static bool HHVM_METHOD(SessionHandler, hhopen,
1816 const String& save_path, const String& session_id) {
1817 return PS(default_mod) &&
1818 PS(default_mod)->open(save_path.data(), session_id.data());
1821 static bool HHVM_METHOD(SessionHandler, hhclose) {
1822 return PS(default_mod) && PS(default_mod)->close();
1825 static String HHVM_METHOD(SessionHandler, hhread, const String& session_id) {
1826 String value;
1827 if (PS(default_mod) && PS(default_mod)->read(PS(id).data(), value)) {
1828 php_session_decode(value);
1829 return value;
1831 return uninit_null();
1834 static bool HHVM_METHOD(SessionHandler, hhwrite,
1835 const String& session_id, const String& session_data) {
1836 return PS(default_mod) &&
1837 PS(default_mod)->write(session_id.data(), session_data.data());
1840 static bool HHVM_METHOD(SessionHandler, hhdestroy, const String& session_id) {
1841 return PS(default_mod) && PS(default_mod)->destroy(session_id.data());
1844 static bool HHVM_METHOD(SessionHandler, hhgc, int maxlifetime) {
1845 int nrdels = -1;
1846 return PS(default_mod) && PS(default_mod)->gc(maxlifetime, &nrdels);
1849 ///////////////////////////////////////////////////////////////////////////////
1851 static class SessionExtension : public Extension {
1852 public:
1853 SessionExtension() : Extension("session", NO_EXTENSION_VERSION_YET) { }
1854 virtual void moduleLoad(Hdf config) {
1855 HHVM_ME(SessionHandler, hhopen);
1856 HHVM_ME(SessionHandler, hhclose);
1857 HHVM_ME(SessionHandler, hhread);
1858 HHVM_ME(SessionHandler, hhwrite);
1859 HHVM_ME(SessionHandler, hhdestroy);
1860 HHVM_ME(SessionHandler, hhgc);
1863 virtual void threadInit() {
1864 Extension* ext = Extension::GetExtension(s_session_ext_name);
1865 assert(ext);
1866 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1867 "session.save_path", "",
1868 IniSetting::SetAndGet<std::string>(
1869 ini_on_update_save_dir, nullptr
1871 &PS(save_path));
1872 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1873 "session.name", "PHPSESSID",
1874 &PS(session_name));
1875 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1876 "session.save_handler", "files",
1877 IniSetting::SetAndGet<std::string>(
1878 ini_on_update_save_handler, ini_get_save_handler
1880 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1881 "session.auto_start", "0",
1882 &PS(auto_start));
1883 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1884 "session.gc_probability", "1",
1885 &PS(gc_probability));
1886 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1887 "session.gc_divisor", "100",
1888 &PS(gc_divisor));
1889 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1890 "session.gc_maxlifetime", "1440",
1891 &PS(gc_maxlifetime));
1892 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1893 "session.serialize_handler", "php",
1894 IniSetting::SetAndGet<std::string>(
1895 ini_on_update_serializer, ini_get_serializer
1897 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1898 "session.cookie_lifetime", "0",
1899 &PS(cookie_lifetime));
1900 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1901 "session.cookie_path", "/",
1902 &PS(cookie_path));
1903 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1904 "session.cookie_domain", "",
1905 &PS(cookie_domain));
1906 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1907 "session.cookie_secure", "",
1908 &PS(cookie_secure));
1909 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1910 "session.cookie_httponly", "",
1911 &PS(cookie_httponly));
1912 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1913 "session.use_cookies", "1",
1914 &PS(use_cookies));
1915 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1916 "session.use_only_cookies", "1",
1917 &PS(use_only_cookies));
1918 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1919 "session.referer_check", "",
1920 &PS(extern_referer_chk));
1921 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1922 "session.entropy_file", "",
1923 &PS(entropy_file));
1924 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1925 "session.entropy_length", "0",
1926 &PS(entropy_length));
1927 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1928 "session.cache_limiter", "nocache",
1929 &PS(cache_limiter));
1930 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1931 "session.cache_expire", "180",
1932 &PS(cache_expire));
1933 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1934 "session.use_trans_sid", "0",
1935 IniSetting::SetAndGet<bool>(
1936 ini_on_update_trans_sid, nullptr
1938 &PS(use_trans_sid));
1939 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1940 "session.hash_function", "0",
1941 &PS(hash_func));
1942 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1943 "session.hash_bits_per_character", "4",
1944 &PS(hash_bits_per_character));
1947 virtual void requestInit() {
1948 // warm up the session data
1949 s_session->requestInit();
1951 } s_session_extension;
1953 ///////////////////////////////////////////////////////////////////////////////