line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTTP::Cookies::Opera; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
182592
|
use strict; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
213
|
|
4
|
3
|
|
|
3
|
|
18
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
1087
|
|
5
|
|
|
|
|
|
|
|
6
|
3
|
|
|
3
|
|
8373
|
use parent qw(HTTP::Cookies); |
|
3
|
|
|
|
|
2073
|
|
|
3
|
|
|
|
|
18
|
|
7
|
3
|
|
|
3
|
|
105060
|
use Carp qw(croak); |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
409
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.08'; |
10
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
11
|
|
|
|
|
|
|
|
12
|
3
|
|
|
3
|
|
17
|
use constant DEBUG => !! $ENV{HTTP_COOKIES_OPERA_DEBUG}; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
412
|
|
13
|
3
|
|
|
3
|
|
18
|
use constant FILE_VER => 1; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
148
|
|
14
|
3
|
|
|
3
|
|
18
|
use constant APP_VER => 2; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
215
|
|
15
|
3
|
|
|
3
|
|
20
|
use constant TAG_LEN => 1; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
152
|
|
16
|
3
|
|
|
3
|
|
16
|
use constant LEN_LEN => 2; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
8015
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub load { |
19
|
3
|
|
|
3
|
1
|
129
|
my ($self, $file) = @_; |
20
|
3
|
50
|
33
|
|
|
33
|
$file ||= $self->{file} or return; |
21
|
|
|
|
|
|
|
|
22
|
3
|
50
|
|
|
|
541
|
open my $fh, '<', $file or die "$file: $!"; |
23
|
3
|
|
|
|
|
13
|
binmode $fh; |
24
|
3
|
50
|
|
|
|
197
|
12 == read($fh, my $header, 12) or croak 'bad file header'; |
25
|
3
|
|
|
|
|
24
|
my ($file_ver, $app_ver, $tag_len, $len_len) = unpack 'NNnn', $header; |
26
|
|
|
|
|
|
|
|
27
|
3
|
50
|
33
|
|
|
125
|
croak 'unexpected file format' |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
28
|
|
|
|
|
|
|
unless FILE_VER == $file_ver >> 12 and APP_VER == $app_ver >> 12 |
29
|
|
|
|
|
|
|
and TAG_LEN == $tag_len and LEN_LEN == $len_len; |
30
|
|
|
|
|
|
|
|
31
|
3
|
|
|
|
|
7
|
my (@domain_parts, @path_parts, %cookie); |
32
|
|
|
|
|
|
|
|
33
|
3
|
|
|
|
|
14
|
while (TAG_LEN == read $fh, my $tag, TAG_LEN) { |
34
|
329
|
|
|
|
|
680
|
$tag = unpack 'C', $tag; |
35
|
329
|
|
|
|
|
687
|
DEBUG and printf "tag: %#x\n", $tag; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# End of domain component. |
38
|
329
|
100
|
|
|
|
2263
|
if (0x84 == $tag) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
39
|
21
|
|
|
|
|
273
|
pop @domain_parts; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
# End of path component. |
42
|
|
|
|
|
|
|
elsif (0x85 == $tag) { |
43
|
27
|
|
|
|
|
34
|
pop @path_parts; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# Add last constructed cookie as this path will have no more. |
46
|
27
|
|
|
|
|
74
|
$self->_add_cookie(\%cookie); |
47
|
|
|
|
|
|
|
} |
48
|
3
|
|
|
|
|
9
|
elsif (0x99 == $tag) { $cookie{secure} = 1 } |
49
|
|
|
|
|
|
|
elsif (0x3 == $tag) { |
50
|
|
|
|
|
|
|
# Add previous cookie now that it is fully constructed. |
51
|
35
|
|
|
|
|
134
|
$self->_add_cookie(\%cookie); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Reset cookie for new record. |
54
|
35
|
|
|
|
|
2223
|
%cookie = ( |
55
|
|
|
|
|
|
|
domain => join('.', reverse @domain_parts), |
56
|
|
|
|
|
|
|
path => '/' . join('/', @path_parts), |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Record is a flag and contains no payload. |
61
|
329
|
100
|
|
|
|
3479
|
next if 0x80 & $tag; |
62
|
|
|
|
|
|
|
|
63
|
242
|
50
|
|
|
|
4205
|
LEN_LEN == read $fh, my $len, LEN_LEN or croak 'bad file'; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# Tags have unique ids among top-level domain/path/cookie records as |
66
|
|
|
|
|
|
|
# well as the payload records, so simplify parsing by treating the |
67
|
|
|
|
|
|
|
# payload records as top-level records during the next iteration. |
68
|
242
|
100
|
|
|
|
919
|
next if 0x3 >= $tag; |
69
|
|
|
|
|
|
|
|
70
|
180
|
|
|
|
|
708
|
$len = unpack 'n', $len; |
71
|
180
|
|
|
|
|
173
|
DEBUG and printf " len: %d\n", $len; |
72
|
180
|
50
|
|
|
|
507
|
$len == read $fh, my $payload, $len or croak 'bad file'; |
73
|
|
|
|
|
|
|
|
74
|
180
|
100
|
|
|
|
727
|
if (0x1e == $tag) { push @domain_parts, $payload } |
|
18
|
100
|
|
|
|
37
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
75
|
9
|
|
|
|
|
56
|
elsif (0x1d == $tag) { push @path_parts, $payload } |
76
|
35
|
|
|
|
|
164
|
elsif (0x10 == $tag) { $cookie{key} = $payload } |
77
|
35
|
|
|
|
|
68
|
elsif (0x11 == $tag) { $cookie{val} = $payload } |
78
|
|
|
|
|
|
|
elsif (0x12 == $tag) { |
79
|
|
|
|
|
|
|
# Time is stored in 8 bytes for Opera >=10, 4 bytes for <10. |
80
|
35
|
50
|
|
|
|
86
|
$payload = unpack 8 == $len ? 'x4N' : 'N', $payload; |
81
|
35
|
|
|
|
|
157
|
$cookie{maxage} = $payload - time; |
82
|
35
|
|
|
|
|
224
|
DEBUG and $payload = scalar localtime $payload; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
elsif (0x1a == $tag) { |
85
|
|
|
|
|
|
|
# Version- not yet seen. |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
180
|
|
|
|
|
633
|
DEBUG and printf " payload: %s\n", $payload; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
3
|
|
|
|
|
53
|
close $fh; |
92
|
|
|
|
|
|
|
|
93
|
3
|
|
|
|
|
27
|
return 1; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub _add_cookie { |
97
|
62
|
|
|
62
|
|
4325
|
my ($self, $cookie) = @_; |
98
|
|
|
|
|
|
|
|
99
|
62
|
100
|
|
|
|
201
|
return unless exists $cookie->{key}; |
100
|
|
|
|
|
|
|
|
101
|
56
|
|
|
|
|
276
|
$self->set_cookie( |
102
|
|
|
|
|
|
|
undef, @$cookie{qw(key val path domain)}, undef, undef, |
103
|
|
|
|
|
|
|
@$cookie{qw(secure maxage)}, undef, undef |
104
|
|
|
|
|
|
|
); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub save { |
108
|
1
|
|
|
1
|
1
|
12
|
my ($self, $file) = @_; |
109
|
1
|
50
|
33
|
|
|
7
|
$file ||= $self->{file} or return; |
110
|
|
|
|
|
|
|
|
111
|
1
|
50
|
|
|
|
176
|
open my $fh, '>', $file or die "$file: $!"; |
112
|
1
|
|
|
|
|
4
|
binmode $fh; |
113
|
|
|
|
|
|
|
|
114
|
1
|
|
|
|
|
16
|
print $fh pack 'NNnn', FILE_VER << 12, APP_VER << 12, TAG_LEN, LEN_LEN; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Cannot call scan() as it iterates over the domains in lexical order, |
117
|
|
|
|
|
|
|
# but Opera requires the cookies to be stored in a hierarchy of domain |
118
|
|
|
|
|
|
|
# components (i.e. com -> opera -> www). |
119
|
9
|
50
|
|
|
|
17
|
my @domains = sort { $a->[0] cmp $b->[0] } map { |
|
5
|
|
|
|
|
22
|
|
120
|
|
|
|
|
|
|
# Do not split IP addresses into components. |
121
|
1
|
|
|
|
|
5
|
my @parts = /^(?:\d+\.){3}\d+$/ ? ($_) : reverse split '\.'; |
122
|
5
|
|
|
|
|
21
|
[ join('.', @parts), $_, \@parts ] |
123
|
1
|
|
|
|
|
3
|
} keys %{$self->{COOKIES}}; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# Add an empty domain field to close the last open domain record. |
126
|
1
|
|
|
|
|
3
|
push @domains, []; |
127
|
|
|
|
|
|
|
|
128
|
1
|
|
|
|
|
2
|
my @prev_domain; |
129
|
1
|
|
|
|
|
4
|
for my $aref (@domains) { |
130
|
6
|
|
|
|
|
10
|
my ($sort_key, $domain, $parts) = @$aref; |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Opera does not support cross-subdomain cookies. |
133
|
|
|
|
|
|
|
# |
134
|
|
|
|
|
|
|
# TODO: if a domain cookie and a cross-subdomain cookie both exist |
135
|
|
|
|
|
|
|
# for the same key, which should take precedence? |
136
|
6
|
100
|
66
|
|
|
35
|
my $is_cross = $parts && length $parts->[-1] ? 0 : pop @$parts || 1; |
|
|
|
50
|
|
|
|
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# Close domain component records for previous domain. |
139
|
6
|
|
|
|
|
16
|
for (my $i = @prev_domain - 1; 0 <= $i; $i--) { |
140
|
11
|
|
|
|
|
15
|
my $prev = $prev_domain[$i]; |
141
|
11
|
100
|
100
|
|
|
69
|
if (length $prev and $prev ne ($parts->[$i] || '')) { |
|
|
|
66
|
|
|
|
|
142
|
6
|
|
|
|
|
2
|
DEBUG and print " closing: $prev\n"; |
143
|
6
|
|
|
|
|
6
|
pop @prev_domain; |
144
|
6
|
|
|
|
|
18
|
print $fh pack 'C', 0x84; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
6
|
100
|
|
|
|
13
|
last unless $domain; |
149
|
5
|
|
|
|
|
6
|
DEBUG and print "domain: $domain\n"; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Open domain component records for next domain. |
152
|
5
|
|
|
|
|
13
|
for (my $i = @prev_domain; $i < @$parts; $i++) { |
153
|
6
|
|
|
|
|
9
|
my $curr = $parts->[$i]; |
154
|
6
|
50
|
50
|
|
|
115
|
if (length $curr and $curr ne ($prev_domain[$i] || '')) { |
|
|
|
33
|
|
|
|
|
155
|
6
|
|
|
|
|
5
|
DEBUG and print " opening: $curr\n"; |
156
|
6
|
|
|
|
|
10
|
push @prev_domain, $curr; |
157
|
6
|
|
|
|
|
16
|
print $fh pack 'Cn', 0x1, 3 + length($curr); |
158
|
6
|
|
|
|
|
9
|
print $fh pack 'Cn', 0x1e, length($curr); |
159
|
6
|
|
|
|
|
6
|
print $fh $curr; |
160
|
6
|
100
|
|
|
|
23
|
print $fh pack 'C', 0x85 if $i < @$parts - 1; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
5
|
|
|
|
|
7
|
my @paths = sort keys %{$self->{COOKIES}{$domain}}; |
|
5
|
|
|
|
|
18
|
|
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Add an empty path field to close the last open path record. |
167
|
5
|
|
|
|
|
7
|
push @paths, ''; |
168
|
|
|
|
|
|
|
|
169
|
5
|
|
|
|
|
6
|
my @prev_path; |
170
|
5
|
|
|
|
|
6
|
for my $path (@paths) { |
171
|
12
|
|
|
|
|
26
|
my @parts = split '/', $path; |
172
|
12
|
|
|
|
|
14
|
shift @parts; |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Close path component records for previous path. |
175
|
12
|
|
|
|
|
31
|
for (my $i = @prev_path - 1; 0 <= $i; $i--) { |
176
|
3
|
|
|
|
|
4
|
my $prev = $prev_path[$i]; |
177
|
3
|
50
|
50
|
|
|
155
|
if (length $prev and $prev ne ($parts[$i] || '')) { |
|
|
|
33
|
|
|
|
|
178
|
3
|
|
|
|
|
3
|
DEBUG and print " closing: $prev\n"; |
179
|
3
|
|
|
|
|
4
|
print $fh pack 'C', 0x85; |
180
|
3
|
|
|
|
|
8
|
pop @prev_path; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
12
|
100
|
|
|
|
24
|
last unless $path; |
185
|
7
|
|
|
|
|
5
|
DEBUG and print " path: $path\n"; |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# Open path component records for next path. |
188
|
7
|
|
|
|
|
19
|
for (my $i = @prev_path; $i < @parts; $i++) { |
189
|
3
|
|
|
|
|
4
|
my $curr = $parts[$i]; |
190
|
3
|
50
|
50
|
|
|
23
|
if (length $curr and $curr ne ($prev_path[$i] || '')) { |
|
|
|
33
|
|
|
|
|
191
|
3
|
|
|
|
|
42
|
DEBUG and print " opening: $curr\n"; |
192
|
3
|
|
|
|
|
8
|
print $fh pack 'Cn', 0x2, 3 + length($curr); |
193
|
3
|
|
|
|
|
6
|
print $fh pack 'Cn', 0x1d, length($curr); |
194
|
3
|
|
|
|
|
2
|
print $fh $curr; |
195
|
3
|
|
|
|
|
9
|
push @prev_path, $curr; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
7
|
|
|
|
|
13
|
my $href = $self->{COOKIES}{$domain}{$path}; |
200
|
7
|
|
|
|
|
25
|
while (my ($key, $aref) = each %$href) { |
201
|
|
|
|
|
|
|
my ( |
202
|
11
|
|
|
|
|
91
|
$version, $val, $port, $path_spec, $secure, $expires, |
203
|
|
|
|
|
|
|
$discard, $rest |
204
|
|
|
|
|
|
|
) = @$aref; |
205
|
|
|
|
|
|
|
|
206
|
11
|
50
|
33
|
|
|
25
|
next if $discard and not $self->{ignore_discard}; |
207
|
11
|
50
|
33
|
|
|
109
|
if (defined $expires and time > $expires) { |
208
|
0
|
|
|
|
|
0
|
DEBUG and print " expired cookie: $key\n"; |
209
|
0
|
|
|
|
|
0
|
next; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
11
|
|
|
|
|
121
|
DEBUG and print " cookie: $key -> $val\n"; |
213
|
11
|
|
|
|
|
24
|
print $fh pack 'Cn', 0x3, |
214
|
|
|
|
|
|
|
17 + length($key) + length($val) + !! $secure; |
215
|
11
|
|
|
|
|
22
|
print $fh pack('Cn', 0x10, length($key)), $key; |
216
|
11
|
|
|
|
|
14
|
print $fh pack('Cn', 0x11, length($val)), $val; |
217
|
11
|
|
|
|
|
23
|
print $fh pack 'Cnx4N', 0x12, 8, $expires; |
218
|
11
|
100
|
|
|
|
60
|
print $fh pack 'C', 0x99 if $secure; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
5
|
|
|
|
|
12
|
print $fh pack 'C', 0x85; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
1
|
|
|
|
|
2
|
print $fh pack 'C', 0x84; |
226
|
1
|
|
|
|
|
78
|
close $fh; |
227
|
|
|
|
|
|
|
|
228
|
1
|
|
|
|
|
74
|
return 1; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
1; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
__END__ |