Return by value from invokeFunc()
[hiphop-php.git] / hphp / runtime / ext / session / ext_session.cpp
blob052535c354a2d234823ddb9f48f243be45b8fc9d
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 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-event-handler.h"
44 #include "hphp/runtime/base/request-local.h"
45 #include "hphp/runtime/base/string-buffer.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 template<class F> void scan(F& mark) const {
86 mark(ps_session_handler);
89 std::string save_path;
90 bool reset_save_path{false};
91 std::string session_name;
92 std::string extern_referer_chk;
93 std::string entropy_file;
94 int64_t entropy_length{0};
95 std::string cache_limiter;
96 int64_t cookie_lifetime{0};
97 std::string cookie_path;
98 std::string cookie_domain;
99 Status session_status{None};
100 bool cookie_secure{false};
101 bool cookie_httponly{false};
102 bool mod_data{false};
103 bool mod_user_implemented{false};
105 SessionModule* mod{nullptr};
106 SessionModule* default_mod{nullptr};
108 int64_t gc_probability{0};
109 int64_t gc_divisor{0};
110 int64_t gc_maxlifetime{0};
111 int64_t cache_expire{0};
113 Object ps_session_handler;
114 SessionSerializer* serializer{nullptr};
116 bool auto_start{false};
117 bool use_cookies{false};
118 bool use_only_cookies{false};
119 bool use_trans_sid{false}; // contains INI value of whether to use trans-sid
120 bool apply_trans_sid{false}; // whether to enable trans-sid for current req
121 bool send_cookie{false};
122 bool define_sid{false};
123 bool invalid_session_id{false}; /* allows the driver to report about an
124 invalid session id and request id regeneration */
126 std::string hash_func;
127 int64_t hash_bits_per_character{0};
130 const StaticString s_session_ext_name("session");
132 struct SessionRequestData final : Session {
133 void init() {
134 id.detach();
135 session_status = Session::None;
136 ps_session_handler.reset();
137 save_path.clear();
138 if (reset_save_path) IniSetting::ResetSystemDefault("session.save_path");
141 void destroy() {
142 id.reset();
143 session_status = Session::None;
144 // Note: we should not destroy user save handler here
145 // (if the session is restarted during request, the handler
146 // should be alive), it's destroyed only in the request shutdown.
149 void requestShutdownImpl();
151 template<class F> void scan(F& mark) const {
152 Session::scan(mark);
153 mark(id);
156 public:
157 String id;
160 static __thread SessionRequestData* s_session;
162 void SessionRequestData::requestShutdownImpl() {
163 if (mod_is_open()) {
164 try {
165 mod->close();
166 } catch (...) {}
168 ps_session_handler.reset();
169 id.reset();
172 std::vector<SessionModule*> SessionModule::RegisteredModules;
175 * Note that we cannot use the BASE64 alphabet here, because
176 * it contains "/" and "+": both are unacceptable for simple inclusion
177 * into URLs.
179 static char hexconvtab[] =
180 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
182 static void bin_to_readable(const String& in, StringBuffer &out, char nbits) {
183 unsigned char *p = (unsigned char *)in.data();
184 unsigned char *q = (unsigned char *)in.data() + in.size();
185 unsigned short w = 0;
186 int have = 0;
187 int mask = (1 << nbits) - 1;
189 while (true) {
190 if (have < nbits) {
191 if (p < q) {
192 w |= *p++ << have;
193 have += 8;
194 } else {
195 /* consumed everything? */
196 if (have == 0) break;
197 /* No? We need a final round */
198 have = nbits;
202 /* consume nbits */
203 out.append(hexconvtab[w & mask]);
204 w >>= nbits;
205 have -= nbits;
209 const StaticString
210 s_REMOTE_ADDR("REMOTE_ADDR"),
211 s__SERVER("_SERVER"),
212 s__SESSION("_SESSION"),
213 s__COOKIE("_COOKIE"),
214 s__GET("_GET"),
215 s__POST("_POST");
217 String SessionModule::create_sid() {
218 String remote_addr = php_global(s__SERVER)
219 .toArray()[s_REMOTE_ADDR].toString();
221 struct timeval tv;
222 gettimeofday(&tv, nullptr);
224 StringBuffer buf;
225 buf.printf("%.15s%ld%ld%0.8F", remote_addr.data(),
226 tv.tv_sec, (long int)tv.tv_usec, math_combined_lcg() * 10);
228 if (String(s_session->hash_func).isNumeric()) {
229 switch (String(s_session->hash_func).toInt64()) {
230 case md5: s_session->hash_func = "md5"; break;
231 case sha1: s_session->hash_func = "sha1"; break;
235 Variant context = HHVM_FN(hash_init)(s_session->hash_func);
236 if (same(context, false)) {
237 Logger::Error("Invalid session hash function: %s",
238 s_session->hash_func.c_str());
239 return String();
241 if (!HHVM_FN(hash_update)(context.toResource(), buf.detach())) {
242 Logger::Error("hash_update() failed");
243 return String();
246 if (s_session->entropy_length > 0) {
247 int fd = ::open(s_session->entropy_file.c_str(), O_RDONLY);
248 if (fd >= 0) {
249 unsigned char rbuf[2048];
250 int n;
251 int to_read = s_session->entropy_length;
252 while (to_read > 0) {
253 n = ::read(fd, rbuf, (to_read < (int)sizeof(rbuf) ?
254 to_read : (int)sizeof(buf)));
255 if (n <= 0) break;
256 if (!HHVM_FN(hash_update)(context.toResource(),
257 String((const char *)rbuf, n, CopyString))) {
258 Logger::Error("hash_update() failed");
259 ::close(fd);
260 return String();
262 to_read -= n;
264 ::close(fd);
268 auto const hashed = HHVM_FN(hash_final)(
269 context.toResource(), /* raw */ true
270 ).toString();
272 if (s_session->hash_bits_per_character < 4 ||
273 s_session->hash_bits_per_character > 6) {
274 s_session->hash_bits_per_character = 4;
275 raise_warning("The ini setting hash_bits_per_character is out of range "
276 "(should be 4, 5, or 6) - using 4 for now");
279 StringBuffer readable;
280 bin_to_readable(hashed, readable, s_session->hash_bits_per_character);
281 return readable.detach();
284 ///////////////////////////////////////////////////////////////////////////////
285 // SystemlibSessionModule
287 static StaticString s_SessionHandlerInterface("SessionHandlerInterface");
289 static StaticString s_open("open");
290 static StaticString s_close("close");
291 static StaticString s_read("read");
292 static StaticString s_write("write");
293 static StaticString s_gc("gc");
294 static StaticString s_destroy("destroy");
296 LowPtr<Class> SystemlibSessionModule::s_SHIClass = nullptr;
299 * Relies on the fact that only one SessionModule will be active
300 * in a given thread at any one moment.
302 IMPLEMENT_REQUEST_LOCAL(SystemlibSessionInstance,
303 SystemlibSessionModule::s_obj);
305 Func* SystemlibSessionModule::lookupFunc(Class *cls, StringData *fname) {
306 Func *f = cls->lookupMethod(fname);
307 if (!f) {
308 raise_error("class %s must declare method %s()",
309 m_classname, fname->data());
312 if (f->attrs() & AttrStatic) {
313 raise_error("%s::%s() must not be declared static",
314 m_classname, fname->data());
317 if (f->attrs() & (AttrPrivate|AttrProtected|AttrAbstract)) {
318 raise_error("%s::%s() must be declared public",
319 m_classname, fname->data());
322 return f;
325 void SystemlibSessionModule::lookupClass() {
326 Class *cls;
327 if (!(cls = Unit::loadClass(String(m_classname, CopyString).get()))) {
328 raise_error("Unable to locate systemlib class '%s'", m_classname);
331 if (cls->attrs() & (AttrTrait|AttrInterface)) {
332 raise_error("'%s' must be a real class, not an interface or trait",
333 m_classname);
336 if (!s_SHIClass) {
337 s_SHIClass = Unit::lookupClass(s_SessionHandlerInterface.get());
338 if (!s_SHIClass) {
339 raise_error("Unable to locate '%s' interface",
340 s_SessionHandlerInterface.data());
344 if (!cls->classof(s_SHIClass)) {
345 raise_error("SystemLib session module '%s' must implement '%s'",
346 m_classname,
347 s_SessionHandlerInterface.data());
350 if (LookupResult::MethodFoundWithThis !=
351 lookupCtorMethod(m_ctor, cls, arGetContextClass(vmfp()))) {
352 raise_error("Unable to call %s's constructor", m_classname);
355 m_open = lookupFunc(cls, s_open.get());
356 m_close = lookupFunc(cls, s_close.get());
357 m_read = lookupFunc(cls, s_read.get());
358 m_write = lookupFunc(cls, s_write.get());
359 m_gc = lookupFunc(cls, s_gc.get());
360 m_destroy = lookupFunc(cls, s_destroy.get());
361 m_cls = cls;
364 const Object& SystemlibSessionModule::getObject() {
365 if (const auto& o = s_obj->getObject()) {
366 return o;
369 VMRegAnchor _;
370 if (!m_cls) {
371 lookupClass();
373 s_obj->setObject(Object{m_cls});
374 const auto& obj = s_obj->getObject();
375 tvRefcountedDecRef(
376 g_context->invokeFuncFew(m_ctor, obj.get())
378 return obj;
381 bool SystemlibSessionModule::open(const char *save_path,
382 const char *session_name) {
383 const auto& obj = getObject();
385 Variant savePath = String(save_path, CopyString);
386 Variant sessionName = String(session_name, CopyString);
387 TypedValue args[2] = { *savePath.asCell(), *sessionName.asCell() };
388 auto ret = Variant::attach(
389 g_context->invokeFuncFew(m_open, obj.get(), nullptr, 2, args)
392 if (ret.isBoolean() && ret.toBoolean()) {
393 s_session->mod_data = true;
394 return true;
397 raise_warning("Failed calling %s::open()", m_classname);
398 return false;
401 bool SystemlibSessionModule::close() {
402 const auto& obj = s_obj->getObject();
403 if (!obj) {
404 // close() can be called twice in some circumstances
405 s_session->mod_data = false;
406 return true;
409 auto ret = Variant::attach(
410 g_context->invokeFuncFew(m_close, obj.get())
412 s_obj->destroy();
414 if (ret.isBoolean() && ret.toBoolean()) {
415 return true;
418 raise_warning("Failed calling %s::close()", m_classname);
419 return false;
422 bool SystemlibSessionModule::read(const char *key, String &value) {
423 const auto& obj = getObject();
425 Variant sessionKey = String(key, CopyString);
426 auto ret = Variant::attach(
427 g_context->invokeFuncFew(m_read, obj.get(),
428 nullptr, 1, sessionKey.asCell())
431 if (ret.isString()) {
432 value = ret.toString();
433 return true;
436 raise_warning("Failed calling %s::read()", m_classname);
437 return false;
440 bool SystemlibSessionModule::write(const char *key, const String& value) {
441 const auto& obj = getObject();
443 Variant sessionKey = String(key, CopyString);
444 Variant sessionVal = value;
445 TypedValue args[2] = { *sessionKey.asCell(), *sessionVal.asCell() };
446 auto ret = Variant::attach(
447 g_context->invokeFuncFew(m_write, obj.get(), nullptr, 2, args)
450 if (ret.isBoolean() && ret.toBoolean()) {
451 return true;
454 raise_warning("Failed calling %s::write()", m_classname);
455 return false;
458 bool SystemlibSessionModule::destroy(const char *key) {
459 const auto& obj = getObject();
461 Variant sessionKey = String(key, CopyString);
462 auto ret = Variant::attach(
463 g_context->invokeFuncFew(m_destroy, obj.get(),
464 nullptr, 1, sessionKey.asCell())
467 if (ret.isBoolean() && ret.toBoolean()) {
468 return true;
471 raise_warning("Failed calling %s::destroy()", m_classname);
472 return false;
475 bool SystemlibSessionModule::gc(int maxlifetime, int *nrdels) {
476 const auto& obj = getObject();
478 Variant maxLifeTime = maxlifetime;
479 auto ret = Variant::attach(
480 g_context->invokeFuncFew(m_gc, obj.get(),
481 nullptr, 1, maxLifeTime.asCell())
484 if (ret.isInteger()) {
485 if (nrdels) {
486 *nrdels = ret.toInt64();
488 return true;
491 raise_warning("Failed calling %s::gc()", m_classname);
492 return false;
495 //////////////////////////////////////////////////////////////////////////////
496 // SystemlibSessionModule implementations
498 static struct RedisSessionModule : SystemlibSessionModule {
499 RedisSessionModule() :
500 SystemlibSessionModule("redis", "RedisSessionModule") { }
501 } s_redis_session_module;
503 static struct MemcacheSessionModule : SystemlibSessionModule {
504 MemcacheSessionModule() :
505 SystemlibSessionModule("memcache", "MemcacheSessionModule") { }
506 } s_memcache_session_module;
508 static struct MemcachedSessionModule : SystemlibSessionModule {
509 MemcachedSessionModule() :
510 SystemlibSessionModule("memcached", "MemcachedSessionModule") { }
511 } s_memcached_session_module;
513 //////////////////////////////////////////////////////////////////////////////
514 // FileSessionModule
516 struct FileSessionData {
517 FileSessionData() : m_fd(-1), m_dirdepth(0), m_st_size(0), m_filemode(0600) {
520 bool open(const char *save_path, const char *session_name) {
521 String tmpdir;
522 if (*save_path == '\0') {
523 tmpdir = HHVM_FN(sys_get_temp_dir)();
524 save_path = tmpdir.data();
527 /* split up input parameter */
528 const char *argv[3];
529 int argc = 0;
530 const char *last = save_path;
531 const char *p = strchr(save_path, ';');
532 while (p) {
533 argv[argc++] = last; last = ++p; p = strchr(p, ';');
534 if (argc > 1) break;
536 argv[argc++] = last;
538 if (argc > 1) {
539 errno = 0;
540 m_dirdepth = (size_t) strtol(argv[0], nullptr, 10);
541 if (errno == ERANGE) {
542 raise_warning("The first parameter in session.save_path is invalid");
543 return false;
547 if (argc > 2) {
548 errno = 0;
549 m_filemode = strtol(argv[1], nullptr, 8);
550 if (errno == ERANGE || m_filemode < 0 || m_filemode > 07777) {
551 raise_warning("The second parameter in session.save_path is invalid");
552 return false;
556 save_path = argv[argc - 1];
557 if (File::TranslatePath(save_path).empty()) {
558 raise_warning("Unable to open save_path %s", save_path);
559 return false;
562 m_fd = -1;
563 m_basedir = save_path;
564 s_session->mod_data = true;
565 return true;
568 bool close() {
569 closeImpl();
570 m_lastkey.clear();
571 m_basedir.clear();
572 s_session->mod_data = false;
573 return true;
576 bool read(const char *key, String &value) {
577 openImpl(key);
578 if (m_fd < 0) {
579 return false;
582 struct stat sbuf;
583 if (fstat(m_fd, &sbuf)) {
584 return false;
586 m_st_size = sbuf.st_size;
587 if (m_st_size == 0) {
588 value = "";
589 return true;
592 String s = String(m_st_size, ReserveString);
593 char *val = s.mutableData();
595 #if defined(HAVE_PREAD)
596 long n = pread(m_fd, val, m_st_size, 0);
597 #else
598 lseek(m_fd, 0, SEEK_SET);
599 long n = ::read(m_fd, val, m_st_size);
600 #endif
602 if (n != (int)m_st_size) {
603 if (n == -1) {
604 raise_warning("read failed: %s (%d)", folly::errnoStr(errno).c_str(),
605 errno);
606 } else {
607 raise_warning("read returned less bytes than requested");
609 return false;
612 value = s.setSize(m_st_size);
613 return true;
616 bool write(const char *key, const String& value) {
617 openImpl(key);
618 if (m_fd < 0) {
619 return false;
622 struct stat sbuf;
623 if (fstat(m_fd, &sbuf)) {
624 return false;
626 m_st_size = sbuf.st_size;
629 * truncate file, if the amount of new data is smaller than
630 * the existing data set.
632 if (value.size() < (int)m_st_size) {
633 if (ftruncate(m_fd, 0) < 0) {
634 raise_warning("truncate failed: %s (%d)",
635 folly::errnoStr(errno).c_str(), errno);
636 return false;
640 #if defined(HAVE_PWRITE)
641 long n = pwrite(m_fd, value.data(), value.size(), 0);
642 #else
643 lseek(m_fd, 0, SEEK_SET);
644 long n = ::write(m_fd, value.data(), value.size());
645 #endif
647 if (n != value.size()) {
648 if (n == -1) {
649 raise_warning("write failed: %s (%d)",
650 folly::errnoStr(errno).c_str(), errno);
651 } else {
652 raise_warning("write wrote less bytes than requested");
654 return false;
657 return true;
660 bool destroy(const char *key) {
661 char buf[PATH_MAX];
662 if (!createPath(buf, sizeof(buf), key)) {
663 return false;
666 if (m_fd != -1) {
667 closeImpl();
668 if (unlink(buf) == -1) {
669 /* This is a little safety check for instances when we are dealing
670 with a regenerated session that was not yet written to disk */
671 if (!access(buf, F_OK)) {
672 return false;
677 return true;
680 bool gc(int maxlifetime, int *nrdels) {
681 /* we don't perform any cleanup, if dirdepth is larger than 0.
682 we return true, since all cleanup should be handled by
683 an external entity (i.e. find -ctime x | xargs rm) */
684 if (m_dirdepth == 0) {
685 *nrdels = CleanupDir(m_basedir.c_str(), maxlifetime);
687 return true;
690 private:
691 int m_fd;
692 std::string m_lastkey;
693 std::string m_basedir;
694 size_t m_dirdepth;
695 size_t m_st_size;
696 int m_filemode;
698 /* If you change the logic here, please also update the error message in
699 * ps_files_open() appropriately */
700 static bool IsValid(const char *key) {
701 const char *p; char c;
702 bool ret = true;
703 for (p = key; (c = *p); p++) {
704 /* valid characters are a..z,A..Z,0..9 */
705 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
706 || (c >= '0' && c <= '9') || c == ',' || c == '-')) {
707 ret = false;
708 break;
711 size_t len = p - key;
712 if (len == 0) {
713 ret = false;
715 return ret;
718 #define FILE_PREFIX "sess_"
720 bool createPath(char *buf, size_t buflen, const char *key) {
721 size_t key_len = strlen(key);
722 if (key_len <= m_dirdepth ||
723 buflen < (m_basedir.size() + 2 * m_dirdepth + key_len +
724 5 + sizeof(FILE_PREFIX))) {
725 return false;
728 const char *p = key;
729 int n = m_basedir.size();
730 memcpy(buf, m_basedir.c_str(), n);
731 buf[n++] = PHP_DIR_SEPARATOR;
732 for (int i = 0; i < (int)m_dirdepth; i++) {
733 buf[n++] = *p++;
734 buf[n++] = PHP_DIR_SEPARATOR;
736 memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
737 n += sizeof(FILE_PREFIX) - 1;
738 memcpy(buf + n, key, key_len);
739 n += key_len;
740 buf[n] = '\0';
742 return true;
745 #ifndef O_BINARY
746 #define O_BINARY 0
747 #endif
749 void closeImpl() {
750 if (m_fd != -1) {
751 #ifdef PHP_WIN32
752 /* On Win32 locked files that are closed without being explicitly
753 unlocked will be unlocked only when "system resources become
754 available". */
755 flock(m_fd, LOCK_UN);
756 #endif
757 ::close(m_fd);
758 m_fd = -1;
762 void openImpl(const char *key) {
763 if (m_fd < 0 || !m_lastkey.empty() || m_lastkey != key) {
764 m_lastkey.clear();
765 closeImpl();
767 if (!IsValid(key)) {
768 raise_warning("The session id contains illegal characters, "
769 "valid characters are a-z, A-Z, 0-9 and '-,'");
770 s_session->invalid_session_id = true;
771 return;
774 char buf[PATH_MAX];
775 if (!createPath(buf, sizeof(buf), key)) {
776 return;
779 m_lastkey = key;
780 m_fd = ::open(buf, O_CREAT | O_RDWR | O_BINARY, m_filemode);
782 if (m_fd != -1) {
783 flock(m_fd, LOCK_EX);
785 #ifdef F_SETFD
786 # ifndef FD_CLOEXEC
787 # define FD_CLOEXEC 1
788 # endif
789 if (fcntl(m_fd, F_SETFD, FD_CLOEXEC)) {
790 raise_warning("fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)",
791 m_fd, folly::errnoStr(errno).c_str(), errno);
793 #endif
794 } else {
795 raise_warning("open(%s, O_RDWR) failed: %s (%d)", buf,
796 folly::errnoStr(errno).c_str(), errno);
801 static int CleanupDir(const char *dirname, int maxlifetime) {
802 DIR *dir = opendir(dirname);
803 if (!dir) {
804 raise_notice("ps_files_cleanup_dir: opendir(%s) failed: %s (%d)",
805 dirname, folly::errnoStr(errno).c_str(), errno);
806 return 0;
809 time_t now;
810 time(&now);
812 size_t dirname_len = strlen(dirname);
813 char dentry[sizeof(struct dirent) + PATH_MAX];
814 struct dirent *entry = (struct dirent *) &dentry;
815 struct stat sbuf;
816 int nrdels = 0;
818 /* Prepare buffer (dirname never changes) */
819 char buf[PATH_MAX];
820 memcpy(buf, dirname, dirname_len);
821 buf[dirname_len] = PHP_DIR_SEPARATOR;
823 while (readdir_r(dir, (struct dirent *)dentry, &entry) == 0 && entry) {
824 /* does the file start with our prefix? */
825 if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
826 size_t entry_len = strlen(entry->d_name);
828 /* does it fit into our buffer? */
829 if (entry_len + dirname_len + 2 < PATH_MAX) {
830 /* create the full path.. */
831 memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
833 /* NUL terminate it and */
834 buf[dirname_len + entry_len + 1] = '\0';
836 /* check whether its last access was more than maxlifet ago */
837 if (stat(buf, &sbuf) == 0 && (now - sbuf.st_mtime) > maxlifetime) {
838 unlink(buf);
839 nrdels++;
845 closedir(dir);
846 return nrdels;
849 IMPLEMENT_THREAD_LOCAL(FileSessionData, s_file_session_data);
851 struct FileSessionModule : SessionModule {
852 FileSessionModule() : SessionModule("files") {
854 virtual bool open(const char *save_path, const char *session_name) {
855 return s_file_session_data->open(save_path, session_name);
857 virtual bool close() {
858 return s_file_session_data->close();
860 virtual bool read(const char *key, String &value) {
861 return s_file_session_data->read(key, value);
863 virtual bool write(const char *key, const String& value) {
864 return s_file_session_data->write(key, value);
866 virtual bool destroy(const char *key) {
867 return s_file_session_data->destroy(key);
869 virtual bool gc(int maxlifetime, int *nrdels) {
870 return s_file_session_data->gc(maxlifetime, nrdels);
873 static FileSessionModule s_file_session_module;
875 ///////////////////////////////////////////////////////////////////////////////
876 // UserSessionModule
878 struct UserSessionModule : SessionModule {
879 UserSessionModule() : SessionModule("user") {}
881 bool open(const char *save_path, const char *session_name) override {
882 auto func = make_packed_array(s_session->ps_session_handler, s_open);
883 auto args = make_packed_array(String(save_path), String(session_name));
885 auto res = vm_call_user_func(func, args);
886 s_session->mod_user_implemented = true;
887 return handleReturnValue(res);
890 bool close() override {
891 auto func = make_packed_array(s_session->ps_session_handler, s_close);
892 auto args = Array::Create();
894 auto res = vm_call_user_func(func, args);
895 s_session->mod_user_implemented = false;
896 return handleReturnValue(res);
899 bool read(const char *key, String &value) override {
900 Variant ret = vm_call_user_func(
901 make_packed_array(s_session->ps_session_handler, s_read),
902 make_packed_array(String(key))
904 if (ret.isString()) {
905 value = ret.toString();
906 return true;
908 return false;
911 bool write(const char *key, const String& value) override {
912 return handleReturnValue(vm_call_user_func(
913 make_packed_array(s_session->ps_session_handler, s_write),
914 make_packed_array(String(key, CopyString), value)
918 bool destroy(const char *key) override {
919 return handleReturnValue(vm_call_user_func(
920 make_packed_array(s_session->ps_session_handler, s_destroy),
921 make_packed_array(String(key))
925 bool gc(int maxlifetime, int *nrdels) override {
926 return handleReturnValue(vm_call_user_func(
927 make_packed_array(s_session->ps_session_handler, s_gc),
928 make_packed_array((int64_t)maxlifetime)
932 private:
933 bool handleReturnValue(const Variant& ret) {
934 if (ret.isBoolean()) {
935 return ret.toBoolean();
937 if (ret.isInteger()) {
938 // BC fallbacks for values which work in PHP5 & PHP7
939 auto i = ret.toInt64();
940 if (i == -1) return false;
941 if (i == 0) return true;
943 raise_warning("Session callback expects true/false return value");
944 return false;
947 static UserSessionModule s_user_session_module;
949 ///////////////////////////////////////////////////////////////////////////////
950 // session serializers
952 struct SessionSerializer {
953 explicit SessionSerializer(const char *name) : m_name(name) {
954 RegisteredSerializers.push_back(this);
956 virtual ~SessionSerializer() {}
958 const char *getName() const { return m_name; }
960 virtual String encode() = 0;
961 virtual bool decode(const String& value) = 0;
963 static SessionSerializer *Find(const char *name) {
964 for (unsigned int i = 0; i < RegisteredSerializers.size(); i++) {
965 SessionSerializer *ss = RegisteredSerializers[i];
966 if (ss && strcasecmp(name, ss->m_name) == 0) {
967 return ss;
970 return nullptr;
973 private:
974 static std::vector<SessionSerializer*> RegisteredSerializers;
976 const char *m_name;
978 std::vector<SessionSerializer*> SessionSerializer::RegisteredSerializers;
980 #define PS_BIN_NR_OF_BITS 8
981 #define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
982 #define PS_BIN_MAX (PS_BIN_UNDEF-1)
984 struct BinarySessionSerializer : SessionSerializer {
985 BinarySessionSerializer() : SessionSerializer("php_binary") {}
987 virtual String encode() {
988 StringBuffer buf;
989 VariableSerializer vs(VariableSerializer::Type::Serialize);
990 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
991 Variant key = iter.first();
992 if (key.isString()) {
993 String skey = key.toString();
994 if (skey.size() <= PS_BIN_MAX) {
995 buf.append((unsigned char)skey.size());
996 buf.append(skey);
997 buf.append(vs.serialize(iter.second(), /* ret */ true,
998 /* keepCount */ true));
1000 } else {
1001 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1004 return buf.detach();
1007 virtual bool decode(const String& value) {
1008 const char *endptr = value.data() + value.size();
1009 VariableUnserializer vu(nullptr, 0, VariableUnserializer::Type::Serialize);
1010 for (const char *p = value.data(); p < endptr; ) {
1011 int namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
1012 if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
1013 return false;
1016 int has_value = *p & PS_BIN_UNDEF ? 0 : 1;
1017 String key(p + 1, namelen, CopyString);
1018 p += namelen + 1;
1019 if (has_value) {
1020 vu.set(p, endptr);
1021 try {
1022 auto sess = php_global_exchange(s__SESSION, init_null());
1023 forceToArray(sess).set(key, vu.unserialize());
1024 php_global_set(s__SESSION, std::move(sess));
1025 p = vu.head();
1026 } catch (const ResourceExceededException&) {
1027 throw;
1028 } catch (const Exception&) {}
1031 return true;
1034 static BinarySessionSerializer s_binary_session_serializer;
1036 #define PS_DELIMITER '|'
1037 #define PS_UNDEF_MARKER '!'
1039 struct PhpSessionSerializer : SessionSerializer {
1040 PhpSessionSerializer() : SessionSerializer("php") {}
1042 virtual String encode() {
1043 StringBuffer buf;
1044 VariableSerializer vs(VariableSerializer::Type::Serialize);
1045 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
1046 Variant key = iter.first();
1047 if (key.isString()) {
1048 String skey = key.toString();
1049 buf.append(skey);
1050 if (skey.find(PS_DELIMITER) >= 0 || skey.find(PS_UNDEF_MARKER) >= 0) {
1051 return String();
1053 buf.append(PS_DELIMITER);
1054 buf.append(vs.serialize(iter.second(), /* ret */ true,
1055 /* keepCount */ true));
1056 } else {
1057 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1060 return buf.detach();
1063 virtual bool decode(const String& value) {
1064 const char *p = value.data();
1065 const char *endptr = value.data() + value.size();
1066 VariableUnserializer vu(nullptr, 0, VariableUnserializer::Type::Serialize);
1067 while (p < endptr) {
1068 const char *q = p;
1069 while (*q != PS_DELIMITER) {
1070 if (++q >= endptr) return true;
1072 int has_value;
1073 if (p[0] == PS_UNDEF_MARKER) {
1074 p++;
1075 has_value = 0;
1076 } else {
1077 has_value = 1;
1080 String key(p, q - p, CopyString);
1081 q++;
1082 if (has_value) {
1083 vu.set(q, endptr);
1084 try {
1085 auto sess = php_global_exchange(s__SESSION, init_null());
1086 forceToArray(sess).set(key, vu.unserialize());
1087 php_global_set(s__SESSION, std::move(sess));
1088 q = vu.head();
1089 } catch (const ResourceExceededException&) {
1090 throw;
1091 } catch (const Exception&) {}
1093 p = q;
1095 return true;
1098 static PhpSessionSerializer s_php_session_serializer;
1100 struct PhpSerializeSessionSerializer : SessionSerializer {
1101 PhpSerializeSessionSerializer() : SessionSerializer("php_serialize") {}
1103 virtual String encode() {
1104 VariableSerializer vs(VariableSerializer::Type::Serialize);
1105 return vs.serialize(php_global(s__SESSION).toArray(), true, true);
1108 virtual bool decode(const String& value) {
1109 VariableUnserializer vu(value.data(), value.size(),
1110 VariableUnserializer::Type::Serialize);
1112 try {
1113 auto sess = vu.unserialize();
1114 php_global_set(s__SESSION, sess.toArray());
1115 } catch (const ResourceExceededException&) {
1116 throw;
1117 } catch (const Exception&) {}
1119 return true;
1122 static PhpSerializeSessionSerializer s_php_serialize_session_serializer;
1124 struct WddxSessionSerializer : SessionSerializer {
1125 WddxSessionSerializer() : SessionSerializer("wddx") {}
1127 virtual String encode() {
1128 auto wddxPacket = req::make<WddxPacket>(empty_string_variant_ref,
1129 true, true);
1130 for (ArrayIter iter(php_global(s__SESSION).toArray()); iter; ++iter) {
1131 Variant key = iter.first();
1132 if (key.isString()) {
1133 wddxPacket->recursiveAddVar(key.toString(), iter.second(), true);
1134 } else {
1135 raise_notice("Skipping numeric key %" PRId64, key.toInt64());
1139 return wddxPacket->packet_end();
1142 virtual bool decode(const String& value) {
1143 Array params = Array::Create();
1144 params.append(value);
1145 Variant ret = vm_call_user_func("wddx_deserialize", params, true);
1146 if (ret.isArray()) {
1147 Array arr = ret.toArray();
1148 auto session = php_global_exchange(s__SESSION, init_null());
1149 for (ArrayIter iter(arr); iter; ++iter) {
1150 auto const key = iter.first();
1151 auto const value = iter.second();
1152 forceToArray(session).set(key, value);
1154 php_global_set(s__SESSION, std::move(session));
1157 return true;
1160 static WddxSessionSerializer s_wddx_session_serializer;
1162 ///////////////////////////////////////////////////////////////////////////////
1164 static bool session_check_active_state() {
1165 if (s_session->session_status == Session::Active) {
1166 raise_warning("A session is active. You cannot change the session"
1167 " module's ini settings at this time");
1168 return false;
1170 return true;
1173 static bool mod_is_open() {
1174 return s_session->mod_data || s_session->mod_user_implemented;
1177 static bool ini_on_update_save_handler(const std::string& value) {
1178 if (!session_check_active_state()) return false;
1179 s_session->mod = SessionModule::Find(value.c_str());
1180 return true;
1183 static std::string ini_get_save_handler() {
1184 auto &mod = s_session->mod;
1185 if (mod == nullptr) {
1186 return "";
1188 return mod->getName();
1191 static bool ini_on_update_serializer(const std::string& value) {
1192 if (!session_check_active_state()) return false;
1193 SessionSerializer *serializer = SessionSerializer::Find(value.data());
1194 if (serializer == nullptr) {
1195 raise_warning("ini_set(): Cannot find serialization handler '%s'",
1196 value.data());
1197 return false;
1199 s_session->serializer = serializer;
1200 return true;
1203 static std::string ini_get_serializer() {
1204 auto &serializer = s_session->serializer;
1205 if (serializer == nullptr) {
1206 return "";
1208 return serializer->getName();
1211 static bool ini_on_update_trans_sid(const bool& value) {
1212 return session_check_active_state();
1215 static bool ini_on_update_save_dir(const std::string& value) {
1216 if (value.find('\0') != std::string::npos) {
1217 return false;
1219 if (g_context.isNull()) return false;
1220 const char *path = value.data() + value.rfind(';') + 1;
1221 if (File::TranslatePath(path).empty()) {
1222 return false;
1224 s_session->save_path = path;
1225 return true;
1228 ///////////////////////////////////////////////////////////////////////////////
1230 static bool php_session_destroy() {
1231 bool retval = true;
1233 if (s_session->session_status != Session::Active) {
1234 raise_warning("Trying to destroy uninitialized session");
1235 return false;
1238 if (s_session->mod->destroy(s_session->id.data()) == false) {
1239 retval = false;
1240 raise_warning("Session object destruction failed");
1243 if (mod_is_open()) {
1244 s_session->mod->close();
1247 s_session->destroy();
1249 return retval;
1252 static String php_session_encode() {
1253 if (!s_session->serializer) {
1254 raise_warning("Unknown session.serialize_handler. "
1255 "Failed to encode session object");
1256 return String();
1258 return s_session->serializer->encode();
1261 static void php_session_decode(const String& value) {
1262 if (!s_session->serializer) {
1263 raise_warning("Unknown session.serialize_handler. "
1264 "Failed to decode session object");
1265 return;
1267 if (!s_session->serializer->decode(value)) {
1268 php_session_destroy();
1269 raise_warning("Failed to decode session object. "
1270 "Session has been destroyed");
1274 static void php_session_initialize() {
1275 /* check session name for invalid characters */
1276 if (strpbrk(s_session->id.data(), "\r\n\t <>'\"\\")) {
1277 s_session->id.reset();
1280 if (!s_session->mod) {
1281 raise_error("No storage module chosen - failed to initialize session");
1282 return;
1285 /* Open session handler first */
1286 if (!s_session->mod->open(s_session->save_path.c_str(),
1287 s_session->session_name.c_str())) {
1288 raise_error("Failed to initialize storage module: %s (path: %s)",
1289 s_session->mod->getName(), s_session->save_path.c_str());
1290 return;
1293 /* If there is no ID, use session module to create one */
1294 int attempts = 3;
1295 if (s_session->id.empty()) {
1296 new_session:
1297 s_session->id = s_session->mod->create_sid();
1298 if (s_session->id.empty()) {
1299 raise_error("Failed to create session id: %s", s_session->mod->getName());
1300 return;
1302 if (s_session->use_cookies) {
1303 s_session->send_cookie = true;
1307 /* Read data */
1308 /* Question: if you create a SID here, should you also try to read data?
1309 * I'm not sure, but while not doing so will remove one session operation
1310 * it could prove useful for those sites which wish to have "default"
1311 * session information
1314 /* Unconditionally destroy existing arrays -- possible dirty data */
1315 php_global_set(s__SESSION, Variant{staticEmptyArray()});
1317 s_session->invalid_session_id = false;
1318 String value;
1319 if (s_session->mod->read(s_session->id.data(), value)) {
1320 php_session_decode(value);
1321 } else if (s_session->invalid_session_id) {
1322 /* address instances where the session read fails due to an invalid id */
1323 s_session->invalid_session_id = false;
1324 s_session->id.reset();
1325 if (--attempts > 0) {
1326 goto new_session;
1331 static void php_session_save_current_state() {
1332 bool ret = false;
1333 if (mod_is_open()) {
1334 String value = php_session_encode();
1335 if (!value.isNull()) {
1336 ret = s_session->mod->write(s_session->id.data(), value);
1339 if (!ret) {
1340 raise_warning("Failed to write session data (%s). Please verify that the "
1341 "current setting of session.save_path is correct (%s)",
1342 s_session->mod->getName(), s_session->save_path.c_str());
1344 if (mod_is_open()) {
1345 s_session->mod->close();
1349 ///////////////////////////////////////////////////////////////////////////////
1350 // Cookie Management
1352 static void php_session_send_cookie() {
1353 Transport *transport = g_context->getTransport();
1354 if (!transport) return;
1356 if (transport->headersSent()) {
1357 raise_warning("Cannot send session cookie - headers already sent");
1358 return;
1361 int64_t expire = 0;
1362 if (s_session->cookie_lifetime > 0) {
1363 struct timeval tv;
1364 gettimeofday(&tv, nullptr);
1365 expire = tv.tv_sec + s_session->cookie_lifetime;
1367 transport->setCookie(s_session->session_name,
1368 s_session->id,
1369 expire,
1370 s_session->cookie_path,
1371 s_session->cookie_domain,
1372 s_session->cookie_secure,
1373 s_session->cookie_httponly, true);
1376 static void php_session_reset_id() {
1377 if (s_session->use_cookies && s_session->send_cookie) {
1378 php_session_send_cookie();
1379 s_session->send_cookie = false;
1382 if (s_session->define_sid) {
1383 StringBuffer var;
1384 var.append(String(s_session->session_name));
1385 var.append('=');
1386 var.append(s_session->id);
1387 Variant v = var.detach();
1389 static const auto s_SID = makeStaticString("SID");
1390 auto const handle = lookupCnsHandle(s_SID);
1391 if (!handle) {
1392 f_define(String{s_SID}, v);
1393 } else {
1394 TypedValue* cns = &rds::handleToRef<TypedValue>(handle);
1395 v.setEvalScalar();
1396 cns->m_data = v.asTypedValue()->m_data;
1397 cns->m_type = v.asTypedValue()->m_type;
1398 if (rds::isNormalHandle(handle)) rds::initHandle(handle);
1403 ///////////////////////////////////////////////////////////////////////////////
1404 // Cache Limiters
1406 typedef struct {
1407 char *name;
1408 void (*func)();
1409 } php_session_cache_limiter_t;
1411 #define CACHE_LIMITER(name) _php_cache_limiter_##name
1412 #define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)()
1413 #define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
1414 #define ADD_HEADER(hdr) g_context->getTransport()->addHeader(hdr)
1416 #define LAST_MODIFIED "Last-Modified: "
1417 #define EXPIRES "Expires: "
1418 #define MAX_STR 512
1420 static char *month_names[] = {
1421 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1422 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1425 static char *week_days[] = {
1426 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
1429 static inline void strcpy_gmt(char *ubuf, time_t *when) {
1430 char buf[MAX_STR];
1431 struct tm tm, *res;
1432 int n;
1434 res = gmtime_r(when, &tm);
1436 if (!res) {
1437 buf[0] = '\0';
1438 return;
1441 n = snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", // SAFE
1442 week_days[tm.tm_wday], tm.tm_mday,
1443 month_names[tm.tm_mon], tm.tm_year + 1900,
1444 tm.tm_hour, tm.tm_min,
1445 tm.tm_sec);
1446 memcpy(ubuf, buf, n);
1447 ubuf[n] = '\0';
1450 const StaticString s_PATH_TRANSLATED("PATH_TRANSLATED");
1452 static inline void last_modified() {
1453 String path = php_global(s__SERVER).toArray()[s_PATH_TRANSLATED].toString();
1454 if (!path.empty()) {
1455 struct stat sb;
1456 if (stat(path.data(), &sb) == -1) {
1457 return;
1460 char buf[MAX_STR + 1];
1461 memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
1462 strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
1463 ADD_HEADER(buf);
1467 CACHE_LIMITER_FUNC(public) {
1468 char buf[MAX_STR + 1];
1469 struct timeval tv;
1470 time_t now;
1472 gettimeofday(&tv, nullptr);
1473 now = tv.tv_sec + s_session->cache_expire * 60;
1474 memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
1475 strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
1476 ADD_HEADER(buf);
1478 snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%" PRId64,
1479 s_session->cache_expire * 60); /* SAFE */
1480 ADD_HEADER(buf);
1482 last_modified();
1485 CACHE_LIMITER_FUNC(private_no_expire) {
1486 char buf[MAX_STR + 1];
1488 snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%" PRId64 ", "
1489 "pre-check=%" PRId64, s_session->cache_expire * 60,
1490 s_session->cache_expire * 60); /* SAFE */
1491 ADD_HEADER(buf);
1493 last_modified();
1496 CACHE_LIMITER_FUNC(private) {
1497 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1498 CACHE_LIMITER(private_no_expire)();
1501 CACHE_LIMITER_FUNC(nocache) {
1502 ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1504 /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
1505 ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, "
1506 "post-check=0, pre-check=0");
1508 /* For HTTP/1.0 conforming clients */
1509 ADD_HEADER("Pragma: no-cache");
1512 static php_session_cache_limiter_t php_session_cache_limiters[] = {
1513 CACHE_LIMITER_ENTRY(public)
1514 CACHE_LIMITER_ENTRY(private)
1515 CACHE_LIMITER_ENTRY(private_no_expire)
1516 CACHE_LIMITER_ENTRY(nocache)
1520 static int php_session_cache_limiter() {
1521 if (s_session->cache_limiter[0] == '\0') return 0;
1523 Transport *transport = g_context->getTransport();
1524 if (transport) {
1525 if (transport->headersSent()) {
1526 raise_warning("Cannot send session cache limiter - "
1527 "headers already sent");
1528 return -2;
1531 php_session_cache_limiter_t *lim;
1532 for (lim = php_session_cache_limiters; lim->name; lim++) {
1533 if (!strcasecmp(lim->name, s_session->cache_limiter.c_str())) {
1534 lim->func();
1535 return 0;
1540 return -1;
1543 ///////////////////////////////////////////////////////////////////////////////
1545 static int64_t HHVM_FUNCTION(session_status) {
1546 return s_session->session_status;
1549 static Variant HHVM_FUNCTION(session_module_name,
1550 const Variant& newname /* = null_string */) {
1551 String oldname;
1552 if (s_session->mod && s_session->mod->getName()) {
1553 oldname = String(s_session->mod->getName(), CopyString);
1556 if (!newname.isNull()) {
1557 if (!SessionModule::Find(newname.toString().data())) {
1558 raise_warning("Cannot find named PHP session module (%s)",
1559 newname.toString().data());
1560 return false;
1562 if (mod_is_open()) {
1563 s_session->mod->close();
1565 s_session->mod_data = false;
1567 HHVM_FN(ini_set)("session.save_handler", newname.toString());
1570 return oldname;
1573 const StaticString s_session_write_close("session_write_close");
1575 static bool HHVM_FUNCTION(session_set_save_handler,
1576 const Object& sessionhandler,
1577 bool register_shutdown /* = true */) {
1579 if (s_session->mod &&
1580 s_session->session_status != Session::None &&
1581 s_session->mod != &s_user_session_module) {
1582 return false;
1585 if (s_session->session_status == Session::Active) {
1586 return false;
1589 if (s_session->default_mod == nullptr) {
1590 s_session->default_mod = s_session->mod;
1593 s_session->ps_session_handler = sessionhandler;
1595 // remove previous shutdown function
1596 g_context->removeShutdownFunction(s_session_write_close,
1597 ExecutionContext::ShutDown);
1598 if (register_shutdown) {
1599 HHVM_FN(register_shutdown_function)(s_session_write_close);
1602 if (ini_get_save_handler() != "user") {
1603 HHVM_FN(ini_set)("session.save_handler", "user");
1605 return true;
1608 static String HHVM_FUNCTION(session_id,
1609 const Variant& newid /* = null_string */) {
1610 String ret = s_session->id;
1611 if (ret.isNull()) {
1612 ret = empty_string();
1615 if (!newid.isNull()) {
1616 s_session->id = newid.toString();
1619 return ret;
1622 static bool HHVM_FUNCTION(session_regenerate_id,
1623 bool delete_old_session /* = false */) {
1624 Transport *transport = g_context->getTransport();
1625 if (transport && transport->headersSent()) {
1626 raise_warning("Cannot regenerate session id - headers already sent");
1627 return false;
1630 if (s_session->session_status == Session::Active) {
1631 if (!s_session->id.empty()) {
1632 if (delete_old_session &&
1633 !s_session->mod->destroy(s_session->id.data())) {
1634 raise_warning("Session object destruction failed");
1635 return false;
1637 s_session->id.reset();
1640 s_session->id = s_session->mod->create_sid();
1641 s_session->send_cookie = true;
1642 php_session_reset_id();
1643 return true;
1645 return false;
1648 static Variant HHVM_FUNCTION(session_encode) {
1649 String ret = php_session_encode();
1650 if (ret.isNull()) {
1651 return false;
1653 return ret;
1656 static bool HHVM_FUNCTION(session_decode, const String& data) {
1657 if (s_session->session_status != Session::None) {
1658 php_session_decode(data);
1659 return true;
1661 return false;
1664 const StaticString
1665 s_REQUEST_URI("REQUEST_URI"),
1666 s_HTTP_REFERER("HTTP_REFERER");
1668 static bool HHVM_FUNCTION(session_start) {
1669 s_session->apply_trans_sid = s_session->use_trans_sid;
1671 String value;
1672 switch (s_session->session_status) {
1673 case Session::Active:
1674 raise_notice("A session had already been started - "
1675 "ignoring session_start()");
1676 return false;
1677 case Session::Disabled:
1679 if (!s_session->mod && IniSetting::Get("session.save_handler", value)) {
1680 s_session->mod = SessionModule::Find(value.data());
1681 if (!s_session->mod) {
1682 raise_warning("Cannot find save handler '%s' - "
1683 "session startup failed", value.data());
1684 return false;
1687 if (!s_session->serializer &&
1688 IniSetting::Get("session.serialize_handler", value)) {
1689 s_session->serializer = SessionSerializer::Find(value.data());
1690 if (!s_session->serializer) {
1691 raise_warning("Cannot find serialization handler '%s' - "
1692 "session startup failed", value.data());
1693 return false;
1696 s_session->session_status = Session::None;
1697 /* fallthrough */
1699 default:
1700 assert(s_session->session_status == Session::None);
1701 s_session->define_sid = true;
1702 s_session->send_cookie = true;
1706 * Cookies are preferred, because initially
1707 * cookie and get variables will be available.
1709 if (s_session->id.empty()) {
1710 if (s_session->use_cookies) {
1711 auto cookies = php_global(s__COOKIE).toArray();
1712 if (cookies.exists(String(s_session->session_name))) {
1713 s_session->id = cookies[String(s_session->session_name)].toString();
1714 s_session->apply_trans_sid = false;
1715 s_session->send_cookie = false;
1716 s_session->define_sid = false;
1720 if (!s_session->use_only_cookies && !s_session->id) {
1721 auto get = php_global(s__GET).toArray();
1722 if (get.exists(String(s_session->session_name))) {
1723 s_session->id = get[String(s_session->session_name)].toString();
1724 s_session->send_cookie = false;
1728 if (!s_session->use_only_cookies && !s_session->id) {
1729 auto post = php_global(s__POST).toArray();
1730 if (post.exists(String(s_session->session_name))) {
1731 s_session->id = post[String(s_session->session_name)].toString();
1732 s_session->send_cookie = false;
1737 int lensess = s_session->session_name.size();
1739 /* check the REQUEST_URI symbol for a string of the form
1740 '<session-name>=<session-id>' to allow URLs of the form
1741 http://yoursite/<session-name>=<session-id>/script.php */
1742 if (!s_session->use_only_cookies && s_session->id.empty()) {
1743 value = php_global(s__SERVER).toArray()[s_REQUEST_URI].toString();
1744 const char *p = strstr(value.data(), s_session->session_name.c_str());
1745 if (p && p[lensess] == '=') {
1746 p += lensess + 1;
1747 const char *q;
1748 if ((q = strpbrk(p, "/?\\"))) {
1749 s_session->id = String(p, q - p, CopyString);
1750 s_session->send_cookie = false;
1755 /* check whether the current request was referred to by
1756 an external site which invalidates the previously found id */
1757 if (!s_session->id.empty() && s_session->extern_referer_chk[0] != '\0') {
1758 value = php_global(s__SERVER).toArray()[s_HTTP_REFERER].toString();
1759 if (!strstr(value.data(), s_session->extern_referer_chk.c_str())) {
1760 s_session->id.reset();
1761 s_session->send_cookie = true;
1762 if (s_session->use_trans_sid) {
1763 s_session->apply_trans_sid = true;
1768 php_session_initialize();
1770 if (!s_session->use_cookies && s_session->send_cookie) {
1771 if (s_session->use_trans_sid) {
1772 s_session->apply_trans_sid = true;
1774 s_session->send_cookie = false;
1777 php_session_reset_id();
1779 s_session->session_status = Session::Active;
1781 php_session_cache_limiter();
1783 if (mod_is_open() && s_session->gc_probability > 0) {
1784 int nrdels = -1;
1786 int nrand = (int) ((float) s_session->gc_divisor * math_combined_lcg());
1787 if (nrand < s_session->gc_probability) {
1788 s_session->mod->gc(s_session->gc_maxlifetime, &nrdels);
1792 if (s_session->session_status != Session::Active) {
1793 return false;
1795 return true;
1798 static bool HHVM_FUNCTION(session_destroy) {
1799 return php_session_destroy();
1802 static void HHVM_FUNCTION(session_unset) {
1803 if (s_session->session_status == Session::None) {
1804 return;
1806 php_global_set(s__SESSION, empty_array());
1807 return;
1810 static void HHVM_FUNCTION(session_write_close) {
1811 if (s_session->session_status == Session::Active) {
1812 s_session->session_status = Session::None;
1813 php_session_save_current_state();
1817 static bool HHVM_METHOD(SessionHandler, hhopen,
1818 const String& save_path, const String& session_id) {
1819 return s_session->default_mod &&
1820 s_session->default_mod->open(save_path.data(), session_id.data());
1823 static bool HHVM_METHOD(SessionHandler, hhclose) {
1824 return s_session->default_mod && s_session->default_mod->close();
1827 static Variant HHVM_METHOD(SessionHandler, hhread, const String& session_id) {
1828 String value;
1829 if (s_session->default_mod &&
1830 s_session->default_mod->read(s_session->id.data(), value)) {
1831 php_session_decode(value);
1832 return value;
1834 return init_null();
1837 static bool HHVM_METHOD(SessionHandler, hhwrite,
1838 const String& session_id, const String& session_data) {
1839 return s_session->default_mod &&
1840 s_session->default_mod->write(session_id.data(), session_data);
1843 static bool HHVM_METHOD(SessionHandler, hhdestroy, const String& session_id) {
1844 return s_session->default_mod &&
1845 s_session->default_mod->destroy(session_id.data());
1848 static bool HHVM_METHOD(SessionHandler, hhgc, int maxlifetime) {
1849 int nrdels = -1;
1850 return s_session->default_mod &&
1851 s_session->default_mod->gc(maxlifetime, &nrdels);
1854 void ext_session_request_shutdown() {
1855 HHVM_FN(session_write_close)();
1856 s_session->requestShutdownImpl();
1859 ///////////////////////////////////////////////////////////////////////////////
1861 static struct SessionExtension final : Extension {
1862 SessionExtension() : Extension("session", NO_EXTENSION_VERSION_YET) { }
1863 void moduleInit() override {
1864 HHVM_RC_INT(PHP_SESSION_DISABLE, Session::Disabled);
1865 HHVM_RC_INT(PHP_SESSION_NONE, Session::None);
1866 HHVM_RC_INT(PHP_SESSION_ACTIVE, Session::Active);
1868 HHVM_FE(session_status);
1869 HHVM_FE(session_module_name);
1870 HHVM_FE(session_id);
1871 HHVM_FE(session_regenerate_id);
1872 HHVM_FE(session_encode);
1873 HHVM_FE(session_decode);
1874 HHVM_FE(session_start);
1875 HHVM_FE(session_destroy);
1876 HHVM_FE(session_unset);
1877 HHVM_FE(session_write_close);
1879 HHVM_ME(SessionHandler, hhopen);
1880 HHVM_ME(SessionHandler, hhclose);
1881 HHVM_ME(SessionHandler, hhread);
1882 HHVM_ME(SessionHandler, hhwrite);
1883 HHVM_ME(SessionHandler, hhdestroy);
1884 HHVM_ME(SessionHandler, hhgc);
1885 HHVM_NAMED_FE(__SystemLib\\session_set_save_handler,
1886 HHVM_FN(session_set_save_handler)
1889 loadSystemlib();
1892 void threadInit() override {
1893 // TODO: t5226715 We shouldn't need to check s_session here, but right now
1894 // this is called for every request.
1895 if (s_session) return;
1896 s_session = new SessionRequestData;
1897 Extension* ext = ExtensionRegistry::get(s_session_ext_name);
1898 assert(ext);
1899 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1900 "session.save_path", "",
1901 IniSetting::SetAndGet<std::string>(
1902 ini_on_update_save_dir, nullptr
1904 &s_session->save_path);
1905 Variant v;
1906 if (IniSetting::GetSystem("session.save_path", v) &&
1907 !v.toString().empty()) {
1908 s_session->reset_save_path = true;
1910 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1911 "session.name", "PHPSESSID",
1912 &s_session->session_name);
1913 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1914 "session.save_handler", "files",
1915 IniSetting::SetAndGet<std::string>(
1916 ini_on_update_save_handler, ini_get_save_handler
1918 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1919 "session.auto_start", "0",
1920 &s_session->auto_start);
1921 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1922 "session.gc_probability", "1",
1923 &s_session->gc_probability);
1924 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1925 "session.gc_divisor", "100",
1926 &s_session->gc_divisor);
1927 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1928 "session.gc_maxlifetime", "1440",
1929 &s_session->gc_maxlifetime);
1930 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1931 "session.serialize_handler", "php",
1932 IniSetting::SetAndGet<std::string>(
1933 ini_on_update_serializer, ini_get_serializer
1935 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1936 "session.cookie_lifetime", "0",
1937 &s_session->cookie_lifetime);
1938 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1939 "session.cookie_path", "/",
1940 &s_session->cookie_path);
1941 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1942 "session.cookie_domain", "",
1943 &s_session->cookie_domain);
1944 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1945 "session.cookie_secure", "",
1946 &s_session->cookie_secure);
1947 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1948 "session.cookie_httponly", "",
1949 &s_session->cookie_httponly);
1950 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1951 "session.use_cookies", "1",
1952 &s_session->use_cookies);
1953 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1954 "session.use_only_cookies", "1",
1955 &s_session->use_only_cookies);
1956 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1957 "session.referer_check", "",
1958 &s_session->extern_referer_chk);
1959 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1960 "session.entropy_file", "",
1961 &s_session->entropy_file);
1962 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1963 "session.entropy_length", "0",
1964 &s_session->entropy_length);
1965 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1966 "session.cache_limiter", "nocache",
1967 &s_session->cache_limiter);
1968 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1969 "session.cache_expire", "180",
1970 &s_session->cache_expire);
1971 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1972 "session.use_trans_sid", "0",
1973 IniSetting::SetAndGet<bool>(
1974 ini_on_update_trans_sid, nullptr
1976 &s_session->use_trans_sid);
1977 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1978 "session.hash_function", "0",
1979 &s_session->hash_func);
1980 IniSetting::Bind(ext, IniSetting::PHP_INI_ALL,
1981 "session.hash_bits_per_character", "4",
1982 &s_session->hash_bits_per_character);
1985 void threadShutdown() override {
1986 delete s_session;
1987 s_session = nullptr;
1990 void requestInit() override {
1991 s_session->init();
1994 void vscan(IMarker& mark) const override {
1995 if (s_session) s_session->scan(mark);
1999 No need for requestShutdown; its handled explicitly by a call to
2000 ext_session_request_shutdown()
2002 } s_session_extension;
2004 ///////////////////////////////////////////////////////////////////////////////