File Coverage

blib/lib/HTTP/Cookies/Chrome.pm
Criterion Covered Total %
statement 101 102 99.0
branch 7 12 58.3
condition 9 18 50.0
subroutine 28 28 100.0
pod 2 2 100.0
total 147 162 90.7


line stmt bran cond sub pod time code
1 4     4   42681 use 5.010;
  4         13  
  4         148  
2 4     4   2425 use utf8;
  4         38  
  4         17  
3              
4             package HTTP::Cookies::Chrome;
5 4     4   186 use strict;
  4         6  
  4         126  
6              
7 4     4   15 use warnings;
  4         5  
  4         135  
8 4     4   16 no warnings;
  4         5  
  4         117  
9              
10 4     4   2140 use POSIX;
  4         21055  
  4         23  
11              
12             =encoding utf8
13              
14             =head1 NAME
15              
16             HTTP::Cookies::Chrome - Cookie storage and management for Google Chrome
17              
18             =head1 SYNOPSIS
19              
20             use HTTP::Cookies::Chrome;
21              
22             my $cookie_jar = HTTP::Cookies::Chrome->new;
23             $cookie_jar->load( $path_to_cookies );
24              
25             # otherwise same as HTTP::Cookies
26              
27             =head1 DESCRIPTION
28              
29             This package overrides the C and C methods of
30             C so it can work with Google Chrome cookie files,
31             which are SQLite databases.
32              
33             NOTE: This does not handle encrypted cookies files yet (https://github.com/briandfoy/HTTP-Cookies-Chrome/issues/1).
34              
35             See L.
36              
37             =head2 The Chrome cookies table
38              
39             creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY
40             host_key TEXT NOT NULL
41             name TEXT NOT NULL
42             value TEXT NOT NULL
43             path TEXT NOT NULL
44             expires_utc INTEGER NOT NULL
45             secure INTEGER NOT NULL
46             httponly INTEGER NOT NULL
47             last_access_utc INTEGER NOT NULL
48              
49             =head1 SOURCE AVAILABILITY
50              
51             This module is in Github:
52              
53             http://github.com/briandfoy/HTTP-Cookies-Chrome
54              
55             =head1 AUTHOR
56              
57             brian d foy, C<< >>
58              
59             =head1 CREDITS
60              
61             Jon Orwant pointed out the problem with dates too far in the future
62              
63             =head1 COPYRIGHT AND LICENSE
64              
65             Copyright (c) 2009-2014 brian d foy. All rights reserved.
66              
67             This program is free software; you can redistribute it and/or modify
68             it under the same terms as Perl itself.
69              
70             =cut
71              
72              
73 4     4   8420 use base qw( HTTP::Cookies );
  4         9  
  4         2222  
74 4     4   36375 use vars qw( $VERSION );
  4         9  
  4         152  
75              
76 4     4   16 use constant TRUE => 1;
  4         5  
  4         198  
77 4     4   33 use constant FALSE => 0;
  4         4  
  4         904  
78              
79             $VERSION = '1.001';
80              
81 4     4   6515 use DBI;
  4         58611  
  4         2734  
82              
83 3     3   41 sub _dbh { $_[0]->{dbh} }
84              
85             sub _connect {
86 4     4   7 my( $self, $file ) = @_;
87 4         43 my $dbh = DBI->connect( "dbi:SQLite:dbname=$file", '', '',
88             {
89             sqlite_see_if_its_a_number => 1,
90             } );
91 4         26398 $_[0]->{dbh} = $dbh;
92             }
93            
94             sub _get_rows {
95 3     3   256 my( $self, $file ) = @_;
96            
97 3         13 my $dbh = $self->_connect( $file );
98            
99 3         51 my $sth = $dbh->prepare( 'SELECT * FROM cookies' );
100            
101 3         1018 $sth->execute;
102            
103 42         83 my @rows = map { bless $_, 'HTTP::Cookies::Chrome::Record' }
  3         265  
104 3         8 @{ $sth->fetchall_arrayref };
105            
106 3         135 $dbh->disconnect;
107            
108 3         72 \ @rows;
109             }
110            
111             sub load {
112 3     3 1 787 my( $self, $file ) = @_;
113              
114 3   100     31 $file ||= $self->{'file'} || return;
      66        
115              
116             # $cookie_jar->set_cookie( $version, $key, $val, $path,
117             # $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest )
118              
119 2         3 foreach my $row ( @{ $self->_get_rows( $file ) } ) {
  2         9  
120 28         692 $self->set_cookie(
121             undef,
122             $row->name,
123             $row->value,
124             $row->path,
125             $row->host_key,
126             undef,
127             undef,
128             $row->secure,
129             ($row->expires_utc / 1_000_000) - gmtime,
130             0,
131             {}
132             );
133             }
134              
135 2         52 1;
136             }
137              
138             sub save {
139 1     1 1 379 my( $self, $new_file ) = @_;
140              
141 1   0     4 $new_file ||= $self->{'file'} || return;
      33        
142            
143 1         5 my $dbh = $self->_connect( $new_file );
144            
145 1         53 $self->_create_table;
146 1         6509 $self->_prepare_insert;
147 1         115 $self->_filter_cookies;
148 1         5352 $dbh->disconnect;
149              
150 1         20 1;
151             }
152              
153             sub _filter_cookies {
154 1     1   2 my( $self ) = @_;
155              
156             $self->scan(
157             sub {
158 14     14   94506 my( $version, $key, $val, $path, $domain, $port,
159             $path_spec, $secure, $expires, $discard, $rest ) = @_;
160            
161 14 50 33     60 return if $discard && not $self->{ignore_discard};
162            
163 14 50 33     101 return if defined $expires && time > $expires;
164            
165 14         16 $expires = do {
166 14 50       35 unless( $expires ) { 0 }
  0         0  
167             else {
168 14         27 $expires * 1_000_000
169             }
170             };
171            
172 14 50       33 $secure = $secure ? TRUE : FALSE;
173            
174 14 100       164 my $bool = $domain =~ /^\./ ? TRUE : FALSE;
175            
176 14         51 $self->_insert(
177             $domain,
178             $key,
179             $val,
180             $path,
181             $expires,
182             $secure,
183             );
184             }
185 1         20 );
186              
187             }
188              
189             sub _create_table {
190 1     1   3 my( $self ) = @_;
191              
192 1         5 $self->_dbh->do( 'DROP TABLE IF EXISTS cookies' );
193              
194 1         128595 $self->_dbh->do( <<'SQL' );
195             CREATE TABLE cookies (
196             creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY,
197             host_key TEXT NOT NULL,
198             name TEXT NOT NULL,
199             value TEXT NOT NULL,
200             path TEXT NOT NULL,
201             expires_utc INTEGER NOT NULL,
202             secure INTEGER NOT NULL,
203             httponly INTEGER NOT NULL,
204             last_access_utc INTEGER NOT NULL
205             )
206             SQL
207             }
208            
209             sub _prepare_insert {
210 1     1   4 my( $self ) = @_;
211            
212 1         5 my $sth = $self->{insert_sth} = $self->_dbh->prepare_cached( <<'SQL' );
213             INSERT INTO cookies VALUES
214             (
215             ?,
216             ?, ?, ?, ?,
217             ?,
218             ?,
219             ?,
220             ?
221             )
222             SQL
223              
224             }
225              
226             {
227             my $creation_offset = 0;
228              
229             sub _insert {
230 14     14   26 my( $self,
231             $domain, $key, $value, $path, $expires, $secure, ) = @_;
232            
233 14         28 my $sth = $self->{insert_sth};
234            
235 14         40 my $creation = $self->_get_utc_microseconds( $creation_offset++ );
236              
237 14         5012 my $last_access = $self->_get_utc_microseconds;
238 14         3893 my $httponly = 0;
239              
240 14         237 $sth->execute(
241             $creation, # 1
242             $domain, # 2
243             $key, # 3
244             $value, # 4
245             $path, # 5
246             $expires, # 6
247             $secure, # 7
248             $httponly, # 8
249             $last_access, # 9
250             );
251              
252             }
253             }
254              
255             sub _get_utc_microseconds {
256 4     4   38 no warnings 'uninitialized';
  4         6  
  4         165  
257 4     4   9702 use bignum;
  4         21344  
  4         23  
258 28   100 28   1419 POSIX::strftime( '%s', gmtime() ) * 1_000_000 + ($_[1]//0);
259             }
260              
261             BEGIN {
262             package HTTP::Cookies::Chrome::Record;
263 4     4   199719 use vars qw($AUTOLOAD);
  4         9  
  4         673  
264              
265 4     4   10 my %columns = map { state $n = 0; $_, $n++ } qw(
  36         35  
  36         223  
266             creation_utc
267             host_key
268             name
269             value
270             path
271             expires_utc
272             secure
273             httponly
274             last_access_utc
275             );
276            
277             sub AUTOLOAD {
278 168     168   163 my( $self ) = @_;
279 168         153 my $method = $AUTOLOAD;
280 168         756 $method =~ s/.*:://;
281            
282 168 50       327 die "" unless exists $columns{$method};
283            
284 168         780 $self->[ $columns{$method} ];
285             }
286              
287 42     42   4192 sub DESTROY { return 1 }
288             }
289              
290             1;