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