| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Hypersonic::Session; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
1603
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
45
|
|
|
4
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
53
|
|
|
5
|
1
|
|
|
1
|
|
20
|
use 5.010; |
|
|
1
|
|
|
|
|
4
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.15'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# Session management for Hypersonic |
|
10
|
|
|
|
|
|
|
# Uses signed cookies for session ID, memory store for data |
|
11
|
|
|
|
|
|
|
# Digest::SHA provides fast C-based HMAC-SHA256 |
|
12
|
|
|
|
|
|
|
# Only activated when session_config() is called |
|
13
|
|
|
|
|
|
|
|
|
14
|
1
|
|
|
1
|
|
859
|
use Digest::SHA qw(hmac_sha256_hex); |
|
|
1
|
|
|
|
|
5196
|
|
|
|
1
|
|
|
|
|
165
|
|
|
15
|
1
|
|
|
1
|
|
11
|
use Hypersonic::JIT::Util; |
|
|
1
|
|
|
|
|
8
|
|
|
|
1
|
|
|
|
|
63
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Session slot constants (used in Request object) |
|
18
|
|
|
|
|
|
|
use constant { |
|
19
|
1
|
|
|
|
|
134
|
SLOT_SESSION => 12, |
|
20
|
|
|
|
|
|
|
SLOT_SESSION_ID => 13, |
|
21
|
|
|
|
|
|
|
SLOT_SESSION_MODIFIED => 14, |
|
22
|
1
|
|
|
1
|
|
6
|
}; |
|
|
1
|
|
|
|
|
7
|
|
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# Export slot constants |
|
25
|
1
|
|
|
1
|
|
8
|
use Exporter 'import'; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
4178
|
|
|
26
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
|
27
|
|
|
|
|
|
|
SLOT_SESSION SLOT_SESSION_ID SLOT_SESSION_MODIFIED |
|
28
|
|
|
|
|
|
|
); |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# Global session store - memory-based (survives across requests) |
|
31
|
|
|
|
|
|
|
my %SESSION_STORE; |
|
32
|
|
|
|
|
|
|
my $SESSION_CONFIG; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# JIT compilation state |
|
35
|
|
|
|
|
|
|
my $COMPILED = 0; |
|
36
|
|
|
|
|
|
|
my $MODULE_ID = 0; |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# ============================================================ |
|
39
|
|
|
|
|
|
|
# JIT Compilation of Cryptographic Operations |
|
40
|
|
|
|
|
|
|
# ============================================================ |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Unified compile interface |
|
43
|
|
|
|
|
|
|
sub compile { |
|
44
|
0
|
|
|
0
|
0
|
0
|
my ($class, %opts) = @_; |
|
45
|
0
|
|
|
|
|
0
|
return $class->compile_session_ops(%opts); |
|
46
|
|
|
|
|
|
|
} |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub compile_session_ops { |
|
49
|
5
|
|
|
5
|
1
|
22
|
my ($class, %opts) = @_; |
|
50
|
|
|
|
|
|
|
|
|
51
|
5
|
50
|
|
|
|
14
|
return 1 if $COMPILED; |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Check if OpenSSL is available (via TLS module) |
|
54
|
5
|
|
|
|
|
12
|
eval { require Hypersonic::TLS }; |
|
|
5
|
|
|
|
|
42
|
|
|
55
|
5
|
50
|
|
|
|
15
|
if ($@) { |
|
56
|
|
|
|
|
|
|
warn "Hypersonic::Session: Cannot load TLS module, using pure Perl crypto: $@\n" |
|
57
|
0
|
0
|
|
|
|
0
|
if $ENV{HYPERSONIC_DEBUG}; |
|
58
|
0
|
|
|
|
|
0
|
return 0; |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
5
|
|
|
|
|
31
|
my $has_openssl = Hypersonic::TLS::check_openssl(); |
|
62
|
5
|
50
|
|
|
|
12
|
unless ($has_openssl) { |
|
63
|
|
|
|
|
|
|
warn "Hypersonic::Session: OpenSSL not available, using pure Perl crypto\n" |
|
64
|
5
|
50
|
|
|
|
16
|
if $ENV{HYPERSONIC_DEBUG}; |
|
65
|
5
|
|
|
|
|
12
|
return 0; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Get OpenSSL flags - must be non-empty for compilation to work |
|
69
|
0
|
|
0
|
|
|
0
|
my $extra_cflags = Hypersonic::TLS::get_extra_cflags() // ''; |
|
70
|
0
|
|
0
|
|
|
0
|
my $extra_ldflags = Hypersonic::TLS::get_extra_ldflags() // ''; |
|
71
|
|
|
|
|
|
|
|
|
72
|
0
|
0
|
|
|
|
0
|
unless ($extra_cflags =~ /-I/) { |
|
73
|
|
|
|
|
|
|
warn "Hypersonic::Session: OpenSSL headers not found, using pure Perl crypto\n" |
|
74
|
0
|
0
|
|
|
|
0
|
if $ENV{HYPERSONIC_DEBUG}; |
|
75
|
0
|
|
|
|
|
0
|
return 0; |
|
76
|
|
|
|
|
|
|
} |
|
77
|
|
|
|
|
|
|
|
|
78
|
0
|
|
0
|
|
|
0
|
my $cache_dir = $opts{cache_dir} // '_hypersonic_cache/session'; |
|
79
|
0
|
|
|
|
|
0
|
my $module_name = 'Hypersonic::Session::Ops_' . $MODULE_ID++; |
|
80
|
|
|
|
|
|
|
|
|
81
|
0
|
|
|
|
|
0
|
my $builder = XS::JIT::Builder->new; |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Add required includes via centralized utility |
|
84
|
0
|
|
|
|
|
0
|
Hypersonic::JIT::Util->add_standard_includes($builder, |
|
85
|
|
|
|
|
|
|
qw(unistd fcntl openssl)); |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
88
|
|
|
|
|
|
|
# jit_hmac_sha256_hex: Generate HMAC-SHA256 signature |
|
89
|
|
|
|
|
|
|
# Input: data (SV*), key (SV*), output_len (IV, optional, default 32) |
|
90
|
|
|
|
|
|
|
# Output: hex-encoded signature (SV*) |
|
91
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
92
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_hmac_sha256_hex') |
|
93
|
|
|
|
|
|
|
->xs_preamble |
|
94
|
|
|
|
|
|
|
->line('int i;') |
|
95
|
|
|
|
|
|
|
->line('if (items < 2) croak("Usage: _jit_hmac_sha256_hex(data, key, [output_len])");') |
|
96
|
|
|
|
|
|
|
->line('STRLEN data_len, key_len;') |
|
97
|
|
|
|
|
|
|
->line('const unsigned char* data = (const unsigned char*)SvPV(ST(0), data_len);') |
|
98
|
|
|
|
|
|
|
->line('const unsigned char* key = (const unsigned char*)SvPV(ST(1), key_len);') |
|
99
|
|
|
|
|
|
|
->line('IV out_len = items > 2 ? SvIV(ST(2)) : 32;') |
|
100
|
|
|
|
|
|
|
->line('if (out_len > 64) out_len = 64;') |
|
101
|
|
|
|
|
|
|
->line('if (out_len < 1) out_len = 1;') |
|
102
|
|
|
|
|
|
|
->blank |
|
103
|
|
|
|
|
|
|
->line('unsigned char digest[EVP_MAX_MD_SIZE];') |
|
104
|
|
|
|
|
|
|
->line('unsigned int digest_len = 0;') |
|
105
|
|
|
|
|
|
|
->line('HMAC(EVP_sha256(), key, (int)key_len, data, data_len, digest, &digest_len);') |
|
106
|
|
|
|
|
|
|
->blank |
|
107
|
|
|
|
|
|
|
->line('char hex[129];') |
|
108
|
|
|
|
|
|
|
->line('int hex_bytes = (int)(out_len / 2);') |
|
109
|
|
|
|
|
|
|
->line('if (hex_bytes > 32) hex_bytes = 32;') |
|
110
|
|
|
|
|
|
|
->line('for (i = 0; i < hex_bytes; i++) {') |
|
111
|
|
|
|
|
|
|
->line(' sprintf(hex + i*2, "%02x", digest[i]);') |
|
112
|
|
|
|
|
|
|
->line('}') |
|
113
|
|
|
|
|
|
|
->line('hex[out_len] = \'\\0\';') |
|
114
|
|
|
|
|
|
|
->blank |
|
115
|
|
|
|
|
|
|
->line('ST(0) = sv_2mortal(newSVpv(hex, out_len));') |
|
116
|
|
|
|
|
|
|
->xs_return('1') |
|
117
|
|
|
|
|
|
|
->xs_end; |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
120
|
|
|
|
|
|
|
# jit_constant_time_compare: Timing-attack resistant compare |
|
121
|
|
|
|
|
|
|
# Input: s1 (SV*), s2 (SV*) |
|
122
|
|
|
|
|
|
|
# Output: 1 if equal, 0 if not (IV) |
|
123
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
124
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_constant_time_compare') |
|
125
|
|
|
|
|
|
|
->xs_preamble |
|
126
|
|
|
|
|
|
|
->line('STRLEN i;') |
|
127
|
|
|
|
|
|
|
->line('if (items != 2) croak("Usage: _jit_constant_time_compare(s1, s2)");') |
|
128
|
|
|
|
|
|
|
->line('STRLEN len1, len2;') |
|
129
|
|
|
|
|
|
|
->line('const unsigned char* s1 = (const unsigned char*)SvPV(ST(0), len1);') |
|
130
|
|
|
|
|
|
|
->line('const unsigned char* s2 = (const unsigned char*)SvPV(ST(1), len2);') |
|
131
|
|
|
|
|
|
|
->blank |
|
132
|
|
|
|
|
|
|
->comment('Length mismatch - still do constant-time work') |
|
133
|
|
|
|
|
|
|
->line('STRLEN max_len = len1 > len2 ? len1 : len2;') |
|
134
|
|
|
|
|
|
|
->line('unsigned char diff = (len1 != len2) ? 1 : 0;') |
|
135
|
|
|
|
|
|
|
->blank |
|
136
|
|
|
|
|
|
|
->line('for (i = 0; i < max_len; i++) {') |
|
137
|
|
|
|
|
|
|
->line(' unsigned char c1 = (i < len1) ? s1[i] : 0;') |
|
138
|
|
|
|
|
|
|
->line(' unsigned char c2 = (i < len2) ? s2[i] : 0;') |
|
139
|
|
|
|
|
|
|
->line(' diff |= c1 ^ c2;') |
|
140
|
|
|
|
|
|
|
->line('}') |
|
141
|
|
|
|
|
|
|
->blank |
|
142
|
|
|
|
|
|
|
->line('ST(0) = sv_2mortal(newSViv(diff == 0 ? 1 : 0));') |
|
143
|
|
|
|
|
|
|
->xs_return('1') |
|
144
|
|
|
|
|
|
|
->xs_end; |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
147
|
|
|
|
|
|
|
# jit_generate_session_id: Generate secure random session ID |
|
148
|
|
|
|
|
|
|
# Input: (none) |
|
149
|
|
|
|
|
|
|
# Output: 32-char hex string (SV*) |
|
150
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
151
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_generate_session_id') |
|
152
|
|
|
|
|
|
|
->xs_preamble |
|
153
|
|
|
|
|
|
|
->line('int i;') |
|
154
|
|
|
|
|
|
|
->line('unsigned char bytes[16];') |
|
155
|
|
|
|
|
|
|
->blank |
|
156
|
|
|
|
|
|
|
->comment('Try /dev/urandom first (most portable)') |
|
157
|
|
|
|
|
|
|
->line('int fd = open("/dev/urandom", O_RDONLY);') |
|
158
|
|
|
|
|
|
|
->if('fd >= 0') |
|
159
|
|
|
|
|
|
|
->line('ssize_t n = read(fd, bytes, 16);') |
|
160
|
|
|
|
|
|
|
->line('close(fd);') |
|
161
|
|
|
|
|
|
|
->if('n != 16') |
|
162
|
|
|
|
|
|
|
->line('croak("Failed to read 16 bytes from /dev/urandom");') |
|
163
|
|
|
|
|
|
|
->endif |
|
164
|
|
|
|
|
|
|
->else |
|
165
|
|
|
|
|
|
|
->comment('Fallback to OpenSSL RAND_bytes') |
|
166
|
|
|
|
|
|
|
->if('RAND_bytes(bytes, 16) != 1') |
|
167
|
|
|
|
|
|
|
->line('croak("Failed to generate random bytes");') |
|
168
|
|
|
|
|
|
|
->endif |
|
169
|
|
|
|
|
|
|
->endif |
|
170
|
|
|
|
|
|
|
->blank |
|
171
|
|
|
|
|
|
|
->comment('Convert to hex') |
|
172
|
|
|
|
|
|
|
->line('char hex[33];') |
|
173
|
|
|
|
|
|
|
->line('for (i = 0; i < 16; i++) {') |
|
174
|
|
|
|
|
|
|
->line(' sprintf(hex + i*2, "%02x", bytes[i]);') |
|
175
|
|
|
|
|
|
|
->line('}') |
|
176
|
|
|
|
|
|
|
->line('hex[32] = \'\\0\';') |
|
177
|
|
|
|
|
|
|
->blank |
|
178
|
|
|
|
|
|
|
->line('ST(0) = sv_2mortal(newSVpv(hex, 32));') |
|
179
|
|
|
|
|
|
|
->xs_return('1') |
|
180
|
|
|
|
|
|
|
->xs_end; |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
183
|
|
|
|
|
|
|
# jit_verify_signature: Combined verify operation |
|
184
|
|
|
|
|
|
|
# Input: signed_cookie (SV*), secret (SV*) |
|
185
|
|
|
|
|
|
|
# Output: session_id if valid (SV*), undef if invalid |
|
186
|
|
|
|
|
|
|
# Format: 32-char-hex-id.16-char-hex-sig (49 chars total) |
|
187
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
188
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_verify_signature') |
|
189
|
|
|
|
|
|
|
->xs_preamble |
|
190
|
|
|
|
|
|
|
->line('int i;') |
|
191
|
|
|
|
|
|
|
->line('if (items != 2) croak("Usage: _jit_verify_signature(signed_cookie, secret)");') |
|
192
|
|
|
|
|
|
|
->line('STRLEN cookie_len, secret_len;') |
|
193
|
|
|
|
|
|
|
->line('const char* cookie = SvPV(ST(0), cookie_len);') |
|
194
|
|
|
|
|
|
|
->line('const char* secret = SvPV(ST(1), secret_len);') |
|
195
|
|
|
|
|
|
|
->blank |
|
196
|
|
|
|
|
|
|
->comment('Validate format: 32-char-hex-id.16-char-hex-sig') |
|
197
|
|
|
|
|
|
|
->if('cookie_len != 49 || cookie[32] != \'.\'') |
|
198
|
|
|
|
|
|
|
->line('ST(0) = &PL_sv_undef;') |
|
199
|
|
|
|
|
|
|
->line('XSRETURN(1);') |
|
200
|
|
|
|
|
|
|
->endif |
|
201
|
|
|
|
|
|
|
->blank |
|
202
|
|
|
|
|
|
|
->comment('Validate hex characters in session_id') |
|
203
|
|
|
|
|
|
|
->line('for (i = 0; i < 32; i++) {') |
|
204
|
|
|
|
|
|
|
->line(' char c = cookie[i];') |
|
205
|
|
|
|
|
|
|
->line(' if (!((c >= \'0\' && c <= \'9\') || (c >= \'a\' && c <= \'f\'))) {') |
|
206
|
|
|
|
|
|
|
->line(' ST(0) = &PL_sv_undef;') |
|
207
|
|
|
|
|
|
|
->line(' XSRETURN(1);') |
|
208
|
|
|
|
|
|
|
->line(' }') |
|
209
|
|
|
|
|
|
|
->line('}') |
|
210
|
|
|
|
|
|
|
->blank |
|
211
|
|
|
|
|
|
|
->comment('Extract session_id') |
|
212
|
|
|
|
|
|
|
->line('char session_id[33];') |
|
213
|
|
|
|
|
|
|
->line('memcpy(session_id, cookie, 32);') |
|
214
|
|
|
|
|
|
|
->line('session_id[32] = \'\\0\';') |
|
215
|
|
|
|
|
|
|
->blank |
|
216
|
|
|
|
|
|
|
->comment('Get provided signature') |
|
217
|
|
|
|
|
|
|
->line('const char* provided_sig = cookie + 33;') |
|
218
|
|
|
|
|
|
|
->blank |
|
219
|
|
|
|
|
|
|
->comment('Compute expected HMAC signature') |
|
220
|
|
|
|
|
|
|
->line('unsigned char digest[EVP_MAX_MD_SIZE];') |
|
221
|
|
|
|
|
|
|
->line('unsigned int digest_len = 0;') |
|
222
|
|
|
|
|
|
|
->line('HMAC(EVP_sha256(), secret, (int)secret_len, (unsigned char*)session_id, 32, digest, &digest_len);') |
|
223
|
|
|
|
|
|
|
->blank |
|
224
|
|
|
|
|
|
|
->comment('Convert first 8 bytes to hex (16 chars)') |
|
225
|
|
|
|
|
|
|
->line('char expected[17];') |
|
226
|
|
|
|
|
|
|
->line('for (i = 0; i < 8; i++) {') |
|
227
|
|
|
|
|
|
|
->line(' sprintf(expected + i*2, "%02x", digest[i]);') |
|
228
|
|
|
|
|
|
|
->line('}') |
|
229
|
|
|
|
|
|
|
->line('expected[16] = \'\\0\';') |
|
230
|
|
|
|
|
|
|
->blank |
|
231
|
|
|
|
|
|
|
->comment('Constant-time comparison') |
|
232
|
|
|
|
|
|
|
->line('unsigned char diff = 0;') |
|
233
|
|
|
|
|
|
|
->line('for (i = 0; i < 16; i++) {') |
|
234
|
|
|
|
|
|
|
->line(' diff |= expected[i] ^ provided_sig[i];') |
|
235
|
|
|
|
|
|
|
->line('}') |
|
236
|
|
|
|
|
|
|
->blank |
|
237
|
|
|
|
|
|
|
->if('diff != 0') |
|
238
|
|
|
|
|
|
|
->line('ST(0) = &PL_sv_undef;') |
|
239
|
|
|
|
|
|
|
->line('XSRETURN(1);') |
|
240
|
|
|
|
|
|
|
->endif |
|
241
|
|
|
|
|
|
|
->blank |
|
242
|
|
|
|
|
|
|
->line('ST(0) = sv_2mortal(newSVpv(session_id, 32));') |
|
243
|
|
|
|
|
|
|
->xs_return('1') |
|
244
|
|
|
|
|
|
|
->xs_end; |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
247
|
|
|
|
|
|
|
# jit_session_get_set: Get or set session value |
|
248
|
|
|
|
|
|
|
# Input: req (AV*), key (SV*), [value (SV*)] |
|
249
|
|
|
|
|
|
|
# Output: value (SV*) or undef |
|
250
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
251
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_session_get_set') |
|
252
|
|
|
|
|
|
|
->xs_preamble |
|
253
|
|
|
|
|
|
|
->line('if (items < 2) croak("Usage: _jit_session_get_set(req, key, [value])");') |
|
254
|
|
|
|
|
|
|
->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));') |
|
255
|
|
|
|
|
|
|
->line('SV* session_sv = ary[' . SLOT_SESSION . '];') |
|
256
|
|
|
|
|
|
|
->blank |
|
257
|
|
|
|
|
|
|
->comment('Check if session data exists') |
|
258
|
|
|
|
|
|
|
->if('!session_sv || !SvROK(session_sv) || SvTYPE(SvRV(session_sv)) != SVt_PVHV') |
|
259
|
|
|
|
|
|
|
->line('ST(0) = &PL_sv_undef;') |
|
260
|
|
|
|
|
|
|
->line('XSRETURN(1);') |
|
261
|
|
|
|
|
|
|
->endif |
|
262
|
|
|
|
|
|
|
->blank |
|
263
|
|
|
|
|
|
|
->line('HV* session = (HV*)SvRV(session_sv);') |
|
264
|
|
|
|
|
|
|
->line('STRLEN klen;') |
|
265
|
|
|
|
|
|
|
->line('const char* key = SvPV(ST(1), klen);') |
|
266
|
|
|
|
|
|
|
->blank |
|
267
|
|
|
|
|
|
|
->if('items == 2') |
|
268
|
|
|
|
|
|
|
->comment('Getter mode') |
|
269
|
|
|
|
|
|
|
->line('SV** val = hv_fetch(session, key, klen, 0);') |
|
270
|
|
|
|
|
|
|
->line('ST(0) = (val && *val) ? *val : &PL_sv_undef;') |
|
271
|
|
|
|
|
|
|
->else |
|
272
|
|
|
|
|
|
|
->comment('Setter mode') |
|
273
|
|
|
|
|
|
|
->line('hv_store(session, key, klen, newSVsv(ST(2)), 0);') |
|
274
|
|
|
|
|
|
|
->line('ary[' . SLOT_SESSION_MODIFIED . '] = newSViv(1);') |
|
275
|
|
|
|
|
|
|
->line('ST(0) = ST(2);') |
|
276
|
|
|
|
|
|
|
->endif |
|
277
|
|
|
|
|
|
|
->xs_return('1') |
|
278
|
|
|
|
|
|
|
->xs_end; |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
281
|
|
|
|
|
|
|
# jit_session_get_all: Get all session data |
|
282
|
|
|
|
|
|
|
# Input: req (AV*) |
|
283
|
|
|
|
|
|
|
# Output: session hashref (SV*) or undef |
|
284
|
|
|
|
|
|
|
# -------------------------------------------------------- |
|
285
|
0
|
|
|
|
|
0
|
$builder->xs_function('jit_session_get_all') |
|
286
|
|
|
|
|
|
|
->xs_preamble |
|
287
|
|
|
|
|
|
|
->line('if (items < 1) croak("Usage: _jit_session_get_all(req)");') |
|
288
|
|
|
|
|
|
|
->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));') |
|
289
|
|
|
|
|
|
|
->line('SV* session_sv = ary[' . SLOT_SESSION . '];') |
|
290
|
|
|
|
|
|
|
->if('session_sv && SvROK(session_sv)') |
|
291
|
|
|
|
|
|
|
->line('ST(0) = session_sv;') |
|
292
|
|
|
|
|
|
|
->else |
|
293
|
|
|
|
|
|
|
->line('ST(0) = &PL_sv_undef;') |
|
294
|
|
|
|
|
|
|
->endif |
|
295
|
|
|
|
|
|
|
->xs_return('1') |
|
296
|
|
|
|
|
|
|
->xs_end; |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# Compile via XS::JIT |
|
299
|
0
|
|
|
|
|
0
|
eval { |
|
300
|
0
|
|
|
|
|
0
|
XS::JIT->compile( |
|
301
|
|
|
|
|
|
|
code => $builder->code, |
|
302
|
|
|
|
|
|
|
name => $module_name, |
|
303
|
|
|
|
|
|
|
cache_dir => $cache_dir, |
|
304
|
|
|
|
|
|
|
extra_cflags => $extra_cflags, |
|
305
|
|
|
|
|
|
|
extra_ldflags => $extra_ldflags, |
|
306
|
|
|
|
|
|
|
functions => { |
|
307
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_hmac_sha256_hex' => { source => 'jit_hmac_sha256_hex', is_xs_native => 1 }, |
|
308
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_constant_time_compare' => { source => 'jit_constant_time_compare', is_xs_native => 1 }, |
|
309
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_generate_session_id' => { source => 'jit_generate_session_id', is_xs_native => 1 }, |
|
310
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_verify_signature' => { source => 'jit_verify_signature', is_xs_native => 1 }, |
|
311
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_session_get_set' => { source => 'jit_session_get_set', is_xs_native => 1 }, |
|
312
|
|
|
|
|
|
|
'Hypersonic::Session::_jit_session_get_all' => { source => 'jit_session_get_all', is_xs_native => 1 }, |
|
313
|
|
|
|
|
|
|
}, |
|
314
|
|
|
|
|
|
|
); |
|
315
|
0
|
|
|
|
|
0
|
$COMPILED = 1; |
|
316
|
|
|
|
|
|
|
}; |
|
317
|
0
|
0
|
|
|
|
0
|
if ($@) { |
|
318
|
|
|
|
|
|
|
warn "Hypersonic::Session: JIT compilation failed, using pure Perl: $@\n" |
|
319
|
0
|
0
|
|
|
|
0
|
if $ENV{HYPERSONIC_DEBUG}; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
|
|
322
|
0
|
|
|
|
|
0
|
return $COMPILED; |
|
323
|
|
|
|
|
|
|
} |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# Check if JIT is compiled |
|
326
|
0
|
|
|
0
|
1
|
0
|
sub is_jit_compiled { $COMPILED } |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# ============================================================ |
|
329
|
|
|
|
|
|
|
# Core Cryptographic Functions (JIT or Perl fallback) |
|
330
|
|
|
|
|
|
|
# ============================================================ |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# Session ID generation using secure random |
|
333
|
|
|
|
|
|
|
sub _generate_session_id { |
|
334
|
|
|
|
|
|
|
# Use JIT-compiled version if available and actually defined |
|
335
|
6
|
50
|
33
|
6
|
|
166395
|
if ($COMPILED && defined &_jit_generate_session_id) { |
|
336
|
0
|
|
|
|
|
0
|
return _jit_generate_session_id(); |
|
337
|
|
|
|
|
|
|
} |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
# Perl fallback |
|
340
|
6
|
|
|
|
|
17
|
my $bytes; |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# Try /dev/urandom first (Unix) |
|
343
|
6
|
50
|
|
|
|
166
|
if (-r '/dev/urandom') { |
|
344
|
6
|
50
|
|
|
|
363
|
open my $fh, '<:raw', '/dev/urandom' or die "Cannot open /dev/urandom: $!"; |
|
345
|
6
|
|
|
|
|
494
|
read($fh, $bytes, 16); |
|
346
|
6
|
|
|
|
|
93
|
close $fh; |
|
347
|
|
|
|
|
|
|
} else { |
|
348
|
|
|
|
|
|
|
# Fallback to Perl's rand (less secure, but works everywhere) |
|
349
|
0
|
|
|
|
|
0
|
$bytes = pack('L*', map { int(rand(2**32)) } 1..4); |
|
|
0
|
|
|
|
|
0
|
|
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
|
|
352
|
6
|
|
|
|
|
39
|
return unpack('H*', $bytes); |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Sign a session ID with HMAC-SHA256 |
|
356
|
|
|
|
|
|
|
sub _sign { |
|
357
|
4
|
|
|
4
|
|
5779
|
my ($session_id, $secret) = @_; |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Use JIT-compiled HMAC if available and actually defined |
|
360
|
4
|
50
|
33
|
|
|
31
|
if ($COMPILED && defined &_jit_hmac_sha256_hex) { |
|
361
|
0
|
|
|
|
|
0
|
my $sig = _jit_hmac_sha256_hex($session_id, $secret, 16); |
|
362
|
0
|
|
|
|
|
0
|
return "$session_id.$sig"; |
|
363
|
|
|
|
|
|
|
} |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# Perl fallback |
|
366
|
4
|
|
|
|
|
82
|
my $sig = substr(hmac_sha256_hex($session_id, $secret), 0, 16); |
|
367
|
4
|
|
|
|
|
29
|
return "$session_id.$sig"; |
|
368
|
|
|
|
|
|
|
} |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# Verify and extract session ID from signed cookie |
|
371
|
|
|
|
|
|
|
sub _verify { |
|
372
|
5
|
|
|
5
|
|
2817
|
my ($signed_cookie, $secret) = @_; |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# Use JIT-compiled combined verify if available and actually defined |
|
375
|
5
|
50
|
33
|
|
|
26
|
if ($COMPILED && defined &_jit_verify_signature) { |
|
376
|
0
|
|
|
|
|
0
|
return _jit_verify_signature($signed_cookie, $secret); |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# Perl fallback |
|
380
|
5
|
100
|
66
|
|
|
132
|
return unless $signed_cookie && $signed_cookie =~ /^([a-f0-9]{32})\.([a-f0-9]{16})$/; |
|
381
|
|
|
|
|
|
|
|
|
382
|
4
|
|
|
|
|
33
|
my ($session_id, $sig) = ($1, $2); |
|
383
|
4
|
|
|
|
|
45
|
my $expected = substr(hmac_sha256_hex($session_id, $secret), 0, 16); |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
# Constant-time comparison to prevent timing attacks |
|
386
|
4
|
50
|
|
|
|
20
|
return unless length($sig) == length($expected); |
|
387
|
4
|
|
|
|
|
6
|
my $diff = 0; |
|
388
|
4
|
|
|
|
|
71
|
$diff |= ord(substr($sig, $_, 1)) ^ ord(substr($expected, $_, 1)) for 0 .. length($sig) - 1; |
|
389
|
|
|
|
|
|
|
|
|
390
|
4
|
100
|
|
|
|
25
|
return $diff == 0 ? $session_id : undef; |
|
391
|
|
|
|
|
|
|
} |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Constant-time string comparison |
|
394
|
|
|
|
|
|
|
sub _constant_time_compare { |
|
395
|
0
|
|
|
0
|
|
0
|
my ($s1, $s2) = @_; |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# Use JIT-compiled version if available and actually defined |
|
398
|
0
|
0
|
0
|
|
|
0
|
if ($COMPILED && defined &_jit_constant_time_compare) { |
|
399
|
0
|
|
|
|
|
0
|
return _jit_constant_time_compare($s1, $s2); |
|
400
|
|
|
|
|
|
|
} |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# Perl fallback |
|
403
|
0
|
0
|
0
|
|
|
0
|
return 0 unless defined $s1 && defined $s2; |
|
404
|
0
|
0
|
|
|
|
0
|
return 0 unless length($s1) == length($s2); |
|
405
|
|
|
|
|
|
|
|
|
406
|
0
|
|
|
|
|
0
|
my $diff = 0; |
|
407
|
0
|
|
|
|
|
0
|
$diff |= ord(substr($s1, $_, 1)) ^ ord(substr($s2, $_, 1)) for 0 .. length($s1) - 1; |
|
408
|
|
|
|
|
|
|
|
|
409
|
0
|
0
|
|
|
|
0
|
return $diff == 0 ? 1 : 0; |
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# ============================================================ |
|
413
|
|
|
|
|
|
|
# Session Configuration and Middleware |
|
414
|
|
|
|
|
|
|
# ============================================================ |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Configure session handling |
|
417
|
|
|
|
|
|
|
sub configure { |
|
418
|
7
|
|
|
7
|
0
|
3855
|
my ($class, %opts) = @_; |
|
419
|
|
|
|
|
|
|
|
|
420
|
7
|
100
|
100
|
|
|
82
|
die "Session secret is required" unless $opts{secret} && length($opts{secret}) >= 16; |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
# Try to compile JIT ops on first configuration |
|
423
|
5
|
50
|
|
|
|
52
|
$class->compile_session_ops(cache_dir => $opts{cache_dir}) unless $COMPILED; |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
$SESSION_CONFIG = { |
|
426
|
|
|
|
|
|
|
secret => $opts{secret}, |
|
427
|
|
|
|
|
|
|
cookie_name => $opts{cookie_name} // 'hsid', |
|
428
|
|
|
|
|
|
|
max_age => $opts{max_age} // 86400, # 1 day default |
|
429
|
|
|
|
|
|
|
path => $opts{path} // '/', |
|
430
|
|
|
|
|
|
|
httponly => $opts{httponly} // 1, |
|
431
|
|
|
|
|
|
|
secure => $opts{secure} // 0, |
|
432
|
5
|
|
100
|
|
|
129
|
samesite => $opts{samesite} // 'Lax', |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
433
|
|
|
|
|
|
|
}; |
|
434
|
|
|
|
|
|
|
|
|
435
|
5
|
|
|
|
|
15
|
return $SESSION_CONFIG; |
|
436
|
|
|
|
|
|
|
} |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
# Get session config |
|
439
|
0
|
|
|
0
|
0
|
0
|
sub config { $SESSION_CONFIG } |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
# Generate before middleware for session loading |
|
442
|
|
|
|
|
|
|
sub before_middleware { |
|
443
|
|
|
|
|
|
|
return sub { |
|
444
|
4
|
|
|
4
|
|
752
|
my ($req) = @_; |
|
445
|
|
|
|
|
|
|
|
|
446
|
4
|
50
|
|
|
|
14
|
my $config = $SESSION_CONFIG or return; |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
# Get session cookie |
|
449
|
4
|
|
|
|
|
20
|
my $signed_cookie = $req->cookie($config->{cookie_name}); |
|
450
|
|
|
|
|
|
|
|
|
451
|
4
|
|
|
|
|
9
|
my ($session_id, $data); |
|
452
|
|
|
|
|
|
|
|
|
453
|
4
|
100
|
|
|
|
11
|
if ($signed_cookie) { |
|
454
|
|
|
|
|
|
|
# Verify signature and get session ID |
|
455
|
1
|
|
|
|
|
15
|
$session_id = _verify($signed_cookie, $config->{secret}); |
|
456
|
|
|
|
|
|
|
|
|
457
|
1
|
50
|
33
|
|
|
15
|
if ($session_id && exists $SESSION_STORE{$session_id}) { |
|
458
|
1
|
|
|
|
|
3
|
$data = $SESSION_STORE{$session_id}; |
|
459
|
|
|
|
|
|
|
} |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# Create new session if needed |
|
463
|
4
|
100
|
66
|
|
|
46
|
unless ($session_id && $data) { |
|
464
|
3
|
|
|
|
|
11
|
$session_id = _generate_session_id(); |
|
465
|
3
|
|
|
|
|
21
|
$data = { _created => time(), _new => 1 }; |
|
466
|
3
|
|
|
|
|
9
|
$SESSION_STORE{$session_id} = $data; |
|
467
|
|
|
|
|
|
|
} |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# Store in request (use the session slot) |
|
470
|
4
|
|
|
|
|
14
|
$req->[SLOT_SESSION] = $data; |
|
471
|
4
|
|
|
|
|
9
|
$req->[SLOT_SESSION_ID] = $session_id; |
|
472
|
4
|
|
|
|
|
11
|
$req->[SLOT_SESSION_MODIFIED] = 0; |
|
473
|
|
|
|
|
|
|
|
|
474
|
4
|
|
|
|
|
16
|
return; # Continue to handler |
|
475
|
4
|
|
|
4
|
0
|
59
|
}; |
|
476
|
|
|
|
|
|
|
} |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
# Generate after middleware for session saving |
|
479
|
|
|
|
|
|
|
sub after_middleware { |
|
480
|
|
|
|
|
|
|
return sub { |
|
481
|
2
|
|
|
2
|
|
16
|
my ($req, $res) = @_; |
|
482
|
|
|
|
|
|
|
|
|
483
|
2
|
50
|
|
|
|
9
|
my $config = $SESSION_CONFIG or return $res; |
|
484
|
|
|
|
|
|
|
|
|
485
|
2
|
|
|
|
|
6
|
my $session_id = $req->[SLOT_SESSION_ID]; |
|
486
|
2
|
|
|
|
|
5
|
my $data = $req->[SLOT_SESSION]; |
|
487
|
2
|
|
|
|
|
5
|
my $modified = $req->[SLOT_SESSION_MODIFIED]; |
|
488
|
2
|
|
33
|
|
|
17
|
my $is_new = $data && $data->{_new}; |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
# Only set cookie if session is new or modified |
|
491
|
2
|
50
|
33
|
|
|
12
|
if ($session_id && ($is_new || $modified)) { |
|
|
|
|
33
|
|
|
|
|
|
492
|
2
|
50
|
|
|
|
9
|
delete $data->{_new} if $data; |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# Sign the session ID |
|
495
|
2
|
|
|
|
|
10
|
my $signed = _sign($session_id, $config->{secret}); |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
# Set cookie on response |
|
498
|
|
|
|
|
|
|
$res->cookie($config->{cookie_name}, $signed, |
|
499
|
|
|
|
|
|
|
path => $config->{path}, |
|
500
|
|
|
|
|
|
|
max_age => $config->{max_age}, |
|
501
|
|
|
|
|
|
|
httponly => $config->{httponly}, |
|
502
|
|
|
|
|
|
|
secure => $config->{secure}, |
|
503
|
|
|
|
|
|
|
samesite => $config->{samesite}, |
|
504
|
2
|
|
|
|
|
28
|
); |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
# Save session data |
|
507
|
2
|
50
|
|
|
|
9
|
$SESSION_STORE{$session_id} = $data if $data; |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
|
|
510
|
2
|
|
|
|
|
7
|
return $res; |
|
511
|
3
|
|
|
3
|
0
|
22
|
}; |
|
512
|
|
|
|
|
|
|
} |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
# ============================================================ |
|
515
|
|
|
|
|
|
|
# Session Data Access Methods |
|
516
|
|
|
|
|
|
|
# ============================================================ |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
# Session accessor - get or set session values |
|
519
|
|
|
|
|
|
|
# Called as: $req->session('key') or $req->session('key', $value) |
|
520
|
|
|
|
|
|
|
sub get_set { |
|
521
|
5
|
|
|
5
|
0
|
40
|
my ($req, $key, $value) = @_; |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# Use JIT-compiled version if available |
|
524
|
5
|
50
|
33
|
|
|
23
|
if ($COMPILED && defined &_jit_session_get_set) { |
|
525
|
0
|
0
|
|
|
|
0
|
return @_ == 2 |
|
526
|
|
|
|
|
|
|
? _jit_session_get_set($req, $key) |
|
527
|
|
|
|
|
|
|
: _jit_session_get_set($req, $key, $value); |
|
528
|
|
|
|
|
|
|
} |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
# Perl fallback |
|
531
|
5
|
|
|
|
|
9
|
my $data = $req->[SLOT_SESSION]; |
|
532
|
5
|
50
|
|
|
|
11
|
return unless $data; |
|
533
|
|
|
|
|
|
|
|
|
534
|
5
|
100
|
|
|
|
14
|
if (@_ == 2) { |
|
535
|
|
|
|
|
|
|
# Getter |
|
536
|
4
|
|
|
|
|
24
|
return $data->{$key}; |
|
537
|
|
|
|
|
|
|
} else { |
|
538
|
|
|
|
|
|
|
# Setter |
|
539
|
1
|
|
|
|
|
6
|
$data->{$key} = $value; |
|
540
|
1
|
|
|
|
|
3
|
$req->[SLOT_SESSION_MODIFIED] = 1; |
|
541
|
1
|
|
|
|
|
2
|
return $value; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
} |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# Get all session data |
|
546
|
|
|
|
|
|
|
sub get_all { |
|
547
|
1
|
|
|
1
|
0
|
2
|
my ($req) = @_; |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
# Use JIT-compiled version if available |
|
550
|
1
|
50
|
33
|
|
|
3
|
if ($COMPILED && defined &_jit_session_get_all) { |
|
551
|
0
|
|
|
|
|
0
|
return _jit_session_get_all($req); |
|
552
|
|
|
|
|
|
|
} |
|
553
|
|
|
|
|
|
|
|
|
554
|
1
|
|
|
|
|
2
|
return $req->[SLOT_SESSION]; |
|
555
|
|
|
|
|
|
|
} |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# Clear session |
|
558
|
|
|
|
|
|
|
sub clear { |
|
559
|
1
|
|
|
1
|
0
|
4
|
my ($req) = @_; |
|
560
|
|
|
|
|
|
|
|
|
561
|
1
|
|
|
|
|
3
|
my $session_id = $req->[SLOT_SESSION_ID]; |
|
562
|
1
|
50
|
|
|
|
6
|
delete $SESSION_STORE{$session_id} if $session_id; |
|
563
|
|
|
|
|
|
|
|
|
564
|
1
|
|
|
|
|
4
|
$req->[SLOT_SESSION] = {}; |
|
565
|
1
|
|
|
|
|
4
|
$req->[SLOT_SESSION_MODIFIED] = 1; |
|
566
|
|
|
|
|
|
|
} |
|
567
|
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
# Regenerate session ID (for security, e.g., after login) |
|
569
|
|
|
|
|
|
|
sub regenerate { |
|
570
|
1
|
|
|
1
|
0
|
3
|
my ($req) = @_; |
|
571
|
|
|
|
|
|
|
|
|
572
|
1
|
|
|
|
|
4
|
my $old_id = $req->[SLOT_SESSION_ID]; |
|
573
|
1
|
|
50
|
|
|
6
|
my $data = $req->[SLOT_SESSION] || {}; |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# Delete old session |
|
576
|
1
|
50
|
|
|
|
6
|
delete $SESSION_STORE{$old_id} if $old_id; |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
# Create new session ID |
|
579
|
1
|
|
|
|
|
3
|
my $new_id = _generate_session_id(); |
|
580
|
1
|
|
|
|
|
5
|
$SESSION_STORE{$new_id} = $data; |
|
581
|
|
|
|
|
|
|
|
|
582
|
1
|
|
|
|
|
3
|
$req->[SLOT_SESSION_ID] = $new_id; |
|
583
|
1
|
|
|
|
|
3
|
$req->[SLOT_SESSION_MODIFIED] = 1; |
|
584
|
|
|
|
|
|
|
|
|
585
|
1
|
|
|
|
|
6
|
return $new_id; |
|
586
|
|
|
|
|
|
|
} |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
# Cleanup expired sessions (call periodically) |
|
589
|
|
|
|
|
|
|
sub cleanup { |
|
590
|
1
|
|
|
1
|
0
|
5
|
my ($max_age) = @_; |
|
591
|
1
|
|
0
|
|
|
5
|
$max_age //= $SESSION_CONFIG->{max_age} // 86400; |
|
|
|
|
33
|
|
|
|
|
|
592
|
|
|
|
|
|
|
|
|
593
|
1
|
|
|
|
|
3
|
my $cutoff = time() - $max_age; |
|
594
|
|
|
|
|
|
|
|
|
595
|
1
|
|
|
|
|
13
|
for my $id (keys %SESSION_STORE) { |
|
596
|
1
|
|
|
|
|
5
|
my $data = $SESSION_STORE{$id}; |
|
597
|
1
|
50
|
33
|
|
|
16
|
if ($data && $data->{_created} && $data->{_created} < $cutoff) { |
|
|
|
|
33
|
|
|
|
|
|
598
|
1
|
|
|
|
|
6
|
delete $SESSION_STORE{$id}; |
|
599
|
|
|
|
|
|
|
} |
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
} |
|
602
|
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# For testing - get store size |
|
604
|
4
|
|
|
4
|
|
870
|
sub _store_size { scalar keys %SESSION_STORE } |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# For testing - clear entire store |
|
607
|
5
|
|
|
5
|
|
20294
|
sub _clear_store { %SESSION_STORE = () } |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
# For testing - reset JIT state |
|
610
|
0
|
|
|
0
|
|
|
sub _reset_jit { $COMPILED = 0; $MODULE_ID = 0; } |
|
|
0
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
1; |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
__END__ |