File Coverage

blib/lib/HTTP/Cookies/Chrome.pm
Criterion Covered Total %
statement 100 102 98.0
branch 7 12 58.3
condition 9 18 50.0
subroutine 28 28 100.0
pod 2 2 100.0
total 146 162 90.1


line stmt bran cond sub pod time code
1 4     4   116620 use 5.010;
  4         33  
2 4     4   2116 use utf8;
  4         48  
  4         21  
3              
4             package HTTP::Cookies::Chrome;
5 4     4   139 use strict;
  4         6  
  4         71  
6              
7 4     4   22 use warnings;
  4         6  
  4         78  
8 4     4   15 no warnings;
  4         5  
  4         118  
9              
10 4     4   1744 use POSIX;
  4         21856  
  4         17  
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             https://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 © 2009-2018, brian d foy . All rights reserved.
66              
67             This program is free software; you can redistribute it and/or modify
68             it under the terms of the Artistic License 2.0.
69              
70             =cut
71              
72              
73 4     4   9472 use base qw( HTTP::Cookies );
  4         8  
  4         2143  
74 4     4   40316 use vars qw( $VERSION );
  4         9  
  4         131  
75              
76 4     4   18 use constant TRUE => 1;
  4         6  
  4         178  
77 4     4   19 use constant FALSE => 0;
  4         7  
  4         171  
78              
79             $VERSION = '1.002';
80              
81 4     4   5453 use DBI;
  4         57929  
  4         2783  
82              
83 3     3   46 sub _dbh { $_[0]->{dbh} }
84              
85             sub _connect {
86 4     4   22 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         28394 $_[0]->{dbh} = $dbh;
92             }
93              
94             sub _get_rows {
95 3     3   300 my( $self, $file ) = @_;
96              
97 3         17 my $dbh = $self->_connect( $file );
98              
99 3         20 my $sth = $dbh->prepare( 'SELECT * FROM cookies' );
100              
101 3         1118 $sth->execute;
102              
103 42         99 my @rows = map { bless $_, 'HTTP::Cookies::Chrome::Record' }
104 3         12 @{ $sth->fetchall_arrayref };
  3         260  
105              
106 3         171 $dbh->disconnect;
107              
108 3         77 \ @rows;
109             }
110              
111             sub load {
112 3     3 1 1102 my( $self, $file ) = @_;
113              
114 3   100     35 $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         2 foreach my $row ( @{ $self->_get_rows( $file ) } ) {
  2         12  
120 28         678 $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         53 1;
136             }
137              
138             sub save {
139 1     1 1 703 my( $self, $new_file ) = @_;
140              
141 1   0     4 $new_file ||= $self->{'file'} || return;
      33        
142              
143 1         4 my $dbh = $self->_connect( $new_file );
144              
145 1         7 $self->_create_table;
146 1         18188 $self->_prepare_insert;
147 1         195 $self->_filter_cookies;
148 1         8731 $dbh->disconnect;
149              
150 1         18 1;
151             }
152              
153             sub _filter_cookies {
154 1     1   5 my( $self ) = @_;
155              
156             $self->scan(
157             sub {
158 14     14   116764 my( $version, $key, $val, $path, $domain, $port,
159             $path_spec, $secure, $expires, $discard, $rest ) = @_;
160              
161 14 50 33     55 return if $discard && not $self->{ignore_discard};
162              
163 14 50 33     90 return if defined $expires && time > $expires;
164              
165 14         23 $expires = do {
166 14 50       29 unless( $expires ) { 0 }
  0         0  
167             else {
168 14         36 $expires * 1_000_000
169             }
170             };
171              
172 14 50       25 $secure = $secure ? TRUE : FALSE;
173              
174 14 100       80 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         19 );
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         205 $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   8 my( $self ) = @_;
211              
212 1         9 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   36 my( $self,
231             $domain, $key, $value, $path, $expires, $secure, ) = @_;
232              
233 14         26 my $sth = $self->{insert_sth};
234              
235 14         69 my $creation = $self->_get_utc_microseconds( $creation_offset++ );
236              
237 14         5791 my $last_access = $self->_get_utc_microseconds;
238 14         3512 my $httponly = 0;
239              
240 14         183 $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   35 no warnings 'uninitialized';
  4         8  
  4         186  
257 4     4   2169 use bignum;
  4         22973  
  4         21  
258 28   100 28   1332 POSIX::strftime( '%s', gmtime() ) * 1_000_000 + ($_[1]//0);
259             }
260              
261 0         0 BEGIN {
262             package HTTP::Cookies::Chrome::Record;
263 4     4   268398 use vars qw($AUTOLOAD);
  4         10  
  4         636  
264              
265 4     4   17 my %columns = map { state $n = 0; $_, $n++ } qw(
  36         42  
  36         229  
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   246 my( $self ) = @_;
279 168         188 my $method = $AUTOLOAD;
280 168         408 $method =~ s/.*:://;
281              
282 168 50       309 die "" unless exists $columns{$method};
283              
284 168         652 $self->[ $columns{$method} ];
285             }
286              
287 42     42   4880 sub DESTROY { return 1 }
288             }
289              
290             1;