File Coverage

blib/lib/Cookie/Jar.pm
Criterion Covered Total %
statement 637 2142 29.7
branch 223 1948 11.4
condition 151 1049 14.3
subroutine 64 103 62.1
pod 40 41 97.5
total 1115 5283 21.1


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Cookies API for Server & Client - ~/lib/Cookie/Jar.pm
3             ## Version v0.3.0
4             ## Copyright(c) 2023 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2019/10/08
7             ## Modified 2023/05/30
8             ## You can use, copy, modify and redistribute this package and associated
9             ## files under the same terms as Perl itself.
10             ##----------------------------------------------------------------------------
11             package Cookie::Jar;
12             BEGIN
13 0         0 {
14 2     2   3547 use strict;
  2         5  
  2         66  
15 2     2   10 use warnings;
  2         6  
  2         60  
16 2     2   10 use warnings::register;
  2         4  
  2         242  
17 2     2   12 use parent qw( Module::Generic );
  2         8  
  2         15  
18 2     2   146 use vars qw( $VERSION $COOKIES_DEBUG $MOD_PERL $MOD_PERL_VERSION );
  2         9  
  2         440  
19 2     2   8 our( $MOD_PERL, $MOD_PERL_VERSION );
20 2 50 33     18 if( exists( $ENV{MOD_PERL} )
21             &&
22             ( $MOD_PERL = $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ ) )
23             {
24 0         0 $MOD_PERL_VERSION = $1;
25 0         0 select( ( select( STDOUT ), $| = 1 )[0] );
26 0         0 require Apache2::Const;
27 0         0 Apache2::Const->import( compile => qw( :common :http OK DECLINED ) );
28 0         0 require APR::Pool;
29 0         0 require APR::Table;
30 0         0 require Apache2::RequestUtil;
31 0         0 require APR::Request::Apache2;
32 0         0 require APR::Request::Cookie;
33             }
34 2     2   18 use Cookie;
  2         4  
  2         11  
35 2     2   1729 use Cookie::Domain;
  2         8  
  2         22  
36 2     2   653 use DateTime;
  2         4  
  2         52  
37 2     2   9 use JSON;
  2         8  
  2         26  
38 2     2   1346 use Module::Generic::HeaderValue;
  2         6946  
  2         21  
39 2     2   509 use Nice::Try;
  2         27  
  2         20  
40 2     2   10015163 use Scalar::Util;
  2         6  
  2         134  
41 2     2   11 use URI::Escape ();
  2         5  
  2         116  
42 2         6 our $VERSION = 'v0.3.0';
43             # This flag to allow extensive debug message to be enabled
44 2         47 our $COOKIES_DEBUG = 0;
45 2     2   13 use constant CRYPTX_VERSION => '0.074';
  2         4  
  2         227  
46             };
47              
48 2     2   12 use strict;
  2         34  
  2         58  
49 2     2   14 use warnings;
  2         4  
  2         3243  
50              
51             sub init
52             {
53 8     8 1 79287 my $self = shift( @_ );
54             # Apache2::RequestRec object
55 8         19 my $req;
56 8 50 66     145 $req = shift( @_ ) if( @_ && ( @_ % 2 ) );
57             # For decryption and encryption
58 8         105 $self->{algo} = undef;
59             # If a cookie file is provided, yes, we'll automatically load and save from and to it.
60 8         31 $self->{autosave} = 1;
61             # For decryption and encryption
62 8         41 $self->{encrypt} = 0;
63 8         37 $self->{file} = '';
64 8         39 $self->{host} = '';
65             # For decryption and encryption
66 8         21 $self->{iv} = undef;
67             # For decryption and encryption
68 8         19 $self->{secret} = undef;
69             # Cookie file type; can also be 'lwp' or 'netscape'
70 8         36 $self->{type} = 'json';
71 8         32 $self->{_init_strict_use_sub} = 1;
72 8         53 $self->SUPER::init( @_ );
73 8 50       1071 $self->{request} = $req if( $req );
74             # Repository of all objects
75 8         31 $self->{_cookies} = [];
76             # Index by host, path, name
77 8         32 $self->{_index} = {};
78 8         40 my $file = $self->file;
79 8 0 33     7225 if( $file && $file->exists && !$file->is_empty )
      33        
80             {
81 0         0 my $encrypt = $self->encrypt;
82 0         0 my $type = $self->type;
83 0         0 my $type2sub =
84             {
85             json => \&load,
86             lwp => \&load_as_lwp,
87             netscape => \&load_as_netscape,
88             };
89 0 0       0 return( $self->error( "Unknown cookie jar type '$type'. This can be either json, lwp or netscape" ) ) if( !CORE::exists( $type2sub->{ $type } ) );
90 0         0 my $loader = $type2sub->{ $type };
91            
92 0 0       0 if( $encrypt )
93             {
94 0 0       0 $loader->( $self, $file,
95             algo => $self->algo,
96             key => $self->secret,
97             ) || return( $self->pass_error );
98             }
99             else
100             {
101 0 0       0 $loader->( $self, $file ) || return( $self->pass_error );
102             }
103             }
104 8         53 return( $self );
105             }
106              
107             sub add
108             {
109 16     16 1 128 my $self = shift( @_ );
110 16         42 my $this;
111 16 100       85 if( scalar( @_ ) == 1 )
    50          
112             {
113 13         45 $this = shift( @_ );
114             }
115             elsif( scalar( @_ ) )
116             {
117 3         24 $this = $self->_get_args_as_hash( @_ );
118             }
119             else
120             {
121 0         0 return( $self->error( "No data was provided to add a cookie in the repository." ) );
122             }
123 16 100 33     620 if( ref( $this ) eq 'HASH' )
    100 33        
    50          
124             {
125 3         24 $this = $self->make( $this );
126 3 50       21 return( $self->pass_error ) if( !defined( $this ) );
127             }
128             # A string ?
129             elsif( !ref( $this ) )
130             {
131 3   50     27 my $hv = Module::Generic::HeaderValue->new_from_header( $this, decode => 1, debug => $self->debug ) ||
132             return( $self->error( Module::Generic::HeaderValue->error ) );
133 3         30928 my $ref = {};
134 3         27 $ref->{name} = $hv->value->first;
135 3         2347 $ref->{value} = $hv->value->second;
136             $hv->params->foreach(sub
137             {
138 15     15   2468 my( $n, $v ) = @_;
139 15         246 $ref->{ $n } = $v;
140 15         28 return(1);
141 3         2018 });
142 3 50       66 $ref->{secure} = 1 if( CORE::exists( $ref->{secure} ) );
143             # In case those were provided too in the cookie line
144 3 50       23 $ref->{samesite} = 1 if( CORE::exists( $ref->{samesite} ) );
145 3 50       15 $ref->{httponly} = 1 if( CORE::exists( $ref->{httponly} ) );
146 3         58 $this = $self->make( %$ref );
147 3 50       58 return( $self->pass_error ) if( !defined( $this ) );
148             }
149             elsif( !$self->_is_object( $this ) ||
150             ( $self->_is_object( $this ) && !$this->isa( 'Cookie' ) ) )
151             {
152 0         0 return( $self->error( "I was expecting an hash reference or a Cookie object, but instead I got '$this'." ) );
153             }
154 16         469 my $ref = $self->_cookies;
155 16         14232 my $idx = $self->_index;
156 16 50       16230 $this->name or return( $self->error( "No cookie name was set in this cookie." ) );
157 16   50     13351 my $key = $self->key( $this ) || return( $self->pass_error );
158 16         489 $ref->push( $this );
159 16 50       220 $idx->{ $key } = [] if( !CORE::exists( $idx->{ $key } ) );
160 16         857 push( @{$idx->{ $key }}, $this );
  16         74  
161 16         367 return( $this );
162             }
163              
164 0     0 1 0 sub add_cookie_header { return( shift->add_request_header( @_ ) ); }
165              
166             sub add_request_header
167             {
168 5     5 1 6097 my $self = shift( @_ );
169 5   50     40 my $req = shift( @_ ) || return( $self->error( "No request object was provided." ) );
170 5 50       62 return( $self->error( "Request object provided is not an object." ) ) if( !Scalar::Util::blessed( $req ) );
171 5 50 33     81 return( $self->error( "Request object provided does not support the uri or header methods." ) ) if( !$req->can( 'uri' ) || !$req->can( 'header' ) );
172 5   50     278 my $uri = $req->uri || return( $self->error( "No uri set in the request object." ) );
173 5         216 my $scheme = $uri->scheme;
174 5 50       294 unless( $scheme =~ /^https?\z/ )
175             {
176 0         0 return( '' );
177             }
178 5         42 my( $host, $port, $path );
179 5 50       46 if( $host = $req->header( 'Host' ) )
180             {
181 5         544 $host =~ s/:(\d+)$//;
182 5         22 $host = lc( $host );
183 5         23 $port = $1;
184             }
185             else
186             {
187 0         0 $host = lc( $uri->host );
188             }
189 5 50       56 my $is_secure = ( $scheme eq 'https' ? 1 : 0 );
190             # URI::URL method
191 5 50       39 if( $uri->can( 'epath' ) )
192             {
193 0         0 $path = $uri->epath;
194             }
195             else
196             {
197             # URI::_generic method
198 5         44 $path = $uri->path;
199             }
200 5 50       126 $path = '/' unless( CORE::length( $path ) );
201 5 50 33     54 $port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
202             # my $now = time();
203 5         345 my $now = DateTime->now;
204 5 50       2873 $path = $self->_normalize_path( $path ) if( CORE::index( $path, '%' ) != -1 );
205 5         15 my $root;
206 5 50       58 if( $self->_is_ip( $host ) )
207             {
208 0         0 $root = $host;
209             }
210             else
211             {
212 5   50     2900 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
213 5         20691 my $res = $dom->stat( $host );
214 5 50       3123 return( $self->pass_error( $dom->error ) ) if( !defined( $res ) );
215 5 50 33     198 if( !CORE::length( $res ) || ( $res && !$res->domain->length ) )
      33        
216             {
217 0         0 return( $self->error( "No root domain found for host \"$host\"." ) );
218             }
219 5         204952 $root = $res->domain;
220             }
221             # rfc6265, section 5.4
222             # "Either:
223             # The cookie's host-only-flag is true and the canonicalized request-host is identical to the cookie's domain.
224             # Or:
225             # The cookie's host-only-flag is false and the canonicalized request-host domain-matches the cookie's domain."
226             # Meaning, $host is, for example, www.example.or.jp and cookie domain was not set and defaulted to example.or.jp, then it matches; or
227             # cookie domain was explicitly set to www.example.or.jp and matches www.example.or.jp
228             # <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
229             # cookie values for the "Cookie" header
230 5         27924 my @values = ();
231 5         193 my @ok_cookies = ();
232             # Get all cookies for the canonicalised request-host and its sub domains, then we check each one found according to rfc6265 algorithm as stated above
233 5         147 my $cookies = $self->get_by_domain( $root, with_subdomain => 1 );
234             # Ref: rfc6265, section 5.4
235             # <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
236 5         274 foreach my $c ( @$cookies )
237             {
238 12 0 33     95 unless( $c->host_only && $root eq $c->domain ||
      0        
      33        
239             !$c->host_only && $host eq $c->domain )
240             {
241 0         0 next;
242             }
243 12 100 33     11074 if( index( $path, $c->path ) != 0 )
    50 100        
    100 33        
    50          
244             {
245 1         915 next;
246             }
247             elsif( !$is_secure && $c->secure )
248             {
249 0         0 next;
250             }
251             # elsif( $c->expires && $c->expires->epoch < $now )
252             elsif( $c->expires && $c->expires < $now )
253             {
254 1         1212 next;
255             }
256             elsif( $c->port && $c->port != $port )
257             {
258 0         0 next;
259             }
260 10         10379 push( @ok_cookies, $c );
261             }
262            
263             # sort cookies by path and by creation date.
264             # Ref: rfc6265, section 5.4.2:
265             # "Cookies with longer paths are listed before cookies with shorter paths."
266             # "Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times."
267             # <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
268             # The OR here actually means AND, since the <=> comparison returns false when 2 elements are equal
269             # So when 2 path are the same, we differentiate them by their creation date
270 5 50       39 foreach my $c ( sort{ $b->path->length <=> $a->path->length || $a->created_on <=> $b->created_on } @ok_cookies )
  5         1160  
271             {
272 10         137942 push( @values, $c->as_string({ is_request => 1 }) );
273             # rfc6265, section 5.4.3
274             # <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
275             # "Update the last-access-time of each cookie in the cookie-list to the current date and time."
276 10         121 $c->accessed_on( time() );
277             }
278              
279 5 50       41164 if( @values )
280             {
281 5 50       1440 if( my $old = $req->header( 'Cookie' ) )
282             {
283 0         0 unshift( @values, $old );
284             }
285 5         1038 $req->header( Cookie => join( '; ', @values ) );
286             }
287 5         401 return( $req );
288             }
289              
290             sub add_response_header
291             {
292 0     0 1 0 my $self = shift( @_ );
293 0         0 my $resp = shift( @_ );
294 0         0 my $r = $self->request;
295 0 0       0 if( $resp )
296             {
297 0 0       0 return( $self->error( "Request object provided is not an object." ) ) if( !$self->_is_object( $resp ) );
298 0 0       0 return( $self->error( "Request object provided does not support the header methods." ) ) if( !$resp->can( 'header' ) );
299             }
300 0         0 my @values = ();
301 0         0 my $ref = $self->_cookies;
302 0         0 foreach my $c ( sort{ $a->path->length <=> $b->path->length } @$ref )
  0         0  
303             {
304            
305 0         0 $c->debug( $self->debug );
306 0 0       0 if( $c->discard )
307             {
308 0         0 next;
309             }
310            
311 0 0       0 if( $resp )
    0          
312             {
313 0         0 $resp->headers->push_header( 'Set-Cookie' => "$c" );
314             }
315             elsif( $r )
316             {
317             # APR::Table
318             # We use 'add' and not 'set'
319 0         0 $r->err_headers_out->add( 'Set-Cookie' => "$c" );
320             }
321             else
322             {
323 0         0 push( @values, "Set-Cookie: $c" );
324             }
325             }
326 0 0       0 if( @values )
327             {
328 0 0       0 return( wantarray() ? @values : join( "\015\012", @values ) );
329             }
330             # We return our object only if a response object or an Apache2::RequestRec was set
331             # because otherwise if the user is expecting the cookie as a returned string,
332             # we do not want to return our object instead when there is no cookie to return.
333 0 0 0     0 return( $self ) if( $r || $resp );
334 0         0 return( '' );
335             }
336              
337             # NOTE: the algorithm used, if any, to decrypt or encrypt the cookie jar file
338 0     0 1 0 sub algo { return( shift->_set_get_scalar( 'algo', @_ ) ); }
339              
340 8     8 1 51 sub autosave { return( shift->_set_get_boolean( 'autosave', @_ ) ); }
341              
342             sub delete
343             {
344 1     1 1 7 my $self = shift( @_ );
345 2     2   18 no overloading;
  2         5  
  2         3280  
346 1         8 my $ref = $self->_cookies;
347 1         887 my $idx = $self->_index;
348 1 50 33     886 if( scalar( @_ ) == 1 && $self->_is_a( $_[0], 'Cookie' ) )
349             {
350 1         58 my $c = shift( @_ );
351 1         19 my $addr = Scalar::Util::refaddr( $c );
352 1         10 my $removed = $self->new_array;
353 1         47 for( my $i = 0; $i < scalar( @$ref ); $i++ )
354             {
355 3         24 my $this = $ref->[$i];
356 3 100       28 if( Scalar::Util::refaddr( $this ) eq $addr )
357             {
358 1         14 my $key = $self->key( $this );
359 1 50       32 if( CORE::exists( $idx->{ $key } ) )
360             {
361             # if( !$self->_is_array( $idx->{ $key } ) )
362 1 50       44 if( !Scalar::Util::reftype( $idx->{ $key } ) eq 'ARRAY' )
363             {
364 0         0 return( $self->error( "I was expecting an array for key '$key', but got '", overload::StrVal( $idx->{ $key } ), "' (", ref( $idx->{ $key } ), ")" ) );
365             }
366 1         36 for( my $j = 0; $j < scalar( @{$idx->{ $key }} ); $j++ )
  2         15  
367             {
368 1 50       33 if( Scalar::Util::refaddr( $idx->{ $key }->[$j] ) eq $addr )
369             {
370 1         33 CORE::splice( @{$idx->{ $key }}, $j, 1 );
  1         9  
371 1         31 $j--;
372             }
373             }
374             # Cleanup
375 1 50       19 CORE::delete( $idx->{ $key } ) if( scalar( @{$idx->{ $key }} ) == 0 );
  1         7  
376             }
377 1         85 CORE::splice( @$ref, $i, 1 );
378 1         8 $i--;
379 1         21 $removed->push( $c );
380             }
381             }
382 1         93 return( $removed );
383             }
384             else
385             {
386 0         0 my( $name, $host, $path ) = @_;
387 0   0     0 $host ||= $self->host || '';
      0        
388 0   0     0 $path //= '';
389 0 0 0     0 return( $self->error( "No cookie object provided nor any cookie name either." ) ) if( !defined( $name ) || !CORE::length( "$name" ) );
390 0         0 my $key = $self->key( $name => $host, $path );
391 0         0 my $removed = $self->new_array;
392 0 0       0 return( $removed ) if( !CORE::exists( $idx->{ $key } ) );
393 0 0       0 return( $self->error( "I was expecting an array for key '$key', but got '", overload::StrVal( $idx->{ $key } ), "'" ) ) if( !$self->_is_array( $idx->{ $key } ) );
394 0         0 $removed->push( @{$idx->{ $key }} );
  0         0  
395 0         0 foreach my $c ( @$removed )
396             {
397 0 0 0     0 next if( !ref( $c ) || !$self->_is_a( $c, 'Cookie' ) );
398 0         0 my $addr = Scalar::Util::refaddr( $c );
399 0         0 for( my $i = 0; $i < scalar( @$ref ); $i++ )
400             {
401 0 0       0 if( Scalar::Util::refaddr( $ref->[$i] ) eq $addr )
402             {
403 0         0 CORE::splice( @$ref, $i, 1 );
404 0         0 last;
405             }
406             }
407             }
408             # Remove cookie and return the previous entry
409 0         0 CORE::delete( $idx->{ $key } );
410 0         0 return( $removed );
411             }
412             }
413              
414             sub do
415             {
416 2     2 1 1379 my $self = shift( @_ );
417 2   50     35 my $code = shift( @_ ) || return( $self->error( "No callback code was provided." ) );
418 2 50       33 return( $self->error( "Callback code provided is not a code." ) ) if( ref( $code ) ne 'CODE' );
419 2         19 my $ref = $self->_cookies->clone;
420 2         1468 my $all = $self->new_array;
421 2         79 foreach my $c ( @$ref )
422             {
423 6 50 33     126 next if( !ref( $c ) || !$self->_is_a( $c, 'Cookie' ) );
424 6 50 33     319 try
  6         19  
  6         26  
  6         55  
  0         0  
  6         15  
  6         24  
  6         18  
425 6     6   15 {
426 6         16 local $_ = $c;
427 6         38 my $rv = $code->( $c );
428 6 50       1393629 if( !defined( $rv ) )
    50          
429             {
430 0         0 last;
431             }
432             elsif( $rv )
433             {
434 6         43 $all->push( $c );
435             }
436             }
437 6 50 50     71 catch( $e )
  6 0 33     155  
  0 0       0  
  6 0       25  
  6 0       20  
  6 0       19  
  6 0       27  
  6 0       48  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  6         85  
  0         0  
  0         0  
  6         33  
  6         34  
  6         35  
  6         44  
  6         19  
  6         41  
  0         0  
  0         0  
  0         0  
  0         0  
438 0     0   0 {
439 0         0 return( $self->error( "An unexpected error occurred while calling code reference on cookie named \"", $ref->{ $c }->name, "\": $e" ) );
440 2 0 0 2   17 }
  2 0 0     4  
  2 0 33     5436  
  0 0 0     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  6 0       33  
  0 0       0  
  6 0       162  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  6         54  
  0         0  
  0         0  
  0         0  
  0         0  
  6         44  
441             }
442 2         19 return( $all );
443             }
444              
445             # NOTE: Should we decrypt or encrypt the cookie jar file?
446 0     0 1 0 sub encrypt { return( shift->_set_get_boolean( 'encrypt', @_ ) ); }
447              
448             sub exists
449             {
450 0     0 1 0 my $self = shift( @_ );
451 0         0 my( $name, $host, $path ) = @_;
452 0   0     0 $host ||= $self->host || '';
      0        
453 0   0     0 $path //= '';
454 0 0 0     0 return( $self->error( "No cookie name was provided to check if it exists." ) ) if( !defined( $name ) || !CORE::length( $name ) );
455 0         0 my $c = $self->get( $name => $host, $path );
456 0 0       0 return( defined( $c ) ? 1 : 0 );
457             }
458              
459             # From http client point of view
460             sub extract
461             {
462 4     4 1 538 my $self = shift( @_ );
463 4   50     51 my $resp = shift( @_ ) || return( $self->error( "No response object was provided." ) );
464 4 50       28 return( $self->error( "Response object provided is not an object." ) ) if( !Scalar::Util::blessed( $resp ) );
465 4         12 my $uri;
466 4 50 0     35 if( $self->_is_a( $resp, 'HTTP::Response' ) )
    0          
467             {
468 4         205 my $req = $resp->request;
469 4 50       63 return( $self->error( "No HTTP::Request object is set in this HTTP::Response." ) ) if( !$resp->request );
470 4         50 $uri = $resp->request->uri;
471             }
472             elsif( $resp->can( 'uri' ) && $resp->can( 'header' ) )
473             {
474 0         0 $uri = $resp->uri;
475             }
476             else
477             {
478 0         0 return( $self->error( "Response object provided does not support the uri or scheme methods and is not a class or subclass of HTTP::Response either." ) );
479             }
480 4   50     109 my $all = Module::Generic::HeaderValue->new_from_multi( [$resp->header( 'Set-Cookie' )], debug => $self->debug, decode => 1 ) ||
481             return( $self->pass_error( Module::Generic::HeaderValue->error ) );
482 4 50       33788 return( $resp ) unless( $all->length );
483 4 50       160127 $uri || return( $self->error( "No uri set in the response object." ) );
484 4         714 my( $host, $port, $path );
485 4 50 33     28 if( $host = $resp->header( 'Host' ) ||
486             ( $resp->request && ( $host = $resp->request->header( 'Host' ) ) ) )
487             {
488 4         638 $host =~ s/:(\d+)$//;
489 4         16 $host = lc( $host );
490 4         21 $port = $1;
491             }
492             else
493             {
494 0         0 $host = lc( $uri->host );
495             }
496            
497             # URI::URL method
498 4 50       54 if( $uri->can( 'epath' ) )
499             {
500 0         0 $path = $uri->epath;
501             }
502             else
503             {
504             # URI::_generic method
505 4         32 $path = $uri->path;
506             }
507 4 50       96 $path = '/' unless( CORE::length( $path ) );
508 4 50 33     39 $port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
509 4         272 my $root;
510 4 50       42 if( $self->_is_ip( $host ) )
511             {
512 0         0 $root = $host;
513             }
514             else
515             {
516 4   50     3347 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
517 4         16431 my $res = $dom->stat( $host );
518 4 50       2389 if( !defined( $res ) )
519             {
520 0         0 return( $self->pass_error( $dom->error ) );
521             }
522             # Possibly empty
523 4 50       188 $root = $res ? $res->domain : '';
524             }
525            
526 4         21651 foreach my $o ( @$all )
527             {
528 4         331 my( $name, $value ) = $o->value->list;
529 4   50     2993 my $c = Cookie->new( name => $name, value => $value ) ||
530             return( $self->pass_error( Cookie->error ) );
531 4 100       30 if( CORE::length( $o->param( 'expires' ) ) )
    50          
532             {
533 2         1464 my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
534 2 50       1406 if( $dt )
535             {
536 0         0 $c->expires( $dt );
537             }
538             else
539             {
540 2         13 $c->expires( $o->param( 'expires' ) );
541             }
542             }
543             elsif( CORE::length( $o->param( 'max-age' ) ) )
544             {
545 0         0 $c->max_age( $o->param( 'max-age' ) );
546             }
547            
548 4 50       5197 if( $o->param( 'domain' ) )
549             {
550             # rfc6265, section 5.2.3:
551             # "If the first character of the attribute-value string is %x2E ("."): Let cookie-domain be the attribute-value without the leading %x2E (".") character."
552             # Ref: <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3>
553 0         0 my $c_dom = $o->param( 'domain' );
554             # Remove leading dot as per rfc specifications
555 0         0 $c_dom =~ s/^\.//g;
556             # "Convert the cookie-domain to lower case."
557 0         0 $c_dom = lc( $c_dom );
558             # Check the domain name is legitimate, i.e. sent from a host that has authority
559             # "The user agent will reject cookies unless the Domain attribute specifies a scope for the cookie that would include the origin server. For example, the user agent will accept a cookie with a Domain attribute of "example.com" or of "foo.example.com" from foo.example.com, but the user agent will not accept a cookie with a Domain attribute of "bar.example.com" or of "baz.foo.example.com"."
560             # <https://tools.ietf.org/html/rfc6265#section-4.1.2.3>
561 0 0 0     0 if( CORE::length( $c_dom ) >= CORE::length( $root ) &&
      0        
562             ( $c_dom eq $host || $host =~ /\.$c_dom$/ ) )
563             {
564 0         0 $c->domain( $c_dom );
565             }
566             else
567             {
568 0         0 next;
569             }
570             }
571             # "If omitted, defaults to the host of the current document URL, not including subdomains."
572             # <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
573             else
574             {
575 4 50       2629 if( $root )
576             {
577 4         134 $c->domain( $root );
578 4         4379 $c->implicit(1);
579             }
580             else
581             {
582             }
583             }
584            
585             # rfc6265: "If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value."
586 4 100 66     4021 if( defined( $o->param( 'path' ) ) && CORE::length( $o->param( 'path' ) ) )
587             {
588 3         3966 $c->path( $o->param( 'path' ) );
589             }
590             else
591             {
592 1         666 my $frag = $self->new_array( [split( /\//, $path )] );
593             # Not perfect
594 1 50 33     69 if( $path eq '/' || substr( $path, -1, 1 ) eq '/' )
595             {
596 1         17 $c->path( $path );
597             }
598             else
599             {
600 0         0 $frag->pop;
601 0         0 $c->path( $frag->join( '/' )->scalar );
602             }
603             }
604 4 50       4241 $c->port( $port ) if( defined( $port ) );
605 4 50       163849 $c->http_only(1) if( $o->param( 'httponly' ) );
606 4 50       2611 $c->secure(1) if( $o->param( 'secure' ) );
607 4 50       2573 $c->same_site(1) if( $o->param( 'samesite' ) );
608            
609 4         2521 my @old = $self->get({ name => $c->name, host => $c->domain, path => $c->path });
610 4 100       36 if( scalar( @old ) )
611             {
612 1 50       16 $c->created_on( $old[0]->created_on ) if( $old[0]->created_on );
613             # $self->replace( $c );
614 1         1035 for( @old )
615             {
616 1         11 my $arr;
617             $arr = $self->delete( $_ ) || do
618 1   33     27 {
619             };
620             }
621             }
622 4 50       67 $self->add( $c ) || return( $self->pass_error );
623             }
624 4         630 return( $self );
625             }
626              
627 0     0 1 0 sub extract_cookies { return( shift->extract( @_ ) ); }
628              
629             sub extract_one
630             {
631 1     1 1 33 my $self = shift( @_ );
632 1         13 my $str = shift( @_ );
633 1         20 my $opts = $self->_get_args_as_hash( @_ );
634 1   50     205 $opts->{path} //= '/';
635 1 50       17 return( $self->error( "No cookie data was provided." ) ) if( !length( "$str" ) );
636            
637 1         7 my( $host, $root );
638 1 50 33     23 if( defined( $opts->{host} ) && CORE::length( $opts->{host} ) )
639             {
640 1         4 $host = $opts->{host};
641 1 50       20 if( $self->_is_ip( $host ) )
642             {
643 0         0 $root = $host;
644             }
645             else
646             {
647 1   50     661 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
648 1         4147 my $res = $dom->stat( $host );
649 1 50       754 if( !defined( $res ) )
650             {
651 0         0 return( $self->pass_error( $dom->error ) );
652             }
653             # Possibly empty
654 1 50       32 $root = $res ? $res->domain : '';
655             }
656             }
657              
658 1   50     5754 my $o = Module::Generic::HeaderValue->new_from_header( "$str" ) ||
659             return( $self->pass_error( Module::Generic::HeaderValue->error ) );
660 1         9328 my( $name, $value ) = $o->value->list;
661 1   50     783 my $c = Cookie->new( name => $name, value => $value ) ||
662             return( $self->pass_error( Cookie->error ) );
663 1 50       23 if( CORE::length( $o->param( 'expires' ) ) )
    0          
664             {
665 1         677 my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
666 1 50       709 if( $dt )
667             {
668 0         0 $c->expires( $dt );
669             }
670             else
671             {
672 1         18 $c->expires( $o->param( 'expires' ) );
673             }
674             }
675             elsif( CORE::length( $o->param( 'max-age' ) ) )
676             {
677 0         0 $c->max_age( $o->param( 'max-age' ) );
678             }
679            
680 1 50       1270 if( $o->param( 'domain' ) )
681             {
682             # rfc6265, section 5.2.3:
683             # "If the first character of the attribute-value string is %x2E ("."): Let cookie-domain be the attribute-value without the leading %x2E (".") character."
684             # Ref: <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3>
685 0         0 my $c_dom = $o->param( 'domain' );
686             # Remove leading dot as per rfc specifications
687 0         0 $c_dom =~ s/^\.//g;
688             # "Convert the cookie-domain to lower case."
689 0         0 $c_dom = lc( $c_dom );
690 0         0 $c->domain( $c_dom );
691             }
692             # "If omitted, defaults to the host of the current document URL, not including subdomains."
693             # <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
694             else
695             {
696 1 50       650 if( $root )
697             {
698 1         41 $c->domain( $root );
699 1         1089 $c->implicit(1);
700             }
701             else
702             {
703             }
704             }
705            
706             # rfc6265: "If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value."
707 1 50 33     971 if( defined( $o->param( 'path' ) ) && CORE::length( $o->param( 'path' ) ) )
708             {
709 1         1304 $c->path( $o->param( 'path' ) );
710             }
711             else
712             {
713 0         0 my $frag = $self->new_array( [split( /\//, $opts->{path} )] );
714             # Not perfect
715 0 0 0     0 if( $opts->{path} eq '/' || substr( $opts->{path}, -1, 1 ) eq '/' )
716             {
717 0         0 $c->path( $opts->{path} );
718             }
719             else
720             {
721 0         0 $frag->pop;
722 0         0 $c->path( $frag->join( '/' )->scalar );
723             }
724             }
725 1 50 33     1107 $c->port( $opts->{port} ) if( defined( $opts->{port} ) && $self->_is_integer( $opts->{port} ) );
726 1 50       40699 $c->http_only(1) if( $o->param( 'httponly' ) );
727 1 50       651 $c->secure(1) if( $o->param( 'secure' ) );
728 1 50       637 $c->same_site(1) if( $o->param( 'samesite' ) );
729 1         642 return( $c );
730             }
731              
732             # From server point of view
733             sub fetch
734             {
735 1     1 1 3852 my $self = shift( @_ );
736 1         20 my $opts = $self->_get_args_as_hash( @_ );
737 1   50     206 $opts->{string} //= '';
738 1 50       15 $opts->{store} = 1 if( !CORE::exists( $opts->{store} ) );
739 1   50     32 my $host = $opts->{host} || $self->host || '';
740 1         1045 my $cookie_header;
741 1         19 my $r = $self->request;
742 1         54 my $cookies = [];
743 1 50 33     32 if( $r )
    50 33        
    0          
744             {
745 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
746 0     0   0 {
747 0         0 my $pool = $r->pool;
748             # my $o = APR::Request::Apache2->handle( $r->pool );
749 0         0 my $o = APR::Request::Apache2->handle( $r );
750 0 0       0 if( $o->jar_status =~ /^(?:Missing input data|Success)$/ )
751             {
752             # all cookie names in order of appearance in the Cookie request header
753 0         0 my @all = $o->jar;
754 0         0 foreach my $cookie_name ( @all )
755             {
756 0         0 my @values = $o->jar( $cookie_name );
757 0         0 foreach my $v ( @values )
758             {
759             # And of course, Apache/modperl does not uri decode the cookie value...
760 0         0 $v = URI::Escape::uri_unescape( $v );
761 0         0 my $c = $self->make( name => $cookie_name, value => $v );
762 0         0 push( @$cookies, $c );
763             }
764             }
765             }
766             else
767             {
768             }
769             }
770 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
771 0     0   0 {
772 2 0 0 2   17 }
  2 0 0     5  
  2 0 0     4543  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
773 0         0 $cookie_header = $r->headers_in->get( 'Cookie' );
774             }
775             elsif( $opts->{request} && $self->_is_object( $opts->{request} ) && $opts->{request}->can( 'header' ) )
776             {
777 1         55 $cookie_header = $opts->{request}->header( 'Cookie' );
778             }
779             elsif( CORE::length( $opts->{string} ) )
780             {
781 0         0 $cookie_header = $opts->{string};
782             }
783             else
784             {
785 0   0     0 $cookie_header = $ENV{HTTP_COOKIE} // '';
786             }
787 1 50       65 if( !scalar( @$cookies ) )
788             {
789 1         19 my $ref = $self->parse( $cookie_header );
790 1         16 foreach my $def ( @$ref )
791             {
792 3   50     32 my $c = $self->make( name => $def->{name}, value => $def->{value} ) ||
793             return( $self->pass_error );
794 3         17 push( @$cookies, $c );
795             }
796             }
797             # We are called in void context like $jar->fetch which means we fetch the cookies and add them to our stack internally
798 1 50       32 if( $opts->{store} )
799             {
800 1         15 foreach my $c ( @$cookies )
801             {
802 3 50       21 $self->add( $c ) || return( $self->pass_error );
803             }
804             }
805 1         15 return( $self->new_array( $cookies ) );
806             }
807              
808             # NOTE: the location of the cookie jar file
809 16     16 1 119 sub file { return( shift->_set_get_file( 'file', @_ ) ); }
810              
811             sub get
812             {
813 8     8 1 3661 my $self = shift( @_ );
814             # If called on the server side, $host and $path would likely be undefined
815             # my( $name, $host, $path ) = @_;
816 8         76 my( $name, $host, $path );
817 8 50 66     253 if( scalar( @_ ) == 1 && $self->_is_a( $_[0], 'Cookie' ) )
    100 66        
    50 33        
818             {
819 0         0 my $c = shift( @_ );
820 0         0 $name = $c->name;
821 0         0 $host = $c->host;
822 0         0 $path = $c->path;
823             }
824             elsif( scalar( @_ ) == 1 && ref( $_[0] ) eq 'HASH' )
825             {
826 4         261 my $this = shift( @_ );
827 4         26 ( $name, $host, $path ) = @$this{qw( name host path )};
828             }
829             elsif( scalar( @_ ) > 0 && scalar( @_ ) <= 3 )
830             {
831 4         22 ( $name, $host, $path ) = @_;
832             }
833             else
834             {
835 0         0 return( $self->error( "Error calling get: I was expecting either a Cookie object, or a list or hash reference of parameters." ) );
836             }
837 8 50 33     57 return( $self->error( "No cookie name was provided to get its object." ) ) if( !defined( $name ) || !CORE::length( $name ) );
838 8   0     110 $host //= $self->host || '';
      33        
839 8   100     100 $path //= '';
840 8         102 my $ref = $self->_cookies;
841 8         7116 my $idx = $self->_index;
842 8         7588 my $key = $self->key( $name => $host, $path );
843             # Return immediately if we found a perfect match
844 8 50       253 if( CORE::exists( $idx->{ $key } ) )
845             {
846 0 0       0 return( wantarray() ? @{$idx->{ $key }} : $idx->{ $key }->[0] );
  0         0  
847             }
848             # If it does not exist, we check each of our cookie to see if it is a higher level cookie.
849             # For example, $host is www.example.org and our cookie key host part is example.org
850             # In this case, example.org would match, because the cookie would apply also to sub domains.
851 8         229 my @found = ();
852 8         69 foreach my $c ( @$ref )
853             {
854 18         195 my $c_name = $c->name;
855 18         16751 my $c_host = $c->domain;
856 18         16851 my $c_path = $c->path;
857            
858 18 100       16379 next unless( $c_name eq $name );
859            
860            
861 5 50 33     125 if( !defined( $host ) || !CORE::length( $host ) )
862             {
863 0         0 push( @found, $c );
864 0         0 next;
865             }
866            
867 5 50 33     85 if( defined( $c_host ) &&
      66        
868             ( $host eq $c_host || index( reverse( $host ), reverse( ".${c_host}" ) ) == 0 ) )
869             {
870 4 100 66     95 if( defined( $path ) && CORE::length( "$path" ) )
871             {
872 1 50       15 if( index( $path, $c_path ) == 0 )
873             {
874 1         29 push( @found, $c );
875             }
876             }
877             else
878             {
879 3         11 push( @found, $c );
880             }
881             }
882             }
883            
884 8 100       125 if( scalar( @found ) )
885             {
886 4 100       40 return( wantarray() ? @found : $found[0] );
887             }
888            
889             # Ultimately, check if there is a cookie entry with just the cookie name and no host
890             # which happens for cookies repository on server side
891 4 100       31 if( CORE::exists( $idx->{ $name } ) )
892             {
893 1 50       38 return( wantarray() ? @{$idx->{ $name }} : $idx->{ $name }->[0] );
  0         0  
894             }
895 3         108 return;
896             }
897              
898             sub get_by_domain
899             {
900 5     5 1 44 my $self = shift( @_ );
901 5         51 my $host = shift( @_ );
902 5         59 my $opts = $self->_get_args_as_hash( @_ );
903 5         1055 $opts->{with_subdomain} = 0;
904 5 50       99 $opts->{sort} = 1 if( !CORE::exists( $opts->{sort} ) );
905 5         53 my $all = $self->new_array;
906 5 50 33     172 return( $all ) if( !defined( $host ) || !CORE::length( $host ) );
907 5         72 $host = lc( $host );
908 5         103 my $ref = $self->_cookies;
909 5         5062 foreach my $c ( @$ref )
910             {
911 12         321 my $dom = $c->domain;
912 12 50 0     11985 $all->push( $c ) if( $dom eq $host || ( $opts->{with_subdomain} && $host =~ /\.$dom$/ ) );
      33        
913             }
914 5         168 my $new = [];
915 5 50       57 if( $opts->{sort} )
916             {
917 5         115 $new = [sort{ $a->path cmp $b->path } @$all];
  10         5779  
918             }
919             else
920             {
921 0         0 $new = [sort{ $b->path cmp $a->path } @$all];
  0         0  
922             }
923 5         3710 return( $self->new_array( $new ) );
924             }
925              
926 3     3 1 71 sub host { return( shift->_set_get_scalar_as_object( 'host', @_ ) ); }
927              
928 0     0 1 0 sub iv { return( shift->_initialisation_vector( @_ ) ); }
929              
930             sub key
931             {
932 25     25 1 101 my $self = shift( @_ );
933 25         138 my( $name, $host, $path );
934 25 100 66     226 if( scalar( @_ ) == 1 && $self->_is_a( $_[0], 'Cookie' ) )
935             {
936 17         746 my $c = shift( @_ );
937 17         62 $name = $c->name;
938 17         15358 $host = $c->domain;
939 17         15082 $path = $c->path;
940             }
941             else
942             {
943 8         32 ( $name, $host, $path ) = @_;
944 8 50 66     123 return( $self->error( "Received cookie object '", overload::StrVal( $name ), "' along with cookie host '$host' and path '$path' while I was expecting cookie name, host and path. If you want to call key() with a cookie object, pass it with no other argument." ) ) if( ref( $name ) && $self->_is_a( $name, ref( $self ) ) );
945             }
946 25 50       16020 return( $self->error( "No cookie name was provided to get its key." ) ) if( !CORE::length( $name ) );
947 25 100 66     421 return( join( ';', $host, $path, $name ) ) if( defined( $host ) && CORE::length( $host ) );
948 3         18 return( $name );
949             }
950              
951 0     0 0 0 sub length { return( shift->repo->length ); }
952              
953             # Load cookie data from json cookie file
954             sub load
955             {
956 1     1 1 46 my $self = shift( @_ );
957 1   50     8 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
958 1         41 my $opts = $self->_get_args_as_hash( @_ );
959 1   50     32 $opts->{host} //= '';
960 1   50     30 $opts->{decrypt} //= 0;
961 1   50     16 $opts->{algo} //= '';
962             # Initialisation Vector for encryption
963             # Re-use it if it was previously set
964 1   50     36 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
965 1   50     790 my $host = $opts->{host} || $self->host || '';
966 1   50     946 my $f = $self->new_file( $file ) || return( $self->pass_error );
967 1         149632 my $json = $f->load;
968 1 50       10403 return( $self->pass_error( $f->error ) ) if( !defined( $json ) );
969             # No need to go further
970 1 50       26 if( !CORE::length( $json ) )
971             {
972 0         0 return( $self );
973             }
974            
975 1 50       25 if( $opts->{decrypt} )
976             {
977 0         0 my $key = $opts->{key};
978 0         0 my $algo = $opts->{algo};
979 0 0 0     0 return( $self->error( "Cookies file encryption was enabled, but no key was set to decrypt it." ) ) if( !defined( $key ) || !CORE::length( "$key" ) );
980 0 0 0     0 return( $self->error( "Cookies file encryption was enabled, but no algorithm was set to decrypt it." ) ) if( !defined( $algo ) || !CORE::length( "$algo" ) );
981 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
982 0     0   0 {
983 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
984 0   0     0 my $p = $self->_encrypt_objects( @$opts{qw( key algo iv )} ) || return( $self->pass_error );
985 0         0 my $crypt = $p->{crypt};
986 0         0 my $bin = Crypt::Misc::decode_b64( "$json" );
987 0         0 $json = $crypt->decrypt( "$bin", @$p{qw( key iv )} );
988             }
989 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
990 0     0   0 {
991 0         0 return( $self->error( "An error occurred while trying to decrypt cookies file \"$file\": $e" ) );
992 2 0 0 2   17 }
  2 0 0     5  
  2 0 0     2000  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
993             }
994            
995 1         108 my $j = JSON->new->relaxed->utf8;
996 1         13 my $hash;
997 1 50 33     9 try
  1         8  
  1         9  
  1         23  
  0         0  
  1         10  
  1         11  
  1         3  
998 1     1   8 {
999 1         165 $hash = $j->decode( $json );
1000             }
1001 1 0 50     26 catch( $e )
  1 0 33     17  
  1 0       11  
  1 0       7  
  1 0       6  
  1 0       6  
  1 0       9  
  1 0       22  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  1         21  
  0         0  
  1         15  
  0         0  
  0         0  
  1         19  
  1         18  
  1         7  
  1         15  
  0         0  
  0         0  
  0         0  
  0         0  
1002 0     0   0 {
1003 0         0 return( $self->error( "Unable to decode ", CORE::length( $json ), " bytes of json data to perl: $e" ) );
1004 2 0 0 2   16 }
  2 0 0     3  
  2 0 33     3325  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  1 0       14  
  0 0       0  
  1 0       144  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         13  
  0         0  
  0         0  
  0         0  
  0         0  
  1         13  
1005 1 50       14 if( ref( $hash ) ne 'HASH' )
1006             {
1007 0         0 return( $self->error( "Data retrieved from json cookie file \"$file\" does not contain an hash as expected, but instead I got '$hash'." ) );
1008             }
1009 1         11 my $last_update = CORE::delete( $hash->{last_update} );
1010 1         12 my $repo = CORE::delete( $hash->{cookies} );
1011 1 50       16 return( $self->error( "I was expecting the JSON cookies properties to be an array, but instead I got '$repo'" ) ) if( ref( $repo ) ne 'ARRAY' );
1012 1         14 foreach my $def ( @$repo )
1013             {
1014 3 50 33     120 if( !CORE::exists( $def->{name} ) ||
    50 33        
1015             !CORE::exists( $def->{value} ) )
1016             {
1017 0         0 next;
1018             }
1019             elsif( !defined( $def->{name} ) || !CORE::length( $def->{name} ) )
1020             {
1021             next:
1022 0         0 }
1023             my $c = $self->make( $def ) || do
1024 3   33     40 {
1025             next;
1026             };
1027 3 50       25 $self->add( $c ) || return( $self->pass_error );
1028             }
1029 1         36 return( $self );
1030             }
1031              
1032             sub load_as_lwp
1033             {
1034 1     1 1 5880 my $self = shift( @_ );
1035 1   50     13 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1036 1         29 my $opts = $self->_get_args_as_hash( @_ );
1037 1   50     36 $opts->{decrypt} //= 0;
1038 1   50     36 $opts->{algo} //= '';
1039             # Initialisation Vector for encryption
1040             # Re-use it if it was previously set
1041 1   50     30 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
1042 1         714 my $f = $self->new_file( $file );
1043 1   50     150577 my $host = $opts->{host} || $self->host || '';
1044 1 50       1066 $f->open( '<', { binmode => ( $opts->{decrypt} ? 'raw' : 'utf-8' ) }) || return( $self->pass_error( $f->error ) );
    50          
1045             my $code = sub
1046             {
1047 4 100   4   8329 if( /^Set-Cookie3:[[:blank:]\h]*(.*?)$/ )
1048             {
1049 3         51 my $c = $self->add( $1 );
1050             }
1051             else
1052             {
1053             }
1054 1         7604 };
1055            
1056 1 50       19 if( $opts->{decrypt} )
1057             {
1058 0         0 my $raw = $f->load;
1059 0         0 $f->close;
1060 0         0 my $key = $opts->{key};
1061 0         0 my $algo = $opts->{algo};
1062 0 0 0     0 return( $self->error( "Cookies file encryption was enabled, but no key was set to decrypt it." ) ) if( !defined( $key ) || !CORE::length( "$key" ) );
1063 0 0 0     0 return( $self->error( "Cookies file encryption was enabled, but no algorithm was set to decrypt it." ) ) if( !defined( $algo ) || !CORE::length( "$algo" ) );
1064 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1065 0     0   0 {
1066 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
1067 0   0     0 my $p = $self->_encrypt_objects( @$opts{qw( key algo iv )} ) || return( $self->pass_error );
1068 0         0 my $crypt = $p->{crypt};
1069 0         0 my $bin = Crypt::Misc::decode_b64( "$raw" );
1070 0         0 my $data = $crypt->decrypt( "$bin", @$p{qw( key iv )} );
1071 0         0 my $scalar = $self->new_scalar( \$data );
1072 0   0     0 my $io = $scalar->open || return( $self->pass_error( $! ) );
1073 0 0       0 $io->line( $code, chomp => 1, auto_next => 1 ) || return( $self->pass_error( $f->error ) );
1074 0         0 $io->close;
1075             }
1076 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1077 0     0   0 {
1078 0         0 return( $self->error( "An error occurred while trying to decrypt cookies file \"$file\": $e" ) );
1079 2 0 0 2   17 }
  2 0 0     5  
  2 0 0     3059  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1080             }
1081             else
1082             {
1083 1 50       39 $f->line( $code, chomp => 1, auto_next => 1 ) || return( $self->pass_error( $f->error ) );
1084 1         1507 $f->close;
1085             }
1086 1         2506 return( $self );
1087             }
1088              
1089             sub load_as_mozilla
1090             {
1091 0     0 1 0 my $self = shift( @_ );
1092 0   0     0 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1093 0   0     0 my $f = $self->new_file( $file ) || return( $self->pass_error );
1094 0         0 my $opts = $self->_get_args_as_hash( @_ );
1095 0   0     0 $opts->{use_dbi} //= 0;
1096 0   0     0 $opts->{sqlite} //= '';
1097             # First, we copy the file, because Firefox locks it
1098 0         0 my $tmpfile = $self->new_tempfile( extension => 'sqlite' );
1099 0   0     0 my $sqldb = $f->copy( $tmpfile ) || return( $self->pass_error );
1100             # Now, try to load DBI and DBD::SQLite
1101 0         0 my $dbi_error;
1102             my $sqlite_bin;
1103 0         0 my $cookies = [];
1104 0 0 0     0 my $requires_dbi = ( CORE::exists( $opts->{use_dbi} ) && defined( $opts->{use_dbi} ) && $opts->{use_dbi} ) ? 1 : 0;
1105 0         0 require version;
1106 0         0 my $sql = <<EOT;
1107             SELECT
1108             name
1109             ,value
1110             ,host AS "domain"
1111             ,path
1112             ,expiry AS "expires"
1113             ,isSecure AS "secure"
1114             ,sameSite AS "same_site"
1115             ,isHttpOnly AS "http_only"
1116             ,CAST( ( lastAccessed / 1000000 ) AS "INTEGER" ) AS "accessed"
1117             ,CAST( ( creationTime / 1000000 ) AS "INTEGER" ) AS "created"
1118             FROM moz_cookies
1119             EOT
1120              
1121             # If the user explicitly required the use of DBI/DBD::SQLite; or
1122             # the user has not explicitly required the use of DBI/DBD::SQLite nor of sqlite3 binary
1123 0 0 0     0 if( $requires_dbi ||
      0        
1124             ( !$opts->{use_dbi} && !$opts->{sqlite} ) )
1125             {
1126             eval
1127 0         0 {
1128 0         0 require DBI;
1129 0         0 require DBD::SQLite;
1130             };
1131 0 0       0 $dbi_error = $@ if( $@ );
1132             # User explicitly required the use of DBI/DBD::SQLite, but it failed, so we return an error
1133 0 0 0     0 if( defined( $dbi_error ) && exists( $opts->{use_dbi} ) && defined( $opts->{use_dbi} ) && $opts->{use_dbi} )
    0 0        
      0        
1134             {
1135 0         0 return( $self->error( "Unable to load either DBI or DBD::SQLite: $@" ) );
1136             }
1137             elsif( !defined( $dbi_error ) )
1138             {
1139             # As of Firefox 106.0.5 (2022-11-06), the cookie table structure is:
1140             # CREATE TABLE moz_cookies(
1141             # id INTEGER PRIMARY KEY,
1142             # originAttributes TEXT NOT NULL DEFAULT '',
1143             # name TEXT,
1144             # value TEXT,
1145             # host TEXT,
1146             # path TEXT,
1147             # expiry INTEGER,
1148             # lastAccessed INTEGER,
1149             # creationTime INTEGER,
1150             # isSecure INTEGER,
1151             # isHttpOnly INTEGER,
1152             # inBrowserElement INTEGER DEFAULT 0,
1153             # sameSite INTEGER DEFAULT 0,
1154             # rawSameSite INTEGER DEFAULT 0,
1155             # schemeMap INTEGER DEFAULT 0,
1156             # CONSTRAINT moz_uniqueid UNIQUE(name, host, path, originAttributes)
1157             # );
1158             # 'expiry' is a unix timestamp
1159             # 'lastAccessed' and 'creationTime' are in microseconds
1160 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1161 0     0   0 {
1162 0   0     0 my $dbh = DBI->connect( "dbi:SQLite:dbname=${sqldb}", '', '', { RaiseError => 1 } ) ||
1163             die( "Unable to connect to SQLite database file ${sqldb}: ", $DBI::errstr );
1164 0   0     0 my $tbl_check = $dbh->table_info( undef, undef, 'moz_cookies', 'TABLE' ) ||
1165             die( "Error checking for existence of table 'moz_cookies' in SQLite database file ${sqldb}: ", $dbh->errstr );
1166 0 0       0 $tbl_check->execute || die( "Error executing query to check existence of table 'moz_cookies': ", $tbl_check->errstr );
1167 0         0 my $found = $tbl_check->fetchrow;
1168 0         0 $tbl_check->finish;
1169 0 0       0 if( !$found )
1170             {
1171 0         0 die( "No table 'moz_cookies' found in SQLite database ${sqldb}" );
1172             }
1173 0   0     0 my $sth = $dbh->prepare( $sql ) ||
1174             die( "Error preparing the sql query to get all mozilla cookies from database ${sqldb}: ", $dbh->errstr, "\nSQL query was: ${sql}" );
1175 0 0       0 $sth->execute() ||
1176             die( "Error executing sql query to get all mozilla cookies from database ${sqldb}: ", $sth->errstr, "\nSQL query was: ${sql}" );
1177 0         0 $cookies = $sth->fetchall_arrayref;
1178 0         0 $sth->finish;
1179 0         0 $dbh->disconnect;
1180 0         0 $sqldb->remove;
1181             }
1182 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1183 0     0   0 {
1184 0 0       0 if( $requires_dbi )
1185             {
1186 0         0 return( $self->error( "Error trying to get mozilla cookies from SQLite database ${sqldb} using DBI: $e" ) );
1187             }
1188             else
1189             {
1190 0 0       0 warn( "Non fatal error occurred while trying to get mozilla cookies from SQLite database ${sqldb} using DBI: $e\n" ) if( $self->_warnings_is_enabled );
1191             }
1192 2 0 0 2   19 }
  2 0 0     6  
  2 0 0     1366  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1193             }
1194             }
1195            
1196             # If there is no cookies found yet; and
1197             # the user did not require exclusively the use of DBI, but required the use of sqlite3 binary
1198             # the user did not require the use of DBI nor the use of sqlite3 binary
1199 0 0 0     0 if( !scalar( @$cookies ) && !$requires_dbi )
1200             {
1201             # If the user required specific sqlite3 binary
1202 0 0 0     0 if( exists( $opts->{sqlite} ) && defined( $opts->{sqlite} ) && CORE::length( $opts->{sqlite} ) )
      0        
1203             {
1204 0 0       0 if( !-e( $opts->{sqlite} ) )
    0          
1205             {
1206 0         0 return( $self->error( "sqlite3 binary path provided \"$opts->{sqlite}\" does not exist." ) );
1207             }
1208             elsif( !-x( $opts->{sqlite} ) )
1209             {
1210 0         0 return( $self->error( "sqlite3 binary path provided \"$opts->{sqlite}\" is not executable by user id $>" ) );
1211             }
1212 0         0 $sqlite_bin = $opts->{sqlite};
1213             }
1214             else
1215             {
1216 0         0 require File::Which;
1217 0         0 my $bin = File::Which::which( 'sqlite3' );
1218 0 0       0 if( !defined( $bin ) )
1219             {
1220 0         0 return( $self->error( "DBI and/or DBD::SQLite modules are not installed and I could not find thr sqlite3 binary anywhere." ) );
1221             }
1222 0         0 $sqlite_bin = $bin;
1223             }
1224            
1225 0         0 $sql =~ s/\n/ /gs;
1226 0 0       0 open( my $fh, '-|', $sqlite_bin, "${sqldb}", $sql ) ||
1227             return( $self->error( "Failed to execute sqlite3 binary with sql query to get all mozilla cookies from database ${sqldb}: $!" ) );
1228             # $cookies = [map{ [split( /\|/, $_ )] } <$fh>];
1229 0         0 while( defined( $_ = <$fh> ) )
1230             {
1231 0         0 chomp;
1232 0         0 push( @$cookies, [split( /\|/, $_ )] );
1233             }
1234 0         0 close( $fh );
1235             }
1236            
1237 0         0 foreach my $ref ( @$cookies )
1238             {
1239 0         0 my( $name, $value, $domain, $path, $expires, $secure, $same_site, $http_only, $accessed, $created ) = @$ref;
1240 0 0       0 $self->add({
1241             name => $name,
1242             value => $value,
1243             domain => $domain,
1244             path => $path,
1245             expires => $expires,
1246             secure => $secure,
1247             http_only => $http_only,
1248             same_site => $same_site,
1249             accessed_on => $accessed,
1250             created_on => $created,
1251             }) || return( $self->pass_error );
1252             }
1253 0         0 return( $self );
1254             }
1255              
1256             sub load_as_netscape
1257             {
1258 0     0 1 0 my $self = shift( @_ );
1259 0   0     0 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1260 0   0     0 my $f = $self->new_file( $file ) || return( $self->pass_error );
1261 0         0 my $opts = $self->_get_args_as_hash( @_ );
1262 0   0     0 my $host = $opts->{host} || $self->host || '';
1263 0 0       0 $f->open || return( $self->pass_error( $f->error ) );
1264             $f->line(sub
1265             {
1266 0     0   0 my( $domain, $sub_too, $path, $secure, $expires, $name, $value ) = split( /\t/, $_ );
1267 0 0       0 $secure = ( lc( $secure ) eq 'true' ? 1 : 0 );
1268             # rfc6265 makes obsolete domains prepended with a dot.
1269 0 0       0 $domain = substr( $domain, 1 ) if( substr( $domain, 1, 1 ) eq '.' );
1270 0         0 $self->add({
1271             name => $name,
1272             value => $value,
1273             domain => $domain,
1274             path => $path,
1275             expires => $expires,
1276             secure => $secure,
1277             });
1278 0 0       0 }, chomp => 1, auto_next => 1 ) || return( $self->pass_error( $f->error ) );
1279 0         0 return( $self );
1280             }
1281              
1282             sub make
1283             {
1284 15     15 1 10895 my $self = shift( @_ );
1285 15         87 my $opts = $self->_get_args_as_hash( @_ );
1286 2     2   16 no overloading;
  2         5  
  2         4295  
1287 15 50       2262 return( $self->error( "Cookie name was not provided." ) ) if( !$opts->{name} );
1288 15         95 $opts->{debug} = $self->debug;
1289 15         423 my $c = Cookie->new( debug => $self->debug );
1290 15 50       129 return( $self->pass_error( Cookie->error ) ) if( !defined( $c ) );
1291 15 50       85 $c->apply( $opts ) || return( $self->pass_error( $c->error ) );
1292 15         138 return( $c );
1293             }
1294              
1295             sub merge
1296             {
1297 0     0 1 0 my $self = shift( @_ );
1298 0   0     0 my $jar = shift( @_ ) || return( $self->error( "No Cookie::Jar object was provided to merge." ) );
1299 0         0 my $opts = $self->_get_args_as_hash( @_ );
1300 0 0       0 return( $self->error( "Cookie::Jar object provided (", overload::StrVal( $jar ), ") is not a Cookie::Jar object." ) ) if( !$self->_is_a( $jar, 'Cookie::Jar' ) );
1301             # We require the do method on purpose, because the scan method is from the old HTTP::Cookies api which does not send an object, but a list of cookie property value
1302 0 0       0 return( $self->error( "Cookie::Jar object provided does not have a method \"do\"." ) ) if( !$jar->can( 'do' ) );
1303 0   0     0 $opts->{overwrite} //= 0;
1304 0   0     0 $opts->{host} //= $self->host || '';
      0        
1305 0   0     0 $opts->{die} //= 0;
1306 0         0 my $n = 0;
1307 0         0 my $error;
1308             $jar->do(sub
1309             {
1310             # Skip the rest if we already found an error
1311 0 0   0   0 return if( defined( $error ) );
1312 0         0 my $c = shift( @_ );
1313 0 0       0 if( $self->_is_object( $c ) )
1314             {
1315 0 0 0     0 if( $self->_is_a( $c, 'Cookie' ) )
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
1316             {
1317 0 0       0 if( $opts->{overwrite} )
1318             {
1319 0         0 $self->replace( $c );
1320             }
1321             else
1322             {
1323 0         0 $self->add( $c );
1324             }
1325 0         0 $n++;
1326             }
1327             elsif( $c->can( 'name' ) &&
1328             $c->can( 'value' ) &&
1329             $c->can( 'domain' ) &&
1330             $c->can( 'path' ) &&
1331             $c->can( 'expires' ) &&
1332             $c->can( 'max_age' ) &&
1333             $c->can( 'port' ) &&
1334             $c->can( 'secure' ) &&
1335             $c->can( 'same_site' ) &&
1336             $c->can( 'http_only' ) )
1337             {
1338 0         0 my $new = $jar->make(
1339             name => $c->name,
1340             value => $c->value,
1341             domain => $c->domain,
1342             path => $c->path,
1343             expires => $c->expires,
1344             max_age => $c->max_age,
1345             http_only => $c->http_only,
1346             same_site => $c->same_site,
1347             secure => $c->secure,
1348             );
1349 0 0       0 if( !defined( $new ) )
1350             {
1351 0         0 $error = $jar->error;
1352 0 0       0 die( $error ) if( $opts->{die} );
1353             }
1354             else
1355             {
1356 0 0       0 if( $opts->{overwrite} )
1357             {
1358 0         0 $self->replace( $new );
1359             }
1360             else
1361             {
1362 0         0 $self->add( $new );
1363             }
1364 0         0 $n++;
1365             }
1366             }
1367             else
1368             {
1369 0         0 $error = "Cookie object received (" . overload::StrVal( $c ) . ") is not a Cookie object and does not support the methods name, value, domain, path, port, expires, max_age, secure, same_site and http_only";
1370 0 0       0 die( $error ) if( $opts->{die} );
1371             }
1372             }
1373 0         0 });
1374 0 0       0 return( $self->error( $error ) ) if( defined( $error ) );
1375 0         0 return( $self );
1376             }
1377              
1378             # Swell:
1379             # "if the Cookie header field contains two cookies with the same name (e.g., that were set with different Path or Domain attributes), servers SHOULD NOT rely upon the order in which these cookies appear in the header field."
1380             # <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2.2>
1381             sub parse
1382             {
1383 23     23 1 35463 my $self = shift( @_ );
1384 23         39 my $raw = shift( @_ );
1385 23         72 my $ref = $self->new_array;
1386 23 100 100     493 return( $ref ) unless( defined( $raw ) && CORE::length( $raw ) );
1387 21         252 my @pairs = grep( /=/, split( /; ?/, $raw ) );
1388 21         59 foreach my $pair ( @pairs )
1389             {
1390             # Remove leading and trailing whitespaces
1391 60         738 $pair =~ s/^[[:blank:]\h]+|[[:blank:]\h]+$//g;
1392 60         167 my( $k, $v ) = split( '=', $pair, 2 );
1393 60         140 $k = URI::Escape::uri_unescape( $k );
1394 60 50       460 $v = '' unless( defined( $v ) );
1395 60         94 $v =~ s/\A"(.*)"\z/$1/;
1396 60         88 $v = URI::Escape::uri_unescape( $v );
1397 60         688 $ref->push( { name => $k, value => $v } );
1398             }
1399 21         189 return( $ref );
1400             }
1401              
1402             sub purge
1403             {
1404 0     0 1 0 my $self = shift( @_ );
1405 0         0 my $ref = $self->_cookies;
1406 0         0 my $removed = $self->new_array;
1407 0         0 for( my $i = 0; $i < scalar( @$ref ); $i++ )
1408             {
1409 0         0 my $c = $ref->[$i];
1410 0 0       0 if( $c->is_expired )
1411             {
1412 0 0       0 $self->delete( $c ) || return( $self->pass_error );
1413 0         0 $removed->push( $c );
1414             }
1415             }
1416 0         0 return( $removed );
1417             }
1418              
1419 8     8 1 179566 sub repo { return( shift->_set_get_array_as_object( '_cookies', @_ ) ); }
1420              
1421             sub replace
1422             {
1423 0     0 1 0 my $self = shift( @_ );
1424 0         0 my( $c, $old ) = @_;
1425 0         0 my $idx = $self->_index;
1426 0         0 my $ref = $self->_cookies;
1427 0 0       0 return( $self->error( "No cookie object was provided." ) ) if( !defined( $c ) );
1428 0 0       0 return( $self->error( "Cookie object provided is not a Cookie object." ) ) if( !$self->_is_a( $c, 'Cookie' ) );
1429 0         0 my $replaced = $self->new_array;
1430 0 0       0 if( defined( $old ) )
1431             {
1432 0 0       0 return( $self->error( "Old cookie object to be replaced is not a Cookie object." ) ) if( !$self->_is_a( $old, 'Cookie' ) );
1433 0 0 0     0 if( $c->name ne $old->name ||
      0        
1434             $c->domain ne $old->domain ||
1435             $c->path ne $old->path )
1436             {
1437 0         0 return( $self->error( "New cookie name '", $c->name, "' with host '", $c->domain, "' and path '", $c->path, "' does not match old cookie name '", $old->name, "' with host '", $old->host, "' and path '", $old->path, "'" ) );
1438             }
1439 0   0     0 my $key = $self->key( $old ) || return( $self->pass_error );
1440 0         0 my $addr = Scalar::Util::refaddr( $old );
1441 0 0       0 if( CORE::exists( $idx->{ $key } ) )
1442             {
1443 0         0 for( my $i = 0; $i < scalar( @{$idx->{ $key }} ); $i++ )
  0         0  
1444             {
1445 0 0       0 if( Scalar::Util::refaddr( $idx->{ $key }->[$i] ) eq $addr )
1446             {
1447 0         0 $idx->{ $key }->[$i] = $c;
1448 0         0 last;
1449             }
1450             }
1451             }
1452 0         0 for( my $i = 0; $i < scalar( @$ref ); $i++ )
1453             {
1454 0 0       0 if( Scalar::Util::refaddr( $ref->[$i] ) eq $addr )
1455             {
1456 0         0 $replaced->push( $ref->[$i] );
1457 0         0 $ref->[$i] = $c;
1458 0         0 last;
1459             }
1460             }
1461             }
1462             else
1463             {
1464 0   0     0 my $key = $self->key( $c ) || return( $self->pass_error );
1465 0 0       0 $replaced->push( CORE::exists( $idx->{ $key } ) ? @{$idx->{ $key }} : () );
  0         0  
1466 0         0 foreach my $old ( @$replaced )
1467             {
1468 0         0 my $addr = Scalar::Util::refaddr( $old );
1469 0         0 for( my $j = 0; $j < scalar( @$ref ); $j++ )
1470             {
1471 0 0       0 if( Scalar::Util::refaddr( $ref->[$j] ) eq $addr )
1472             {
1473 0         0 CORE::splice( @$ref, $j, 1 );
1474 0         0 $j--;
1475 0         0 last;
1476             }
1477             }
1478             }
1479 0         0 $idx->{ $key } = [ $c ];
1480             }
1481 0         0 return( $replaced );
1482             }
1483              
1484 5     5 1 52 sub request { return( shift->_set_get_object_without_init( 'request', 'Apache2::RequestRec', @_ ) ); }
1485              
1486             sub save
1487             {
1488 1     1 1 370890 my $self = shift( @_ );
1489 1   50     18 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1490 1         42 my $opts = $self->_get_args_as_hash( @_ );
1491 1   50     42 $opts->{encrypt} //= 0;
1492 1   50     28 $opts->{algo} //= '';
1493             # Initialisation Vector for encryption
1494             # Re-use it if it was previously set
1495 1   50     40 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
1496 1   50     766 $opts->{format} //= '';
1497 1 50       9 return( $self->save_as_lwp( $opts ) ) if( $opts->{format} eq 'lwp' );
1498 1         9 my $all = [];
1499 1         17 my $ref = $self->_cookies;
1500 1         938 foreach my $c ( @$ref )
1501             {
1502 3         40 push( @$all, $c->as_hash );
1503             }
1504 1         10 my $tz;
1505             # DateTime::TimeZone::Local will die ungracefully if the local timezeon is not set with the error:
1506             # "Cannot determine local time zone"
1507 1 50 33     8 try
  1         6  
  1         3  
  1         19  
  0         0  
  1         7  
  1         11  
  1         7  
1508 1     1   6 {
1509 1         19 $tz = DateTime::TimeZone->new( name => 'local' );
1510             }
1511 1 0 50     26 catch( $e )
  1 0 33     641  
  1 0       8  
  1 0       9  
  1 0       6  
  1 0       7  
  1 0       7  
  1 0       17  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  1         12  
  0         0  
  1         8  
  0         0  
  0         0  
  1         14  
  1         12  
  1         8  
  1         9  
  0         0  
  0         0  
  0         0  
  0         0  
1512 0     0   0 {
1513 0         0 $tz = DateTime::TimeZone->new( name => 'UTC' );
1514 2 0 0 2   19 }
  2 0 0     4  
  2 0 33     2089  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  1 0       15  
  0 0       0  
  1 0       122  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         10  
  0         0  
  0         0  
  0         0  
  0         0  
  1         10  
1515 1         21 my $today = DateTime->now( time_zone => $tz );
1516 1         394 my $dt_fmt = DateTime::Format::Strptime->new(
1517             pattern => '%FT%T%z',
1518             # Unnecessary
1519             # locale => 'en_GB',
1520             time_zone => $tz->name,
1521             );
1522 1         2066 $today->set_formatter( $dt_fmt );
1523 1         78 my $data = { cookies => $all, updated_on => "$today" };
1524            
1525 1   50     803 my $f = $self->new_file( $file ) || return( $self->pass_error );
1526 1         155323 my $j = JSON->new->allow_nonref->pretty->canonical->convert_blessed;
1527 1         13 my $json;
1528 1 50 33     21 try
  1         7  
  1         13  
  1         24  
  0         0  
  1         15  
  1         24  
  1         10  
1529 1     1   3 {
1530 1         125 $json = $j->encode( $data );
1531             }
1532 1 0 50     38 catch( $e )
  1 0 33     2239  
  1 0       16  
  1 0       15  
  1 0       439  
  1 0       7  
  1 0       8  
  1 0       21  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  1         22  
  0         0  
  1         12  
  0         0  
  0         0  
  1         17  
  1         269  
  1         12  
  1         19  
  0         0  
  0         0  
  0         0  
  0         0  
1533 0     0   0 {
1534 0         0 return( $self->error( "Unable to encode data to json: $e" ) );
1535 2 0 0 2   15 }
  2 0 0     4  
  2 0 33     6903  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  1 0       32  
  0 0       0  
  1 0       229  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         16  
  0         0  
  0         0  
  0         0  
  0         0  
  1         16  
1536              
1537 1 50       49 $f->open( '>', { binmode => ( $opts->{encrypt} ? 'raw' : 'utf8' ) }) ||
    50          
1538             return( $self->pass_error( $f->error ) );
1539 1 50       51057 if( $opts->{encrypt} )
1540             {
1541 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
1542 0   0     0 my $p = $self->_encrypt_objects( @$opts{qw( key algo iv )} ) || return( $self->pass_error );
1543 0         0 my $crypt = $p->{crypt};
1544             # $value = Crypt::Misc::encode_b64( $crypt->encrypt( "$value", $p->{key}, $p->{iv} ) );
1545 0         0 my $encrypted = $crypt->encrypt( "$json", @$p{qw( key iv )} );
1546 0         0 my $b64 = Crypt::Misc::encode_b64( $encrypted );
1547 0         0 $f->unload( $b64 );
1548             }
1549             else
1550             {
1551 1         23 $f->unload( $json );
1552             }
1553 1         2786 $f->close;
1554 1         2547 return( $self );
1555             }
1556              
1557             sub save_as_lwp
1558             {
1559 1     1 1 372147 my $self = shift( @_ );
1560 1   50     27 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1561 1         46 my $opts = $self->_get_args_as_hash( @_ );
1562 1   50     53 $opts->{encrypt} //= 0;
1563 1   50     42 $opts->{algo} //= '';
1564             # Initialisation Vector for encryption
1565             # Re-use it if it was previously set
1566 1   50     48 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
1567 1   50     804 $opts->{skip_discard} //= 0;
1568 1   50     19 $opts->{skip_expired} //= 0;
1569 1 50       8 return( $self->error( "No file to write cookies was specified." ) ) if( !$file );
1570 1   50     37 my $f = $self->new_file( $file ) || return( $self->pass_error );
1571            
1572 1         148342 my $raw = '';
1573 1         16 my $p = {};
1574 1 50       25 if( $opts->{encrypt} )
1575             {
1576 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
1577 0   0     0 $p = $self->_encrypt_objects( @$opts{qw( key algo iv )} ) || return( $self->pass_error );
1578             }
1579            
1580 1   50     73 my $io = $f->open( '>', { binmode => ( $opts->{encrypt} ? 'raw' : 'utf-8' ) }) ||
1581             return( $self->error( "Unable to write cookies to file \"$file\": ", $f->error ) );
1582 1 50       54620 if( $opts->{encrypt} )
1583             {
1584 0         0 $raw = "#LWP-Cookies-1.0\n";
1585             }
1586             else
1587             {
1588 1 50       21 $io->print( "#LWP-Cookies-1.0\n" ) || return( $self->error( "Unable to write to cookie file \"$file\": $!" ) );
1589             }
1590 1         313 my $now = DateTime->now;
1591             $self->scan(sub
1592             {
1593 3     3   20 my $c = shift( @_ );
1594 3 0 33     42 return(1) if( $c->discard && $opts->{skip_discard} );
1595 3 0 33     2745 return(1) if( $c->expires && $c->expires < $now && $opts->{skip_expired} );
      33        
1596 3         2875 my $vals = $c->as_hash;
1597 3 50       24 $vals->{path_spec} = 1 if( CORE::length( $vals->{path} ) );
1598             # In HTTP::Cookies logic, version 1 is rfc2109, version 2 is rfc6265
1599 3         55 $vals->{version} = 2;
1600 3         48 my $hv = Module::Generic::HeaderValue->new( [CORE::delete( @$vals{qw( name value )} )] );
1601 3         3194 $hv->param( path => sprintf( '"%s"', $vals->{path} ) );
1602 3         3753 $hv->param( domain => $vals->{domain} );
1603 3 50 33     1952 $hv->param( port => $vals->{port} ) if( defined( $vals->{port} ) && CORE::length( $vals->{port} ) );
1604 3 50 33     64 $hv->param( path_spec => undef() ) if( defined( $vals->{path_spec} ) && $vals->{path_spec} );
1605 3 50 33     1975 $hv->param( secure => undef() ) if( defined( $vals->{secure} ) && $vals->{secure} );
1606 3 50 33     2030 $hv->param( expires => sprintf( '"%s"', "$vals->{expires}" ) ) if( defined( $vals->{secure} ) && $vals->{expires} );
1607 3 0 33     28 $hv->param( discard => undef() ) if( defined( $vals->{discard} ) && $vals->{discard} );
1608 3 50 33     35 if( defined( $vals->{comment} ) && CORE::length( $vals->{comment} ) )
1609             {
1610 0         0 $vals->{comment} =~ s/(?<!\\)\"/\\\"/g;
1611 0         0 $hv->param( comment => sprintf( '"%s"', $vals->{comment} ) );
1612             }
1613 3 50 33     28 $hv->param( commentURL => $vals->{commentURL} ) if( defined( $vals->{commentURL} ) && CORE::length( $vals->{commentURL} ) );
1614 3         28 $hv->param( version => $vals->{version} );
1615 3 50       1921 if( $opts->{encrypt} )
1616             {
1617 0         0 $raw .= 'Set-Cookie3: ' . $hv->as_string . "\n";
1618             }
1619             else
1620             {
1621 3 50       36 $io->print( 'Set-Cookie3: ', $hv->as_string, "\n" ) || return( $self->error( "Unable to write to cookie file \"$file\": $!" ) );
1622             }
1623 1         954 });
1624 1 50       58 if( $opts->{encrypt} )
1625             {
1626 0         0 my $crypt = $p->{crypt};
1627 0         0 my $encrypted = $crypt->encrypt( "$raw", @$p{qw( key iv )} );
1628 0         0 my $b64 = Crypt::Misc::encode_b64( $encrypted );
1629 0         0 $io->print( $b64 );
1630             }
1631 1         21 $io->close;
1632 1         230 return( $self );
1633             }
1634              
1635             sub save_as_mozilla
1636             {
1637 0     0 1 0 my $self = shift( @_ );
1638 0   0     0 my $file = shift( @_ ) || return( $self->error( "No database file to write cookies was specified." ) );
1639 0         0 my $opts = $self->_get_args_as_hash( @_ );
1640 0   0     0 $opts->{log_sql} //= '';
1641 0   0     0 $opts->{overwrite} //= 0;
1642 0   0     0 $opts->{rollback} //= 0;
1643 0   0     0 $opts->{skip_discard} //= 0;
1644 0   0     0 $opts->{skip_expired} //= 0;
1645 0   0     0 $opts->{sqlite} //= '';
1646 0   0     0 $opts->{use_dbi} //= 0;
1647 0   0     0 my $sqldb = $self->new_file( $file ) || return( $self->pass_error );
1648 0         0 my $dbi_error;
1649             my $sqlite_bin;
1650 0 0 0     0 my $requires_dbi = ( CORE::exists( $opts->{use_dbi} ) && defined( $opts->{use_dbi} ) && $opts->{use_dbi} ) ? 1 : 0;
1651 0         0 require version;
1652 0         0 my $db_file_exists = $sqldb->exists;
1653 0         0 my $table_moz_cookies_exists = 0;
1654             # As of Firefox 106.0.5 (2022-11-06), the cookie table structure is:
1655             # 'expiry' is a unix timestamp
1656             # 'lastAccessed' and 'creationTime' are in microseconds
1657 0         0 my $create_table_sql = <<EOT;
1658             CREATE TABLE moz_cookies(
1659             id INTEGER PRIMARY KEY,
1660             originAttributes TEXT NOT NULL DEFAULT '',
1661             name TEXT,
1662             value TEXT,
1663             host TEXT,
1664             path TEXT,
1665             expiry INTEGER,
1666             lastAccessed INTEGER,
1667             creationTime INTEGER,
1668             isSecure INTEGER,
1669             isHttpOnly INTEGER,
1670             inBrowserElement INTEGER DEFAULT 0,
1671             sameSite INTEGER DEFAULT 0,
1672             rawSameSite INTEGER DEFAULT 0,
1673             schemeMap INTEGER DEFAULT 0,
1674             CONSTRAINT moz_uniqueid UNIQUE(name, host, path, originAttributes)
1675             )
1676             EOT
1677 0         0 my $core_fields =
1678             {
1679             name => { type => 'TEXT', constant => 'SQL_VARCHAR' },
1680             value => { type => 'TEXT', constant => 'SQL_VARCHAR' },
1681             host => { type => 'TEXT', constant => 'SQL_VARCHAR' },
1682             path => { type => 'TEXT', constant => 'SQL_VARCHAR' },
1683             expiry => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1684             isSecure => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1685             sameSite => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1686             isHttpOnly => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1687             lastAccessed => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1688             creationTime => { type => 'INTEGER', constant => 'SQL_INTEGER' },
1689             };
1690             # To hold the cookies data to be saved
1691 0         0 my $cookies = [];
1692 0         0 my $now = DateTime->now;
1693 0         0 my $can_do_upsert = 0;
1694             my $get_cookies = sub
1695             {
1696 0     0   0 my $c = shift( @_ );
1697 0 0 0     0 return(1) if( $c->discard && $opts->{skip_discard} );
1698 0 0 0     0 return(1) if( $c->expires && $c->expires < $now && $opts->{skip_expired} );
      0        
1699             # Offset 0 is the value, offset 1 is the data type for DBI and offset 2 is the field name used for the sqlite3 binary method
1700             my $row =
1701             [
1702             [$c->name->scalar, $core_fields->{name}->{constant}, 'name'],
1703             [$c->value->scalar, $core_fields->{value}->{constant}, 'value'],
1704             [$c->domain->scalar, $core_fields->{host}->{constant}, 'host'],
1705             [$c->path->scalar, $core_fields->{path}->{constant}, 'path'],
1706             [( $c->expires ? $c->expires->epoch : undef ), $core_fields->{expiry}->{constant}, 'expiry'],
1707             [( $c->secure ? 1 : 0 ), $core_fields->{isSecure}->{constant}, 'isSecure'],
1708             [( $c->same_site->lc eq 'strict' ? 1 : 0 ), $core_fields->{sameSite}->{constant}, 'sameSite'],
1709             [( $c->http_only ? 1 : 0 ), $core_fields->{isHttpOnly}->{constant}, 'isHttpOnly'],
1710             [( $c->accessed_on ? ( $c->accessed_on->epoch * 1000000 ) : undef ), $core_fields->{lastAccessed}->{constant}, 'lastAccessed'],
1711 0 0       0 [( $c->created_on ? ( $c->created_on->epoch * 1000000 ) : undef ), $core_fields->{creationTime}->{constant}, 'creationTime'],
    0          
    0          
    0          
    0          
    0          
1712             ];
1713 0 0       0 if( $can_do_upsert )
1714             {
1715 0         0 push( @$row, [$c->value->scalar, $core_fields->{value}->{constant}, 'value'] );
1716 0 0       0 push( @$row, [( $c->expires ? $c->expires->epoch : undef ), $core_fields->{expiry}->{constant}, 'expiry'] );
1717 0 0       0 push( @$row, [( $c->secure ? 1 : 0 ), $core_fields->{isSecure}->{constant}, 'isSecure'] );
1718 0 0       0 push( @$row, [( $c->same_site->lc eq 'strict' ? 1 : 0 ), $core_fields->{sameSite}->{constant}, 'sameSite'] );
1719 0 0       0 push( @$row, [( $c->http_only ? 1 : 0 ), $core_fields->{isHttpOnly}->{constant}, 'isHttpOnly'] );
1720 0 0       0 push( @$row, [( $c->accessed_on ? ( $c->accessed_on->epoch * 1000000 ) : undef ), $core_fields->{lastAccessed}->{constant}, 'lastAccessed'] );
1721 0 0       0 push( @$row, [( $c->created_on ? ( $c->created_on->epoch * 1000000 ) : undef ), $core_fields->{creationTime}->{constant}, 'creationTime'] );
1722             }
1723 0         0 push( @$cookies, $row );
1724 0         0 };
1725              
1726             # From SQLite version 3.24.0
1727             # update if there is a constraint violation on 'moz_uniqueid', i.e. name, host, path, originAttributes
1728 0         0 my $upsert_sql = <<EOT;
1729             INSERT INTO moz_cookies (name, value, host, path, expiry, isSecure, sameSite, isHttpOnly, lastAccessed, creationTime)
1730             VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1731             ON CONFLICT(name, host, path, originAttributes)
1732             DO UPDATE SET value = ?, expiry = ?, isSecure = ?, sameSite = ?, isHttpOnly = ?, lastAccessed = ?, creationTime = ?
1733             EOT
1734 0         0 my $insert_ignore_sql = <<EOT;
1735             INSERT OR IGNORE INTO moz_cookies (name, value, host, path, expiry, isSecure, sameSite, isHttpOnly, lastAccessed, creationTime)
1736             VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1737             EOT
1738 0         0 my $insert_replace_sql = <<EOT;
1739             INSERT OR REPLACE INTO moz_cookies (name, value, host, path, expiry, isSecure, sameSite, isHttpOnly, lastAccessed, creationTime)
1740             VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1741             EOT
1742            
1743             # Required version for upsert
1744 0         0 my $req_v = version->parse( '3.24.0' );
1745 0         0 my $log_file;
1746 0 0       0 if( $opts->{log_sql} )
1747             {
1748 0   0     0 $log_file = $self->new_file( $opts->{log_sql} ) ||
1749             return( $self->pass_error );
1750             }
1751              
1752             # If the user explicitly required the use of DBI/DBD::SQLite; or
1753             # the user has not explicitly required the use of DBI/DBD::SQLite nor of sqlite3 binary
1754 0 0 0     0 if( $requires_dbi ||
      0        
1755             ( !$opts->{use_dbi} && !$opts->{sqlite} ) )
1756             {
1757             eval
1758 0         0 {
1759 0         0 require DBI;
1760 0         0 require DBD::SQLite;
1761             };
1762 0 0       0 $dbi_error = $@ if( $@ );
1763             # User explicitly required the use of DBI/DBD::SQLite, but it failed, so we return an error
1764 0 0 0     0 if( defined( $dbi_error ) && exists( $opts->{use_dbi} ) && defined( $opts->{use_dbi} ) && $opts->{use_dbi} )
    0 0        
      0        
1765             {
1766 0         0 return( $self->error( "Unable to load either DBI or DBD::SQLite: $@" ) );
1767             }
1768             elsif( !defined( $dbi_error ) )
1769             {
1770 0         0 foreach my $f ( keys( %$core_fields ) )
1771             {
1772             my $code = DBI->can( $core_fields->{ $f }->{constant} ) ||
1773 0   0     0 die( "Invalid data type '", $core_fields->{ $f }->{constant}, "' for DBI." );
1774 0         0 $core_fields->{ $f }->{constant} = $code->();
1775             }
1776            
1777 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1778 0     0   0 {
1779 0   0     0 my $dbh = DBI->connect( "dbi:SQLite:dbname=${sqldb}", '', '', { RaiseError => 1, AutoCommit => 1 } ) ||
1780             die( "Unable to connect to SQLite database file ${sqldb}: ", $DBI::errstr );
1781 0 0       0 if( $opts->{log_sql} )
1782             {
1783 0 0       0 $log_file->open( '>>', { binmode => 'utf-8', autoflush => 1 } ) ||
1784             return( $self->pass_error( $log_file->error ) );
1785             $dbh->sqlite_trace(sub
1786             {
1787 0         0 my $sql = shift( @_ );
1788 0         0 $log_file->print( $sql, "\n" );
1789 0         0 });
1790             }
1791 0         0 my $rv;
1792 0         0 my $version_sql = q{SELECT sqlite_version()};
1793 0   0     0 my $version_sth = $dbh->prepare( $version_sql ) ||
1794             die( "Errror preparing sql query to get the SQLite driver version: ", $dbh->errstr, "\nSQL query was ${version_sql}" );
1795 0   0     0 $rv = $version_sth->execute() ||
1796             die( "Errror executing sql query to get the SQLite driver version: ", $version_sth->errstr, "\nSQL query was ${version_sql}" );
1797 0         0 my $sqlite_version = $version_sth->fetchrow;
1798 0         0 $version_sth->finish;
1799 0         0 my $sql_v = version->parse( $sqlite_version );
1800            
1801 0 0       0 if( $db_file_exists )
1802             {
1803             # my $tbl_check = $dbh->table_info( undef, undef, 'moz_cookies', 'TABLE' ) ||
1804 0   0     0 my $tbl_check = $dbh->prepare( q{SELECT name FROM sqlite_master WHERE type IN ('table') AND name IS 'moz_cookies'} ) ||
1805             die( "Error preparing sql query to check for existence of table 'moz_cookies' in SQLite database file ${sqldb}: ", $dbh->errstr );
1806 0   0     0 $rv = $tbl_check->execute || die( "Error executing query to check existence of table 'moz_cookies': ", $tbl_check->errstr );
1807 0         0 $table_moz_cookies_exists = $tbl_check->fetchrow;
1808 0         0 $tbl_check->finish;
1809 0 0       0 if( $table_moz_cookies_exists )
1810             {
1811             # Drop the table altogether
1812 0 0       0 if( $opts->{overwrite} )
1813             {
1814 0         0 my $drop_sql = q{DROP TABLE moz_cookies};
1815 0   0     0 my $drop_sth = $dbh->prepare( $drop_sql ) ||
1816             die( "Error preparing query to drop existing table moz_cookies in SQLIte database file ${sqldb}: ", $dbh->errstr, "\nSQL query was ${$drop_sql}" );
1817 0   0     0 $rv = $drop_sth->execute() ||
1818             die( "Error executing query to drop existing table moz_cookies in SQLIte database file ${sqldb}: ", $drop_sth->errstr, "\nSQL query was ${$drop_sql}" );
1819 0         0 $drop_sth->finish;
1820 0         0 $table_moz_cookies_exists = 0;
1821             }
1822             else
1823             {
1824             # PRAGMA table_info() returns cid, name, type, notnull, dflt_value, pk
1825 0         0 my $tbl_info_sql = q{PRAGMA TABLE_INFO(moz_cookies)};
1826 0   0     0 my $tbl_info_sth = $dbh->prepare( $tbl_info_sql ) ||
1827             die( "Error while trying to prepare query to get the existing table 'moz_cookies' information: ", $dbh->errstr, "\nSQL query is: ${tbl_info_sql}" );
1828 0   0     0 $rv = $tbl_info_sth->execute ||
1829             die( "Error while trying to execute query to get the existing table 'moz_cookies' information: ", $tbl_info_sth->errstr, "\nSQL query is: ${tbl_info_sql}" );
1830 0         0 my $all = $tbl_info_sth->fetchall_arrayref( {} );
1831 0         0 $tbl_info_sth->finish;
1832             # Check existing table field for missing fields
1833 0         0 my $fields = {};
1834 0         0 foreach my $this ( @$all )
1835             {
1836 0         0 $fields->{ $this->{name} } = $this;
1837             }
1838 0         0 my $missing = [];
1839 0         0 my $bad_datatype = [];
1840 0         0 foreach my $f ( keys( %$core_fields ) )
1841             {
1842 0 0       0 if( !CORE::exists( $fields->{ $f } ) )
    0          
1843             {
1844 0         0 push( @$missing, $f );
1845             }
1846             elsif( $core_fields->{ $f }->{type} ne uc( $fields->{ $f }->{type} ) )
1847             {
1848 0         0 push( @$bad_datatype, $f );
1849             }
1850             }
1851 0 0 0     0 if( scalar( @$missing ) || scalar( @$bad_datatype ) )
1852             {
1853 0         0 return( $self->error( sprintf( "Found an existing SQLite database file ${sqldb} with a table 'moz_cookies', but found %d missing fields (%s) and %d fields with inappropriate data type (%s)", scalar( @$missing ), join( ', ', @$missing ), scalar( @$bad_datatype ), join( ', ', @$bad_datatype ) ) ) );
1854             }
1855             }
1856             }
1857             }
1858            
1859 0         0 my $errors = [];
1860 0         0 my $insert_sth;
1861             # Create the table if it does not exist
1862 0 0       0 if( !$table_moz_cookies_exists )
1863             {
1864 0   0     0 my $create_table_sth = $dbh->prepare( $create_table_sql ) ||
1865             die( "Error preparing query to create table moz_cookies in SQLite database file ${sqldb}: ", $dbh->errstr, "\nSQL query was: ${create_table_sql}" );
1866 0   0     0 my $rv = $create_table_sth->execute() ||
1867             die( "Error executing query to create table moz_cookies in SQLite database file ${sqldb}: ", $create_table_sth->errstr, "\nSQL query was: ${create_table_sql}" );
1868 0         0 $create_table_sth->finish;
1869 0   0     0 $insert_sth = $dbh->prepare( $insert_ignore_sql ) ||
1870             die( "Error preparing the sql query to add/ignore cookies to 'moz_cookies' in SQLite database file ${sqldb}: ", $dbh->errstr, "\nSQL query was: ${insert_ignore_sql}" );
1871             }
1872             # or update the data
1873             else
1874             {
1875 0 0       0 $can_do_upsert = ( $sql_v >= $req_v ) ? 1 : 0;
1876             # if version is greater or equal to 3.24.0 we can do upsert, otherwise we do insert replace
1877 0 0       0 if( $can_do_upsert )
1878             {
1879 0   0     0 $insert_sth = $dbh->prepare( $upsert_sql ) ||
1880             die( "Error preparing the sql query to add cookies to 'moz_cookies' in SQLite database file ${sqldb}: ", $dbh->errstr, "\nSQL query was: ${upsert_sql}" );
1881             }
1882             else
1883             {
1884 0   0     0 $insert_sth = $dbh->prepare( $insert_replace_sql ) ||
1885             die( "Error preparing the sql query to add/replace cookies to 'moz_cookies' in SQLite database file ${sqldb}: ", $dbh->errstr, "\nSQL query was: ${insert_replace_sql}" );
1886             }
1887             }
1888            
1889             # NOTE: call to scan() must be after setting $can_do_upsert
1890 0         0 $self->scan( $get_cookies );
1891            
1892 0 0       0 if( $opts->{rollback} )
1893             {
1894 0         0 $dbh->begin_work;
1895             }
1896            
1897 0         0 foreach my $c ( @$cookies )
1898             {
1899             eval
1900 0         0 {
1901 0         0 for( my $i = 0; $i < scalar( @$c ); $i++ )
1902             {
1903 0 0       0 $insert_sth->bind_param( $i + 1, $c->[$i]->[0], $c->[$i]->[1] ) ||
1904             die( "Error binding parameter No. ", ( $i + 1 ), " with value '", $c->[$i]->[0], "': ", $insert_sth->errstr );
1905             }
1906 0   0     0 $rv = $insert_sth->execute() ||
1907             die( "Failed to execute query to insert cookie '", $c->name->scalar, "' -> ", $insert_sth->errstr, "\nQuery was ${insert_ignore_sql}" );
1908             };
1909 0 0       0 if( $@ )
1910             {
1911             # offset 0 -> name, offset 2 -> domain
1912 0         0 push( @$errors, [$c->[0], $c->[2], $@] );
1913 0 0       0 if( $opts->{rollback} )
1914             {
1915 0         0 $dbh->rollback;
1916 0         0 last;
1917             }
1918             }
1919             }
1920 0         0 $insert_sth->finish;
1921 0         0 $dbh->disconnect;
1922             }
1923 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1924 0     0   0 {
1925 0 0       0 if( $requires_dbi )
1926             {
1927 0         0 return( $self->error( "Error trying to save mozilla cookies to SQLite database ${sqldb} using DBI: $e" ) );
1928             }
1929             else
1930             {
1931 0         0 $dbi_error = $e;
1932 0 0       0 warn( "Non fatal error occurred while trying to save mozilla cookies to SQLite database ${sqldb} using DBI: $e\n" ) if( $self->_warnings_is_enabled );
1933             }
1934 2 0 0 2   18 }
  2 0 0     4  
  2 0 0     5247  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1935             }
1936             }
1937            
1938             # If the user did not require exclusively the use of DBI, but required the use of sqlite3 binary
1939             # the user did not require the use of DBI nor the use of sqlite3 binary
1940 0 0 0     0 if( ( defined( $dbi_error ) && !$requires_dbi ) ||
      0        
      0        
      0        
1941             ( exists( $opts->{sqlite} ) && defined( $opts->{sqlite} ) && CORE::length( $opts->{sqlite} ) ) )
1942             {
1943             # If the user required specific sqlite3 binary
1944 0 0 0     0 if( exists( $opts->{sqlite} ) && defined( $opts->{sqlite} ) && CORE::length( $opts->{sqlite} ) )
      0        
1945             {
1946 0 0       0 if( !-e( $opts->{sqlite} ) )
    0          
1947             {
1948 0         0 return( $self->error( "sqlite3 binary path provided \"$opts->{sqlite}\" does not exist." ) );
1949             }
1950             elsif( !-x( $opts->{sqlite} ) )
1951             {
1952 0         0 return( $self->error( "sqlite3 binary path provided \"$opts->{sqlite}\" is not executable by user id $>" ) );
1953             }
1954 0         0 $sqlite_bin = $opts->{sqlite};
1955             }
1956             else
1957             {
1958 0         0 require File::Which;
1959 0         0 my $bin = File::Which::which( 'sqlite3' );
1960 0 0       0 if( !defined( $bin ) )
1961             {
1962 0         0 return( $self->error( "DBI and/or DBD::SQLite modules are not installed and I could not find thr sqlite3 binary anywhere." ) );
1963             }
1964 0         0 $sqlite_bin = $bin;
1965             }
1966            
1967 0         0 my $fh;
1968             # Get SQLite version
1969             # open( $fh, '-|', $sqlite_bin, "SELECT sqlite_version()" ) ||
1970 0 0       0 open( $fh, '-|', $sqlite_bin, "--version" ) ||
1971             return( $self->error( "Failed to execute sqlite3 binary ${sqlite_bin} to get its version number: $!" ) );
1972 0         0 my $sqlite_version = <$fh>;
1973 0         0 my $sql_v;
1974 0 0       0 if( defined( $sqlite_version ) )
1975             {
1976 0         0 chomp( $sqlite_version );
1977 0         0 $sqlite_version = [split( /[[:blank:]\h]+/, $sqlite_version )]->[0];
1978 0         0 $sql_v = version->parse( $sqlite_version );
1979 0         0 close( $fh );
1980             }
1981            
1982             # Check if table moz_cookies exists
1983 0 0       0 open( $fh, '-|', $sqlite_bin, "${sqldb}", "SELECT name FROM sqlite_master WHERE type IN ('table') AND name IS 'moz_cookies'" ) ||
1984             return( $self->error( "Failed to execute sqlite3 binary ${sqlite_bin} to check if table moz_cookies exists: $!" ) );
1985             # chomp( $table_moz_cookies_exists = <$fh> );
1986 0         0 $table_moz_cookies_exists = <$fh>;
1987 0   0     0 $table_moz_cookies_exists //= '';
1988 0         0 chomp( $table_moz_cookies_exists );
1989 0         0 close( $fh );
1990            
1991             # Now, get the data to save
1992 0 0       0 open( $fh, '|-', $sqlite_bin, '--bail', "${sqldb}" ) ||
1993             return( $self->error( "Failed to execute sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: $!" ) );
1994 0         0 $fh->autoflush;
1995 0 0       0 if( $opts->{log_sql} )
1996             {
1997 0 0       0 print( $fh ".trace ${log_file}\n" ) ||
1998             return( $self->error( "Failed to print sqlite command to enable logging to file ${log_file}: $!" ) );
1999             }
2000 0 0 0     0 if( $table_moz_cookies_exists && $opts->{overwrite} )
2001             {
2002 0 0       0 print( $fh "DROP TABLE IF EXISTS moz_cookies;\n" ) ||
2003             return( $self->error( "Failed to print sql commands to sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: $!" ) );
2004 0         0 $table_moz_cookies_exists = 0;
2005             }
2006            
2007 0 0       0 if( $opts->{rollback} )
2008             {
2009 0 0       0 print( $fh "BEGIN TRANSACTION;\n" ) ||
2010             return( $self->error( "Failed to print sql commands to sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: $!" ) );
2011             }
2012 0         0 my $template;
2013 0 0       0 if( !$table_moz_cookies_exists )
2014             {
2015 0         0 chomp( $create_table_sql );
2016 0 0       0 print( $fh "${create_table_sql};\n" ) ||
2017             return( $self->error( "Failed to print sql commands to sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: $!" ) );
2018 0         0 $template = $insert_ignore_sql;
2019             }
2020             else
2021             {
2022 0 0 0     0 $can_do_upsert = ( defined( $sql_v ) && $sql_v >= $req_v ) ? 1 : 0;
2023             # if version is greater or equal to 3.24.0 we can do upsert, otherwise we do insert replace
2024 0 0       0 if( $can_do_upsert )
2025             {
2026 0         0 $template = $upsert_sql;
2027             }
2028             else
2029             {
2030 0         0 $template = $insert_replace_sql;
2031             }
2032             }
2033 0         0 chomp( $template );
2034             # This stores the data in $cookies array reference
2035             # NOTE: call to scan() must be after setting $can_do_upsert
2036 0         0 $self->scan( $get_cookies );
2037 0         0 my $row = $cookies->[0];
2038 0         0 foreach my $ref ( @$row )
2039             {
2040 0 0       0 if( $core_fields->{ $ref->[2] }->{constant} eq 'SQL_INTEGER' )
2041             {
2042 0         0 $template =~ s/\?/%s/;
2043             }
2044             else
2045             {
2046 0         0 $template =~ s/\?/'%s'/;
2047             }
2048             }
2049            
2050 0         0 foreach my $row ( @$cookies )
2051             {
2052 0         0 my $sql = sprintf( $template, map( $_->[0], @$row ) );
2053             print( $fh "${sql};\n" ) || do
2054 0 0       0 {
2055 0         0 my $err = $!;
2056 0 0       0 if( $opts->{rollback} )
2057             {
2058 0         0 print( $fh "ROLLBACK TRANSACTION;\n" );
2059             }
2060 0         0 return( $self->error( "Failed to print sql commands to sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: ${err}" ) );
2061             };
2062             }
2063            
2064 0 0       0 if( $opts->{rollback} )
2065             {
2066 0 0       0 print( $fh "END TRANSACTION;\n" ) ||
2067             return( $self->error( "Failed to print sql commands to sqlite3 binary ${sqlite_bin} to save all mozilla cookies to database ${sqldb}: $!" ) );
2068             }
2069 0         0 close( $fh );
2070             }
2071              
2072 0 0 0     0 if( $opts->{log_sql} &&
      0        
2073             defined( $log_file ) &&
2074             $log_file->opened )
2075             {
2076 0         0 $log_file->close;
2077             }
2078 0         0 return( $self );
2079             }
2080              
2081             sub save_as_netscape
2082             {
2083 0     0 1 0 my $self = shift( @_ );
2084 0         0 my $opts = $self->_get_args_as_hash( @_ );
2085 0   0     0 $opts->{file} //= '';
2086 0   0     0 $opts->{skip_discard} //= 0;
2087 0   0     0 $opts->{skip_expired} //= 0;
2088 0 0       0 return( $self->error( "No file to write cookies was specified." ) ) if( !$opts->{file} );
2089 0   0     0 my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error );
2090 0   0     0 my $io = $f->open( '>', { binmode => 'utf-8' }) ||
2091             return( $self->error( "Unable to write cookies to file \"$opts->{file}\": ", $f->error ) );
2092 0         0 $io->print( "# Netscape HTTP Cookie File:\n" );
2093 0         0 my $now = DateTime->now;
2094             $self->scan(sub
2095             {
2096 0     0   0 my $c = shift( @_ );
2097 0 0 0     0 return(1) if( $c->discard && $opts->{skip_discard} );
2098 0 0 0     0 return(1) if( $c->expires && $c->expires < $now && $opts->{skip_expired} );
      0        
2099 0         0 my @temp = ( $c->domain );
2100 0 0       0 push( @temp, $c->domain->substr( 1, 1 ) eq '.' ? 'TRUE' : 'FALSE' );
2101 0         0 push( @temp, $c->path );
2102 0 0       0 push( @temp, $c->secure ? 'TRUE' : 'FALSE' );
2103 0         0 push( @temp, $c->expires->epoch );
2104 0         0 push( @temp, $c->name );
2105 0         0 push( @temp, $c->value );
2106 0         0 $io->print( join( "\t", @temp ), "\n" );
2107 0         0 });
2108 0         0 $io->close;
2109 0         0 return( $self );
2110             }
2111              
2112             # For backward compatibility with HTTP::Cookies
2113 1     1 1 26 sub scan { return( shift->do( @_ ) ); }
2114              
2115             # NOTE: the secret key to be used to decrypt or encrypt the cookie jar file
2116 0     0 1 0 sub secret { return( shift->_set_get_scalar( 'secret', @_ ) ); }
2117              
2118             sub set
2119             {
2120 4     4 1 61 my $self = shift( @_ );
2121 4         8 my $c = shift( @_ );
2122 4         26 my $opts = $self->_get_args_as_hash( @_ );
2123 4 50       714 return( $self->error( "No cookie name was provided to set." ) ) if( !$c->name->length );
2124 4 50       163735 return( $self->error( "Cookie value should be an object." ) ) if( !Scalar::Util::blessed( $c ) );
2125 4 50       579 return( $self->error( "Cookie object does not have any as_string method." ) ) if( !$c->can( 'as_string' ) );
2126 4   50     92 $opts->{response} //= '';
2127 4         38 my $r = $self->request;
2128 4 50 33     227 if( $r )
    50 33        
2129             {
2130 0         0 $r->err_headers_out->add( 'Set-Cookie', $c->as_string );
2131             }
2132             elsif( $opts->{response} && $self->_is_object( $opts->{response} ) && $opts->{response}->can( 'header' ) )
2133             {
2134 4         190 $opts->{response}->header( 'Set-Cookie' => $c->as_string );
2135             }
2136             else
2137             {
2138 0         0 return( "Set-Cookie: " . $c->as_string );
2139             }
2140 4         445 return( $self );
2141             }
2142              
2143             # NOTE: cookie jar file type, e.g.: json, lwp or netscape
2144 0     0 1 0 sub type { return( shift->_set_get_scalar( 'type', @_ ) ); }
2145              
2146 33     33   210 sub _cookies { return( shift->_set_get_array_as_object( '_cookies', @_ ) ); }
2147              
2148             sub _encrypt_objects
2149             {
2150 0     0   0 my $self = shift( @_ );
2151 0         0 my( $key, $algo, $iv ) = @_;
2152 0 0 0     0 return( $self->error( "Key provided is empty!" ) ) if( !defined( $key ) || !CORE::length( "$key" ) );
2153 0 0 0     0 return( $self->error( "No algorithm was provided to encrypt cookie value. You can choose any <NAME> for which there exists Crypt::Cipher::<NAME>" ) ) if( !defined( $algo ) || !CORE::length( "$algo" ) );
2154 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2155 0     0   0 {
2156 0 0       0 $self->_load_class( 'Crypt::Mode::CBC', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
2157 0 0       0 $self->_load_class( 'Bytes::Random::Secure' ) || return( $self->pass_error );
2158 0   0     0 my $crypt = Crypt::Mode::CBC->new( "$algo" ) || return( $self->error( "Unable to create a Crypt::Mode::CBC object." ) );
2159 0         0 my $class = "Crypt::Cipher::${algo}";
2160 0 0       0 $self->_load_class( $class ) || return( $self->pass_error );
2161 0         0 my $key_len = $class->keysize;
2162 0         0 my $block_len = $class->blocksize;
2163 0 0       0 return( $self->error( "The size of the key provided (", CORE::length( $key ), ") does not match the minimum key size required for this algorithm \"$algo\" (${key_len})." ) ) if( CORE::length( $key ) < $key_len );
2164             # Generate an "IV", i.e. Initialisation Vector based on the required block size
2165 0 0 0     0 if( defined( $iv ) && CORE::length( "$iv" ) )
2166             {
2167 0 0       0 if( CORE::length( $iv ) != $block_len )
2168             {
2169 0         0 return( $self->error( "The Initialisation Vector provided for cookie encryption has a length (", CORE::length( $iv ), ") which does not match the algorithm ($algo) size requirement ($block_len). Please refer to the Cookie::Jar package documentation." ) );
2170             }
2171             }
2172             else
2173             {
2174 0         0 $iv = Bytes::Random::Secure::random_bytes( $block_len );
2175             # Save it for decryption
2176 0         0 $self->_initialisation_vector( $iv );
2177             }
2178 0         0 my $key_pack = pack( 'H' x $key_len, $key );
2179 0         0 my $iv_pack = pack( 'H' x $block_len, $iv );
2180 0         0 return({ 'crypt' => $crypt, key => $key_pack, iv => $iv_pack });
2181             }
2182 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2183 0     0   0 {
2184 0         0 return( $self->error( "Error getting the encryption objects for algorithm \"$algo\": $e" ) );
2185 2 0 0 2   16 }
  2 0 0     4  
  2 0 0     1153  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2186             }
2187              
2188 25     25   160 sub _index { return( shift->_set_get_hash_as_mix_object( '_index', @_ ) ); }
2189              
2190             # For cookies file encryption
2191 4     4   42 sub _initialisation_vector { return( shift->_set_get_scalar_as_object( '_initialisation_vector', @_ ) ); }
2192              
2193             sub _normalize_path # so that plain string compare can be used
2194             {
2195 0     0   0 my $self = shift( @_ );
2196 0         0 my $str = shift( @_ );
2197 0         0 my $x;
2198 0         0 $str =~ s{
2199             %([0-9a-fA-F][0-9a-fA-F])
2200             }
2201 0         0 {
2202 0 0 0     0 $x = uc( $1 );
2203             $x eq '2F' || $x eq '25' ? "%$x" : pack( 'C', hex( $x ) );
2204 0         0 }egx;
  0         0  
2205 0         0 $str =~ s/([\0-\x20\x7f-\xff])/sprintf( '%%%02X', ord( $1 ) )/eg;
2206             return( $str );
2207             }
2208              
2209             sub DESTROY
2210 8     8   35909 {
2211 8         68 my $self = shift( @_ );
2212 8 50 33     7896 my $file = $self->file;
2213             if( $self->autosave && $file )
2214 0           {
2215 0           my $encrypt = $self->encrypt;
2216 0           my $type = $self->type;
2217             my $type2sub =
2218             {
2219             json => \&save,
2220             lwp => \&save_as_lwp,
2221             mozilla => \&save_as_mozilla,
2222             netscape => \&save_as_netscape,
2223 0 0         };
2224             if( !CORE::exists( $type2sub->{ $type } ) )
2225 0 0         {
2226 0           warn( "Unknown cookie jar type '$type'. This can be either json, lwp or netscape\n" ) if( $self->_warnings_is_enabled );
2227             return;
2228             }
2229 0          
2230             my $unloader = $type2sub->{ $type };
2231 0 0        
2232             if( $encrypt )
2233             {
2234             $unloader->( $self, $file,
2235             algo => $self->algo,
2236             key => $self->secret,
2237 0 0         ) || do
2238 0 0         {
2239             warn( $self->error, "\n" ) if( $self->_warnings_is_enabled );
2240             };
2241             }
2242             else
2243             {
2244 0 0         $unloader->( $self, $file ) || do
2245 0 0         {
2246             warn( $self->error, "\n" ) if( $self->_warnings_is_enabled );
2247             };
2248             }
2249             }
2250             };
2251              
2252             1;
2253             # NOTE: POD
2254             __END__
2255              
2256             =encoding utf8
2257              
2258             =head1 NAME
2259              
2260             Cookie::Jar - Cookie Jar Class for Server & Client
2261              
2262             =head1 SYNOPSIS
2263              
2264             use Cookie::Jar;
2265             my $jar = Cookie::Jar->new( request => $r ) ||
2266             die( "An error occurred while trying to get the cookie jar:", Cookie::Jar->error );
2267             # set the default host
2268             $jar->host( 'www.example.com' );
2269             $jar->fetch;
2270             # or using a HTTP::Request object
2271             # Retrieve cookies from Cookie header sent from client
2272             $jar->fetch( request => $http_request );
2273             if( $jar->exists( 'my-cookie' ) )
2274             {
2275             # do something
2276             }
2277             # get the cookie
2278             my $sid = $jar->get( 'my-cookie' );
2279             # get all cookies
2280             my @all = $jar->get( 'my-cookie', 'example.com', '/' );
2281             # set a new Set-Cookie header
2282             $jar->set( 'my-cookie' => $cookie_object );
2283             # Remove cookie from jar
2284             $jar->delete( 'my-cookie' );
2285             # or using the object itself:
2286             $jar->delete( $cookie_object );
2287              
2288             # Create and add cookie to jar
2289             $jar->add(
2290             name => 'session',
2291             value => 'lang=en-GB',
2292             path => '/',
2293             secure => 1,
2294             same_site => 'Lax',
2295             ) || die( $jar->error );
2296             # or add an existing cookie
2297             $jar->add( $some_cookie_object );
2298              
2299             my $c = $jar->make({
2300             name => 'my-cookie',
2301             domain => 'example.com',
2302             value => 'sid1234567',
2303             path => '/',
2304             expires => '+10D',
2305             # or alternatively
2306             maxage => 864000
2307             # to make it exclusively accessible by regular http request and not ajax
2308             http_only => 1,
2309             # should it be used under ssl only?
2310             secure => 1,
2311             });
2312              
2313             # Add the Set-Cookie headers
2314             $jar->add_response_header;
2315             # Alternatively, using a HTTP::Response object or equivalent
2316             $jar->add_response_header( $http_response );
2317             $jar->delete( 'some_cookie' );
2318             $jar->do(sub
2319             {
2320             # cookie object is available as $_ or as first argument in @_
2321             });
2322              
2323             # For client side
2324             # Takes a HTTP::Response object or equivalent
2325             # Extract cookies from Set-Cookie headers received from server
2326             $jar->extract( $http_response );
2327             # get by domain; by default sort it
2328             my $all = $jar->get_by_domain( 'example.com' );
2329             # Reverse sort
2330             $all = $jar->get_by_domain( 'example.com', sort => 0 );
2331              
2332             # Save cookies repository as json
2333             $jar->save( '/some/where/mycookies.json' ) || die( $jar->error );
2334             # Load cookies into jar
2335             $jar->load( '/some/where/mycookies.json' ) || die( $jar->error );
2336              
2337             # Save encrypted
2338             $jar->save( '/some/where/mycookies.json',
2339             {
2340             encrypt => 1,
2341             key => $key,
2342             iv => $iv,
2343             algo => 'AES',
2344             }) || die( $jar->error );
2345             # Load cookies from encrypted file
2346             $jar->load( '/some/where/mycookies.json',
2347             {
2348             decrypt => 1,
2349             key => $key,
2350             iv => $iv,
2351             algo => 'AES'
2352             }) || die( $jar->error );
2353              
2354             # Merge repository
2355             $jar->merge( $jar2 ) || die( $jar->error );
2356            
2357             # For autosave
2358             my $jar = Cookie::Jar->new(
2359             file => '/some/where/cookies.json',
2360             # True by default
2361             autosave => 1,
2362             encrypt => 1,
2363             secret => 'My big secret',
2364             algo => 'AES',
2365             ) || die( Cookie::Jar->error );
2366              
2367             say "There are ", $jar->length, " cookies in the repository.";
2368            
2369             # Take a string from a Set-Cookie header and get a Cookie object
2370             my $c = $jar->extract_one( $cookie_string );
2371              
2372             =head1 VERSION
2373              
2374             v0.3.0
2375              
2376             =head1 DESCRIPTION
2377              
2378             This is a module to handle L<cookies|Cookie>, according to the latest standard as set by L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265>, both by the http server and the client. Most modules out there are either antiquated, i.e. they do not support latest cookie L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265>, or they focus only on http client side.
2379              
2380             For example, Apache2::Cookie does not work well in decoding cookies, and L<Cookie::Baker> C<Set-Cookie> timestamp format is wrong. They use Mon-09-Jan 2020 12:17:30 GMT where it should be, as per rfc 6265 Mon, 09 Jan 2020 12:17:30 GMT
2381              
2382             Also L<APR::Request::Cookie> and L<Apache2::Cookie> which is a wrapper around L<APR::Request::Cookie> return a cookie object that returns the value of the cookie upon stringification instead of the full C<Set-Cookie> parameters. Clearly they designed it with a bias leaned toward collecting cookies from the browser.
2383              
2384             This module supports modperl and uses a L<Apache2::RequestRec> if provided, or can use package objects that implement similar interface as L<HTTP::Request> and L<HTTP::Response>, or if none of those above are available or provided, this module returns its results as a string.
2385              
2386             This module is also compatible with L<LWP::UserAgent>, so you can use like this:
2387              
2388             use LWP::UserAgent;
2389             use Cookie::Jar;
2390            
2391             my $ua = LWP::UserAgent->new(
2392             cookie_jar => Cookie::Jar->new
2393             );
2394              
2395             It is also compatible with L<HTTP::Promise>, such as:
2396              
2397             use HTTP::Promise;
2398             my $ua = HTTP::Promise->new( cookie_jar => Cookie::Jar->new );
2399              
2400             This module does not die upon error, but instead returns C<undef> and sets an L<error|Module::Generic/error>, so you should always check the return value of a method.
2401              
2402             =head1 METHODS
2403              
2404             =head2 new
2405              
2406             This initiates the package and takes the following parameters:
2407              
2408             =over 4
2409              
2410             =item C<request>
2411              
2412             This is an optional parameter to provide a L<Apache2::RequestRec> object. When provided, it will be used in various methods to get or set cookies from or onto http headers.
2413              
2414             package MyApacheHandler;
2415             use Apache2::Request ();
2416             use Cookie::Jar;
2417            
2418             sub handler : method
2419             {
2420             my( $class, $r ) = @_;
2421             my $jar = Cookie::Jar->new( $r );
2422             # Load cookies;
2423             $jar->fetch;
2424             $r->log_error( "$class: Found ", $jar->repo->length, " cookies." );
2425             $jar->add(
2426             name => 'session',
2427             value => 'lang=en-GB',
2428             path => '/',
2429             secure => 1,
2430             same_site => 'Lax',
2431             );
2432             # Will use Apache2::RequestRec object to set the Set-Cookie headers
2433             $jar->add_response_header || do
2434             {
2435             $r->log_reason( "Unable to add Set-Cookie to response header: ", $jar->error );
2436             return( Apache2::Const::HTTP_INTERNAL_SERVER_ERROR );
2437             };
2438             # Do some more computing
2439             return( Apache2::Const::OK );
2440             }
2441              
2442             =item C<debug>
2443              
2444             Optional. If set with a positive integer, this will activate verbose debugging message
2445              
2446             =back
2447              
2448             =head2 add
2449              
2450             Provided with an hash or hash reference of cookie parameters (see L<Cookie>) and this will create a new L<cookie|Cookie> and add it to the cookie repository.
2451              
2452             Alternatively, you can also provide directly an existing L<cookie object|Cookie>
2453              
2454             my $c = $jar->add( $cookie_object ) || die( $jar->error );
2455              
2456             =head2 add_cookie_header
2457              
2458             This is an alias for L</add_request_header> for backward compatibility with L<HTTP::Cookies>
2459              
2460             =head2 add_request_header
2461              
2462             Provided with a request object, such as, but not limited to L<HTTP::Request> and this will add all relevant cookies in the repository into the C<Cookie> http request header. The object method needs to have the C<header> method in order to get, or set the C<Cookie> or C<Set-Cookie> headers and the C<uri> method.
2463              
2464             As long as the object provided supports the C<uri> and C<header> method, you can provide any class of object you want.
2465              
2466             Please refer to the L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265> for more information on the applicable rule when adding cookies to the outgoing request header.
2467              
2468             Basically, it will add, for a given domain, first all cookies whose path is longest and at path equivalent, the cookie creation date is used, with the earliest first. Cookies who have expired are not sent, and there can be cookies bearing the same name for the same domain in different paths.
2469              
2470             =head2 add_response_header
2471              
2472             # Adding cookie to the repository
2473             $jar->add(
2474             name => 'session',
2475             value => 'lang=en-GB',
2476             path => '/',
2477             secure => 1,
2478             same_site => 'Lax',
2479             ) || die( $jar->error );
2480             # then placing it onto the response header
2481             $jar->add_response_header;
2482              
2483             This is the alter ego to L</add_request_header>, in that it performs the equivalent function, but for the server side.
2484              
2485             You can optionally provide, as unique argument, an object, such as but not limited to, L<HTTP::Response>, as long as that class supports the C<header> method
2486              
2487             Alternatively, if an L<Apache object|Apache2::RequestRec> has been set upon object instantiation or later using the L</request> method, then it will be used to set the outgoing C<Set-Cookie> headers (there is one for every cookie sent).
2488              
2489             If no response, nor Apache2 object were set, then this will simply return a list of C<Set-Cookie> in list context, or a string of possibly multiline C<Set-Cookie> headers, or an empty string if there is no cookie found to be sent.
2490              
2491             Be careful not to do the following:
2492              
2493             # get cookies sent by the http client
2494             $jar->fetch || die( $jar->error );
2495             # set the response headers with the cookies from our repository
2496             $jar->add_response_header;
2497              
2498             Why? Well, because L</fetch> retrieves the cookies sent by the http client and store them into the repository. However, cookies sent by the http client only contain the cookie name and value, such as:
2499              
2500             GET /my/path/ HTTP/1.1
2501             Host: www.example.org
2502             Cookie: session_token=eyJleHAiOjE2MzYwNzEwMzksImFsZyI6IkhTMjU2In0.eyJqdGkiOiJkMDg2Zjk0OS1mYWJmLTRiMzgtOTE1ZC1hMDJkNzM0Y2ZmNzAiLCJmaXJzdF9uYW1lIjoiSm9obiIsImlhdCI6MTYzNTk4NDYzOSwiYXpwIjoiNGQ0YWFiYWQtYmJiMy00ODgwLThlM2ItNTA0OWMwZTczNjBlIiwiaXNzIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwibGFzdF9uYW1lIjoiRG9lIiwic3ViIjoiYXV0aHxlNzg5OTgyMi0wYzlkLTQyODctYjc4Ni02NTE3MjkyYTVlODIiLCJjbGllbnRfaWQiOiJiZTI3N2VkYi01MDgzLTRjMWEtYTM4MC03Y2ZhMTc5YzA2ZWQiLCJleHAiOjE2MzYwNzEwMzksImF1ZCI6IjRkNGFhYmFkLWJiYjMtNDg4MC04ZTNiLTUwNDljMGU3MzYwZSJ9.VSiSkGIh41xXIVKn9B6qGjfzcLlnJAZ9jGOPVgXASp0; csrf_token=9849724969dbcffd48c074b894c8fbda14610dc0ae62fac0f78b2aa091216e0b.1635825594; site_prefs=lang%3Den-GB
2503              
2504             As you can see, 3 cookies were sent: C<session_token>, C<csrf_token> and C<site_prefs>
2505              
2506             So, when L</fetch> creates an object for each one and store them, those cookies have no C<path> value and no other attribute, and when L</add_response_header> is then called, it stringifies the cookies and create a C<Set-Cookie> header for each one, but only with their value and no other attribute.
2507              
2508             The http client, when receiving those cookies will derive the missing cookie path to be C</my/path>, i.e. the current uri path, and will create a duplicate cookie from the previously stored cookie with the same name for that host, but that had the path set to C</>
2509              
2510             So you can create a repository and use it to store the cookies sent by the http client using L</fetch>, but in preparation of the server response, either use a separate repository with, for example, C<< my $jar_out = Cookie::Jar->new >> or use L</set> which will not add the cookie to the repository, but rather only set the C<Set-Cookie> header for that cookie.
2511              
2512             # Add Set-Cookie header for that cookie, but do not add cookie to repository
2513             $jar->set( $cookie_object );
2514              
2515             =head2 algo
2516              
2517             String. Sets or gets the algorithm to use when loading or saving the cookie jar.
2518              
2519             =head2 autosave
2520              
2521             Boolean. Sets or gets the boolean value for automatically saving the cookie jar to the given file specified with L</file>
2522              
2523             =head2 delete
2524              
2525             Given a cookie name, an optional host and optional path or a L<Cookie> object, and this will remove it from the cookie repository.
2526              
2527             It returns an L<array object|Module::Generic::Array> upon success, or L<perlfunc/undef> and sets an L<error|Module::Generic/error>. Note that the array object may be empty.
2528              
2529             However, this will NOT remove it from the web browser by sending a Set-Cookie header. For that, you might want to look at the L<Cookie/elapse> method.
2530              
2531             It returns an L<array object|Module::Generic::Array> of cookie objects removed.
2532              
2533             my $arr = $jar->delete( 'my-cookie' );
2534             # alternatively
2535             my $arr = $jar->delete( 'my-cookie' => 'www.example.org' );
2536             # or
2537             my $arr = $jar->delete( $my_cookie_object );
2538             printf( "%d cookie(s) removed.\n", $arr->length );
2539             print( "Cookie value removed was: ", $arr->first->value, "\n" );
2540              
2541             If you are interested in telling the http client to remove all your cookies, you can set the C<Clear-Site-Data> header:
2542              
2543             Clear-Site-Data: "cookies"
2544              
2545             You can instruct the http client to remove other data like local storage:
2546              
2547             Clear-Site-Data: "cookies", "cache", "storage", "executionContexts"
2548              
2549             Although this is widely supported, there is no guarantee the http client will actually comply with this request.
2550              
2551             See L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data> for more information.
2552              
2553             =head2 do
2554              
2555             Provided with an anonymous code or reference to a subroutine, and this will call that code for every cookie in the repository, passing it the cookie object as the sole argument. Also, that cookie object is accessible using C<$_>.
2556              
2557             If the code return C<undef>, it will end the loop, and if the code returns true, this will have the current cookie object added to an L<array object|Module::Generic::Array> returned upon completion of the loop.
2558              
2559             my $found = $jar->do(sub
2560             {
2561             # Part of the path
2562             if( index( $path, $_->path ) == 0 )
2563             {
2564             return(1);
2565             }
2566             return(0);
2567             });
2568             print( "Found cookies: ", $found->map(sub{$_->name})->join( ',' ), "\n" );
2569              
2570             =head2 encrypt
2571              
2572             Boolean. Sets or gets the boolean value for whether to encrypt or not the cookie jar when saving it, or whether to decrypt it when loading cookies from it.
2573              
2574             This defaults to false.
2575              
2576             =head2 exists
2577              
2578             Given a cookie name, this will check if it exists.
2579              
2580             It returns 1 if it does, or 0 if it does not.
2581              
2582             =head2 extract
2583              
2584             Provided with a response object, such as, but not limited to L<HTTP::Response>, and this will retrieve any cookie sent from the remote server, parse them and add their respective to the repository.
2585              
2586             As per the L<rfc6265, section 5.3.11 specifications|https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> if there are duplicate cookies for the same domain and path, only the last one will be retained.
2587              
2588             If the cookie received does not contain any C<Domain> specification, then, in line with rfc6265 specifications, it will take the root of the current domain as the default domain value. Since finding out what is the root for a domain name is a non-trivial exercise, this method relies on L<Cookie::Domain>.
2589              
2590             =head2 extract_cookies
2591              
2592             This is an alias for L</extract> for backward compatibility with L<HTTP::Cookies>
2593              
2594             =head2 extract_one
2595              
2596             This method takes a cookie string, which can be found in the C<Set-Cookie> header, parse it, and returns a L<Cookie> object if successful, or sets an L<error|Module::Generic/error> and return C<undef> or an empty list depending on the context.
2597              
2598             It also takes an hash or hash reference of options.
2599              
2600             The following options are supported:
2601              
2602             =over 4
2603              
2604             =item * C<host>
2605              
2606             If provided, it will be used to find out the host's root domain, and to set the cookie object C<domain> property if none is specified in the cookie string.
2607              
2608             =item * C<path>
2609              
2610             If provided, it will be used to set the cookie object C<path> property.
2611              
2612             =item * C<port>
2613              
2614             If provided, it will be used to set the cookie object C<port> property.
2615              
2616             =back
2617              
2618             =head2 fetch
2619              
2620             This method does the equivalent of L</extract>, but for the server.
2621              
2622             It retrieves all possible cookies from the http request received from the web browser.
2623              
2624             It takes an optional hash or hash reference of parameters, such as C<host>. If it is not provided, the value set with L</host> is used instead.
2625              
2626             If the parameter C<request> containing an http request object, such as, but not limited to L<HTTP::Request>, is provided, it will use it to get the C<Cookie> header value. The object method needs to have the C<header> method in order to get, or set the C<Cookie> or C<Set-Cookie> headers.
2627              
2628             Alternatively, if a value for L</request> has been set, it will use it to get the C<Cookie> header value from Apache modperl.
2629              
2630             You can also provide the C<Cookie> string to parse by providing the C<string> option to this method.
2631              
2632             $jar->fetch( string => q{foo=bar; site_prefs=lang%3Den-GB} ) ||
2633             die( $jar->error );
2634              
2635             Ultimately, if none of those are available, it will use the environment variable C<HTTP_COOKIE>
2636              
2637             If the option C<store> is true (by default it is true), this method will add the fetched cookies to the L<repository|/repo>.
2638              
2639             It returns an hash reference of cookie key => L<cookie object|Cookie>
2640              
2641             A cookie key is made of the host (possibly empty), the path and the cookie name separated by C<;>
2642              
2643             # Cookies added to the repository
2644             $jar->fetch || die( $jar->error );
2645             # Cookies returned, but NOT added to the repository
2646             my $cookies = $jar->fetch || die( $jar->error );
2647              
2648             =head2 file
2649              
2650             Sets or gets the file path to the cookie jar file.
2651              
2652             If provided upon instantiation, and if the file exists on the filesystem and is not empty, C<Cookie::Jar> will load all the cookies from it.
2653              
2654             If L</autosave> is set to a true, C<Cookie::Jar> will automatically save all cookies to the specified cookie jar file, possibly encrypting it if L</algo> and L</secret> are set.
2655              
2656             =head2 get
2657              
2658             Given a cookie name, an optional host and an optional path, this will retrieve its corresponding L<cookie object|Cookie> and return it.
2659              
2660             If not found, it will try to return a value with just the cookie name.
2661              
2662             If nothing is found, this will return and empty list in list context or C<undef> in scalar context.
2663              
2664             You can C<get> multiple cookie object and this method will return a list in list context and the first cookie object found in scalar context.
2665              
2666             # Wrong, an undefined returned value here only means there is no such cookie
2667             my $c = $jar->get( 'my-cookie' );
2668             die( $jar->error ) if( !defined( $c ) );
2669             # Correct
2670             my $c = $jar->get( 'my-cookie' ) || die( "No cookie my-cookie found\n" );
2671             # Possibly get multiple cookie object for the same name
2672             my @cookies = $jar->get( 'my_same_name' ) || die( "No cookies my_same_name found\n" );
2673             # or
2674             my @cookies = $jar->get( 'my_same_name' => 'www.example.org', '/private' ) || die( "No cookies my_same_name found\n" );
2675              
2676             =head2 get_by_domain
2677              
2678             Provided with a host and an optional hash or hash reference of parameters, and this returns an L<array object|Module::Generic::Array> of L<cookie objects|Cookie> matching the domain specified.
2679              
2680             If a C<sort> parameter has been provided and its value is true, this will sort the cookies by path alphabetically. If the sort value exists, but is false, this will sort the cookies by path but in a reverse alphabetical order.
2681              
2682             By default, the cookies are sorted.
2683              
2684             =head2 host
2685              
2686             Sets or gets the default host. This is especially useful for cookies repository used on the server side.
2687              
2688             =head2 key
2689              
2690             Provided with a cookie name and an optional host and this returns a key used to add an entry in the hash repository.
2691              
2692             If no host is provided, the key is just the cookie, otherwise the resulting key is the cookie name and host separated just by C<;>
2693              
2694             You should not need to use this method as it is used internally only.
2695              
2696             =head2 load
2697              
2698             $jar->load( '/home/joe/cookies.json' ) || die( $jar->error );
2699              
2700             # or loading cookies from encrypted file
2701             $jar->load( '/home/joe/cookies_encrypted.json',
2702             {
2703             decrypt => 1,
2704             key => $key,
2705             iv => $iv,
2706             algo => 'AES'
2707             }) || die( $jar->error );
2708              
2709             Give a json cookie file, and an hash or hash reference of options, and this will load its data into the repository. If there are duplicates (same cookie name and host), the latest one added takes precedence, as per the rfc6265 specifications.
2710              
2711             Supported options are:
2712              
2713             =over 4
2714              
2715             =item I<algo> string
2716              
2717             Algorithm to use to decrypt the cookie file.
2718              
2719             It can be any of L<AES|Crypt::Cipher::AES>, L<Anubis|Crypt::Cipher::Anubis>, L<Blowfish|Crypt::Cipher::Blowfish>, L<CAST5|Crypt::Cipher::CAST5>, L<Camellia|Crypt::Cipher::Camellia>, L<DES|Crypt::Cipher::DES>, L<DES_EDE|Crypt::Cipher::DES_EDE>, L<KASUMI|Crypt::Cipher::KASUMI>, L<Khazad|Crypt::Cipher::Khazad>, L<MULTI2|Crypt::Cipher::MULTI2>, L<Noekeon|Crypt::Cipher::Noekeon>, L<RC2|Crypt::Cipher::RC2>, L<RC5|Crypt::Cipher::RC5>, L<RC6|Crypt::Cipher::RC6>, L<SAFERP|Crypt::Cipher::SAFERP>, L<SAFER_K128|Crypt::Cipher::SAFER_K128>, L<SAFER_K64|Crypt::Cipher::SAFER_K64>, L<SAFER_SK128|Crypt::Cipher::SAFER_SK128>, L<SAFER_SK64|Crypt::Cipher::SAFER_SK64>, L<SEED|Crypt::Cipher::SEED>, L<Skipjack|Crypt::Cipher::Skipjack>, L<Twofish|Crypt::Cipher::Twofish>, L<XTEA|Crypt::Cipher::XTEA>, L<IDEA|Crypt::Cipher::IDEA>, L<Serpent|Crypt::Cipher::Serpent> or simply any <NAME> for which there exists Crypt::Cipher::<NAME>
2720              
2721             =item I<decrypt> boolean
2722              
2723             Must be set to true to enable decryption.
2724              
2725             =item I<iv> string
2726              
2727             Set the L<Initialisation Vector|https://en.wikipedia.org/wiki/Initialization_vector> used for file encryption and decryption. This must be the same value used for encryption. See L</save>
2728              
2729             =item I<key> string
2730              
2731             Set the encryption key used to decrypt the cookies file.
2732              
2733             The key must be the same one used to encrypt the file. See L</save>
2734              
2735             =back
2736              
2737             L</load> returns the current object upon success and C<undef> and sets an L<error|Module::Generic/error> upon error.
2738              
2739             =head2 load_as_lwp
2740              
2741             $jar->load_as_lwp( '/home/joe/cookies_lwp.txt' ) ||
2742             die( "Unable to load cookies from file: ", $jar->error );
2743              
2744             # or loading an encrypted file
2745             $jar->load_as_lwp( '/home/joe/cookies_encrypted_lwp.txt',
2746             {
2747             encrypt => 1,
2748             key => $key,
2749             iv => $iv,
2750             algo => 'AES',
2751             }) || die( $jar->error );
2752              
2753             Given a file path to an LWP-style cookie file (see below a snapshot of what it looks like), and an hash or hash reference of options, and this method will read the cookies from the file and add them to our repository, possibly overwriting previous cookies with the same name and domain name.
2754              
2755             The supported options are the same as for L</load>
2756              
2757             LWP-style cookie files are ancient, and barely used anymore, but no matter; if you need to load cookies from such file, it looks like this:
2758              
2759             #LWP-Cookies-1.0
2760             Set-Cookie3: cookie1=value1; domain=example.com; path=; path_spec; secure; version=2
2761             Set-Cookie3: cookie2=value2; domain=api.example.com; path=; path_spec; secure; version=2
2762             Set-Cookie3: cookie3=value3; domain=img.example.com; path=; path_spec; secure; version=2
2763              
2764             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2765              
2766             =head2 load_as_mozilla
2767              
2768             $jar->load_as_mozilla( '/home/joe/cookies.sqlite' ) ||
2769             die( "Unable to load cookies from mozilla cookies.sqlite file: ", $jar->error );
2770              
2771             Given a file path to a mozilla SQLite database file, and an hash or hash reference of options, and this method will attempt to read the cookies from the SQLite database file and add them to our repository, possibly overwriting previous cookies with the same name and domain name.
2772              
2773             To read the SQLite database file, this will try first to load L<DBI> and L<DBD::SQLite> and use them if they are available, otherwise it will resort to using the C<sqlite3> binary if it can find it, using L<File::Which/which>
2774              
2775             If none of those 2 methods succeeded, it will return C<undef> with an L<error|Module::Generic/error>
2776              
2777             Note that contrary to other loading method, this method does not support encryption.
2778              
2779             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2780              
2781             Supported options are:
2782              
2783             =over 4
2784              
2785             =item * C<use_dbi>
2786              
2787             Boolean. If true, this will require the use of L<DBI> and L<DBD::SQLite> and if it cannot load them, it will return an error without trying to alternatively use the C<sqlite3> binary. Default to false.
2788              
2789             =item * C<sqlite>
2790              
2791             String. The file path to a C<sqlite3> binary. If the file path does not exist, or is lacking sufficient permission, this will return an error.
2792              
2793             If it is not provided, and using L<DBI> and L<DBD::SQLite> failed, it will try to find the C<sqlite3> using L<File::Which/which>
2794              
2795             =back
2796              
2797             =head2 load_as_netscape
2798              
2799             $jar->save_as_netscape( '/home/joe/cookies_netscape.txt' ) ||
2800             die( "Unable to save cookies file: ", $jar->error );
2801              
2802             # or saving as an encrypted file
2803             $jar->save_as_netscape( '/home/joe/cookies_encrypted_netscape.txt',
2804             {
2805             encrypt => 1,
2806             key => $key,
2807             iv => $iv,
2808             algo => 'AES',
2809             }) || die( $jar->error );
2810              
2811             Given a file path to a Netscape-style cookie file, and this method will read cookies from the file and add them to our repository, possibly overwriting previous cookies with the same name and domain name.
2812              
2813             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2814              
2815             =head2 make
2816              
2817             Provided with some parameters and this will instantiate a new L<Cookie> object with those parameters and return the new object.
2818              
2819             This does not add the newly created cookie object to the cookies repository.
2820              
2821             For a list of supported parameters, refer to the L<Cookie documentation|Cookie>
2822              
2823             # Make an encrypted cookie
2824             use Bytes::Random::Secure ();
2825             my $c = $jar->make(
2826             name => 'session',
2827             value => $secret_value,
2828             path => '/',
2829             secure => 1,
2830             http_only => 1,
2831             same_site => 'Lax',
2832             key => Bytes::Random::Secure::random_bytes(32),
2833             algo => $algo,
2834             encrypt => 1,
2835             ) || die( $jar->error );
2836             # or as an hash reference of parameters
2837             my $c = $jar->make({
2838             name => 'session',
2839             value => $secret_value,
2840             path => '/',
2841             secure => 1,
2842             http_only => 1,
2843             same_site => 'Lax',
2844             key => Bytes::Random::Secure::random_bytes(32),
2845             algo => $algo,
2846             encrypt => 1,
2847             }) || die( $jar->error );
2848              
2849             =head2 merge
2850              
2851             Provided with another L<Cookie::Jar> object, or at least an object that supports the L</do> method, which takes an anonymous code as argument, and that calls that code passing it each cookie object found in the alternate repository, and this method will add all those cookies in the alternate repository into the current repository.
2852              
2853             $jar->merge( $other_jar ) || die( $jar->error );
2854              
2855             If the cookie objects passed to the anonymous code in this method, are not L<Cookie> object, then at least they must support the methods C<name>, C<value>, C<domain>, C<path>, C<port>, C<secure>, C<max_age>, C<secure>, C<same_site> and , C<http_only>
2856              
2857             This method also takes an hash or hash reference of options:
2858              
2859             =over 4
2860              
2861             =item I<die> boolean
2862              
2863             If true, the anonymous code passed to the C<do> method called, will die upon error. Default to false.
2864              
2865             By default, if an error occurs, C<undef> is returned and the L<error|Module::Generic/error> is set.
2866              
2867             =item I<overwrite> boolean
2868              
2869             If true, when an existing cookie is found it will be overwritten by the new one. Default to false.
2870              
2871             =back
2872              
2873             use Nice::Try;
2874             try
2875             {
2876             $jar->merge( $other_jar, die => 1, overwrite => 1 );
2877             }
2878             catch( $e )
2879             {
2880             die( "Failed to merge cookies repository: $e\n" );
2881             }
2882              
2883             Upon success this will return the current object, and if there was an error, this returns L<perlfunc/undef> and sets an L<error|Module::Generic/error>
2884              
2885             =head2 parse
2886              
2887             This method is used by L</fetch> to parse cookies sent by http client. Parsing is much simpler than for http client receiving cookies from server.
2888              
2889             It takes the raw C<Cookie> string sent by the http client, and returns an hash reference (possibly empty) of cookie name to cookie value pairs.
2890              
2891             my $cookies = $jar->parse( 'foo=bar; site_prefs=lang%3Den-GB' );
2892             # You can safely do as well:
2893             my $cookies = $jar->parse( '' );
2894              
2895             =head2 purge
2896              
2897             Thise takes no argument and will remove from the repository all cookies that have expired. A cookie that has expired is a L<Cookie> that has its C<expires> property set and whose value is in the past.
2898              
2899             This returns an L<array object|Module::Generic::Array> of all the cookies thus removed.
2900              
2901             my $all = $jar->purge;
2902             printf( "Cookie(s) removed were: %s\n", $all->map(sub{ $_->name })->join( ',' ) );
2903             # or
2904             printf( "%d cookie(s) removed from our repository.\n", $jar->purge->length );
2905              
2906             =head2 replace
2907              
2908             Provided with a L<Cookie> object, and an optional other L<Cookie> object, and this method will replace the former cookie provided in the second parameter with the new one provided in the first parameter.
2909              
2910             If only one parameter is provided, the cookies to be replaced will be derived from the replacement cookie's properties, namely: C<name>, C<domain> and C<path>
2911              
2912             It returns an L<array object|Module::Generic::Array> of cookie objects replaced upon success, or C<undef> and set an L<error|Module::Generic/error> upon error.
2913              
2914             =head2 repo
2915              
2916             Set or get the L<array object|Module::Generic::Array> used as the cookie jar repository.
2917              
2918             printf( "%d cookies found\n", $jar->repo->length );
2919              
2920             =head2 request
2921              
2922             Set or get the L<Apache2::RequestRec> object. This object is used to set the C<Set-Cookie> header within modperl.
2923              
2924             =head2 save
2925              
2926             $jar->save( '/home/joe/cookies.json' ) ||
2927             die( "Failed to save cookies: ", $jar->error );
2928              
2929             # or saving the cookies file encrypted
2930             $jar->save( '/home/joe/cookies_encrypted.json',
2931             {
2932             encrypt => 1,
2933             key => $key,
2934             iv => $iv,
2935             algo => 'AES',
2936             }) || die( $jar->error );
2937              
2938             Provided with a file, and an hash or hash reference of options, and this will save the repository of cookies as json data.
2939              
2940             The hash saved to file contains 2 top properties: C<updated_on> containing the last update date and C<cookies> containing an hash of cookie name to cookie properties pairs.
2941              
2942             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
2943              
2944             Supported options are:
2945              
2946             =over 4
2947              
2948             =item I<algo> string
2949              
2950             Algorithm to use to encrypt the cookie file.
2951              
2952             It can be any of L<AES|Crypt::Cipher::AES>, L<Anubis|Crypt::Cipher::Anubis>, L<Blowfish|Crypt::Cipher::Blowfish>, L<CAST5|Crypt::Cipher::CAST5>, L<Camellia|Crypt::Cipher::Camellia>, L<DES|Crypt::Cipher::DES>, L<DES_EDE|Crypt::Cipher::DES_EDE>, L<KASUMI|Crypt::Cipher::KASUMI>, L<Khazad|Crypt::Cipher::Khazad>, L<MULTI2|Crypt::Cipher::MULTI2>, L<Noekeon|Crypt::Cipher::Noekeon>, L<RC2|Crypt::Cipher::RC2>, L<RC5|Crypt::Cipher::RC5>, L<RC6|Crypt::Cipher::RC6>, L<SAFERP|Crypt::Cipher::SAFERP>, L<SAFER_K128|Crypt::Cipher::SAFER_K128>, L<SAFER_K64|Crypt::Cipher::SAFER_K64>, L<SAFER_SK128|Crypt::Cipher::SAFER_SK128>, L<SAFER_SK64|Crypt::Cipher::SAFER_SK64>, L<SEED|Crypt::Cipher::SEED>, L<Skipjack|Crypt::Cipher::Skipjack>, L<Twofish|Crypt::Cipher::Twofish>, L<XTEA|Crypt::Cipher::XTEA>, L<IDEA|Crypt::Cipher::IDEA>, L<Serpent|Crypt::Cipher::Serpent> or simply any <NAME> for which there exists Crypt::Cipher::<NAME>
2953              
2954             =item I<encrypt> boolean
2955              
2956             Must be set to true to enable encryption.
2957              
2958             =item I<iv> string
2959              
2960             Set the L<Initialisation Vector|https://en.wikipedia.org/wiki/Initialization_vector> used for file encryption. If you do not provide one, it will be automatically generated. If you want to provide your own, make sure the size meets the encryption algorithm size requirement. You also need to keep this to decrypt the cookies file.
2961              
2962             To find the right size for the Initialisation Vector, for example for algorithm C<AES>, you could do:
2963              
2964             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->blocksize'
2965              
2966             which would yield C<16>
2967              
2968             =item I<key> string
2969              
2970             Set the encryption key used to encrypt the cookies file.
2971              
2972             The key must be the same one used to decrypt the file and must have a size big enough to satisfy the encryption algorithm requirement, which you can check with, say for C<AES>:
2973              
2974             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->keysize'
2975              
2976             In this case, it will yield C<32>. Replace above C<AES>, by whatever algorithm you have chosen.
2977              
2978             perl -MCrypt::Cipher::Blowfish -lE 'say Crypt::Cipher::Blowfish->keysize'
2979              
2980             would yield C<56> for C<Blowfish>
2981              
2982             You can use L<Bytes::Random::Secure/random_bytes> to generate a random key:
2983              
2984             # will generate a 32 bytes-long key
2985             my $key = Bytes::Random::Secure::random_bytes(32);
2986              
2987             =back
2988              
2989             When encrypting the cookies file, this method will encode the encrypted data in base64 before saving it to file.
2990              
2991             =head2 save_as_lwp
2992              
2993             $jar->save_as_lwp( '/home/joe/cookies_lwp.txt' ) ||
2994             die( "Unable to save cookies file: ", $jar->error );
2995              
2996             # or saving as an encrypted file
2997             $jar->save_as_lwp( '/home/joe/cookies_encrypted_lwp.txt',
2998             {
2999             encrypt => 1,
3000             key => $key,
3001             iv => $iv,
3002             algo => 'AES',
3003             }) || die( $jar->error );
3004              
3005             Provided with a file, and an hash or hash reference of options, and this save the cookies repository as a LWP-style data.
3006              
3007             The supported options are the same as for L</save>
3008              
3009             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3010              
3011             =head2 save_as_mozilla
3012              
3013             $jar->save_as_mozilla( '/home/joe/cookies.sqlite' ) ||
3014             die( "Unable to save cookies as mozilla SQLite database: ", $jar->error );
3015              
3016             # or
3017             $jar->save_as_mozilla( '/home/joe/cookies.sqlite',
3018             {
3019             # force use of DBI/DBD::SQLite
3020             use_dbi => 1,
3021             # or specify the path of the sqlite3 binary
3022             # sqlite => '/some/where/sqlite3',
3023             # Enable logging of SQL queries maybe?
3024             # log_sql => '/some/where/sql.log',
3025             # Overwrite previous data
3026             overwrite => 1,
3027             # abort if an error occurred
3028             rollback => 1,
3029             }) || die( "Unable to save cookies as mozilla SQLite database: ", $jar->error );
3030              
3031             Provided with a file path to a SQLite database and this saves the cookies repository as a mozilla SQLite database.
3032              
3033             The structure of the L<mozilla SQLite database|http://kb.mozillazine.org/Cookies> is:
3034              
3035             CREATE TABLE moz_cookies(
3036             id INTEGER PRIMARY KEY,
3037             originAttributes TEXT NOT NULL DEFAULT '',
3038             name TEXT,
3039             value TEXT,
3040             host TEXT,
3041             path TEXT,
3042             expiry INTEGER,
3043             lastAccessed INTEGER,
3044             creationTime INTEGER,
3045             isSecure INTEGER,
3046             isHttpOnly INTEGER,
3047             inBrowserElement INTEGER DEFAULT 0,
3048             sameSite INTEGER DEFAULT 0,
3049             rawSameSite INTEGER DEFAULT 0,
3050             schemeMap INTEGER DEFAULT 0,
3051             CONSTRAINT moz_uniqueid UNIQUE(name, host, path, originAttributes)
3052             );
3053              
3054             This method will attempt loading L<DBI> and L<DBD::SQLite>, and if it fails, it will alternatively try to use the C<sqlite3> binary.
3055              
3056             Note that, contrary to other save methods, this method does not allow encrypting the SQLite database.
3057              
3058             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3059              
3060             Supported options are:
3061              
3062             =over 4
3063              
3064             =item * C<log_sql>
3065              
3066             String. This specifies a file name that will be opened in append mode and to which the SQL statements issued will be logged.
3067              
3068             =item * C<overwrite>
3069              
3070             Boolean. If true, this will overwrite any existing data if the specified SQLite database file already exists.
3071              
3072             And if false, this will issue sql queries to perform L<upsert|https://www.sqlite.org/lang_UPSERT.html> if the SQLite version is greater or equal to C<3.24.0> (2018-06-04), or otherwise it will issue L<INSERT OR REPLACE|https://www.sqlite.org/lang_insert.html> queries.
3073              
3074             Default false.
3075              
3076             =item * C<rollback>
3077              
3078             Boolean. If true, this will cancel, i.e. rollback, any change mad to the SQLite database upon error, otherwise, any change made will be kept up to the point of when the error occurred. Default to false.
3079              
3080             =item * C<skip_discard>
3081              
3082             Boolean. If true, this will not save cookies that have been marked as being discarded, such as session cookies. Default false.
3083              
3084             =item * C<skip_expired>
3085              
3086             Boolean. If true, this will not save the cookies that have already expired. Default false.
3087              
3088             =item * C<sqlite>
3089              
3090             String. The file path to a C<sqlite3> binary. If the file path does not exist, or is lacking sufficient permission, this will return an error.
3091              
3092             If it is not provided, and using L<DBI> and L<DBD::SQLite> failed, it will try to find the C<sqlite3> using L<File::Which/which>
3093              
3094             =item * C<use_dbi>
3095              
3096             Boolean. Requires the use of L<DBI> and L<DBD::SQLite> and it will return an error if those are not installed.
3097              
3098             If you want to let this method try also to use C<sqlite3> binary if necessary, then do not set this option.
3099              
3100             =back
3101              
3102             =head2 save_as_netscape
3103              
3104             Provided with a file and this saves the cookies repository as a Netscape-style data.
3105              
3106             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3107              
3108             =head2 scan
3109              
3110             This is an alias for L</do>
3111              
3112             =head2 secret
3113              
3114             String. Sets or gets the secret string to use for decrypting or encrypting the cookie jar. This is used in conjonction with L</file>, L</encrypt> and L</algo>
3115              
3116             =head2 set
3117              
3118             Given a cookie object, and an optional hash or hash reference of parameters, and this will add the cookie to the outgoing http headers using the C<Set-Cookie> http header. To do so, it uses the L<Apache2::RequestRec> value set in L</request>, if any, or a L<HTTP::Response> compatible response object provided with the C<response> parameter.
3119              
3120             $jar->set( $c, response => $http_response_object ) ||
3121             die( $jar->error );
3122              
3123             Ultimately if none of those two are provided it returns the C<Set-Cookie> header as a string.
3124              
3125             # Returns something like:
3126             # Set-Cookie: my-cookie=somevalue
3127             print( STDOUT $jar->set( $c ), "\015\012" );
3128              
3129             Unless the latter, this method returns the current object.
3130              
3131             =head2 type
3132              
3133             String. Sets or gets the cookie jar file format type. The supported formats are: C<json>, C<lwp> and C<netscape>
3134              
3135             =head1 IMPORTING COOKIES
3136              
3137             To import cookies, you can either use the methods L<scan|HTTP::Cookies/scan> from L<HTTP::Cookies>, such as:
3138              
3139             use Cookie::Jar;
3140             use HTTP::Cookies;
3141             my $jar = Cookie::Jar->new;
3142             my $old = HTTP::Cookies->new;
3143             $old->load( '/home/joe/old_cookies_file.txt' );
3144             my @keys = qw( version key val path domain port path_spec secure expires discard hash );
3145             $old->scan(sub
3146             {
3147             my @values = @_;
3148             my $ref = {};
3149             @$ref{ @keys } = @values;
3150             my $c = Cookie->new;
3151             $c->apply( $ref ) || die( $c->error );
3152             $jar->add( $c );
3153             });
3154             printf( "%d cookies now in our repository.\n", $jar->repo->length );
3155              
3156             or you could also load a cookie file. L<Cookie::Jar> supports L<LWP> format and old Netscape format:
3157              
3158             $jar->load_as_lwp( '/home/joe/lwp_cookies.txt' );
3159             $jar->load_as_netscape( '/home/joe/netscape_cookies.txt' );
3160              
3161             And of course, if you are using L<Cookie::Jar> json cookies file, you can import them with:
3162              
3163             $jar->load( '/home/joe/cookies.json' );
3164              
3165             =head1 ENCRYPTION
3166              
3167             This package supports encryption and decryption of cookies file, and also the cookies values themselve.
3168              
3169             See methods L</save> and L</load> for encryption options and the L<Cookie> package for options to encrypt or sign cookies value.
3170              
3171             =head1 INSTALLATION
3172              
3173             As usual, to install this module, you can do:
3174              
3175             perl Makefile.PL
3176             make
3177             make test
3178             sudo make install
3179              
3180             If you have Apache/modperl2 installed, this will also prepare the Makefile and run test under modperl.
3181              
3182             The Makefile.PL tries hard to find your Apache configuration, but you can give it a hand by specifying some command line parameters. See L<Apache::TestMM> for available parameters or you can type on the command line:
3183              
3184             perl -MApache::TestConfig -le 'Apache::TestConfig::usage()'
3185              
3186             For example:
3187              
3188             perl Makefile.PL -apxs /usr/bin/apxs -port 1234
3189             # which will also set the path to httpd_conf, otherwise
3190             perl Makefile.PL -httpd_conf /etc/apache2/apache2.conf
3191              
3192             # then
3193             make
3194             make test
3195             sudo make install
3196              
3197             See also L<modperl testing documentation|https://perl.apache.org/docs/general/testing/testing.html>
3198              
3199             But, if for some reason, you do not want to perform the mod_perl tests, you can use C<NO_MOD_PERL=1> when calling C<perl Makefile.PL>, such as:
3200              
3201             NO_MOD_PERL=1 perl Makefile.PL
3202             make
3203             make test
3204             sudo make install
3205              
3206             =head1 AUTHOR
3207              
3208             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
3209              
3210             =head1 SEE ALSO
3211              
3212             L<Cookie>, L<Cookie::Domain>, L<Apache2::Cookies>, L<APR::Request::Cookie>, L<Cookie::Baker>
3213              
3214             L<Latest tentative version of the cookie standard|https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09>
3215              
3216             L<Mozilla documentation on Set-Cookie|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
3217              
3218             L<Information on double submit cookies|https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie>
3219              
3220             =head1 COPYRIGHT & LICENSE
3221              
3222             Copyright (c) 2019-2019 DEGUEST Pte. Ltd.
3223              
3224             You can use, copy, modify and redistribute this package and associated
3225             files under the same terms as Perl itself.
3226              
3227             =cut