File Coverage

blib/lib/HTTP/Cookies/Mozilla.pm
Criterion Covered Total %
statement 99 130 76.1
branch 20 38 52.6
condition 7 24 29.1
subroutine 19 20 95.0
pod 2 2 100.0
total 147 214 68.6


line stmt bran cond sub pod time code
1             package HTTP::Cookies::Mozilla;
2 4     4   259724 use strict;
  4         43  
  4         132  
3              
4 4     4   25 use warnings;
  4         9  
  4         124  
5 4     4   23 no warnings;
  4         9  
  4         275  
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             HTTP::Cookies::Mozilla - Cookie storage and management for Mozilla
12              
13             =head1 SYNOPSIS
14              
15             use HTTP::Cookies::Mozilla;
16              
17             $cookie_jar = HTTP::Cookies::Mozilla->new;
18              
19             # otherwise same as HTTP::Cookies
20              
21             =head1 DESCRIPTION
22              
23             This package overrides the C and C methods of HTTP::Cookies
24             so it can work with Mozilla cookie files.
25              
26             This module should be able to work with all Mozilla derived browsers
27             (FireBird, Camino, et alia).
28              
29             Note that as of FireFox, version 3, the
30             cookie file format changed from plain text files to SQLite databases,
31             so you will need to have either L/L, or the
32             B executable somewhere in the path. Neither one has been
33             put as explicit dependency, anyway, so you'll get an exception if
34             you try to use this module with a new style file but without having
35             any of them:
36              
37             neither DBI nor pipe to sqlite3 worked (%s), install either one
38              
39             If your command-line B is not in the C<$ENV{PATH}>,
40             you can set C<$HTTP::Cookies::Mozilla::SQLITE> to point to the actual
41             program to be used, e.g.:
42              
43             use HTTP::Cookies::Mozilla;
44             $HTTP::Cookies::Mozilla::SQLITE = '/path/to/sqlite3';
45              
46             Usage of the external program is supported under perl 5.8 onwards only,
47             because previous perl versions do not support L with
48             more than three arguments, which are safer. If you are still sticking
49             to perl 5.6, you'll have to install L/L to make
50             FireFox 3 cookies work.
51              
52             See L.
53              
54             =head1 SOURCE AVAILABILITY
55              
56             The source is in GitHub:
57              
58             https://github.com/briandfoy/http-cookies-mozilla
59              
60             =head1 AUTHOR
61              
62             Derived from Gisle Aas's HTTP::Cookies::Netscape package with very
63             few material changes.
64              
65             Flavio Poletti added the SQLite support.
66              
67             Maintained by brian d foy, C<< >>
68              
69             =head1 COPYRIGHT AND LICENSE
70              
71             Parts Copyright 1997-1999 Gisle Aas.
72              
73             Other parts Copyright 2018 by brian d foy,
74              
75             This library is free software; you can redistribute it and/or
76             modify it under the terms of the Artistic License 2.0.
77              
78             =cut
79              
80 4     4   29 use base qw( HTTP::Cookies );
  4         20  
  4         2263  
81 4     4   51192 use vars qw( $VERSION $SQLITE );
  4         15  
  4         204  
82              
83 4     4   25 use Carp qw(carp);
  4         9  
  4         219  
84              
85 4     4   25 use constant TRUE => 'TRUE';
  4         7  
  4         237  
86 4     4   23 use constant FALSE => 'FALSE';
  4         8  
  4         2312  
87              
88             $VERSION = '2.034';
89             $SQLITE = 'sqlite3';
90              
91              
92             sub _load_ff3 {
93 3     3   10 my ($self, $file) = @_;
94 3         7 my $cookies;
95 3         10 my $query = 'SELECT host, path, name, value, isSecure, expiry '
96             . ' FROM moz_cookies';
97             eval {
98 3         26 require DBI;
99 3         41 my $dbh = DBI->connect('dbi:SQLite:dbname=' . $file, '', '',
100             {RaiseError => 1}
101             );
102 3         4296 $cookies = $dbh->selectall_arrayref($query);
103 3         1196 $dbh->disconnect();
104 3         113 1;
105             }
106             or eval {
107 0         0 require 5.008_000; # for >3 arguments open, which is safer
108 0 0       0 open my $fh, '-|', $SQLITE, $file, $query or die $!;
109 0         0 $cookies = [ map { [ split /\|/ ] } <$fh> ];
  0         0  
110 0         0 1;
111             }
112 3 50 33     7 or do {
113 0         0 carp "neither DBI nor pipe to sqlite3 worked ($@), install either one";
114 0         0 return;
115             };
116              
117 3         13 for my $cookie ( @$cookies )
118             {
119 15         359 my( $domain, $path, $key, $val, $secure, $expires ) = @$cookie;
120              
121 15         37 $self->set_cookie( undef, $key, $val, $path, $domain, undef,
122             0, $secure, $expires - _now(), 0 );
123             }
124              
125 3         84 return 1;
126             }
127              
128             sub load {
129 5     5 1 56224 my( $self, $file ) = @_;
130              
131 5   33     50 $file ||= $self->{'file'} || do {
      33        
132             carp "load() did not get a filename!";
133             return;
134             };
135              
136 5 100       39 return $self->_load_ff3($file) if $file =~ m{\.sqlite}i;
137              
138 2         4 local $_;
139 2         9 local $/ = "\n"; # make sure we got standard record separator
140              
141 2         4 my $fh;
142 2 50       86 unless( open $fh, '<:utf8', $file ) {
143 0         0 carp "Could not open file [$file]: $!";
144 0         0 return;
145             }
146              
147 2         42 my $magic = <$fh>;
148              
149 2 50       35 unless( $magic =~ /^\# HTTP Cookie File/ ) {
150 0         0 carp "$file does not look like a Mozilla cookies file";
151 0         0 close $fh;
152 0         0 return;
153             }
154              
155 2         23 while( <$fh> ) {
156 18 100       258 next if /^\s*\#/;
157 12 100       34 next if /^\s*$/;
158 10         29 tr/\n\r//d;
159              
160 10         70 my( $domain, $bool1, $path, $secure, $expires, $key, $val )
161             = split /\t/;
162              
163 10         24 $secure = ( $secure eq TRUE );
164              
165             # The cookie format is an absolute time in epoch seconds, so
166             # we subtract the current time (with appropriate offsets) to
167             # get the max_age for the second-to-last argument.
168 10         20 $self->set_cookie( undef, $key, $val, $path, $domain, undef,
169             0, $secure, $expires - _now(), 0 );
170             }
171              
172 2         70 close $fh;
173              
174 2         13 1;
175             }
176              
177 0         0 BEGIN {
178 4 50   4   3438 my $EPOCH_OFFSET = $^O eq "MacOS" ? 21600 : 0; # difference from Unix epoch
179 65     65   190 sub _epoch_offset { $EPOCH_OFFSET }
180             }
181              
182 45     45   82 sub _now { time() - _epoch_offset() };
183              
184             sub _scansub_maker { # Encapsulate checks logic during cookie scan
185 4     4   10 my ($self, $coresub) = @_;
186              
187             return sub {
188 20     20   298 my( $version, $key, $val, $path, $domain, $port,
189             $path_spec, $secure, $expires, $discard, $rest ) = @_;
190              
191 20 50 33     45 return if $discard && not $self->{ignore_discard};
192              
193 20 50       47 $expires = $expires ? $expires - _epoch_offset() : 0;
194 20 50 33     55 return if defined $expires && _now() > $expires;
195              
196 20         37 return $coresub->($domain, $path, $key, $val, $secure, $expires);
197 4         34 };
198             }
199              
200             sub _save_ff3 {
201 1     1   3 my ($self, $file) = @_;
202              
203 1         4 my @fnames = qw( host path name value isSecure expiry );
204 1         4 my $fnames = join ', ', @fnames;
205              
206             eval {
207 1         6 require DBI;
208 1         6 my $dbh = DBI->connect('dbi:SQLite:dbname=' . $file, '', '',
209             {RaiseError => 1, AutoCommit => 0});
210              
211 1         320 $dbh->do('DROP TABLE IF EXISTS moz_cookies;');
212              
213 1         508 $dbh->do('CREATE TABLE moz_cookies '
214             . ' (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT,'
215             . ' path TEXT,expiry INTEGER, lastAccessed INTEGER, '
216             . ' isSecure INTEGER, isHttpOnly INTEGER);');
217              
218             { # restrict scope for $sth
219 1         172 my $pholds = join ', ', ('?') x @fnames;
  1         5  
220 1         7 my $sth = $dbh->prepare(
221             "INSERT INTO moz_cookies($fnames) VALUES ($pholds)");
222             $self->scan($self->_scansub_maker(
223             sub {
224 5     5   10 my( $domain, $path, $key, $val, $secure, $expires ) = @_;
225 5 50       7 $secure = $secure ? 1 : 0;
226 5         52 $sth->execute($domain, $path, $key, $val, $secure, $expires);
227             }
228             )
229 1         53 );
230 1         18 $sth->finish();
231             }
232              
233 1         19477 $dbh->commit();
234 1         63 $dbh->disconnect();
235 1         45 1;
236             }
237             or eval {
238 0 0       0 open my $fh, '|-', $SQLITE, $file or die $!;
239 0         0 print {$fh} <<'INCIPIT';
  0         0  
240              
241             BEGIN TRANSACTION;
242              
243             DROP TABLE IF EXISTS moz_cookies;
244             CREATE TABLE moz_cookies
245             (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT,
246             path TEXT,expiry INTEGER, lastAccessed INTEGER,
247             isSecure INTEGER, isHttpOnly INTEGER);
248              
249             INCIPIT
250              
251             $self->scan( $self->_scansub_maker(
252             sub {
253 0     0   0 my( $domain, $path, $key, $val, $secure, $expires ) = @_;
254 0 0       0 $secure = $secure ? 1 : 0;
255             my $values = join ', ',
256             map { # Encode all params as hex, a bit overkill
257 0         0 my $hex = unpack 'H*', $_;
  0         0  
258 0         0 "X'$hex'";
259             } ( $domain, $path, $key, $val, $secure, $expires );
260 0         0 print {$fh}
  0         0  
261             "INSERT INTO moz_cookies( $fnames ) VALUES ( $values );\n";
262             }
263             )
264 0         0 );
265              
266 0         0 print {$fh} <<'EPILOGUE';
  0         0  
267              
268             UPDATE moz_cookies SET lastAccessed = id;
269             END TRANSACTION;
270              
271             EPILOGUE
272 0         0 1;
273             }
274 1 50 33     2 or do {
275 0         0 carp "neither DBI nor pipe to sqlite3 worked ($@), install either one";
276 0         0 return;
277             };
278              
279 1         9 return 1;
280             }
281              
282             sub save {
283 4     4 1 1655 my( $self, $file ) = @_;
284              
285 4   0     14 $file ||= $self->{'file'} || do {
      33        
286             carp "save() did not get a filename!";
287             return;
288             };
289              
290 4 100       22 return $self->_save_ff3($file) if $file =~ m{\. sqlite}imsx;
291              
292 3         6 local $_;
293              
294 3         5 my $fh;
295 3 50       298 unless( open $fh, '>:utf8', $file ) {
296 0         0 carp "Could not open file [$file]: $!";
297 0         0 return;
298             }
299              
300 3         32 print $fh <<'EOT';
301             # HTTP Cookie File
302             # http://www.netscape.com/newsref/std/cookie_spec.html
303             # This is a generated file! Do not edit.
304             # To delete cookies, use the Cookie Manager.
305              
306             EOT
307              
308             $self->scan($self->_scansub_maker(
309             sub {
310 15     15   35 my( $domain, $path, $key, $val, $secure, $expires ) = @_;
311 15 50       31 $secure = $secure ? TRUE : FALSE;
312 15 50       51 my $bool = $domain =~ /^\./ ? TRUE : FALSE;
313 15         81 print $fh join( "\t", $domain, $bool, $path, $secure,
314             $expires, $key, $val ), "\n";
315             }
316             )
317 3         26 );
318              
319 3         117 close $fh;
320              
321 3         19 1;
322             }
323              
324             1;