| 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__ |