line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Pcore::HTTP::Cookies; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
45341
|
use Pcore -class; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
9
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
has cookies => ( is => 'ro', isa => HashRef, default => sub { {} } ); |
6
|
|
|
|
|
|
|
|
7
|
0
|
|
|
0
|
0
|
0
|
sub clear ($self) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
8
|
0
|
|
|
|
|
0
|
$self->{cookies} = {}; |
9
|
|
|
|
|
|
|
|
10
|
0
|
|
|
|
|
0
|
return; |
11
|
|
|
|
|
|
|
} |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# COOKIES LIMITATIONS: |
14
|
|
|
|
|
|
|
# http://browsercookielimits.squawky.net/ |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# RFC 2965 - http://www.ietf.org/rfc/rfc2965.txt; |
17
|
|
|
|
|
|
|
# To get a good understanding of cookies read this - http://www.quirksmode.org/js/cookies.html; |
18
|
|
|
|
|
|
|
# Cookies are stored as a single string containing name, value, expiry etc.; |
19
|
|
|
|
|
|
|
# Size limits apply to the entire cookie, not just its value; |
20
|
|
|
|
|
|
|
# If you use characters only in the ASCII range, each character takes 1 byte, so you can typically store 4096 characters; |
21
|
|
|
|
|
|
|
# In UTF-8 some characters are more than 1 byte, hence you can not store as many characters in the same amount of bytes; |
22
|
|
|
|
|
|
|
# The ';' character is reserved as a separator. Do not use it in the key or value; |
23
|
|
|
|
|
|
|
# jQuery Cookie plugin stores the cookie using encodeURIComponent. Hence ÿ is stored as %C3%BF, 6 characters. This works well, as other you would lose the ';' character; |
24
|
|
|
|
|
|
|
# You cannot delete cookies with a key that hits the size limit and has a small value. The method to delete a cookie is to set its expiry value, but when the key is large there is not enough room left to do this. Hence I have not tested limitations around key size; |
25
|
|
|
|
|
|
|
# It appears that some browsers limit by bytes, while others limit the number of characters; |
26
|
|
|
|
|
|
|
|
27
|
22
|
|
|
22
|
0
|
3374
|
sub parse_cookies ( $self, $url, $set_cookie_header ) { |
|
22
|
|
|
|
|
32
|
|
|
22
|
|
|
|
|
54
|
|
|
22
|
|
|
|
|
27
|
|
|
22
|
|
|
|
|
24
|
|
28
|
22
|
50
|
|
|
|
408
|
$url = P->uri($url) if !ref $url; |
29
|
|
|
|
|
|
|
|
30
|
22
|
|
|
|
|
52
|
COOKIE: for ( $set_cookie_header->@* ) { |
31
|
16
|
|
|
|
|
59
|
my ( $kvp, @attrs ) = split /;/sm; |
32
|
|
|
|
|
|
|
|
33
|
16
|
50
|
|
|
|
37
|
next if !defined $kvp; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# trim |
36
|
16
|
|
|
|
|
39
|
$kvp =~ s/\A\s+//sm; |
37
|
16
|
|
|
|
|
38
|
$kvp =~ s/\s+\z//sm; |
38
|
|
|
|
|
|
|
|
39
|
16
|
50
|
|
|
|
38
|
next if $kvp eq q[]; |
40
|
|
|
|
|
|
|
|
41
|
16
|
|
|
|
|
46
|
my $origin_domain_name = $url->host->name; |
42
|
|
|
|
|
|
|
|
43
|
16
|
|
|
|
|
305
|
my $cookie = { |
44
|
|
|
|
|
|
|
domain => $origin_domain_name, |
45
|
|
|
|
|
|
|
path => $url->path->to_string, |
46
|
|
|
|
|
|
|
expires => 0, |
47
|
|
|
|
|
|
|
httponly => 0, |
48
|
|
|
|
|
|
|
secure => 0, |
49
|
|
|
|
|
|
|
}; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# parse and set key and value |
52
|
16
|
100
|
|
|
|
42
|
if ( ( my $idx = index $kvp, q[=] ) != -1 ) { |
53
|
10
|
|
|
|
|
24
|
$cookie->{name} = substr $kvp, 0, $idx; |
54
|
|
|
|
|
|
|
|
55
|
10
|
|
|
|
|
25
|
$cookie->{val} = substr $kvp, $idx + 1; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
else { |
58
|
6
|
|
|
|
|
15
|
$cookie->{name} = $kvp; |
59
|
|
|
|
|
|
|
|
60
|
6
|
|
|
|
|
12
|
$cookie->{val} = q[]; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# parse attributes |
64
|
16
|
|
|
|
|
28
|
for my $attr (@attrs) { |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# trim |
67
|
20
|
|
|
|
|
40
|
$attr =~ s/\A\s+//sm; |
68
|
20
|
|
|
|
|
41
|
$attr =~ s/\s+\z//sm; |
69
|
|
|
|
|
|
|
|
70
|
20
|
50
|
|
|
|
33
|
next if $attr eq q[]; |
71
|
|
|
|
|
|
|
|
72
|
20
|
|
|
|
|
30
|
my ( $k, $v ); |
73
|
|
|
|
|
|
|
|
74
|
20
|
50
|
|
|
|
36
|
if ( ( my $idx = index $attr, q[=] ) != -1 ) { |
75
|
20
|
|
|
|
|
36
|
$k = lc substr $attr, 0, $idx; |
76
|
|
|
|
|
|
|
|
77
|
20
|
|
|
|
|
35
|
$v = substr $attr, $idx + 1; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
else { |
80
|
0
|
|
|
|
|
0
|
$k = lc $attr; |
81
|
|
|
|
|
|
|
|
82
|
0
|
|
|
|
|
0
|
$v = q[]; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
20
|
100
|
|
|
|
46
|
if ( $k eq 'domain' ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
86
|
16
|
100
|
|
|
|
30
|
if ( $v ne q[] ) { |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# http://bayou.io/draft/cookie.domain.html |
89
|
|
|
|
|
|
|
# origin domain - domain from the request |
90
|
|
|
|
|
|
|
# cover domain - domain from cookie attribute |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# if a cookie's origin domain is an IP, the cover domain must be null |
93
|
14
|
50
|
|
|
|
239
|
next COOKIE if $url->host->is_ip; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# parse cover domain |
96
|
14
|
|
|
|
|
213
|
my $cover_domain = P->host($v); |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# a cover domain must not be a IP address |
99
|
14
|
50
|
|
|
|
210
|
next COOKIE if $cover_domain->is_ip; |
100
|
|
|
|
|
|
|
|
101
|
14
|
|
|
|
|
37
|
my $cover_domain_name = $cover_domain->name; |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# if the origin domain is the same domain |
104
|
14
|
100
|
|
|
|
32
|
if ( $cover_domain_name eq $origin_domain_name ) { |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# permit a public suffix domain to specify itself as the cover domain |
107
|
|
|
|
|
|
|
# ignore cover domain, if cover domain is pub. suffix |
108
|
7
|
100
|
|
|
|
111
|
next if $cover_domain->is_pub_suffix; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
else { |
111
|
|
|
|
|
|
|
# the cover domain must not be a TLD, a public suffix, or a parent of a public suffix |
112
|
7
|
100
|
|
|
|
125
|
next COOKIE if $cover_domain->is_pub_suffix; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# the cover domain must cover (be a parent) the origin domain |
115
|
4
|
100
|
|
|
|
20
|
if ( ( my $idx = index $origin_domain_name, q[.] . $cover_domain_name ) > 0 ) { |
116
|
2
|
50
|
|
|
|
9
|
next COOKIE if length($origin_domain_name) != 1 + $idx + length $cover_domain_name; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
else { |
119
|
2
|
|
|
|
|
13
|
next COOKIE; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# accept cover domain cookie |
124
|
6
|
|
|
|
|
57
|
$cookie->{domain} = q[.] . $cover_domain_name; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
elsif ( $k eq 'path' ) { |
128
|
3
|
50
|
|
|
|
9
|
if ( $v ne q[] ) { |
129
|
0
|
|
|
|
|
0
|
$cookie->{path} = $v; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
elsif ( $k eq 'expires' ) { |
133
|
1
|
50
|
|
|
|
8
|
if ( $v ne q[] ) { |
134
|
1
|
50
|
|
|
|
8
|
if ( !$cookie->{expires} ) { # do not process expires attribute, if expires is already set by expires or max-age |
135
|
1
|
50
|
|
|
|
2
|
if ( my $expires = eval { P->date->parse($v) } ) { |
|
1
|
|
|
|
|
26
|
|
136
|
1
|
|
|
|
|
19
|
$cookie->{expires} = $expires->epoch; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
else { |
139
|
|
|
|
|
|
|
# ignore cookie if expires value is invalid |
140
|
0
|
|
|
|
|
0
|
next COOKIE; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
elsif ( $k eq 'max-age' ) { |
146
|
0
|
0
|
|
|
|
0
|
if ( $v ne q[] ) { |
147
|
0
|
0
|
|
|
|
0
|
if ( $v =~ /\A\d+\z/sm ) { |
148
|
0
|
|
|
|
|
0
|
$cookie->{expires} = time + $v; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
else { |
151
|
|
|
|
|
|
|
# ignore cookie if max-age value is invalid |
152
|
0
|
|
|
|
|
0
|
next COOKIE; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
elsif ( $k eq 'httponly' ) { |
157
|
0
|
|
|
|
|
0
|
$cookie->{httponly} = 1; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
elsif ( $k eq 'secure' ) { |
160
|
0
|
|
|
|
|
0
|
$cookie->{secure} = 1; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
11
|
100
|
66
|
|
|
38
|
if ( $cookie->{expires} && $cookie->{expires} < time ) { |
165
|
1
|
|
|
|
|
9
|
$self->remove_cookie( $cookie->{domain}, $cookie->{path}, $cookie->{name} ); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
else { |
168
|
10
|
|
|
|
|
50
|
$self->{cookies}->{ $cookie->{domain} }->{ $cookie->{path} }->{ $cookie->{name} } = $cookie; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
22
|
|
|
|
|
124
|
return; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
12
|
|
|
12
|
0
|
137
|
sub get_cookies ( $self, $url ) { |
|
12
|
|
|
|
|
17
|
|
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
14
|
|
176
|
12
|
|
|
12
|
|
11
|
state $match_path = sub ( $url_path, $cookie_path ) { |
|
12
|
|
|
|
|
15
|
|
|
12
|
|
|
|
|
17
|
|
|
12
|
|
|
|
|
17
|
|
177
|
12
|
50
|
|
|
|
54
|
return 1 if $cookie_path eq $url_path; |
178
|
|
|
|
|
|
|
|
179
|
0
|
0
|
|
|
|
0
|
return 1 if $cookie_path eq q[/]; |
180
|
|
|
|
|
|
|
|
181
|
0
|
0
|
|
|
|
0
|
if ( $url_path =~ /\A\Q$cookie_path\E(.*)/sm ) { |
182
|
0
|
|
|
|
|
0
|
my $rest = $1; |
183
|
|
|
|
|
|
|
|
184
|
0
|
0
|
|
|
|
0
|
return 1 if substr( $cookie_path, -1, 1 ) eq q[/]; |
185
|
|
|
|
|
|
|
|
186
|
0
|
0
|
|
|
|
0
|
return 1 if substr( $rest, 0, 1 ) eq q[/]; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
0
|
return; |
190
|
12
|
|
|
|
|
22
|
}; |
191
|
|
|
|
|
|
|
|
192
|
12
|
|
|
12
|
|
13
|
state $match_domain = sub ( $self, $domain, $domain_cookies, $url ) { |
|
12
|
|
|
|
|
14
|
|
|
12
|
|
|
|
|
14
|
|
|
12
|
|
|
|
|
13
|
|
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
36
|
|
193
|
12
|
|
|
|
|
13
|
my $cookies; |
194
|
|
|
|
|
|
|
|
195
|
12
|
|
|
|
|
18
|
my $time = time; |
196
|
|
|
|
|
|
|
|
197
|
12
|
|
|
|
|
33
|
for my $cookie_path ( keys $domain_cookies->%* ) { |
198
|
12
|
50
|
|
|
|
30
|
if ( $match_path->( $url->path, $cookie_path ) ) { |
199
|
12
|
|
|
|
|
53
|
for my $cookie ( values $domain_cookies->{$cookie_path}->%* ) { |
200
|
14
|
50
|
33
|
|
|
36
|
if ( $cookie->{expires} && $cookie->{expires} < $time ) { |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# remove expired cookie |
203
|
0
|
|
|
|
|
0
|
$self->remove_cookie( $domain, $cookie_path, $cookie->{name} ); |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
else { |
206
|
14
|
50
|
33
|
|
|
29
|
next if $cookie->{secure} && !$url->is_secure; |
207
|
|
|
|
|
|
|
|
208
|
14
|
|
|
|
|
47
|
push $cookies->@*, $cookie->{name} . q[=] . $cookie->{val}; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
12
|
|
|
|
|
35
|
return $cookies; |
215
|
12
|
|
|
|
|
21
|
}; |
216
|
|
|
|
|
|
|
|
217
|
12
|
50
|
|
|
|
220
|
$url = P->uri($url) if !ref $url; |
218
|
|
|
|
|
|
|
|
219
|
12
|
|
|
|
|
16
|
my $cookies; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# origin cookie |
222
|
12
|
|
|
|
|
42
|
my $origin_domain_name = $url->host->name; |
223
|
|
|
|
|
|
|
|
224
|
12
|
100
|
|
|
|
34
|
if ( my $origin_cookies = $self->{cookies}->{$origin_domain_name} ) { |
225
|
6
|
50
|
|
|
|
16
|
if ( my $match_cookies = $match_domain->( $self, $origin_domain_name, $origin_cookies, $url ) ) { |
226
|
6
|
|
|
|
|
13
|
push $cookies->@*, $match_cookies->@*; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# cover cookies |
231
|
|
|
|
|
|
|
# http://bayou.io/draft/cookie.domain.html#Coverage_Model |
232
|
12
|
50
|
|
|
|
238
|
if ( !$url->host->is_ip ) { |
233
|
12
|
|
|
|
|
53
|
my @labels = split /[.]/sm, $url->host->name; |
234
|
|
|
|
|
|
|
|
235
|
12
|
|
|
|
|
18
|
my $origin = 1; |
236
|
|
|
|
|
|
|
|
237
|
12
|
|
|
|
|
26
|
while ( @labels > 1 ) { |
238
|
25
|
|
|
|
|
433
|
my $domain = P->host( join q[.], @labels ); |
239
|
|
|
|
|
|
|
|
240
|
25
|
|
|
|
|
64
|
my $cover_domain_name = q[.] . $domain->name; |
241
|
|
|
|
|
|
|
|
242
|
25
|
100
|
|
|
|
60
|
if ( my $cover_cookies = $self->{cookies}->{$cover_domain_name} ) { |
243
|
6
|
50
|
|
|
|
15
|
if ( my $match_cookies = $match_domain->( $self, $cover_domain_name, $cover_cookies, $url ) ) { |
244
|
6
|
|
|
|
|
13
|
push $cookies->@*, $match_cookies->@*; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
25
|
100
|
100
|
|
|
377
|
last if $domain->is_pub_suffix && !$origin; |
249
|
|
|
|
|
|
|
|
250
|
23
|
|
|
|
|
39
|
$origin = 0; |
251
|
|
|
|
|
|
|
|
252
|
23
|
|
|
|
|
76
|
shift @labels; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
12
|
|
|
|
|
56
|
return $cookies; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
1
|
|
|
1
|
0
|
3
|
sub remove_cookie ( $self, $domain, $path, $name ) { |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
19
|
|
|
1
|
|
|
|
|
3
|
|
260
|
1
|
50
|
|
|
|
12
|
if ( delete $self->{cookies}->{$domain}->{$path}->{$name} ) { |
261
|
1
|
50
|
|
|
|
12
|
delete $self->{cookies}->{$domain}->{$path} if !keys $self->{cookies}->{$domain}->{$path}->%*; |
262
|
|
|
|
|
|
|
|
263
|
1
|
50
|
|
|
|
8
|
delete $self->{cookies}->{$domain} if !keys $self->{cookies}->{$domain}->%*; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
1
|
|
|
|
|
267
|
return; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
1; |
270
|
|
|
|
|
|
|
## -----SOURCE FILTER LOG BEGIN----- |
271
|
|
|
|
|
|
|
## |
272
|
|
|
|
|
|
|
## PerlCritic profile "pcore-script" policy violations: |
273
|
|
|
|
|
|
|
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ |
274
|
|
|
|
|
|
|
## | Sev. | Lines | Policy | |
275
|
|
|
|
|
|
|
## |======+======================+================================================================================================================| |
276
|
|
|
|
|
|
|
## | 3 | | Subroutines::ProhibitExcessComplexity | |
277
|
|
|
|
|
|
|
## | | 27 | * Subroutine "parse_cookies" with high complexity score (38) | |
278
|
|
|
|
|
|
|
## | | 175 | * Subroutine "get_cookies" with high complexity score (23) | |
279
|
|
|
|
|
|
|
## |------+----------------------+----------------------------------------------------------------------------------------------------------------| |
280
|
|
|
|
|
|
|
## | 3 | 115, 135 | ControlStructures::ProhibitDeepNests - Code structure is deeply nested | |
281
|
|
|
|
|
|
|
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ |
282
|
|
|
|
|
|
|
## |
283
|
|
|
|
|
|
|
## -----SOURCE FILTER LOG END----- |
284
|
|
|
|
|
|
|
__END__ |
285
|
|
|
|
|
|
|
=pod |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=encoding utf8 |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=head1 NAME |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
Pcore::HTTP::Cookies |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=head1 SYNOPSIS |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head1 DESCRIPTION |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=cut |