File Coverage

blib/lib/Mojo/UserAgent/CookieJar/ChromeMacOS.pm
Criterion Covered Total %
statement 29 83 34.9
branch 0 22 0.0
condition 0 14 0.0
subroutine 10 17 58.8
pod 4 4 100.0
total 43 140 30.7


line stmt bran cond sub pod time code
1             package Mojo::UserAgent::CookieJar::ChromeMacOS;
2              
3 1     1   58701 use strict;
  1         10  
  1         26  
4 1     1   5 use warnings;
  1         2  
  1         41  
5 1     1   24 use v5.10;
  1         3  
6             our $VERSION = '0.03';
7              
8 1     1   510 use Mojo::Base 'Mojo::UserAgent::CookieJar';
  1         199341  
  1         9  
9              
10 1     1   5091 use Mojo::Cookie::Request;
  1         2  
  1         5  
11 1     1   1300 use DBI;
  1         14913  
  1         70  
12 1     1   689 use File::Temp qw/tempfile/;
  1         9905  
  1         64  
13 1     1   431 use File::Copy ();
  1         2015  
  1         26  
14 1     1   438 use PBKDF2::Tiny qw/derive/;
  1         982  
  1         53  
15 1     1   550 use Crypt::CBC;
  1         6391  
  1         930  
16              
17             # default Chrome cookie file for MacOSx
18             has 'file' => sub {
19             if ($^O eq 'linux') {
20             return $ENV{HOME} . "/.config/google-chrome/Default/Cookies";
21             }
22             return $ENV{HOME} . "/Library/Application Support/Google/Chrome/Default/Cookies";
23             };
24             has 'pass'; # for Linux
25              
26             # readonly
27       0 1   sub add {}
28       0 1   sub collect {}
29              
30             sub find {
31 0     0 1   my ($self, $url) = @_;
32              
33 0 0         return [] unless my $domain = my $host = $url->ihost;
34              
35 0           my $salt = 'saltysalt';
36 0           my $iv = ' ' x 16;
37 0           my $salt_len = 16;
38 0           my $pass = $self->_get_pass();
39 0           my $iterations = 1003;
40 0 0         $iterations = 1 if $^O eq 'linux'; # Linux
41 0           my $key = derive( 'SHA-1', $pass, $salt, $iterations, $salt_len );
42 0           my $cipher = Crypt::CBC->new(
43             -cipher => 'Crypt::OpenSSL::AES',
44             -key => $key,
45             -keysize => 16,
46             -iv => $iv,
47             -header => 'none',
48             -literal_key => 1,
49             );
50              
51 0           my @found;
52 0           my $dbh = $self->__get_dbh;
53              
54 0           my $path = $url->path->to_abs_string;
55 0           while ($domain) {
56 0 0         next if $domain eq 'com'; # skip bad
57 0           my $new = $self->{jar}{$domain} = [];
58              
59 0           my $sth = $dbh->prepare('SELECT * FROM cookies WHERE host_key = ? OR host_key = ?');
60 0           $sth->execute($domain, '.' . $domain);
61 0           while (my $row = $sth->fetchrow_hashref) {
62 0   0       my $value = $row->{value} || $row->{encrypted_value} || '';
63 0 0 0       if ( $value =~ /^v10/ or $value =~ /^v11/ ) {
64 0           $value =~ s/^v10//;
65 0           $value =~ s/^v11//;
66 0           $value = $cipher->decrypt( $value );
67             }
68              
69 0           my $cookie = Mojo::Cookie::Request->new(name => $row->{name}, value => $value);
70 0           push @$new, $cookie;
71              
72             # Taste cookie (no care about expires since Chrome will handle it)
73 0 0 0       next if $row->{secure} && $url->protocol ne 'https';
74 0 0         next unless _path($row->{path}, $path);
75              
76 0           push @found, $cookie;
77             }
78             }
79             # Remove another part
80 0           continue { $domain =~ s/^[^.]*\.*// }
81              
82 0           return \@found;
83             }
84              
85             sub prepare {
86 0     0 1   my ($self, $tx) = @_;
87 0           my $req = $tx->req;
88 0           $req->cookies(@{$self->find($req->url)});
  0            
89             }
90              
91             sub __get_dbh {
92 0     0     my ($self) = @_;
93              
94 0           state $dbh;
95 0 0 0       return $dbh if $dbh && $dbh->ping;
96              
97             # copy to read
98 0           my ($fh, $filename) = tempfile();
99 0           File::Copy::copy($self->file, $filename);
100 0 0         my $sqlite_file = -e $filename ? $filename : $self->file; # make sure copy works
101              
102 0           $dbh = DBI->connect( "dbi:SQLite:dbname=" . $sqlite_file, '', '', {
103             sqlite_see_if_its_a_number => 1,
104             } );
105              
106 0           return $dbh;
107             }
108              
109             sub _get_pass {
110 0     0     my ($self) = @_;
111              
112 0 0         return $self->pass if $self->pass; # for Linux which passed in ->new
113              
114 0           my $pass;
115 0 0         if ($^O eq 'linux') {
116             # # secret-tool search application chrome
117             # [/org/freedesktop/secrets/collection/Default_5fkeyring/1]
118             # label = Chrome Safe Storage
119             # secret = 5B9eGeijTg1xQTh+K70Czg==
120             # created = 2022-02-18 02:23:25
121             # modified = 2022-02-18 02:23:25
122             # schema = chrome_libsecret_os_crypt_password_v2
123             # attribute.application = chrome
124 0           my $text = `secret-tool search application chrome`;
125 0           ($pass) = ($text =~ /secret\s*\=\s*(\S+)/m);
126             } else {
127 0           $pass = `security find-generic-password -w -s "Chrome Safe Storage"`;
128 0           chomp( $pass );
129             }
130              
131 0           $self->pass($pass);
132 0           return $pass;
133             }
134              
135             # copied from Mojo::UserAgent::CookieJar
136 0 0 0 0     sub _path { $_[0] eq '/' || $_[0] eq $_[1] || index($_[1], "$_[0]/") == 0 }
137              
138             1;
139             __END__