File Coverage

blib/lib/HTTP/Cookies/ChromeMacOS.pm
Criterion Covered Total %
statement 9 11 81.8
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 15 86.6


line stmt bran cond sub pod time code
1             package HTTP::Cookies::ChromeMacOS;
2              
3 1     1   50810 use 5.010;
  1         4  
4 1     1   6 use strict;
  1         1  
  1         18  
5 1     1   5 use warnings;
  1         11  
  1         24  
6              
7 1     1   535 use DBI;
  0            
  0            
8             use utf8;
9             use POSIX;
10             use PBKDF2::Tiny qw/derive/;
11             use Crypt::CBC;
12             use Data::Dumper;
13              
14             =head1 NAME
15              
16             HTTP::Cookies::ChromeMacOS - MacOS系统读取Chrome Cookies
17              
18             =head1 VERSION
19              
20             Version 0.01
21              
22             =cut
23              
24             our $VERSION = '0.01';
25              
26              
27             =head1 SYNOPSIS
28              
29             Quick summary of what the module does.
30              
31             Perhaps a little code snippet.
32              
33             use HTTP::Cookies::ChromeMacOS;
34              
35             my $cookie = HTTP::Cookies::ChromeMacOS->new();
36             $cookie->load( "/path/to/Cookies", 'Want to load domain' );
37              
38             # /path/to/Cookies Usually is: ~/Library/Application Support/Google/Chrome/Default/Cookies
39             # Want to load domain can be: google, yahoo, facebook etc or null will load all cookies
40              
41             my $ua = LWP::UserAgent->new(
42             cookie_jar => $cookie,
43             agent => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36',
44             );
45              
46             ...
47              
48             =head1 SUBROUTINES/METHODS
49              
50             =head2 The Chrome cookies table
51              
52             creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY
53             host_key TEXT NOT NULL
54             name TEXT NOT NULL
55             value TEXT NOT NULL
56             path TEXT NOT NULL
57             expires_utc INTEGER NOT NULL
58             secure INTEGER NOT NULL
59             httponly INTEGER NOT NULL
60             last_access_utc INTEGER NOT NULL
61              
62             =cut
63              
64              
65             use base qw( HTTP::Cookies );
66              
67             use constant TRUE => 1;
68             use constant FALSE => 0;
69              
70             my ( $dbh, $pass );
71              
72             sub _get_dbh {
73             my ( $self, $file ) = @_;
74             return $dbh if $dbh && $dbh->ping;
75             $dbh = DBI->connect( "dbi:SQLite:dbname=$file", '', '',
76             {
77             sqlite_see_if_its_a_number => 1,
78             }
79             );
80              
81             return $dbh;
82             }
83              
84             sub _get_rows {
85             my( $self, $file, $domain ) = @_;
86             $domain ||= '';
87              
88             my $dbh = $self->_get_dbh( $file );
89              
90             my @cols = qw/
91             creation_utc
92             host_key
93             name
94             value
95             encrypted_value
96             path
97             expires_utc
98             secure
99             httponly
100             last_access_utc
101             /;
102              
103             my $sql = 'SELECT ' . join( ', ', @cols ) . ' FROM cookies WHERE host_key like "%' . $domain . '%"';
104             my $sth = $dbh->prepare( $sql );
105             $sth->execute;
106              
107             my @rows = map { bless $_, 'HTTP::Cookies::Chrome::Record' } @{ $sth->fetchall_arrayref };
108             $dbh->disconnect;
109              
110             return \@rows;
111             }
112              
113             sub load {
114             my( $self, $file, $domain ) = @_;
115              
116             $file ||= $self->{'file'} || return;
117              
118              
119             my $salt = 'saltysalt';
120             my $iv = ' ' x 16;
121             my $salt_len = 16;
122             my $pass = _get_pass();
123             my $iterations = 1003;
124              
125             my $key = derive( 'SHA-1', $pass, $salt, $iterations, $salt_len );
126              
127              
128             my $cipher = Crypt::CBC->new(
129             -cipher => 'Crypt::OpenSSL::AES',
130             -key => $key,
131             -keysize => 16,
132             -iv => $iv,
133             -header => 'none',
134             -literal_key => 1,
135             );
136              
137              
138             foreach my $row ( @{ $self->_get_rows( $file, $domain ) } ) {
139             my $value = $row->value || $row->encrypted_value;
140             if ( $value =~ /^v10/ ) {
141             $value =~ s/^v10//;
142             $value = $cipher->decrypt( $value );
143             }
144              
145             $self->set_cookie(
146             undef,
147             $row->name,
148             $value,
149             $row->path,
150             $row->host_key,
151             undef,
152             undef,
153             $row->secure,
154             ($row->expires_utc / 1_000_000) - gmtime,
155             0,
156             {}
157             );
158             }
159              
160             return 1;
161             }
162              
163             sub _get_pass {
164             # On Mac, replace password from keychain
165             # On Linux, replace password with 'peanuts'
166             return $pass if $pass;
167             $pass = `security find-generic-password -w -s "Chrome Safe Storage"`;
168             chomp( $pass );
169             return $pass;
170             }
171              
172             sub save {
173             my( $self, $new_file ) = @_;
174              
175             # never save, This is a ReadOnly Version
176             return;
177             }
178              
179             sub _filter_cookies {
180             my( $self ) = @_;
181              
182             $self->scan(
183             sub {
184             my( $version, $key, $val, $path, $domain, $port,
185             $path_spec, $secure, $expires, $discard, $rest ) = @_;
186              
187             return if $discard && not $self->{ignore_discard};
188              
189             return if defined $expires && time > $expires;
190              
191             $expires = do {
192             unless( $expires ) { 0 }
193             else {
194             $expires * 1_000_000
195             }
196             };
197              
198             $secure = $secure ? TRUE : FALSE;
199              
200             my $bool = $domain =~ /^\./ ? TRUE : FALSE;
201              
202             $self->_insert(
203             $domain,
204             $key,
205             $val,
206             $path,
207             $expires,
208             $secure,
209             );
210             }
211             );
212              
213             }
214              
215              
216             sub _get_utc_microseconds {
217             no warnings 'uninitialized';
218             use bignum;
219             POSIX::strftime( '%s', gmtime() ) * 1_000_000 + ($_[1]//0);
220             }
221              
222             # This code from: https://github.com/briandfoy/HTTP-Cookies-Chrome
223             # I did small change
224             BEGIN {
225             package HTTP::Cookies::Chrome::Record;
226             use vars qw($AUTOLOAD);
227              
228             my %columns = map { state $n = 0; $_, $n++ } qw(
229             creation_utc
230             host_key
231             name
232             value
233             encrypted_value
234             path
235             expires_utc
236             secure
237             httponly
238             last_access_utc
239             );
240              
241              
242             sub AUTOLOAD {
243             my( $self ) = @_;
244             my $method = $AUTOLOAD;
245             $method =~ s/.*:://;
246              
247             die "" unless exists $columns{$method};
248              
249             $self->[ $columns{$method} ];
250             }
251              
252             sub DESTROY { return 1 }
253             }
254              
255              
256              
257             =head1 AUTHOR
258              
259             MC Cheung, C<< >>
260              
261             =head1 BUGS
262              
263             Please report any bugs or feature requests to C, or through
264             the web interface at L. I will be notified, and then you'll
265             automatically be notified of progress on your bug as I make changes.
266              
267              
268              
269              
270             =head1 SUPPORT
271              
272             You can find documentation for this module with the perldoc command.
273              
274             perldoc HTTP::Cookies::ChromeMacOS
275              
276              
277             You can also look for information at:
278              
279             =over 4
280              
281             =item * RT: CPAN's request tracker (report bugs here)
282              
283             L
284              
285             =item * AnnoCPAN: Annotated CPAN documentation
286              
287             L
288              
289             =item * CPAN Ratings
290              
291             L
292              
293             =item * Search CPAN
294              
295             L
296              
297             =back
298              
299              
300             =head1 ACKNOWLEDGEMENTS
301              
302              
303             =head1 LICENSE AND COPYRIGHT
304              
305             Copyright 2015 MC Cheung.
306              
307             This program is free software; you can redistribute it and/or modify it
308             under the terms of the the Artistic License (2.0). You may obtain a
309             copy of the full license at:
310              
311             L
312              
313             Any use, modification, and distribution of the Standard or Modified
314             Versions is governed by this Artistic License. By using, modifying or
315             distributing the Package, you accept this license. Do not use, modify,
316             or distribute the Package, if you do not accept this license.
317              
318             If your Modified Version has been derived from a Modified Version made
319             by someone other than you, you are nevertheless required to ensure that
320             your Modified Version complies with the requirements of this license.
321              
322             This license does not grant you the right to use any trademark, service
323             mark, tradename, or logo of the Copyright Holder.
324              
325             This license includes the non-exclusive, worldwide, free-of-charge
326             patent license to make, have made, use, offer to sell, sell, import and
327             otherwise transfer the Package with respect to any patent claims
328             licensable by the Copyright Holder that are necessarily infringed by the
329             Package. If you institute patent litigation (including a cross-claim or
330             counterclaim) against any party alleging that the Package constitutes
331             direct or contributory patent infringement, then this Artistic License
332             to you shall terminate on the date that such litigation is filed.
333              
334             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
335             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
336             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
337             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
338             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
339             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
340             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
341             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
342              
343              
344             =cut
345              
346             1; # End of HTTP::Cookies::ChromeMacOS