line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Plack::Middleware::Session::SerializedCookie;
|
2
|
4
|
|
|
4
|
|
83954
|
use strict;
|
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
176
|
|
3
|
4
|
|
|
4
|
|
22
|
use warnings;
|
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
117
|
|
4
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
115
|
use 5.008;
|
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
161
|
|
6
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
3947
|
use parent 'Plack::Middleware';
|
|
4
|
|
|
|
|
1390
|
|
|
4
|
|
|
|
|
21
|
|
8
|
4
|
|
|
4
|
|
124285
|
use Plack::Request;
|
|
4
|
|
|
|
|
374018
|
|
|
4
|
|
|
|
|
143
|
|
9
|
4
|
|
|
4
|
|
4199
|
use Plack::Response;
|
|
4
|
|
|
|
|
6023
|
|
|
4
|
|
|
|
|
123
|
|
10
|
4
|
|
|
4
|
|
27
|
use Carp;
|
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
2861
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = 1.03;
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub prepare_app {
|
15
|
6
|
|
|
6
|
1
|
102357
|
my $self = shift;
|
16
|
|
|
|
|
|
|
|
17
|
6
|
50
|
|
|
|
44
|
$self->{session_key} = 'plack_session' if ! defined $self->{session_key};
|
18
|
|
|
|
|
|
|
|
19
|
6
|
|
|
|
|
19
|
for my $fname ( qw( serialize deserialize ) ) {
|
20
|
12
|
|
33
|
24
|
|
153
|
$self->{$fname} ||= $self->{serializer} && $self->{serializer}->can($fname) && sub { $self->{serializer}->$fname(@_) } || croak __PACKAGE__.": No '$fname' installed!!"
|
|
24
|
|
66
|
|
|
103
|
|
21
|
|
|
|
|
|
|
}
|
22
|
|
|
|
|
|
|
|
23
|
6
|
|
|
|
|
17
|
$self->{cookie_options} = +{ map { $_ => delete $self->{$_} } grep { exists $self->{$_} } qw(path domain secure) };
|
|
0
|
|
|
|
|
0
|
|
|
18
|
|
|
|
|
50
|
|
24
|
|
|
|
|
|
|
}
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub call {
|
27
|
24
|
|
|
24
|
1
|
11596
|
my($self, $env) = @_;
|
28
|
|
|
|
|
|
|
|
29
|
24
|
|
|
|
|
111
|
my $cookie = Plack::Request->new($env)->cookies->{$self->{session_key}};
|
30
|
24
|
100
|
|
|
|
1225
|
$env->{'psgix.session'} = eval { $self->{deserialize}->( defined($cookie) ? $cookie : undef ) };
|
|
24
|
|
|
|
|
93
|
|
31
|
24
|
50
|
33
|
|
|
879
|
$self->{deserialize_exception}->($@) if $@ && $self->{deserialize_exception};
|
32
|
|
|
|
|
|
|
|
33
|
24
|
|
|
|
|
87
|
my $res = $self->app->($env);
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$self->response_cb( $res, sub {
|
36
|
24
|
|
|
24
|
|
326
|
my $res = shift;
|
37
|
24
|
|
|
|
|
100
|
my $response = Plack::Response->new(@$res);
|
38
|
24
|
50
|
|
|
|
2023
|
$self->{cookie_options}->{expires} = time + $self->{expires} if exists $self->{expires};
|
39
|
24
|
|
|
|
|
96
|
$response->cookies->{$self->{session_key}} = $self->{cookie_options};
|
40
|
24
|
50
|
33
|
|
|
299
|
if( !defined($env->{'psgix.session'}) || $env->{'psgix.session.option'} && $env->{'psgix.session.option'}{expire} ) {
|
|
|
|
33
|
|
|
|
|
41
|
0
|
|
|
|
|
0
|
local $self->{cookie_options}{expires} = 1;
|
42
|
0
|
|
|
|
|
0
|
$res->[1] = $response->finalize->[1];
|
43
|
|
|
|
|
|
|
}
|
44
|
|
|
|
|
|
|
else {
|
45
|
24
|
|
|
|
|
36
|
eval {
|
46
|
24
|
|
|
|
|
82
|
local $self->{cookie_options}{value} = $self->{serialize}->($env->{'psgix.session'});
|
47
|
24
|
|
|
|
|
1262
|
$res->[1] = $response->finalize->[1];
|
48
|
|
|
|
|
|
|
};
|
49
|
24
|
50
|
33
|
|
|
10403
|
$self->{serialize_exception}->($@) if $@ && $self->{serialize_exception};
|
50
|
|
|
|
|
|
|
}
|
51
|
24
|
|
|
|
|
412
|
} );
|
52
|
|
|
|
|
|
|
}
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
1;
|
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head1 NAME
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Plack::Middleware::Session::SerializedCookie -
|
59
|
|
|
|
|
|
|
Session middleware that saves session data in
|
60
|
|
|
|
|
|
|
the customizable serialized cookie
|
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# With serialize/deserialize subs
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
enable "Session::SerializedCookie",
|
67
|
|
|
|
|
|
|
serialize => sub {
|
68
|
|
|
|
|
|
|
my $session = shift;
|
69
|
|
|
|
|
|
|
...
|
70
|
|
|
|
|
|
|
return $serialized_session
|
71
|
|
|
|
|
|
|
},
|
72
|
|
|
|
|
|
|
deserialize => sub {
|
73
|
|
|
|
|
|
|
my $serialized_session = shift;
|
74
|
|
|
|
|
|
|
...
|
75
|
|
|
|
|
|
|
return $session };
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# With Serializer (object that
|
79
|
|
|
|
|
|
|
# implements 'serialize' and 'deserialize')
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
enable "Session::SerializedCookie",
|
82
|
|
|
|
|
|
|
serializer => Data::Serializer->new(...);
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Mixed case
|
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
my $serializer = Data::Serializer->new(...);
|
88
|
|
|
|
|
|
|
enable "Session::SerializedCookie",
|
89
|
|
|
|
|
|
|
serializer => $serializer,
|
90
|
|
|
|
|
|
|
deserialize => sub {
|
91
|
|
|
|
|
|
|
my $session = eval { $serializer->deserialize(@_) };
|
92
|
|
|
|
|
|
|
$session = { ... (initial session) ... } if $@;
|
93
|
|
|
|
|
|
|
return $session;
|
94
|
|
|
|
|
|
|
};
|
95
|
|
|
|
|
|
|
# The missing sub 'serialize' will fall back
|
96
|
|
|
|
|
|
|
# to use the serializer's one.
|
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Additional exception handler
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
enable "Session::SerializedCookie",
|
102
|
|
|
|
|
|
|
serializer => Data::Serializer->new(...),
|
103
|
|
|
|
|
|
|
serialize_exception => sub { my $error_msg = shift; ... },
|
104
|
|
|
|
|
|
|
deserialize_exception => sub { my $error_msg = shift; ... };
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# In the app
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub {
|
110
|
|
|
|
|
|
|
my $env = shift;
|
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Retrieve the session by $env->{'psgix.session'}
|
113
|
|
|
|
|
|
|
# If the session is not presented, or something goes wrong when retrieving,
|
114
|
|
|
|
|
|
|
# it'll be set to {}, an empty hash reference.
|
115
|
|
|
|
|
|
|
my $session = $env->{'psgix.session'};
|
116
|
|
|
|
|
|
|
...
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
$session->{blah} = 'blah blah';
|
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# At the end of the app, $env->{'psgix.session'} will be stored
|
121
|
|
|
|
|
|
|
# to the cookie automatically.
|
122
|
|
|
|
|
|
|
# To expire the session, undef the $env->{'psgix.session'} or
|
123
|
|
|
|
|
|
|
# set $env->{'psgix.session.option'}{expire} = 1
|
124
|
|
|
|
|
|
|
};
|
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# For full and more examples, take a look on eg/ directory.
|
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
This middleware component works like L,
|
132
|
|
|
|
|
|
|
that it provide a simple way to retrieve and store session data
|
133
|
|
|
|
|
|
|
via $env->{'psgix.session'}. It store the session data in the cookie
|
134
|
|
|
|
|
|
|
like what L do, that doesn't
|
135
|
|
|
|
|
|
|
need to store any data in the server side.
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
In addition, this module provide a convenient way to customize
|
138
|
|
|
|
|
|
|
the way to serialize / deserialize the session data.
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
It is sometimes important to customize the serializer because of
|
141
|
|
|
|
|
|
|
various application. Someone might need to store critical data in
|
142
|
|
|
|
|
|
|
the session. Someone might need to store large data there.
|
143
|
|
|
|
|
|
|
Some one might need just a simplest and fastest one, and
|
144
|
|
|
|
|
|
|
don't care if the user can arbitrarily modify the session data.
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 CONFIGURATIONS
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=over 4
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item session_key
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
This is used as the cookie name
|
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item path, domain, secure
|
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
These are used as the cookie params.
|
157
|
|
|
|
|
|
|
See L for these options.
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=item expires
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
This one is used as an advance time from the request time.
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item serialize
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Set this attribute to a sub reference. It will be called
|
166
|
|
|
|
|
|
|
when serializing. The only argument is the session data,
|
167
|
|
|
|
|
|
|
$env->{'psgix.session'} in fact. And this sub should return
|
168
|
|
|
|
|
|
|
the serialized one.
|
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
If this attribute is not presented. This module will try
|
171
|
|
|
|
|
|
|
to use the serializer's member sub 'serialize'.
|
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=item deserialize
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Set this attribute to a sub reference. It is the inverse of
|
176
|
|
|
|
|
|
|
serialize. It will get the serialized session as the only
|
177
|
|
|
|
|
|
|
argument, and should return the session data.
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
Like 'serialize', if this attribute is missed, the module
|
180
|
|
|
|
|
|
|
will try to use serializer's one.
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=item serializer
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Set this attribute to an object. When 'serialize' or 'deserialize'
|
185
|
|
|
|
|
|
|
is missed, the module will use this object's.
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=item serialize_exception, deserialize_exception
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
If there is anything wrong when serializing or deserializing,
|
190
|
|
|
|
|
|
|
the coresponding sub will be called with the error message ($@)
|
191
|
|
|
|
|
|
|
as the only argument.
|
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
Note that exception is expensive. You should avoid exception
|
194
|
|
|
|
|
|
|
when possible, if the efficiency is important at your application.
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=back
|
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head1 CONSIDERATION
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
When customizing your own serializing method, there are some issues
|
201
|
|
|
|
|
|
|
that you might need to consider.
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=over 4
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=item serializer
|
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
This is the primary part of serialization, to transform a bunch of data
|
208
|
|
|
|
|
|
|
into a string, and to transform a string back to the original data.
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
There are several well-known serialization method, such as L,
|
211
|
|
|
|
|
|
|
L, L, L, L, etc. Each of them
|
212
|
|
|
|
|
|
|
has different benefits and different limits. You should read their documents
|
213
|
|
|
|
|
|
|
for more information.
|
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
My favorite one is L. It will try to use L when availible.
|
216
|
|
|
|
|
|
|
This one is both efficient and simple. Though you can only store opaque
|
217
|
|
|
|
|
|
|
data structure with array reference, hash reference, string, and number
|
218
|
|
|
|
|
|
|
data types. You can't store other types such as code reference, blessed object,
|
219
|
|
|
|
|
|
|
tied data, nor references that refer to the same variable or cyclic references (L
|
220
|
|
|
|
|
|
|
will extract them independently and completely).
|
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=item base64
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
It's not allowed to use all the octet codes as the cookie value.
|
225
|
|
|
|
|
|
|
It's a safer way to encode your serialized string into base64 form.
|
226
|
|
|
|
|
|
|
You may take a look on L.
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
Note, you should use this as the final filter, or it will be useless.
|
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item encryption
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Sometimes, you may want to store critical data in the session,
|
233
|
|
|
|
|
|
|
that you don't want the user to know what you have store there.
|
234
|
|
|
|
|
|
|
You may consider to encrypt the session.
|
235
|
|
|
|
|
|
|
My favorite one is AES (L).
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
Besides, to encrypt session can avoid the user to change the session.
|
238
|
|
|
|
|
|
|
If you just want to prevent the user to change the session, but don't
|
239
|
|
|
|
|
|
|
care if the user could read its content, it's not neccessary to encrypt it.
|
240
|
|
|
|
|
|
|
You can use signature instead. See the section L.
|
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=item compression
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
The browser will not allow to store very large cookie data.
|
245
|
|
|
|
|
|
|
You may need to compress it. Though, to think of storing smaller data in session
|
246
|
|
|
|
|
|
|
might be a much better approach.
|
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
You may use L to achieve this.
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=item additional data
|
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Besides to filter the whole session, you may want to just put additional data,
|
253
|
|
|
|
|
|
|
that could help you to do some verification.
|
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
You can store the additional data
|
256
|
|
|
|
|
|
|
by concating them with the serialized string (if they are string form already),
|
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
$serialized_session = join ',', $serialized_session, $addition1, $addition2
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
or more generally injecting into the session data structure before serialization.
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
$session = [$session, $addition1, $addition2]
|
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=over 4
|
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=item signature
|
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Use a one-way hash function to generate the signature from the serialized session
|
269
|
|
|
|
|
|
|
string and a secret string. The users cannot generate the signatures by themselves
|
270
|
|
|
|
|
|
|
without knowing about the secret string. So they can't generate arbitrary session
|
271
|
|
|
|
|
|
|
data with correct signatures.
|
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
There are many one-way hash functions availible, such as SHA1, SHA2, MD5, MD6, etc.
|
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
It's no need to use signature, if you've encrypted the session.
|
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=item timestamp
|
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
With timestamp, you can expire old session strictly. That is, even the browser
|
280
|
|
|
|
|
|
|
never expires the cookie, you can still expire it by rejecting old timestamp.
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item ip address
|
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
This is a more aggresive way to prevent the unwelcome bad guy from
|
285
|
|
|
|
|
|
|
stealing the session.
|
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
Use this feature cautiously. It will block users who change their ip address rapidly.
|
288
|
|
|
|
|
|
|
On the other hand, it's useless if the bad guy is using the same ip address as the
|
289
|
|
|
|
|
|
|
user, 'cause they are at the same intranet, or the bad guy is the spyware on the
|
290
|
|
|
|
|
|
|
same computer.
|
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=back
|
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=back
|
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head1 AUTHOR
|
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
Cindy Wang (CindyLinz)
|
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=head1 SEE ALSO
|
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
L
|
303
|
|
|
|
|
|
|
L
|
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=cut
|