2 +----------------------------------------------------------------------+
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"
21 #include <sys/types.h>
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"
62 ///////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////
77 struct SessionSerializer
;
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};
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
{
131 session_status
= Session::None
;
132 ps_session_handler
.reset();
134 if (reset_save_path
) IniSetting::ResetSystemDefault("session.save_path");
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();
151 THREAD_LOCAL_NO_CHECK(SessionRequestData
, s_session
);
153 void SessionRequestData::requestShutdownImpl() {
159 ps_session_handler
.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
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;
178 int mask
= (1 << nbits
) - 1;
186 /* consumed everything? */
187 if (have
== 0) break;
188 /* No? We need a final round */
194 out
.append(hexconvtab
[w
& mask
]);
201 s_REMOTE_ADDR("REMOTE_ADDR"),
202 s__SERVER("_SERVER"),
203 s__SESSION("_SESSION"),
204 s__COOKIE("_COOKIE"),
208 String
SessionModule::create_sid() {
209 String remote_addr
= php_global(s__SERVER
)
210 .toArray()[s_REMOTE_ADDR
].toString();
213 gettimeofday(&tv
, nullptr);
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());
232 if (!HHVM_FN(hash_update
)(context
.toResource(), buf
.detach())) {
233 Logger::Error("hash_update() failed");
237 if (s_session
->entropy_length
> 0) {
238 int fd
= ::open(s_session
->entropy_file
.c_str(), O_RDONLY
);
240 unsigned char rbuf
[2048];
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
)));
247 if (!HHVM_FN(hash_update
)(context
.toResource(),
248 String((const char *)rbuf
, n
, CopyString
))) {
249 Logger::Error("hash_update() failed");
259 auto const hashed
= HHVM_FN(hash_final
)(
260 context
.toResource(), /* raw */ true
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
);
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());
316 void SystemlibSessionModule::lookupClass() {
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",
328 s_SHIClass
= Unit::lookupClass(s_SessionHandlerInterface
.get());
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'",
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());
355 const Object
& SystemlibSessionModule::getObject() {
356 if (const auto& o
= s_obj
->getObject()) {
364 s_obj
->setObject(Object
{m_cls
});
365 const auto& obj
= s_obj
->getObject();
367 g_context
->invokeFuncFew(m_ctor
, obj
.get())
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;
388 raise_warning("Failed calling %s::open()", m_classname
);
392 bool SystemlibSessionModule::close() {
393 const auto& obj
= s_obj
->getObject();
395 // close() can be called twice in some circumstances
396 s_session
->mod_data
= false;
400 auto ret
= Variant::attach(
401 g_context
->invokeFuncFew(m_close
, obj
.get())
405 if (ret
.isBoolean() && ret
.toBoolean()) {
409 raise_warning("Failed calling %s::close()", m_classname
);
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();
427 raise_warning("Failed calling %s::read()", m_classname
);
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()) {
445 raise_warning("Failed calling %s::write()", m_classname
);
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()) {
462 raise_warning("Failed calling %s::destroy()", m_classname
);
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()) {
477 *nrdels
= ret
.toInt64();
482 raise_warning("Failed calling %s::gc()", m_classname
);
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 //////////////////////////////////////////////////////////////////////////////
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*/) {
513 if (*save_path
== '\0') {
514 tmpdir
= HHVM_FN(sys_get_temp_dir
)();
515 save_path
= tmpdir
.data();
518 /* split up input parameter */
521 const char *last
= save_path
;
522 const char *p
= strchr(save_path
, ';');
524 argv
[argc
++] = last
; last
= ++p
; p
= strchr(p
, ';');
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");
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");
547 save_path
= argv
[argc
- 1];
548 if (File::TranslatePath(save_path
).empty()) {
549 raise_warning("Unable to open save_path %s", save_path
);
554 m_basedir
= save_path
;
555 s_session
->mod_data
= true;
563 s_session
->mod_data
= false;
567 bool read(const char *key
, String
&value
) {
574 if (fstat(m_fd
, &sbuf
)) {
577 m_st_size
= sbuf
.st_size
;
578 if (m_st_size
== 0) {
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);
589 lseek(m_fd
, 0, SEEK_SET
);
590 long n
= ::read(m_fd
, val
, m_st_size
);
593 if (n
!= (int)m_st_size
) {
595 raise_warning("read failed: %s (%d)", folly::errnoStr(errno
).c_str(),
598 raise_warning("read returned less bytes than requested");
603 value
= s
.setSize(m_st_size
);
607 bool write(const char *key
, const String
& value
) {
614 if (fstat(m_fd
, &sbuf
)) {
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
);
631 #if defined(HAVE_PWRITE)
632 long n
= pwrite(m_fd
, value
.data(), value
.size(), 0);
634 lseek(m_fd
, 0, SEEK_SET
);
635 long n
= ::write(m_fd
, value
.data(), value
.size());
638 if (n
!= value
.size()) {
640 raise_warning("write failed: %s (%d)",
641 folly::errnoStr(errno
).c_str(), errno
);
643 raise_warning("write wrote less bytes than requested");
651 bool destroy(const char *key
) {
653 if (!createPath(buf
, sizeof(buf
), key
)) {
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
)) {
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
);
683 std::string m_lastkey
;
684 std::string m_basedir
;
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
;
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
== '-')) {
702 size_t len
= p
- key
;
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
))) {
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
++) {
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
);
743 /* On Win32 locked files that are closed without being explicitly
744 unlocked will be unlocked only when "system resources become
746 flock(m_fd
, LOCK_UN
);
753 void openImpl(const char *key
) {
754 if (m_fd
< 0 || !m_lastkey
.empty() || m_lastkey
!= 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;
766 if (!createPath(buf
, sizeof(buf
), key
)) {
771 m_fd
= ::open(buf
, O_CREAT
| O_RDWR
| O_BINARY
, m_filemode
);
774 flock(m_fd
, LOCK_EX
);
778 # define FD_CLOEXEC 1
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
);
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
);
795 raise_notice("ps_files_cleanup_dir: opendir(%s) failed: %s (%d)",
796 dirname
, folly::errnoStr(errno
).c_str(), errno
);
803 size_t dirname_len
= strlen(dirname
);
804 char dentry
[sizeof(struct dirent
) + PATH_MAX
];
805 struct dirent
*entry
= (struct dirent
*) &dentry
;
809 /* Prepare buffer (dirname never changes) */
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
) {
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 ///////////////////////////////////////////////////////////////////////////////
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();
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
)
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");
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) {
965 static std::vector
<SessionSerializer
*> RegisteredSerializers
;
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() {
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());
988 buf
.append(vs
.serialize(iter
.second(), /* ret */ true,
989 /* keepCount */ true));
992 raise_notice("Skipping numeric key %" PRId64
, key
.toInt64());
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
) {
1007 int has_value
= *p
& PS_BIN_UNDEF
? 0 : 1;
1008 String
key(p
+ 1, namelen
, CopyString
);
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
));
1017 } catch (const ResourceExceededException
&) {
1019 } catch (const Exception
&) {}
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() {
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();
1041 if (skey
.find(PS_DELIMITER
) >= 0 || skey
.find(PS_UNDEF_MARKER
) >= 0) {
1044 buf
.append(PS_DELIMITER
);
1045 buf
.append(vs
.serialize(iter
.second(), /* ret */ true,
1046 /* keepCount */ true));
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
) {
1060 while (*q
!= PS_DELIMITER
) {
1061 if (++q
>= endptr
) return true;
1064 if (p
[0] == PS_UNDEF_MARKER
) {
1071 String
key(p
, q
- p
, CopyString
);
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
));
1080 } catch (const ResourceExceededException
&) {
1082 } catch (const Exception
&) {}
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
);
1104 auto sess
= vu
.unserialize();
1105 php_global_set(s__SESSION
, sess
.toArray());
1106 } catch (const ResourceExceededException
&) {
1108 } catch (const Exception
&) {}
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
,
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);
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
));
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");
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());
1174 static std::string
ini_get_save_handler() {
1175 auto &mod
= s_session
->mod
;
1176 if (mod
== nullptr) {
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'",
1190 s_session
->serializer
= serializer
;
1194 static std::string
ini_get_serializer() {
1195 auto &serializer
= s_session
->serializer
;
1196 if (serializer
== nullptr) {
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
) {
1210 if (g_context
.isNull()) return false;
1211 const char *path
= value
.data() + (value
.rfind(';') + 1);
1212 if (File::TranslatePath(path
).empty()) {
1215 s_session
->save_path
= path
;
1219 ///////////////////////////////////////////////////////////////////////////////
1221 static bool php_session_destroy() {
1224 if (s_session
->session_status
!= Session::Active
) {
1225 raise_warning("Trying to destroy uninitialized session");
1229 if (s_session
->mod
->destroy(s_session
->id
.data()) == false) {
1231 raise_warning("Session object destruction failed");
1234 if (mod_is_open()) {
1235 s_session
->mod
->close();
1238 s_session
->destroy();
1243 static String
php_session_encode() {
1244 if (!s_session
->serializer
) {
1245 raise_warning("Unknown session.serialize_handler. "
1246 "Failed to encode session object");
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");
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");
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());
1284 /* If there is no ID, use session module to create one */
1286 if (s_session
->id
.empty()) {
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());
1293 if (s_session
->use_cookies
) {
1294 s_session
->send_cookie
= true;
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;
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) {
1322 static void php_session_save_current_state() {
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
);
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");
1353 if (s_session
->cookie_lifetime
> 0) {
1355 gettimeofday(&tv
, nullptr);
1356 expire
= tv
.tv_sec
+ s_session
->cookie_lifetime
;
1358 transport
->setCookie(s_session
->session_name
,
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
) {
1375 var
.append(String(s_session
->session_name
));
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
);
1383 f_define(String
{s_SID
}, v
);
1385 TypedValue
* cns
= &rds::handleToRef
<TypedValue
>(handle
);
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 ///////////////////////////////////////////////////////////////////////////////
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: "
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
) {
1425 res
= gmtime_r(when
, &tm
);
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
,
1437 memcpy(ubuf
, buf
, n
);
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()) {
1447 if (stat(path
.data(), &sb
) == -1) {
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
);
1458 CACHE_LIMITER_FUNC(public) {
1459 char buf
[MAX_STR
+ 1];
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
);
1469 snprintf(buf
, sizeof(buf
) , "Cache-Control: public, max-age=%" PRId64
,
1470 s_session
->cache_expire
* 60); /* SAFE */
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 */
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();
1516 if (transport
->headersSent()) {
1517 raise_warning("Cannot send session cache limiter - "
1518 "headers already sent");
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())) {
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 */) {
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());
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());
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
) {
1576 if (s_session
->session_status
== Session::Active
) {
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");
1599 static String
HHVM_FUNCTION(session_id
,
1600 const Variant
& newid
/* = null_string */) {
1601 String ret
= s_session
->id
;
1603 ret
= empty_string();
1606 if (!newid
.isNull()) {
1607 s_session
->id
= newid
.toString();
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");
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");
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();
1639 static Variant
HHVM_FUNCTION(session_encode
) {
1640 String ret
= php_session_encode();
1647 static bool HHVM_FUNCTION(session_decode
, const String
& data
) {
1648 if (s_session
->session_status
!= Session::None
) {
1649 php_session_decode(data
);
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
;
1663 switch (s_session
->session_status
) {
1664 case Session::Active
:
1665 raise_notice("A session had already been started - "
1666 "ignoring session_start()");
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());
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());
1687 s_session
->session_status
= Session::None
;
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
] == '=') {
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) {
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
) {
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
) {
1797 php_global_set(s__SESSION
, empty_array());
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();
1819 HHVM_METHOD(SessionHandler
, hhread
, const String
& /*session_id*/) {
1821 if (s_session
->default_mod
&&
1822 s_session
->default_mod
->read(s_session
->id
.data(), value
)) {
1823 php_session_decode(value
);
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
) {
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
)
1884 void threadInit() override
{
1885 assertx(s_session
.isNull());
1886 s_session
.getCheck();
1887 Extension
* ext
= ExtensionRegistry::get(s_session_ext_name
);
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
);
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
{
1984 No need for requestShutdown; its handled explicitly by a call to
1985 ext_session_request_shutdown()
1987 } s_session_extension
;
1989 ///////////////////////////////////////////////////////////////////////////////