Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / ext / session / ext_session.cpp
blob65d8a81f755b38133f8fc7758db344db938d2d9c
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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/session/ext_session.h"
19 #include <string>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <vector>
26 #include <folly/String.h>
27 #include <folly/portability/Dirent.h>
28 #include <folly/portability/SysFile.h>
29 #include <folly/portability/SysTime.h>
31 #include "hphp/util/lock.h"
32 #include "hphp/util/logger.h"
33 #include "hphp/util/compatibility.h"
35 #include "hphp/runtime/base/array-iterator.h"
36 #include "hphp/runtime/base/builtin-functions.h"
37 #include "hphp/runtime/base/comparisons.h"
38 #include "hphp/runtime/base/datetime.h"
39 #include "hphp/runtime/base/file.h"
40 #include "hphp/runtime/base/ini-setting.h"
41 #include "hphp/runtime/base/object-data.h"
42 #include "hphp/runtime/base/php-globals.h"
43 #include "hphp/runtime/base/request-local.h"
44 #include "hphp/runtime/base/string-buffer.h"
45 #include "hphp/runtime/base/tv-refcount.h"
46 #include "hphp/runtime/base/variable-serializer.h"
47 #include "hphp/runtime/base/variable-unserializer.h"
48 #include "hphp/runtime/base/zend-math.h"
50 #include "hphp/runtime/ext/extension-registry.h"
51 #include "hphp/runtime/ext/hash/ext_hash.h"
52 #include "hphp/runtime/ext/std/ext_std_function.h"
53 #include "hphp/runtime/ext/std/ext_std_misc.h"
54 #include "hphp/runtime/ext/std/ext_std_options.h"
55 #include "hphp/runtime/ext/wddx/ext_wddx.h"
57 #include "hphp/runtime/vm/jit/translator-inline.h"
58 #include "hphp/runtime/vm/method-lookup.h"
60 namespace HPHP {
62 ///////////////////////////////////////////////////////////////////////////////
64 using std::string;
66 static bool ini_on_update_save_handler(const std::string& value);
67 static std::string ini_get_save_handler();
68 static bool ini_on_update_serializer(const std::string& value);
69 static std::string ini_get_serializer();
70 static bool ini_on_update_trans_sid(const bool& value);
71 static bool ini_on_update_save_dir(const std::string& value);
72 static bool mod_is_open();
74 ///////////////////////////////////////////////////////////////////////////////
75 // global data
77 struct SessionSerializer;
78 struct Session {
79 enum Status {
80 Disabled,
81 None,
82 Active
85 std::string save_path;
86 bool reset_save_path{false};
87 std::string session_name;
88 std::string extern_referer_chk;
89 std::string entropy_file;
90 int64_t entropy_length{0};
91 std::string cache_limiter;
92 int64_t cookie_lifetime{0};
93 std::string cookie_path;
94 std::string cookie_domain;
95 Status session_status{None};
96 bool cookie_secure{false};
97 bool cookie_httponly{false};
98 bool mod_data{false};
99 bool mod_user_implemented{false};
101 SessionModule* mod{nullptr};
102 SessionModule* default_mod{nullptr};
104 int64_t gc_probability{0};
105 int64_t gc_divisor{0};
106 int64_t gc_maxlifetime{0};
107 int64_t cache_expire{0};
109 Object ps_session_handler;
110 SessionSerializer* serializer{nullptr};
112 bool auto_start{false};
113 bool use_cookies{false};
114 bool use_only_cookies{false};
115 bool use_trans_sid{false}; // contains INI value of whether to use trans-sid
116 bool apply_trans_sid{false}; // whether to enable trans-sid for current req
117 bool send_cookie{false};
118 bool define_sid{false};
119 bool invalid_session_id{false}; /* allows the driver to report about an
120 invalid session id and request id regeneration */
122 std::string hash_func;
123 int64_t hash_bits_per_character{0};
126 const StaticString s_session_ext_name("session");
128 struct SessionRequestData final : Session {
129 void init() {
130 id.detach();
131 session_status = Session::None;
132 ps_session_handler.reset();
133 save_path.clear();
134 if (reset_save_path) IniSetting::ResetSystemDefault("session.save_path");
137 void destroy() {
138 id.reset();
139 session_status = Session::None;
140 // Note: we should not destroy user save handler here
141 // (if the session is restarted during request, the handler
142 // should be alive), it's destroyed only in the request shutdown.
145 void requestShutdownImpl();
147 public:
148 String id;
151 THREAD_LOCAL_NO_CHECK(SessionRequestData, s_session);
153 void SessionRequestData::requestShutdownImpl() {
154 if (mod_is_open()) {
155 try {
156 mod->close();
157 } catch (...) {}
159 ps_session_handler.reset();
160 id.reset();
163 std::vector<SessionModule*> SessionModule::RegisteredModules;
166 * Note that we cannot use the BASE64 alphabet here, because
167 * it contains "/" and "+": both are unacceptable for simple inclusion
168 * into URLs.
170 static char hexconvtab[] =
171 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
173 static void bin_to_readable(const String& in, StringBuffer &out, char nbits) {
174 unsigned char *p = (unsigned char *)in.data();
175 unsigned char *q = (unsigned char *)in.data() + in.size();
176 unsigned short w = 0;
177 int have = 0;
178 int mask = (1 << nbits) - 1;
180 while (true) {
181 if (have < nbits) {
182 if (p < q) {
183 w |= *p++ << have;
184 have += 8;
185 } else {
186 /* consumed everything? */
187 if (have == 0) break;
188 /* No? We need a final round */
189 have = nbits;
193 /* consume nbits */
194 out.append(hexconvtab[w & mask]);
195 w >>= nbits;
196 have -= nbits;
200 const StaticString
201 s_REMOTE_ADDR("REMOTE_ADDR"),
202 s__SERVER("_SERVER"),
203 s__SESSION("_SESSION"),
204 s__COOKIE("_COOKIE"),
205 s__GET("_GET"),
206 s__POST("_POST");
208 String SessionModule::create_sid() {
209 String remote_addr = php_global(s__SERVER)
210 .toArray()[s_REMOTE_ADDR].toString();
212 struct timeval tv;
213 gettimeofday(&tv, nullptr);
215 StringBuffer buf;
216 buf.printf("%.15s%ld%ld%0.8F", remote_addr.data(),
217 tv.tv_sec, (long int)tv.tv_usec, math_combined_lcg() * 10);
219 if (String(s_session->hash_func).isNumeric()) {
220 switch (String(s_session->hash_func).toInt64()) {
221 case md5: s_session->hash_func = "md5"; break;
222 case sha1: s_session->hash_func = "sha1"; break;
226 Variant context = HHVM_FN(hash_init)(s_session->hash_func);
227 if (same(context, false)) {
228 Logger::Error("Invalid session hash function: %s",
229 s_session->hash_func.c_str());
230 return String();
232 if (!HHVM_FN(hash_update)(context.toResource(), buf.detach())) {
233 Logger::Error("hash_update() failed");
234 return String();
237 if (s_session->entropy_length > 0) {
238 int fd = ::open(s_session->entropy_file.c_str(), O_RDONLY);
239 if (fd >= 0) {
240 unsigned char rbuf[2048];
241 int n;
242 int to_read = s_session->entropy_length;
243 while (to_read > 0) {
244 n = ::read(fd, rbuf, (to_read < (int)sizeof(rbuf) ?
245 to_read : (int)sizeof(buf)));
246 if (n <= 0) break;
247 if (!HHVM_FN(hash_update)(context.toResource(),
248 String((const char *)rbuf, n, CopyString))) {
249 Logger::Error("hash_update() failed");
250 ::close(fd);
251 return String();
253 to_read -= n;
255 ::close(fd);
259 auto const hashed = HHVM_FN(hash_final)(
260 context.toResource(), /* raw */ true
261 ).toString();
263 if (s_session->hash_bits_per_character < 4 ||
264 s_session->hash_bits_per_character > 6) {
265 s_session->hash_bits_per_character = 4;
266 raise_warning("The ini setting hash_bits_per_character is out of range "
267 "(should be 4, 5, or 6) - using 4 for now");
270 StringBuffer readable;
271 bin_to_readable(hashed, readable, s_session->hash_bits_per_character);
272 return readable.detach();
275 ///////////////////////////////////////////////////////////////////////////////
276 // SystemlibSessionModule
278 static StaticString s_SessionHandlerInterface("SessionHandlerInterface");
280 static StaticString s_open("open");
281 static StaticString s_close("close");
282 static StaticString s_read("read");
283 static StaticString s_write("write");
284 static StaticString s_gc("gc");
285 static StaticString s_destroy("destroy");
287 LowPtr<Class> SystemlibSessionModule::s_SHIClass = nullptr;
290 * Relies on the fact that only one SessionModule will be active
291 * in a given thread at any one moment.
293 IMPLEMENT_REQUEST_LOCAL(SystemlibSessionInstance,
294 SystemlibSessionModule::s_obj);
296 Func* SystemlibSessionModule::lookupFunc(Class *cls, StringData *fname) {
297 Func *f = cls->lookupMethod(fname);
298 if (!f) {
299 raise_error("class %s must declare method %s()",
300 m_classname, fname->data());
303 if (f->attrs() & AttrStatic) {
304 raise_error("%s::%s() must not be declared static",
305 m_classname, fname->data());
308 if (f->attrs() & (AttrPrivate|AttrProtected|AttrAbstract)) {
309 raise_error("%s::%s() must be declared public",
310 m_classname, fname->data());
313 return f;
316 void SystemlibSessionModule::lookupClass() {
317 Class *cls;
318 if (!(cls = Unit::loadClass(String(m_classname, CopyString).get()))) {
319 raise_error("Unable to locate systemlib class '%s'", m_classname);
322 if (cls->attrs() & (AttrTrait|AttrInterface)) {
323 raise_error("'%s' must be a real class, not an interface or trait",
324 m_classname);
327 if (!s_SHIClass) {
328 s_SHIClass = Unit::lookupClass(s_SessionHandlerInterface.get());
329 if (!s_SHIClass) {
330 raise_error("Unable to locate '%s' interface",
331 s_SessionHandlerInterface.data());
335 if (!cls->classof(s_SHIClass)) {
336 raise_error("SystemLib session module '%s' must implement '%s'",
337 m_classname,
338 s_SessionHandlerInterface.data());
341 if (LookupResult::MethodFoundWithThis !=
342 lookupCtorMethod(m_ctor, cls, arGetContextClass(vmfp()))) {
343 raise_error("Unable to call %s's constructor", m_classname);
346 m_open = lookupFunc(cls, s_open.get());
347 m_close = lookupFunc(cls, s_close.get());
348 m_read = lookupFunc(cls, s_read.get());
349 m_write = lookupFunc(cls, s_write.get());
350 m_gc = lookupFunc(cls, s_gc.get());
351 m_destroy = lookupFunc(cls, s_destroy.get());
352 m_cls = cls;
355 const Object& SystemlibSessionModule::getObject() {
356 if (const auto& o = s_obj->getObject()) {
357 return o;
360 VMRegAnchor _;
361 if (!m_cls) {
362 lookupClass();
364 s_obj->setObject(Object{m_cls});
365 const auto& obj = s_obj->getObject();
366 tvDecRefGen(
367 g_context->invokeFuncFew(m_ctor, obj.get())
369 return obj;
372 bool SystemlibSessionModule::open(const char *save_path,
373 const char *session_name) {
374 const auto& obj = getObject();
376 Variant savePath = String(save_path, CopyString);
377 Variant sessionName = String(session_name, CopyString);
378 TypedValue args[2] = { *savePath.asCell(), *sessionName.asCell() };
379 auto ret = Variant::attach(
380 g_context->invokeFuncFew(m_open, obj.get(), nullptr, 2, args)
383 if (ret.isBoolean() && ret.toBoolean()) {
384 s_session->mod_data = true;
385 return true;
388 raise_warning("Failed calling %s::open()", m_classname);
389 return false;
392 bool SystemlibSessionModule::close() {
393 const auto& obj = s_obj->getObject();
394 if (!obj) {
395 // close() can be called twice in some circumstances
396 s_session->mod_data = false;
397 return true;
400 auto ret = Variant::attach(
401 g_context->invokeFuncFew(m_close, obj.get())
403 s_obj->destroy();
405 if (ret.isBoolean() && ret.toBoolean()) {
406 return true;
409 raise_warning("Failed calling %s::close()", m_classname);
410 return false;
413 bool SystemlibSessionModule::read(const char *key, String &value) {
414 const auto& obj = getObject();
416 Variant sessionKey = String(key, CopyString);
417 auto ret = Variant::attach(
418 g_context->invokeFuncFew(m_read, obj.get(),
419 nullptr, 1, sessionKey.asCell())
422 if (ret.isString()) {
423 value = ret.toString();
424 return true;
427 raise_warning("Failed calling %s::read()", m_classname);
428 return false;
431 bool SystemlibSessionModule::write(const char *key, const String& value) {
432 const auto& obj = getObject();
434 Variant sessionKey = String(key, CopyString);
435 Variant sessionVal = value;
436 TypedValue args[2] = { *sessionKey.asCell(), *sessionVal.asCell() };
437 auto ret = Variant::attach(
438 g_context->invokeFuncFew(m_write, obj.get(), nullptr, 2, args)
441 if (ret.isBoolean() && ret.toBoolean()) {
442 return true;
445 raise_warning("Failed calling %s::write()", m_classname);
446 return false;
449 bool SystemlibSessionModule::destroy(const char *key) {
450 const auto& obj = getObject();
452 Variant sessionKey = String(key, CopyString);
453 auto ret = Variant::attach(
454 g_context->invokeFuncFew(m_destroy, obj.get(),
455 nullptr, 1, sessionKey.asCell())
458 if (ret.isBoolean() && ret.toBoolean()) {
459 return true;
462 raise_warning("Failed calling %s::destroy()", m_classname);
463 return false;
466 bool SystemlibSessionModule::gc(int maxlifetime, int *nrdels) {
467 const auto& obj = getObject();
469 Variant maxLifeTime = maxlifetime;
470 auto ret = Variant::attach(
471 g_context->invokeFuncFew(m_gc, obj.get(),
472 nullptr, 1, maxLifeTime.asCell())
475 if (ret.isInteger()) {
476 if (nrdels) {
477 *nrdels = ret.toInt64();
479 return true;
482 raise_warning("Failed calling %s::gc()", m_classname);
483 return false;
486 //////////////////////////////////////////////////////////////////////////////
487 // SystemlibSessionModule implementations
489 static struct RedisSessionModule : SystemlibSessionModule {
490 RedisSessionModule() :
491 SystemlibSessionModule("redis", "RedisSessionModule") { }
492 } s_redis_session_module;
494 static struct MemcacheSessionModule : SystemlibSessionModule {
495 MemcacheSessionModule() :
496 SystemlibSessionModule("memcache", "MemcacheSessionModule") { }
497 } s_memcache_session_module;
499 static struct MemcachedSessionModule : SystemlibSessionModule {
500 MemcachedSessionModule() :
501 SystemlibSessionModule("memcached", "MemcachedSessionModule") { }
502 } s_memcached_session_module;
504 //////////////////////////////////////////////////////////////////////////////
505 // FileSessionModule
507 struct FileSessionData {
508 FileSessionData() : m_fd(-1), m_dirdepth(0), m_st_size(0), m_filemode(0600) {
511 bool open(const char* save_path, const char* /*session_name*/) {
512 String tmpdir;
513 if (*save_path == '\0') {
514 tmpdir = HHVM_FN(sys_get_temp_dir)();
515 save_path = tmpdir.data();
518 /* split up input parameter */
519 const char *argv[3];
520 int argc = 0;
521 const char *last = save_path;
522 const char *p = strchr(save_path, ';');
523 while (p) {
524 argv[argc++] = last; last = ++p; p = strchr(p, ';');
525 if (argc > 1) break;
527 argv[argc++] = last;
529 if (argc > 1) {
530 errno = 0;
531 m_dirdepth = (size_t) strtol(argv[0], nullptr, 10);
532 if (errno == ERANGE) {
533 raise_warning("The first parameter in session.save_path is invalid");
534 return false;
538 if (argc > 2) {
539 errno = 0;
540 m_filemode = strtol(argv[1], nullptr, 8);
541 if (errno == ERANGE || m_filemode < 0 || m_filemode > 07777) {
542 raise_warning("The second parameter in session.save_path is invalid");
543 return false;
547 save_path = argv[argc - 1];
548 if (File::TranslatePath(save_path).empty()) {
549 raise_warning("Unable to open save_path %s", save_path);
550 return false;
553 m_fd = -1;
554 m_basedir = save_path;
555 s_session->mod_data = true;
556 return true;
559 bool close() {
560 closeImpl();
561 m_lastkey.clear();
562 m_basedir.clear();
563 s_session->mod_data = false;
564 return true;
567 bool read(const char *key, String &value) {
568 openImpl(key);
569 if (m_fd < 0) {
570 return false;
573 struct stat sbuf;
574 if (fstat(m_fd, &sbuf)) {
575 return false;
577 m_st_size = sbuf.st_size;
578 if (m_st_size == 0) {
579 value = "";
580 return true;
583 String s = String(m_st_size, ReserveString);
584 char *val = s.mutableData();
586 #if defined(HAVE_PREAD)
587 long n = pread(m_fd, val, m_st_size, 0);
588 #else
589 lseek(m_fd, 0, SEEK_SET);
590 long n = ::read(m_fd, val, m_st_size);
591 #endif
593 if (n != (int)m_st_size) {
594 if (n == -1) {
595 raise_warning("read failed: %s (%d)", folly::errnoStr(errno).c_str(),
596 errno);
597 } else {
598 raise_warning("read returned less bytes than requested");
600 return false;
603 value = s.setSize(m_st_size);
604 return true;
607 bool write(const char *key, const String& value) {
608 openImpl(key);
609 if (m_fd < 0) {
610 return false;
613 struct stat sbuf;
614 if (fstat(m_fd, &sbuf)) {
615 return false;
617 m_st_size = sbuf.st_size;
620 * truncate file, if the amount of new data is smaller than
621 * the existing data set.
623 if (value.size() < (int)m_st_size) {
624 if (ftruncate(m_fd, 0) < 0) {
625 raise_warning("truncate failed: %s (%d)",
626 folly::errnoStr(errno).c_str(), errno);
627 return false;
631 #if defined(HAVE_PWRITE)
632 long n = pwrite(m_fd, value.data(), value.size(), 0);
633 #else
634 lseek(m_fd, 0, SEEK_SET);
635 long n = ::write(m_fd, value.data(), value.size());
636 #endif
638 if (n != value.size()) {
639 if (n == -1) {
640 raise_warning("write failed: %s (%d)",
641 folly::errnoStr(errno).c_str(), errno);
642 } else {
643 raise_warning("write wrote less bytes than requested");
645 return false;
648 return true;
651 bool destroy(const char *key) {
652 char buf[PATH_MAX];
653 if (!createPath(buf, sizeof(buf), key)) {
654 return false;
657 if (m_fd != -1) {
658 closeImpl();
659 if (unlink(buf) == -1) {
660 /* This is a little safety check for instances when we are dealing
661 with a regenerated session that was not yet written to disk */
662 if (!access(buf, F_OK)) {
663 return false;
668 return true;
671 bool gc(int maxlifetime, int *nrdels) {
672 /* we don't perform any cleanup, if dirdepth is larger than 0.
673 we return true, since all cleanup should be handled by
674 an external entity (i.e. find -ctime x | xargs rm) */
675 if (m_dirdepth == 0) {
676 *nrdels = CleanupDir(m_basedir.c_str(), maxlifetime);
678 return true;
681 private:
682 int m_fd;
683 std::string m_lastkey;
684 std::string m_basedir;
685 size_t m_dirdepth;
686 size_t m_st_size;
687 int m_filemode;
689 /* If you change the logic here, please also update the error message in
690 * ps_files_open() appropriately */
691 static bool IsValid(const char *key) {
692 const char *p; char c;
693 bool ret = true;
694 for (p = key; (c = *p); p++) {
695 /* valid characters are a..z,A..Z,0..9 */
696 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
697 || (c >= '0' && c <= '9') || c == ',' || c == '-')) {
698 ret = false;
699 break;
702 size_t len = p - key;
703 if (len == 0) {
704 ret = false;
706 return ret;
709 #define FILE_PREFIX "sess_"
711 bool createPath(char *buf, size_t buflen, const char *key) {
712 size_t key_len = strlen(key);
713 if (key_len <= m_dirdepth ||
714 buflen < (m_basedir.size() + 2 * m_dirdepth + key_len +
715 5 + sizeof(FILE_PREFIX))) {
716 return false;
719 const char *p = key;
720 int n = m_basedir.size();
721 memcpy(buf, m_basedir.c_str(), n);
722 buf[n++] = PHP_DIR_SEPARATOR;
723 for (int i = 0; i < (int)m_dirdepth; i++) {
724 buf[n++] = *p++;
725 buf[n++] = PHP_DIR_SEPARATOR;
727 memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
728 n += sizeof(FILE_PREFIX) - 1;
729 memcpy(buf + n, key, key_len);
730 n += key_len;
731 buf[n] = '\0';
733 return true;
736 #ifndef O_BINARY
737 #define O_BINARY 0
738 #endif
740 void closeImpl() {
741 if (m_fd != -1) {
742 #ifdef PHP_WIN32
743 /* On Win32 locked files that are closed without being explicitly
744 unlocked will be unlocked only when "system resources become
745 available". */
746 flock(m_fd, LOCK_UN);
747 #endif
748 ::close(m_fd);
749 m_fd = -1;
753 void openImpl(const char *key) {
754 if (m_fd < 0 || !m_lastkey.empty() || m_lastkey != key) {
755 m_lastkey.clear();
756 closeImpl();
758 if (!IsValid(key)) {
759 raise_warning("The session id contains illegal characters, "
760 "valid characters are a-z, A-Z, 0-9 and '-,'");
761 s_session->invalid_session_id = true;
762 return;
765 char buf[PATH_MAX];
766 if (!createPath(buf, sizeof(buf), key)) {
767 return;
770 m_lastkey = key;
771 m_fd = ::open(buf, O_CREAT | O_RDWR | O_BINARY, m_filemode);
773 if (m_fd != -1) {
774 flock(m_fd, LOCK_EX);
776 #ifdef F_SETFD
777 # ifndef FD_CLOEXEC
778 # define FD_CLOEXEC 1
779 # endif
780 if (fcntl(m_fd, F_SETFD, FD_CLOEXEC)) {
781 raise_warning("fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)",
782 m_fd, folly::errnoStr(errno).c_str(), errno);
784 #endif
785 } else {
786 raise_warning("open(%s, O_RDWR) failed: %s (%d)", buf,
787 folly::errnoStr(errno).c_str(), errno);
792 static int CleanupDir(const char *dirname, int maxlifetime) {
793 DIR *dir = opendir(dirname);
794 if (!dir) {
795 raise_notice("ps_files_cleanup_dir: opendir(%s) failed: %s (%d)",
796 dirname, folly::errnoStr(errno).c_str(), errno);
797 return 0;
800 time_t now;
801 time(&now);
803 size_t dirname_len = strlen(dirname);
804 char dentry[sizeof(struct dirent) + PATH_MAX];
805 struct dirent *entry = (struct dirent *) &dentry;
806 struct stat sbuf;
807 int nrdels = 0;
809 /* Prepare buffer (dirname never changes) */
810 char buf[PATH_MAX];
811 memcpy(buf, dirname, dirname_len);
812 buf[dirname_len] = PHP_DIR_SEPARATOR;
814 while (readdir_r(dir, (struct dirent *)dentry, &entry) == 0 && entry) {
815 /* does the file start with our prefix? */
816 if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
817 size_t entry_len = strlen(entry->d_name);
819 /* does it fit into our buffer? */
820 if (entry_len + dirname_len + 2 < PATH_MAX) {
821 /* create the full path.. */
822 memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
824 /* NUL terminate it and */
825 buf[dirname_len + entry_len + 1] = '\0';
827 /* check whether its last access was more than maxlifet ago */
828 if (stat(buf, &sbuf) == 0 && (now - sbuf.st_mtime) > maxlifetime) {
829 unlink(buf);
830 nrdels++;
836 closedir(dir);
837 return nrdels;
840 THREAD_LOCAL(FileSessionData, s_file_session_data);
842 struct FileSessionModule : SessionModule {
843 FileSessionModule() : SessionModule("files") {
845 virtual bool open(const char *save_path, const char *session_name) {
846 return s_file_session_data->open(save_path, session_name);
848 virtual bool close() {
849 return s_file_session_data->close();
851 virtual bool read(const char *key, String &value) {
852 return s_file_session_data->read(key, value);
854 virtual bool write(const char *key, const String& value) {
855 return s_file_session_data->write(key, value);
857 virtual bool destroy(const char *key) {
858 return s_file_session_data->destroy(key);
860 virtual bool gc(int maxlifetime, int *nrdels) {
861 return s_file_session_data->gc(maxlifetime, nrdels);
864 static FileSessionModule s_file_session_module;
866 ///////////////////////////////////////////////////////////////////////////////
867 // UserSessionModule
869 struct UserSessionModule : SessionModule {
870 UserSessionModule() : SessionModule("user") {}
872 bool open(const char *save_path, const char *session_name) override {
873 auto func = make_packed_array(s_session->ps_session_handler, s_open);
874 auto args = make_packed_array(String(save_path), String(session_name));
876 auto res = vm_call_user_func(func, args);
877 s_session->mod_user_implemented = true;
878 return handleReturnValue(res);
881 bool close() override {
882 auto func = make_packed_array(s_session->ps_session_handler, s_close);
883 auto args = Array::Create();
885 auto res = vm_call_user_func(func, args);
886 s_session->mod_user_implemented = false;
887 return handleReturnValue(res);
890 bool read(const char *key, String &value) override {
891 Variant ret = vm_call_user_func(
892 make_packed_array(s_session->ps_session_handler, s_read),
893 make_packed_array(String(key))
895 if (ret.isString()) {
896 value = ret.toString();
897 return true;
899 return false;
902 bool write(const char *key, const String& value) override {
903 return handleReturnValue(vm_call_user_func(
904 make_packed_array(s_session->ps_session_handler, s_write),
905 make_packed_array(String(key, CopyString), value)
909 bool destroy(const char *key) override {
910 return handleReturnValue(vm_call_user_func(
911 make_packed_array(s_session->ps_session_handler, s_destroy),
912 make_packed_array(String(key))
916 bool gc(int maxlifetime, int* /*nrdels*/) override {
917 return handleReturnValue(vm_call_user_func(
918 make_packed_array(s_session->ps_session_handler, s_gc),
919 make_packed_array((int64_t)maxlifetime)
923 private:
924 bool handleReturnValue(const Variant& ret) {
925 if (ret.isBoolean()) {
926 return ret.toBoolean();
928 if (ret.isInteger()) {
929 // BC fallbacks for values which work in PHP5 & PHP7
930 auto i = ret.toInt64();
931 if (i == -1) return false;
932 if (i == 0) return true;
934 raise_warning("Session callback expects true/false return value");
935 return false;
938 static UserSessionModule s_user_session_module;
940 ///////////////////////////////////////////////////////////////////////////////
941 // session serializers
943 struct SessionSerializer {
944 explicit SessionSerializer(const char *name) : m_name(name) {
945 RegisteredSerializers.push_back(this);
947 virtual ~SessionSerializer() {}
949 const char *getName() const { return m_name; }
951 virtual String encode() = 0;
952 virtual bool decode(const String& value) = 0;
954 static SessionSerializer *Find(const char *name) {
955 for (unsigned int i = 0; i < RegisteredSerializers.size(); i++) {
956 SessionSerializer *ss = RegisteredSerializers[i];
957 if (ss && strcasecmp(name, ss->m_name) == 0) {
958 return ss;
961 return nullptr;
964 private:
965 static std::vector<SessionSerializer*> RegisteredSerializers;
967 const char *m_name;
969 std::vector<SessionSerializer*> SessionSerializer::RegisteredSerializers;
971 #define PS_BIN_NR_OF_BITS 8
972 #define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
973 #define PS_BIN_MAX (PS_BIN_UNDEF-1)
975 struct BinarySessionSerializer : SessionSerializer {
976 BinarySessionSerializer() : SessionSerializer("php_binary") {}
978 virtual String encode() {
979 StringBuffer buf;
980 VariableSerializer vs(VariableSerializer::Type::Serialize);
981 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
982 Variant key = iter.first();
983 if (key.isString()) {
984 String skey = key.toString();
985 if (skey.size() <= PS_BIN_MAX) {
986 buf.append((unsigned char)skey.size());
987 buf.append(skey);
988 buf.append(vs.serialize(iter.second(), /* ret */ true,
989 /* keepCount */ true));
991 } else {
992 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
995 return buf.detach();
998 virtual bool decode(const String& value) {
999 const char *endptr = value.data() + value.size();
1000 VariableUnserializer vu(nullptr, 0, VariableUnserializer::Type::Serialize);
1001 for (const char *p = value.data(); p < endptr; ) {
1002 int namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
1003 if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
1004 return false;
1007 int has_value = *p & PS_BIN_UNDEF ? 0 : 1;
1008 String key(p + 1, namelen, CopyString);
1009 p += namelen + 1;
1010 if (has_value) {
1011 vu.set(p, endptr);
1012 try {
1013 auto sess = php_global_exchange(s__SESSION, init_null());
1014 forceToArray(sess).set(key, vu.unserialize());
1015 php_global_set(s__SESSION, std::move(sess));
1016 p = vu.head();
1017 } catch (const ResourceExceededException&) {
1018 throw;
1019 } catch (const Exception&) {}
1022 return true;
1025 static BinarySessionSerializer s_binary_session_serializer;
1027 #define PS_DELIMITER '|'
1028 #define PS_UNDEF_MARKER '!'
1030 struct PhpSessionSerializer : SessionSerializer {
1031 PhpSessionSerializer() : SessionSerializer("php") {}
1033 virtual String encode() {
1034 StringBuffer buf;
1035 VariableSerializer vs(VariableSerializer::Type::Serialize);
1036 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
1037 Variant key = iter.first();
1038 if (key.isString()) {
1039 String skey = key.toString();
1040 buf.append(skey);
1041 if (skey.find(PS_DELIMITER) >= 0 || skey.find(PS_UNDEF_MARKER) >= 0) {
1042 return String();
1044 buf.append(PS_DELIMITER);
1045 buf.append(vs.serialize(iter.second(), /* ret */ true,
1046 /* keepCount */ true));
1047 } else {
1048 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1051 return buf.detach();
1054 virtual bool decode(const String& value) {
1055 const char *p = value.data();
1056 const char *endptr = value.data() + value.size();
1057 VariableUnserializer vu(nullptr, 0, VariableUnserializer::Type::Serialize);
1058 while (p < endptr) {
1059 const char *q = p;
1060 while (*q != PS_DELIMITER) {
1061 if (++q >= endptr) return true;
1063 int has_value;
1064 if (p[0] == PS_UNDEF_MARKER) {
1065 p++;
1066 has_value = 0;
1067 } else {
1068 has_value = 1;
1071 String key(p, q - p, CopyString);
1072 q++;
1073 if (has_value) {
1074 vu.set(q, endptr);
1075 try {
1076 auto sess = php_global_exchange(s__SESSION, init_null());
1077 forceToArray(sess).set(key, vu.unserialize());
1078 php_global_set(s__SESSION, std::move(sess));
1079 q = vu.head();
1080 } catch (const ResourceExceededException&) {
1081 throw;
1082 } catch (const Exception&) {}
1084 p = q;
1086 return true;
1089 static PhpSessionSerializer s_php_session_serializer;
1091 struct PhpSerializeSessionSerializer : SessionSerializer {
1092 PhpSerializeSessionSerializer() : SessionSerializer("php_serialize") {}
1094 virtual String encode() {
1095 VariableSerializer vs(VariableSerializer::Type::Serialize);
1096 return vs.serialize(php_global(s__SESSION).toArray(), true, true);
1099 virtual bool decode(const String& value) {
1100 VariableUnserializer vu(value.data(), value.size(),
1101 VariableUnserializer::Type::Serialize);
1103 try {
1104 auto sess = vu.unserialize();
1105 php_global_set(s__SESSION, sess.toArray());
1106 } catch (const ResourceExceededException&) {
1107 throw;
1108 } catch (const Exception&) {}
1110 return true;
1113 static PhpSerializeSessionSerializer s_php_serialize_session_serializer;
1115 struct WddxSessionSerializer : SessionSerializer {
1116 WddxSessionSerializer() : SessionSerializer("wddx") {}
1118 virtual String encode() {
1119 auto wddxPacket = req::make<WddxPacket>(empty_string_variant_ref,
1120 true, true);
1121 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
1122 Variant key = iter.first();
1123 if (key.isString()) {
1124 wddxPacket->recursiveAddVar(key.toString(), iter.second(), true);
1125 } else {
1126 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1130 return wddxPacket->packet_end();
1133 virtual bool decode(const String& value) {
1134 Array params = Array::Create();
1135 params.append(value);
1136 Variant ret = vm_call_user_func("wddx_deserialize", params, true);
1137 if (ret.isArray()) {
1138 Array arr = ret.toArray();
1139 auto session = php_global_exchange(s__SESSION, init_null());
1140 for (ArrayIter iter(arr); iter; ++iter) {
1141 auto const key = iter.first();
1142 auto const value = iter.second();
1143 forceToArray(session).set(key, value);
1145 php_global_set(s__SESSION, std::move(session));
1148 return true;
1151 static WddxSessionSerializer s_wddx_session_serializer;
1153 ///////////////////////////////////////////////////////////////////////////////
1155 static bool session_check_active_state() {
1156 if (s_session->session_status == Session::Active) {
1157 raise_warning("A session is active. You cannot change the session"
1158 " module's ini settings at this time");
1159 return false;
1161 return true;
1164 static bool mod_is_open() {
1165 return s_session->mod_data || s_session->mod_user_implemented;
1168 static bool ini_on_update_save_handler(const std::string& value) {
1169 if (!session_check_active_state()) return false;
1170 s_session->mod = SessionModule::Find(value.c_str());
1171 return true;
1174 static std::string ini_get_save_handler() {
1175 auto &mod = s_session->mod;
1176 if (mod == nullptr) {
1177 return "";
1179 return mod->getName();
1182 static bool ini_on_update_serializer(const std::string& value) {
1183 if (!session_check_active_state()) return false;
1184 SessionSerializer *serializer = SessionSerializer::Find(value.data());
1185 if (serializer == nullptr) {
1186 raise_warning("ini_set(): Cannot find serialization handler '%s'",
1187 value.data());
1188 return false;
1190 s_session->serializer = serializer;
1191 return true;
1194 static std::string ini_get_serializer() {
1195 auto &serializer = s_session->serializer;
1196 if (serializer == nullptr) {
1197 return "";
1199 return serializer->getName();
1202 static bool ini_on_update_trans_sid(const bool& /*value*/) {
1203 return session_check_active_state();
1206 static bool ini_on_update_save_dir(const std::string& value) {
1207 if (value.find('\0') != std::string::npos) {
1208 return false;
1210 if (g_context.isNull()) return false;
1211 const char *path = value.data() + (value.rfind(';') + 1);
1212 if (File::TranslatePath(path).empty()) {
1213 return false;
1215 s_session->save_path = path;
1216 return true;
1219 ///////////////////////////////////////////////////////////////////////////////
1221 static bool php_session_destroy() {
1222 bool retval = true;
1224 if (s_session->session_status != Session::Active) {
1225 raise_warning("Trying to destroy uninitialized session");
1226 return false;
1229 if (s_session->mod->destroy(s_session->id.data()) == false) {
1230 retval = false;
1231 raise_warning("Session object destruction failed");
1234 if (mod_is_open()) {
1235 s_session->mod->close();
1238 s_session->destroy();
1240 return retval;
1243 static String php_session_encode() {
1244 if (!s_session->serializer) {
1245 raise_warning("Unknown session.serialize_handler. "
1246 "Failed to encode session object");
1247 return String();
1249 return s_session->serializer->encode();
1252 static void php_session_decode(const String& value) {
1253 if (!s_session->serializer) {
1254 raise_warning("Unknown session.serialize_handler. "
1255 "Failed to decode session object");
1256 return;
1258 if (!s_session->serializer->decode(value)) {
1259 php_session_destroy();
1260 raise_warning("Failed to decode session object. "
1261 "Session has been destroyed");
1265 static void php_session_initialize() {
1266 /* check session name for invalid characters */
1267 if (strpbrk(s_session->id.data(), "\r\n\t <>'\"\\")) {
1268 s_session->id.reset();
1271 if (!s_session->mod) {
1272 raise_error("No storage module chosen - failed to initialize session");
1273 return;
1276 /* Open session handler first */
1277 if (!s_session->mod->open(s_session->save_path.c_str(),
1278 s_session->session_name.c_str())) {
1279 raise_error("Failed to initialize storage module: %s (path: %s)",
1280 s_session->mod->getName(), s_session->save_path.c_str());
1281 return;
1284 /* If there is no ID, use session module to create one */
1285 int attempts = 3;
1286 if (s_session->id.empty()) {
1287 new_session:
1288 s_session->id = s_session->mod->create_sid();
1289 if (s_session->id.empty()) {
1290 raise_error("Failed to create session id: %s", s_session->mod->getName());
1291 return;
1293 if (s_session->use_cookies) {
1294 s_session->send_cookie = true;
1298 /* Read data */
1299 /* Question: if you create a SID here, should you also try to read data?
1300 * I'm not sure, but while not doing so will remove one session operation
1301 * it could prove useful for those sites which wish to have "default"
1302 * session information
1305 /* Unconditionally destroy existing arrays -- possible dirty data */
1306 php_global_set(s__SESSION, Variant{staticEmptyArray()});
1308 s_session->invalid_session_id = false;
1309 String value;
1310 if (s_session->mod->read(s_session->id.data(), value)) {
1311 php_session_decode(value);
1312 } else if (s_session->invalid_session_id) {
1313 /* address instances where the session read fails due to an invalid id */
1314 s_session->invalid_session_id = false;
1315 s_session->id.reset();
1316 if (--attempts > 0) {
1317 goto new_session;
1322 static void php_session_save_current_state() {
1323 bool ret = false;
1324 if (mod_is_open()) {
1325 String value = php_session_encode();
1326 if (!value.isNull()) {
1327 ret = s_session->mod->write(s_session->id.data(), value);
1330 if (!ret) {
1331 raise_warning("Failed to write session data (%s). Please verify that the "
1332 "current setting of session.save_path is correct (%s)",
1333 s_session->mod->getName(), s_session->save_path.c_str());
1335 if (mod_is_open()) {
1336 s_session->mod->close();
1340 ///////////////////////////////////////////////////////////////////////////////
1341 // Cookie Management
1343 static void php_session_send_cookie() {
1344 Transport *transport = g_context->getTransport();
1345 if (!transport) return;
1347 if (transport->headersSent()) {
1348 raise_warning("Cannot send session cookie - headers already sent");
1349 return;
1352 int64_t expire = 0;
1353 if (s_session->cookie_lifetime > 0) {
1354 struct timeval tv;
1355 gettimeofday(&tv, nullptr);
1356 expire = tv.tv_sec + s_session->cookie_lifetime;
1358 transport->setCookie(s_session->session_name,
1359 s_session->id,
1360 expire,
1361 s_session->cookie_path,
1362 s_session->cookie_domain,
1363 s_session->cookie_secure,
1364 s_session->cookie_httponly, true);
1367 static void php_session_reset_id() {
1368 if (s_session->use_cookies && s_session->send_cookie) {
1369 php_session_send_cookie();
1370 s_session->send_cookie = false;
1373 if (s_session->define_sid) {
1374 StringBuffer var;
1375 var.append(String(s_session->session_name));
1376 var.append('=');
1377 var.append(s_session->id);
1378 Variant v = var.detach();
1380 static const auto s_SID = makeStaticString("SID");
1381 auto const handle = lookupCnsHandle(s_SID);
1382 if (!handle) {
1383 f_define(String{s_SID}, v);
1384 } else {
1385 TypedValue* cns = &rds::handleToRef<TypedValue>(handle);
1386 v.setEvalScalar();
1387 cns->m_data = v.asTypedValue()->m_data;
1388 cns->m_type = v.asTypedValue()->m_type;
1389 if (rds::isNormalHandle(handle)) rds::initHandle(handle);
1394 ///////////////////////////////////////////////////////////////////////////////
1395 // Cache Limiters
1397 typedef struct {
1398 char *name;
1399 void (*func)();
1400 } php_session_cache_limiter_t;
1402 #define CACHE_LIMITER(name) _php_cache_limiter_##name
1403 #define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)()
1404 #define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
1405 #define ADD_HEADER(hdr) g_context->getTransport()->addHeader(hdr)
1407 #define LAST_MODIFIED "Last-Modified: "
1408 #define EXPIRES "Expires: "
1409 #define MAX_STR 512
1411 static char *month_names[] = {
1412 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1413 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1416 static char *week_days[] = {
1417 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
1420 static inline void strcpy_gmt(char *ubuf, time_t *when) {
1421 char buf[MAX_STR];
1422 struct tm tm, *res;
1423 int n;
1425 res = gmtime_r(when, &tm);
1427 if (!res) {
1428 buf[0] = '\0';
1429 return;
1432 n = snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", // SAFE
1433 week_days[tm.tm_wday], tm.tm_mday,
1434 month_names[tm.tm_mon], tm.tm_year + 1900,
1435 tm.tm_hour, tm.tm_min,
1436 tm.tm_sec);
1437 memcpy(ubuf, buf, n);
1438 ubuf[n] = '\0';
1441 const StaticString s_PATH_TRANSLATED("PATH_TRANSLATED");
1443 static inline void last_modified() {
1444 String path = php_global(s__SERVER).toArray()[s_PATH_TRANSLATED].toString();
1445 if (!path.empty()) {
1446 struct stat sb;
1447 if (stat(path.data(), &sb) == -1) {
1448 return;
1451 char buf[MAX_STR + 1];
1452 memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
1453 strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
1454 ADD_HEADER(buf);
1458 CACHE_LIMITER_FUNC(public) {
1459 char buf[MAX_STR + 1];
1460 struct timeval tv;
1461 time_t now;
1463 gettimeofday(&tv, nullptr);
1464 now = tv.tv_sec + s_session->cache_expire * 60;
1465 memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
1466 strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
1467 ADD_HEADER(buf);
1469 snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%" PRId64,
1470 s_session->cache_expire * 60); /* SAFE */
1471 ADD_HEADER(buf);
1473 last_modified();
1476 CACHE_LIMITER_FUNC(private_no_expire) {
1477 char buf[MAX_STR + 1];
1479 snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%" PRId64 ", "
1480 "pre-check=%" PRId64, s_session->cache_expire * 60,
1481 s_session->cache_expire * 60); /* SAFE */
1482 ADD_HEADER(buf);
1484 last_modified();
1487 CACHE_LIMITER_FUNC(private) {
1488 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1489 CACHE_LIMITER(private_no_expire)();
1492 CACHE_LIMITER_FUNC(nocache) {
1493 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1495 /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
1496 ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, "
1497 "post-check=0, pre-check=0");
1499 /* For HTTP/1.0 conforming clients */
1500 ADD_HEADER("Pragma: no-cache");
1503 static php_session_cache_limiter_t php_session_cache_limiters[] = {
1504 CACHE_LIMITER_ENTRY(public)
1505 CACHE_LIMITER_ENTRY(private)
1506 CACHE_LIMITER_ENTRY(private_no_expire)
1507 CACHE_LIMITER_ENTRY(nocache)
1511 static int php_session_cache_limiter() {
1512 if (s_session->cache_limiter[0] == '\0') return 0;
1514 Transport *transport = g_context->getTransport();
1515 if (transport) {
1516 if (transport->headersSent()) {
1517 raise_warning("Cannot send session cache limiter - "
1518 "headers already sent");
1519 return -2;
1522 php_session_cache_limiter_t *lim;
1523 for (lim = php_session_cache_limiters; lim->name; lim++) {
1524 if (!strcasecmp(lim->name, s_session->cache_limiter.c_str())) {
1525 lim->func();
1526 return 0;
1531 return -1;
1534 ///////////////////////////////////////////////////////////////////////////////
1536 static int64_t HHVM_FUNCTION(session_status) {
1537 return s_session->session_status;
1540 static Variant HHVM_FUNCTION(session_module_name,
1541 const Variant& newname /* = null_string */) {
1542 String oldname;
1543 if (s_session->mod && s_session->mod->getName()) {
1544 oldname = String(s_session->mod->getName(), CopyString);
1547 if (!newname.isNull()) {
1548 if (!SessionModule::Find(newname.toString().data())) {
1549 raise_warning("Cannot find named PHP session module (%s)",
1550 newname.toString().data());
1551 return false;
1553 if (mod_is_open()) {
1554 s_session->mod->close();
1556 s_session->mod_data = false;
1558 HHVM_FN(ini_set)("session.save_handler", newname.toString());
1561 return oldname;
1564 const StaticString s_session_write_close("session_write_close");
1566 static bool HHVM_FUNCTION(session_set_save_handler,
1567 const Object& sessionhandler,
1568 bool register_shutdown /* = true */) {
1570 if (s_session->mod &&
1571 s_session->session_status != Session::None &&
1572 s_session->mod != &s_user_session_module) {
1573 return false;
1576 if (s_session->session_status == Session::Active) {
1577 return false;
1580 if (s_session->default_mod == nullptr) {
1581 s_session->default_mod = s_session->mod;
1584 s_session->ps_session_handler = sessionhandler;
1586 // remove previous shutdown function
1587 g_context->removeShutdownFunction(s_session_write_close,
1588 ExecutionContext::ShutDown);
1589 if (register_shutdown) {
1590 HHVM_FN(register_shutdown_function)(s_session_write_close);
1593 if (ini_get_save_handler() != "user") {
1594 HHVM_FN(ini_set)("session.save_handler", "user");
1596 return true;
1599 static String HHVM_FUNCTION(session_id,
1600 const Variant& newid /* = null_string */) {
1601 String ret = s_session->id;
1602 if (ret.isNull()) {
1603 ret = empty_string();
1606 if (!newid.isNull()) {
1607 s_session->id = newid.toString();
1610 return ret;
1613 static bool HHVM_FUNCTION(session_regenerate_id,
1614 bool delete_old_session /* = false */) {
1615 Transport *transport = g_context->getTransport();
1616 if (transport && transport->headersSent()) {
1617 raise_warning("Cannot regenerate session id - headers already sent");
1618 return false;
1621 if (s_session->session_status == Session::Active) {
1622 if (!s_session->id.empty()) {
1623 if (delete_old_session &&
1624 !s_session->mod->destroy(s_session->id.data())) {
1625 raise_warning("Session object destruction failed");
1626 return false;
1628 s_session->id.reset();
1631 s_session->id = s_session->mod->create_sid();
1632 s_session->send_cookie = true;
1633 php_session_reset_id();
1634 return true;
1636 return false;
1639 static Variant HHVM_FUNCTION(session_encode) {
1640 String ret = php_session_encode();
1641 if (ret.isNull()) {
1642 return false;
1644 return ret;
1647 static bool HHVM_FUNCTION(session_decode, const String& data) {
1648 if (s_session->session_status != Session::None) {
1649 php_session_decode(data);
1650 return true;
1652 return false;
1655 const StaticString
1656 s_REQUEST_URI("REQUEST_URI"),
1657 s_HTTP_REFERER("HTTP_REFERER");
1659 static bool HHVM_FUNCTION(session_start) {
1660 s_session->apply_trans_sid = s_session->use_trans_sid;
1662 String value;
1663 switch (s_session->session_status) {
1664 case Session::Active:
1665 raise_notice("A session had already been started - "
1666 "ignoring session_start()");
1667 return false;
1668 case Session::Disabled:
1670 if (!s_session->mod && IniSetting::Get("session.save_handler", value)) {
1671 s_session->mod = SessionModule::Find(value.data());
1672 if (!s_session->mod) {
1673 raise_warning("Cannot find save handler '%s' - "
1674 "session startup failed", value.data());
1675 return false;
1678 if (!s_session->serializer &&
1679 IniSetting::Get("session.serialize_handler", value)) {
1680 s_session->serializer = SessionSerializer::Find(value.data());
1681 if (!s_session->serializer) {
1682 raise_warning("Cannot find serialization handler '%s' - "
1683 "session startup failed", value.data());
1684 return false;
1687 s_session->session_status = Session::None;
1688 /* fallthrough */
1690 default:
1691 assertx(s_session->session_status == Session::None);
1692 s_session->define_sid = true;
1693 s_session->send_cookie = true;
1697 * Cookies are preferred, because initially
1698 * cookie and get variables will be available.
1700 if (s_session->id.empty()) {
1701 if (s_session->use_cookies) {
1702 auto cookies = php_global(s__COOKIE).toArray();
1703 if (cookies.exists(String(s_session->session_name))) {
1704 s_session->id = cookies[String(s_session->session_name)].toString();
1705 s_session->apply_trans_sid = false;
1706 s_session->send_cookie = false;
1707 s_session->define_sid = false;
1711 if (!s_session->use_only_cookies && !s_session->id) {
1712 auto get = php_global(s__GET).toArray();
1713 if (get.exists(String(s_session->session_name))) {
1714 s_session->id = get[String(s_session->session_name)].toString();
1715 s_session->send_cookie = false;
1719 if (!s_session->use_only_cookies && !s_session->id) {
1720 auto post = php_global(s__POST).toArray();
1721 if (post.exists(String(s_session->session_name))) {
1722 s_session->id = post[String(s_session->session_name)].toString();
1723 s_session->send_cookie = false;
1728 int lensess = s_session->session_name.size();
1730 /* check the REQUEST_URI symbol for a string of the form
1731 '<session-name>=<session-id>' to allow URLs of the form
1732 http://yoursite/<session-name>=<session-id>/script.php */
1733 if (!s_session->use_only_cookies && s_session->id.empty()) {
1734 value = php_global(s__SERVER).toArray()[s_REQUEST_URI].toString();
1735 const char *p = strstr(value.data(), s_session->session_name.c_str());
1736 if (p && p[lensess] == '=') {
1737 p += lensess + 1;
1738 const char *q;
1739 if ((q = strpbrk(p, "/?\\"))) {
1740 s_session->id = String(p, q - p, CopyString);
1741 s_session->send_cookie = false;
1746 /* check whether the current request was referred to by
1747 an external site which invalidates the previously found id */
1748 if (!s_session->id.empty() && s_session->extern_referer_chk[0] != '\0') {
1749 value = php_global(s__SERVER).toArray()[s_HTTP_REFERER].toString();
1750 if (!strstr(value.data(), s_session->extern_referer_chk.c_str())) {
1751 s_session->id.reset();
1752 s_session->send_cookie = true;
1753 if (s_session->use_trans_sid) {
1754 s_session->apply_trans_sid = true;
1759 php_session_initialize();
1761 if (!s_session->use_cookies && s_session->send_cookie) {
1762 if (s_session->use_trans_sid) {
1763 s_session->apply_trans_sid = true;
1765 s_session->send_cookie = false;
1768 php_session_reset_id();
1770 s_session->session_status = Session::Active;
1772 php_session_cache_limiter();
1774 if (mod_is_open() && s_session->gc_probability > 0) {
1775 int nrdels = -1;
1777 int nrand = (int) ((float) s_session->gc_divisor * math_combined_lcg());
1778 if (nrand < s_session->gc_probability) {
1779 s_session->mod->gc(s_session->gc_maxlifetime, &nrdels);
1783 if (s_session->session_status != Session::Active) {
1784 return false;
1786 return true;
1789 static bool HHVM_FUNCTION(session_destroy) {
1790 return php_session_destroy();
1793 static void HHVM_FUNCTION(session_unset) {
1794 if (s_session->session_status == Session::None) {
1795 return;
1797 php_global_set(s__SESSION, empty_array());
1798 return;
1801 static void HHVM_FUNCTION(session_write_close) {
1802 if (s_session->session_status == Session::Active) {
1803 s_session->session_status = Session::None;
1804 php_session_save_current_state();
1808 static bool HHVM_METHOD(SessionHandler, hhopen,
1809 const String& save_path, const String& session_id) {
1810 return s_session->default_mod &&
1811 s_session->default_mod->open(save_path.data(), session_id.data());
1814 static bool HHVM_METHOD(SessionHandler, hhclose) {
1815 return s_session->default_mod && s_session->default_mod->close();
1818 static Variant
1819 HHVM_METHOD(SessionHandler, hhread, const String& /*session_id*/) {
1820 String value;
1821 if (s_session->default_mod &&
1822 s_session->default_mod->read(s_session->id.data(), value)) {
1823 php_session_decode(value);
1824 return value;
1826 return init_null();
1829 static bool HHVM_METHOD(SessionHandler, hhwrite,
1830 const String& session_id, const String& session_data) {
1831 return s_session->default_mod &&
1832 s_session->default_mod->write(session_id.data(), session_data);
1835 static bool HHVM_METHOD(SessionHandler, hhdestroy, const String& session_id) {
1836 return s_session->default_mod &&
1837 s_session->default_mod->destroy(session_id.data());
1840 static bool HHVM_METHOD(SessionHandler, hhgc, int maxlifetime) {
1841 int nrdels = -1;
1842 return s_session->default_mod &&
1843 s_session->default_mod->gc(maxlifetime, &nrdels);
1846 void ext_session_request_shutdown() {
1847 HHVM_FN(session_write_close)();
1848 s_session->requestShutdownImpl();
1851 ///////////////////////////////////////////////////////////////////////////////
1853 static struct SessionExtension final : Extension {
1854 SessionExtension() : Extension("session", NO_EXTENSION_VERSION_YET) { }
1855 void moduleInit() override {
1856 HHVM_RC_INT(PHP_SESSION_DISABLE, Session::Disabled);
1857 HHVM_RC_INT(PHP_SESSION_NONE, Session::None);
1858 HHVM_RC_INT(PHP_SESSION_ACTIVE, Session::Active);
1860 HHVM_FE(session_status);
1861 HHVM_FE(session_module_name);
1862 HHVM_FE(session_id);
1863 HHVM_FE(session_regenerate_id);
1864 HHVM_FE(session_encode);
1865 HHVM_FE(session_decode);
1866 HHVM_FE(session_start);
1867 HHVM_FE(session_destroy);
1868 HHVM_FE(session_unset);
1869 HHVM_FE(session_write_close);
1871 HHVM_ME(SessionHandler, hhopen);
1872 HHVM_ME(SessionHandler, hhclose);
1873 HHVM_ME(SessionHandler, hhread);
1874 HHVM_ME(SessionHandler, hhwrite);
1875 HHVM_ME(SessionHandler, hhdestroy);
1876 HHVM_ME(SessionHandler, hhgc);
1877 HHVM_NAMED_FE(__SystemLib\\session_set_save_handler,
1878 HHVM_FN(session_set_save_handler)
1881 loadSystemlib();
1884 void threadInit() override {
1885 assertx(s_session.isNull());
1886 s_session.getCheck();
1887 Extension* ext = ExtensionRegistry::get(s_session_ext_name);
1888 assertx(ext);
1889 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1890 "session.save_path", "",
1891 IniSetting::SetAndGet<std::string>(
1892 ini_on_update_save_dir, nullptr
1894 &s_session->save_path);
1895 Variant v;
1896 if (IniSetting::GetSystem("session.save_path", v) &&
1897 !v.toString().empty()) {
1898 s_session->reset_save_path = true;
1900 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1901 "session.name", "PHPSESSID",
1902 &s_session->session_name);
1903 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1904 "session.save_handler", "files",
1905 IniSetting::SetAndGet<std::string>(
1906 ini_on_update_save_handler, ini_get_save_handler
1908 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1909 "session.auto_start", "0",
1910 &s_session->auto_start);
1911 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1912 "session.gc_probability", "1",
1913 &s_session->gc_probability);
1914 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1915 "session.gc_divisor", "100",
1916 &s_session->gc_divisor);
1917 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1918 "session.gc_maxlifetime", "1440",
1919 &s_session->gc_maxlifetime);
1920 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1921 "session.serialize_handler", "php",
1922 IniSetting::SetAndGet<std::string>(
1923 ini_on_update_serializer, ini_get_serializer
1925 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1926 "session.cookie_lifetime", "0",
1927 &s_session->cookie_lifetime);
1928 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1929 "session.cookie_path", "/",
1930 &s_session->cookie_path);
1931 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1932 "session.cookie_domain", "",
1933 &s_session->cookie_domain);
1934 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1935 "session.cookie_secure", "",
1936 &s_session->cookie_secure);
1937 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1938 "session.cookie_httponly", "",
1939 &s_session->cookie_httponly);
1940 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1941 "session.use_cookies", "1",
1942 &s_session->use_cookies);
1943 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1944 "session.use_only_cookies", "1",
1945 &s_session->use_only_cookies);
1946 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1947 "session.referer_check", "",
1948 &s_session->extern_referer_chk);
1949 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1950 "session.entropy_file", "",
1951 &s_session->entropy_file);
1952 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1953 "session.entropy_length", "0",
1954 &s_session->entropy_length);
1955 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1956 "session.cache_limiter", "nocache",
1957 &s_session->cache_limiter);
1958 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1959 "session.cache_expire", "180",
1960 &s_session->cache_expire);
1961 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1962 "session.use_trans_sid", "0",
1963 IniSetting::SetAndGet<bool>(
1964 ini_on_update_trans_sid, nullptr
1966 &s_session->use_trans_sid);
1967 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1968 "session.hash_function", "0",
1969 &s_session->hash_func);
1970 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1971 "session.hash_bits_per_character", "4",
1972 &s_session->hash_bits_per_character);
1975 void threadShutdown() override {
1976 s_session.destroy();
1979 void requestInit() override {
1980 s_session->init();
1984 No need for requestShutdown; its handled explicitly by a call to
1985 ext_session_request_shutdown()
1987 } s_session_extension;
1989 ///////////////////////////////////////////////////////////////////////////////