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 41 41 100.0
total 1116 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   3858 use strict;
  2         7  
  2         81  
15 2     2   10 use warnings;
  2         5  
  2         59  
16 2     2   12 use warnings::register;
  2         5  
  2         329  
17 2     2   19 use parent qw( Module::Generic );
  2         3  
  2         16  
18 2     2   149 use vars qw( $VERSION $COOKIES_DEBUG $MOD_PERL $MOD_PERL_VERSION );
  2         3  
  2         448  
19 2     2   7 our( $MOD_PERL, $MOD_PERL_VERSION );
20 2 50 33     15 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   14 use Cookie;
  2         8  
  2         13  
35 2     2   1211 use Cookie::Domain;
  2         8  
  2         23  
36 2     2   690 use DateTime;
  2         3  
  2         58  
37 2     2   14 use JSON;
  2         4  
  2         25  
38 2     2   1432 use Module::Generic::HeaderValue;
  2         6909  
  2         18  
39 2     2   540 use Nice::Try;
  2         22  
  2         25  
40 2     2   9900892 use Scalar::Util;
  2         5  
  2         135  
41 2     2   15 use URI::Escape ();
  2         5  
  2         113  
42 2         7 our $VERSION = 'v0.3.0';
43             # This flag to allow extensive debug message to be enabled
44 2         48 our $COOKIES_DEBUG = 0;
45 2     2   13 use constant CRYPTX_VERSION => '0.074';
  2         6  
  2         224  
46             };
47              
48 2     2   12 use strict;
  2         43  
  2         65  
49 2     2   16 use warnings;
  2         5  
  2         3104  
50              
51             sub init
52             {
53 8     8 1 77254 my $self = shift( @_ );
54             # Apache2::RequestRec object
55 8         41 my $req;
56 8 50 66     150 $req = shift( @_ ) if( @_ && ( @_ % 2 ) );
57             # For decryption and encryption
58 8         107 $self->{algo} = undef;
59             # If a cookie file is provided, yes, we'll automatically load and save from and to it.
60 8         41 $self->{autosave} = 1;
61             # For decryption and encryption
62 8         37 $self->{encrypt} = 0;
63 8         32 $self->{file} = '';
64 8         62 $self->{host} = '';
65             # For decryption and encryption
66 8         30 $self->{iv} = undef;
67             # For decryption and encryption
68 8         59 $self->{secret} = undef;
69             # Cookie file type; can also be 'lwp' or 'netscape'
70 8         39 $self->{type} = 'json';
71 8         27 $self->{_init_strict_use_sub} = 1;
72 8         50 $self->SUPER::init( @_ );
73 8 50       1019 $self->{request} = $req if( $req );
74             # Repository of all objects
75 8         42 $self->{_cookies} = [];
76             # Index by host, path, name
77 8         62 $self->{_index} = {};
78 8         46 my $file = $self->file;
79 8 0 33     7116 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         28 return( $self );
105             }
106              
107             sub add
108             {
109 16     16 1 140 my $self = shift( @_ );
110 16         55 my $this;
111 16 100       102 if( scalar( @_ ) == 1 )
    50          
112             {
113 13         54 $this = shift( @_ );
114             }
115             elsif( scalar( @_ ) )
116             {
117 3         27 $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     747 if( ref( $this ) eq 'HASH' )
    100 33        
    50          
124             {
125 3         25 $this = $self->make( $this );
126 3 50       16 return( $self->pass_error ) if( !defined( $this ) );
127             }
128             # A string ?
129             elsif( !ref( $this ) )
130             {
131 3   50     25 my $hv = Module::Generic::HeaderValue->new_from_header( $this, decode => 1, debug => $self->debug ) ||
132             return( $self->error( Module::Generic::HeaderValue->error ) );
133 3         31159 my $ref = {};
134 3         30 $ref->{name} = $hv->value->first;
135 3         2392 $ref->{value} = $hv->value->second;
136             $hv->params->foreach(sub
137             {
138 15     15   2626 my( $n, $v ) = @_;
139 15         310 $ref->{ $n } = $v;
140 15         41 return(1);
141 3         2124 });
142 3 50       60 $ref->{secure} = 1 if( CORE::exists( $ref->{secure} ) );
143             # In case those were provided too in the cookie line
144 3 50       29 $ref->{samesite} = 1 if( CORE::exists( $ref->{samesite} ) );
145 3 50       32 $ref->{httponly} = 1 if( CORE::exists( $ref->{httponly} ) );
146 3         47 $this = $self->make( %$ref );
147 3 50       61 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         634 my $ref = $self->_cookies;
155 16         14333 my $idx = $self->_index;
156 16 50       16282 $this->name or return( $self->error( "No cookie name was set in this cookie." ) );
157 16   50     13159 my $key = $self->key( $this ) || return( $self->pass_error );
158 16         514 $ref->push( $this );
159 16 50       289 $idx->{ $key } = [] if( !CORE::exists( $idx->{ $key } ) );
160 16         996 push( @{$idx->{ $key }}, $this );
  16         118  
161 16         417 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 5940 my $self = shift( @_ );
169 5   50     29 my $req = shift( @_ ) || return( $self->error( "No request object was provided." ) );
170 5 50       54 return( $self->error( "Request object provided is not an object." ) ) if( !Scalar::Util::blessed( $req ) );
171 5 50 33     114 return( $self->error( "Request object provided does not support the uri or header methods." ) ) if( !$req->can( 'uri' ) || !$req->can( 'header' ) );
172 5   50     302 my $uri = $req->uri || return( $self->error( "No uri set in the request object." ) );
173 5         219 my $scheme = $uri->scheme;
174 5 50       353 unless( $scheme =~ /^https?\z/ )
175             {
176 0         0 return( '' );
177             }
178 5         55 my( $host, $port, $path );
179 5 50       64 if( $host = $req->header( 'Host' ) )
180             {
181 5         556 $host =~ s/:(\d+)$//;
182 5         25 $host = lc( $host );
183 5         26 $port = $1;
184             }
185             else
186             {
187 0         0 $host = lc( $uri->host );
188             }
189 5 50       33 my $is_secure = ( $scheme eq 'https' ? 1 : 0 );
190             # URI::URL method
191 5 50       67 if( $uri->can( 'epath' ) )
192             {
193 0         0 $path = $uri->epath;
194             }
195             else
196             {
197             # URI::_generic method
198 5         45 $path = $uri->path;
199             }
200 5 50       143 $path = '/' unless( CORE::length( $path ) );
201 5 50 33     60 $port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
202             # my $now = time();
203 5         377 my $now = DateTime->now;
204 5 50       3112 $path = $self->_normalize_path( $path ) if( CORE::index( $path, '%' ) != -1 );
205 5         44 my $root;
206 5 50       40 if( $self->_is_ip( $host ) )
207             {
208 0         0 $root = $host;
209             }
210             else
211             {
212 5   50     2972 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
213 5         20689 my $res = $dom->stat( $host );
214 5 50       2985 return( $self->pass_error( $dom->error ) ) if( !defined( $res ) );
215 5 50 33     41 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         204213 $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         27337 my @values = ();
231 5         169 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         106 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         276 foreach my $c ( @$cookies )
237             {
238 12 0 33     97 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     11145 if( index( $path, $c->path ) != 0 )
    50 100        
    100 33        
    50          
244             {
245 1         931 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         1218 next;
255             }
256             elsif( $c->port && $c->port != $port )
257             {
258 0         0 next;
259             }
260 10         10405 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       43 foreach my $c ( sort{ $b->path->length <=> $a->path->length || $a->created_on <=> $b->created_on } @ok_cookies )
  5         1103  
271             {
272 10         136164 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         136 $c->accessed_on( time() );
277             }
278              
279 5 50       39199 if( @values )
280             {
281 5 50       1225 if( my $old = $req->header( 'Cookie' ) )
282             {
283 0         0 unshift( @values, $old );
284             }
285 5         912 $req->header( Cookie => join( '; ', @values ) );
286             }
287 5         419 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 98 sub autosave { return( shift->_set_get_boolean( 'autosave', @_ ) ); }
341              
342             sub delete
343             {
344 1     1 1 8 my $self = shift( @_ );
345 2     2   19 no overloading;
  2         9  
  2         3202  
346 1         15 my $ref = $self->_cookies;
347 1         881 my $idx = $self->_index;
348 1 50 33     958 if( scalar( @_ ) == 1 && $self->_is_a( $_[0], 'Cookie' ) )
349             {
350 1         61 my $c = shift( @_ );
351 1         13 my $addr = Scalar::Util::refaddr( $c );
352 1         15 my $removed = $self->new_array;
353 1         45 for( my $i = 0; $i < scalar( @$ref ); $i++ )
354             {
355 3         33 my $this = $ref->[$i];
356 3 100       33 if( Scalar::Util::refaddr( $this ) eq $addr )
357             {
358 1         12 my $key = $self->key( $this );
359 1 50       46 if( CORE::exists( $idx->{ $key } ) )
360             {
361             # if( !$self->_is_array( $idx->{ $key } ) )
362 1 50       51 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         49 for( my $j = 0; $j < scalar( @{$idx->{ $key }} ); $j++ )
  2         18  
367             {
368 1 50       35 if( Scalar::Util::refaddr( $idx->{ $key }->[$j] ) eq $addr )
369             {
370 1         45 CORE::splice( @{$idx->{ $key }}, $j, 1 );
  1         17  
371 1         38 $j--;
372             }
373             }
374             # Cleanup
375 1 50       54 CORE::delete( $idx->{ $key } ) if( scalar( @{$idx->{ $key }} ) == 0 );
  1         13  
376             }
377 1         145 CORE::splice( @$ref, $i, 1 );
378 1         6 $i--;
379 1         21 $removed->push( $c );
380             }
381             }
382 1         121 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 1398 my $self = shift( @_ );
417 2   50     34 my $code = shift( @_ ) || return( $self->error( "No callback code was provided." ) );
418 2 50       29 return( $self->error( "Callback code provided is not a code." ) ) if( ref( $code ) ne 'CODE' );
419 2         36 my $ref = $self->_cookies->clone;
420 2         1600 my $all = $self->new_array;
421 2         73 foreach my $c ( @$ref )
422             {
423 6 50 33     106 next if( !ref( $c ) || !$self->_is_a( $c, 'Cookie' ) );
424 6 50 33     403 try
  6         27  
  6         19  
  6         72  
  0         0  
  6         19  
  6         27  
  6         22  
425 6     6   19 {
426 6         14 local $_ = $c;
427 6         36 my $rv = $code->( $c );
428 6 50       1405807 if( !defined( $rv ) )
    50          
429             {
430 0         0 last;
431             }
432             elsif( $rv )
433             {
434 6         47 $all->push( $c );
435             }
436             }
437 6 50 50     67 catch( $e )
  6 0 33     146  
  0 0       0  
  6 0       27  
  6 0       27  
  6 0       23  
  6 0       20  
  6 0       66  
  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         44  
  0         0  
  0         0  
  6         23  
  6         27  
  6         29  
  6         37  
  6         30  
  6         37  
  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   20 }
  2 0 0     6  
  2 0 33     5229  
  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       37  
  0 0       0  
  6 0       203  
  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         42  
  0         0  
  0         0  
  0         0  
  0         0  
  6         58  
441             }
442 2         13 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 569 my $self = shift( @_ );
463 4   50     25 my $resp = shift( @_ ) || return( $self->error( "No response object was provided." ) );
464 4 50       24 return( $self->error( "Response object provided is not an object." ) ) if( !Scalar::Util::blessed( $resp ) );
465 4         34 my $uri;
466 4 50 0     33 if( $self->_is_a( $resp, 'HTTP::Response' ) )
    0          
467             {
468 4         227 my $req = $resp->request;
469 4 50       91 return( $self->error( "No HTTP::Request object is set in this HTTP::Response." ) ) if( !$resp->request );
470 4         60 $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     125 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       34854 return( $resp ) unless( $all->length );
483 4 50       162395 $uri || return( $self->error( "No uri set in the response object." ) );
484 4         937 my( $host, $port, $path );
485 4 50 33     51 if( $host = $resp->header( 'Host' ) ||
486             ( $resp->request && ( $host = $resp->request->header( 'Host' ) ) ) )
487             {
488 4         756 $host =~ s/:(\d+)$//;
489 4         26 $host = lc( $host );
490 4         20 $port = $1;
491             }
492             else
493             {
494 0         0 $host = lc( $uri->host );
495             }
496            
497             # URI::URL method
498 4 50       51 if( $uri->can( 'epath' ) )
499             {
500 0         0 $path = $uri->epath;
501             }
502             else
503             {
504             # URI::_generic method
505 4         42 $path = $uri->path;
506             }
507 4 50       106 $path = '/' unless( CORE::length( $path ) );
508 4 50 33     44 $port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
509 4         302 my $root;
510 4 50       37 if( $self->_is_ip( $host ) )
511             {
512 0         0 $root = $host;
513             }
514             else
515             {
516 4   50     3606 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
517 4         16522 my $res = $dom->stat( $host );
518 4 50       2507 if( !defined( $res ) )
519             {
520 0         0 return( $self->pass_error( $dom->error ) );
521             }
522             # Possibly empty
523 4 50       118 $root = $res ? $res->domain : '';
524             }
525            
526 4         21755 foreach my $o ( @$all )
527             {
528 4         309 my( $name, $value ) = $o->value->list;
529 4   50     3123 my $c = Cookie->new( name => $name, value => $value ) ||
530             return( $self->pass_error( Cookie->error ) );
531 4 100       53 if( CORE::length( $o->param( 'expires' ) ) )
    50          
532             {
533 2         1484 my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
534 2 50       1434 if( $dt )
535             {
536 0         0 $c->expires( $dt );
537             }
538             else
539             {
540 2         12 $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       5389 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       2586 if( $root )
576             {
577 4         86 $c->domain( $root );
578 4         4385 $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     4161 if( defined( $o->param( 'path' ) ) && CORE::length( $o->param( 'path' ) ) )
587             {
588 3         4004 $c->path( $o->param( 'path' ) );
589             }
590             else
591             {
592 1         701 my $frag = $self->new_array( [split( /\//, $path )] );
593             # Not perfect
594 1 50 33     89 if( $path eq '/' || substr( $path, -1, 1 ) eq '/' )
595             {
596 1         19 $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       4339 $c->port( $port ) if( defined( $port ) );
605 4 50       164744 $c->http_only(1) if( $o->param( 'httponly' ) );
606 4 50       2641 $c->secure(1) if( $o->param( 'secure' ) );
607 4 50       2657 $c->same_site(1) if( $o->param( 'samesite' ) );
608            
609 4         2544 my @old = $self->get({ name => $c->name, host => $c->domain, path => $c->path });
610 4 100       26 if( scalar( @old ) )
611             {
612 1 50       47 $c->created_on( $old[0]->created_on ) if( $old[0]->created_on );
613             # $self->replace( $c );
614 1         1031 for( @old )
615             {
616 1         10 my $arr;
617             $arr = $self->delete( $_ ) || do
618 1   33     23 {
619             };
620             }
621             }
622 4 50       80 $self->add( $c ) || return( $self->pass_error );
623             }
624 4         824 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 39 my $self = shift( @_ );
632 1         16 my $str = shift( @_ );
633 1         15 my $opts = $self->_get_args_as_hash( @_ );
634 1   50     182 $opts->{path} //= '/';
635 1 50       8 return( $self->error( "No cookie data was provided." ) ) if( !length( "$str" ) );
636            
637 1         12 my( $host, $root );
638 1 50 33     35 if( defined( $opts->{host} ) && CORE::length( $opts->{host} ) )
639             {
640 1         4 $host = $opts->{host};
641 1 50       22 if( $self->_is_ip( $host ) )
642             {
643 0         0 $root = $host;
644             }
645             else
646             {
647 1   50     734 my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
648 1         4370 my $res = $dom->stat( $host );
649 1 50       615 if( !defined( $res ) )
650             {
651 0         0 return( $self->pass_error( $dom->error ) );
652             }
653             # Possibly empty
654 1 50       19 $root = $res ? $res->domain : '';
655             }
656             }
657              
658 1   50     5478 my $o = Module::Generic::HeaderValue->new_from_header( "$str" ) ||
659             return( $self->pass_error( Module::Generic::HeaderValue->error ) );
660 1         9193 my( $name, $value ) = $o->value->list;
661 1   50     735 my $c = Cookie->new( name => $name, value => $value ) ||
662             return( $self->pass_error( Cookie->error ) );
663 1 50       16 if( CORE::length( $o->param( 'expires' ) ) )
    0          
664             {
665 1         696 my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
666 1 50       718 if( $dt )
667             {
668 0         0 $c->expires( $dt );
669             }
670             else
671             {
672 1         19 $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       1261 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       652 if( $root )
697             {
698 1         29 $c->domain( $root );
699 1         1121 $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     969 if( defined( $o->param( 'path' ) ) && CORE::length( $o->param( 'path' ) ) )
708             {
709 1         1302 $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     1118 $c->port( $opts->{port} ) if( defined( $opts->{port} ) && $self->_is_integer( $opts->{port} ) );
726 1 50       40733 $c->http_only(1) if( $o->param( 'httponly' ) );
727 1 50       658 $c->secure(1) if( $o->param( 'secure' ) );
728 1 50       644 $c->same_site(1) if( $o->param( 'samesite' ) );
729 1         628 return( $c );
730             }
731              
732             # From server point of view
733             sub fetch
734             {
735 1     1 1 3633 my $self = shift( @_ );
736 1         21 my $opts = $self->_get_args_as_hash( @_ );
737 1   50     179 $opts->{string} //= '';
738 1 50       11 $opts->{store} = 1 if( !CORE::exists( $opts->{store} ) );
739 1   50     31 my $host = $opts->{host} || $self->host || '';
740 1         985 my $cookie_header;
741 1         37 my $r = $self->request;
742 1         50 my $cookies = [];
743 1 50 33     34 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     4465  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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         66 $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       76 if( !scalar( @$cookies ) )
788             {
789 1         18 my $ref = $self->parse( $cookie_header );
790 1         17 foreach my $def ( @$ref )
791             {
792 3   50     31 my $c = $self->make( name => $def->{name}, value => $def->{value} ) ||
793             return( $self->pass_error );
794 3         28 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       22 if( $opts->{store} )
799             {
800 1         8 foreach my $c ( @$cookies )
801             {
802 3 50       26 $self->add( $c ) || return( $self->pass_error );
803             }
804             }
805 1         19 return( $self->new_array( $cookies ) );
806             }
807              
808             # NOTE: the location of the cookie jar file
809 16     16 1 120 sub file { return( shift->_set_get_file( 'file', @_ ) ); }
810              
811             sub get
812             {
813 8     8 1 3651 my $self = shift( @_ );
814             # If called on the server side, $host and $path would likely be undefined
815             # my( $name, $host, $path ) = @_;
816 8         62 my( $name, $host, $path );
817 8 50 66     281 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         194 my $this = shift( @_ );
827 4         23 ( $name, $host, $path ) = @$this{qw( name host path )};
828             }
829             elsif( scalar( @_ ) > 0 && scalar( @_ ) <= 3 )
830             {
831 4         32 ( $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     78 return( $self->error( "No cookie name was provided to get its object." ) ) if( !defined( $name ) || !CORE::length( $name ) );
838 8   0     89 $host //= $self->host || '';
      33        
839 8   100     133 $path //= '';
840 8         128 my $ref = $self->_cookies;
841 8         7460 my $idx = $self->_index;
842 8         7532 my $key = $self->key( $name => $host, $path );
843             # Return immediately if we found a perfect match
844 8 50       260 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         304 my @found = ();
852 8         75 foreach my $c ( @$ref )
853             {
854 18         228 my $c_name = $c->name;
855 18         16584 my $c_host = $c->domain;
856 18         16423 my $c_path = $c->path;
857            
858 18 100       16442 next unless( $c_name eq $name );
859            
860            
861 5 50 33     155 if( !defined( $host ) || !CORE::length( $host ) )
862             {
863 0         0 push( @found, $c );
864 0         0 next;
865             }
866            
867 5 50 33     105 if( defined( $c_host ) &&
      66        
868             ( $host eq $c_host || index( reverse( $host ), reverse( ".${c_host}" ) ) == 0 ) )
869             {
870 4 100 66     106 if( defined( $path ) && CORE::length( "$path" ) )
871             {
872 1 50       27 if( index( $path, $c_path ) == 0 )
873             {
874 1         38 push( @found, $c );
875             }
876             }
877             else
878             {
879 3         18 push( @found, $c );
880             }
881             }
882             }
883            
884 8 100       149 if( scalar( @found ) )
885             {
886 4 100       57 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       32 if( CORE::exists( $idx->{ $name } ) )
892             {
893 1 50       59 return( wantarray() ? @{$idx->{ $name }} : $idx->{ $name }->[0] );
  0         0  
894             }
895 3         109 return;
896             }
897              
898             sub get_by_domain
899             {
900 5     5 1 36 my $self = shift( @_ );
901 5         22 my $host = shift( @_ );
902 5         51 my $opts = $self->_get_args_as_hash( @_ );
903 5         996 $opts->{with_subdomain} = 0;
904 5 50       77 $opts->{sort} = 1 if( !CORE::exists( $opts->{sort} ) );
905 5         39 my $all = $self->new_array;
906 5 50 33     179 return( $all ) if( !defined( $host ) || !CORE::length( $host ) );
907 5         71 $host = lc( $host );
908 5         120 my $ref = $self->_cookies;
909 5         4865 foreach my $c ( @$ref )
910             {
911 12         366 my $dom = $c->domain;
912 12 50 0     11335 $all->push( $c ) if( $dom eq $host || ( $opts->{with_subdomain} && $host =~ /\.$dom$/ ) );
      33        
913             }
914 5         173 my $new = [];
915 5 50       49 if( $opts->{sort} )
916             {
917 5         101 $new = [sort{ $a->path cmp $b->path } @$all];
  10         5575  
918             }
919             else
920             {
921 0         0 $new = [sort{ $b->path cmp $a->path } @$all];
  0         0  
922             }
923 5         3664 return( $self->new_array( $new ) );
924             }
925              
926 3     3 1 75 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 143 my $self = shift( @_ );
933 25         94 my( $name, $host, $path );
934 25 100 66     313 if( scalar( @_ ) == 1 && $self->_is_a( $_[0], 'Cookie' ) )
935             {
936 17         788 my $c = shift( @_ );
937 17         98 $name = $c->name;
938 17         15961 $host = $c->domain;
939 17         15355 $path = $c->path;
940             }
941             else
942             {
943 8         23 ( $name, $host, $path ) = @_;
944 8 50 66     107 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       16051 return( $self->error( "No cookie name was provided to get its key." ) ) if( !CORE::length( $name ) );
947 25 100 66     355 return( join( ';', $host, $path, $name ) ) if( defined( $host ) && CORE::length( $host ) );
948 3         19 return( $name );
949             }
950              
951 0     0 1 0 sub length { return( shift->repo->length ); }
952              
953             # Load cookie data from json cookie file
954             sub load
955             {
956 1     1 1 39 my $self = shift( @_ );
957 1   50     12 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
958 1         34 my $opts = $self->_get_args_as_hash( @_ );
959 1   50     46 $opts->{host} //= '';
960 1   50     34 $opts->{decrypt} //= 0;
961 1   50     42 $opts->{algo} //= '';
962             # Initialisation Vector for encryption
963             # Re-use it if it was previously set
964 1   50     33 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
965 1   50     741 my $host = $opts->{host} || $self->host || '';
966 1   50     932 my $f = $self->new_file( $file ) || return( $self->pass_error );
967 1         156894 my $json = $f->load;
968 1 50       10557 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       20 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   19 }
  2 0 0     4  
  2 0 0     1928  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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         109 my $j = JSON->new->relaxed->utf8;
996 1         8 my $hash;
997 1 50 33     8 try
  1         6  
  1         7  
  1         23  
  0         0  
  1         16  
  1         12  
  1         4  
998 1     1   10 {
999 1         170 $hash = $j->decode( $json );
1000             }
1001 1 0 50     35 catch( $e )
  1 0 33     18  
  1 0       21  
  1 0       11  
  1 0       9  
  1 0       4  
  1 0       7  
  1 0       27  
  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         20  
  0         0  
  1         12  
  0         0  
  0         0  
  1         18  
  1         18  
  1         11  
  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     5  
  2 0 33     3397  
  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       161  
  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         21  
  0         0  
  0         0  
  0         0  
  0         0  
  1         13  
1005 1 50       20 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         12 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         19 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     49 {
1025             next;
1026             };
1027 3 50       41 $self->add( $c ) || return( $self->pass_error );
1028             }
1029 1         63 return( $self );
1030             }
1031              
1032             sub load_as_lwp
1033             {
1034 1     1 1 5818 my $self = shift( @_ );
1035 1   50     15 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1036 1         43 my $opts = $self->_get_args_as_hash( @_ );
1037 1   50     43 $opts->{decrypt} //= 0;
1038 1   50     26 $opts->{algo} //= '';
1039             # Initialisation Vector for encryption
1040             # Re-use it if it was previously set
1041 1   50     36 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
1042 1         740 my $f = $self->new_file( $file );
1043 1   50     154810 my $host = $opts->{host} || $self->host || '';
1044 1 50       1048 $f->open( '<', { binmode => ( $opts->{decrypt} ? 'raw' : 'utf-8' ) }) || return( $self->pass_error( $f->error ) );
    50          
1045             my $code = sub
1046             {
1047 4 100   4   8452 if( /^Set-Cookie3:[[:blank:]\h]*(.*?)$/ )
1048             {
1049 3         41 my $c = $self->add( $1 );
1050             }
1051             else
1052             {
1053             }
1054 1         7474 };
1055            
1056 1 50       20 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   22 }
  2 0 0     4  
  2 0 0     2974  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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       30 $f->line( $code, chomp => 1, auto_next => 1 ) || return( $self->pass_error( $f->error ) );
1084 1         1606 $f->close;
1085             }
1086 1         2575 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   17 }
  2 0 0     17  
  2 0 0     1367  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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 10626 my $self = shift( @_ );
1285 15         104 my $opts = $self->_get_args_as_hash( @_ );
1286 2     2   16 no overloading;
  2         4  
  2         4751  
1287 15 50       2374 return( $self->error( "Cookie name was not provided." ) ) if( !$opts->{name} );
1288 15         94 $opts->{debug} = $self->debug;
1289 15         438 my $c = Cookie->new( debug => $self->debug );
1290 15 50       183 return( $self->pass_error( Cookie->error ) ) if( !defined( $c ) );
1291 15 50       112 $c->apply( $opts ) || return( $self->pass_error( $c->error ) );
1292 15         139 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 33502 my $self = shift( @_ );
1384 23         46 my $raw = shift( @_ );
1385 23         73 my $ref = $self->new_array;
1386 23 100 100     489 return( $ref ) unless( defined( $raw ) && CORE::length( $raw ) );
1387 21         320 my @pairs = grep( /=/, split( /; ?/, $raw ) );
1388 21         69 foreach my $pair ( @pairs )
1389             {
1390             # Remove leading and trailing whitespaces
1391 60         812 $pair =~ s/^[[:blank:]\h]+|[[:blank:]\h]+$//g;
1392 60         171 my( $k, $v ) = split( '=', $pair, 2 );
1393 60         133 $k = URI::Escape::uri_unescape( $k );
1394 60 50       505 $v = '' unless( defined( $v ) );
1395 60         96 $v =~ s/\A"(.*)"\z/$1/;
1396 60         93 $v = URI::Escape::uri_unescape( $v );
1397 60         732 $ref->push( { name => $k, value => $v } );
1398             }
1399 21         195 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 179668 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 53 sub request { return( shift->_set_get_object_without_init( 'request', 'Apache2::RequestRec', @_ ) ); }
1485              
1486             sub save
1487             {
1488 1     1 1 379649 my $self = shift( @_ );
1489 1   50     23 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1490 1         45 my $opts = $self->_get_args_as_hash( @_ );
1491 1   50     43 $opts->{encrypt} //= 0;
1492 1   50     36 $opts->{algo} //= '';
1493             # Initialisation Vector for encryption
1494             # Re-use it if it was previously set
1495 1   50     49 $opts->{iv} //= $self->_initialisation_vector->scalar || '';
      33        
1496 1   50     774 $opts->{format} //= '';
1497 1 50       10 return( $self->save_as_lwp( $opts ) ) if( $opts->{format} eq 'lwp' );
1498 1         10 my $all = [];
1499 1         20 my $ref = $self->_cookies;
1500 1         1031 foreach my $c ( @$ref )
1501             {
1502 3         39 push( @$all, $c->as_hash );
1503             }
1504 1         4 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     10 try
  1         7  
  1         6  
  1         15  
  0         0  
  1         8  
  1         4  
  1         3  
1508 1     1   11 {
1509 1         14 $tz = DateTime::TimeZone->new( name => 'local' );
1510             }
1511 1 0 50     19 catch( $e )
  1 0 33     654  
  1 0       8  
  1 0       10  
  1 0       7  
  1 0       4  
  1 0       3  
  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         7  
  0         0  
  1         7  
  0         0  
  0         0  
  1         18  
  1         17  
  1         3  
  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   21 }
  2 0 0     5  
  2 0 33     2174  
  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       132  
  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         7  
  0         0  
  0         0  
  0         0  
  0         0  
  1         12  
1515 1         21 my $today = DateTime->now( time_zone => $tz );
1516 1         397 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         2201 $today->set_formatter( $dt_fmt );
1523 1         77 my $data = { cookies => $all, updated_on => "$today" };
1524            
1525 1   50     952 my $f = $self->new_file( $file ) || return( $self->pass_error );
1526 1         155925 my $j = JSON->new->allow_nonref->pretty->canonical->convert_blessed;
1527 1         13 my $json;
1528 1 50 33     25 try
  1         10  
  1         7  
  1         26  
  0         0  
  1         12  
  1         32  
  1         11  
1529 1     1   8 {
1530 1         101 $json = $j->encode( $data );
1531             }
1532 1 0 50     44 catch( $e )
  1 0 33     2392  
  1 0       14  
  1 0       12  
  1 0       498  
  1 0       9  
  1 0       11  
  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         18  
  0         0  
  0         0  
  1         22  
  1         573  
  1         13  
  1         16  
  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   16 }
  2 0 0     5  
  2 0 33     6892  
  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       274  
  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         18  
  0         0  
  0         0  
  0         0  
  0         0  
  1         15  
1536              
1537 1 50       46 $f->open( '>', { binmode => ( $opts->{encrypt} ? 'raw' : 'utf8' ) }) ||
    50          
1538             return( $self->pass_error( $f->error ) );
1539 1 50       52940 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         27 $f->unload( $json );
1552             }
1553 1         2716 $f->close;
1554 1         2522 return( $self );
1555             }
1556              
1557             sub save_as_lwp
1558             {
1559 1     1 1 400439 my $self = shift( @_ );
1560 1   50     24 my $file = shift( @_ ) || return( $self->error( "No filename was provided." ) );
1561 1         47 my $opts = $self->_get_args_as_hash( @_ );
1562 1   50     61 $opts->{encrypt} //= 0;
1563 1   50     29 $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     816 $opts->{skip_discard} //= 0;
1568 1   50     31 $opts->{skip_expired} //= 0;
1569 1 50       11 return( $self->error( "No file to write cookies was specified." ) ) if( !$file );
1570 1   50     47 my $f = $self->new_file( $file ) || return( $self->pass_error );
1571            
1572 1         153657 my $raw = '';
1573 1         19 my $p = {};
1574 1 50       18 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     63 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       54900 if( $opts->{encrypt} )
1583             {
1584 0         0 $raw = "#LWP-Cookies-1.0\n";
1585             }
1586             else
1587             {
1588 1 50       36 $io->print( "#LWP-Cookies-1.0\n" ) || return( $self->error( "Unable to write to cookie file \"$file\": $!" ) );
1589             }
1590 1         339 my $now = DateTime->now;
1591             $self->scan(sub
1592             {
1593 3     3   14 my $c = shift( @_ );
1594 3 0 33     47 return(1) if( $c->discard && $opts->{skip_discard} );
1595 3 0 33     2574 return(1) if( $c->expires && $c->expires < $now && $opts->{skip_expired} );
      33        
1596 3         2853 my $vals = $c->as_hash;
1597 3 50       29 $vals->{path_spec} = 1 if( CORE::length( $vals->{path} ) );
1598             # In HTTP::Cookies logic, version 1 is rfc2109, version 2 is rfc6265
1599 3         51 $vals->{version} = 2;
1600 3         56 my $hv = Module::Generic::HeaderValue->new( [CORE::delete( @$vals{qw( name value )} )] );
1601 3         3303 $hv->param( path => sprintf( '"%s"', $vals->{path} ) );
1602 3         3758 $hv->param( domain => $vals->{domain} );
1603 3 50 33     1971 $hv->param( port => $vals->{port} ) if( defined( $vals->{port} ) && CORE::length( $vals->{port} ) );
1604 3 50 33     62 $hv->param( path_spec => undef() ) if( defined( $vals->{path_spec} ) && $vals->{path_spec} );
1605 3 50 33     1954 $hv->param( secure => undef() ) if( defined( $vals->{secure} ) && $vals->{secure} );
1606 3 50 33     1985 $hv->param( expires => sprintf( '"%s"', "$vals->{expires}" ) ) if( defined( $vals->{secure} ) && $vals->{expires} );
1607 3 0 33     23 $hv->param( discard => undef() ) if( defined( $vals->{discard} ) && $vals->{discard} );
1608 3 50 33     30 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     27 $hv->param( commentURL => $vals->{commentURL} ) if( defined( $vals->{commentURL} ) && CORE::length( $vals->{commentURL} ) );
1614 3         30 $hv->param( version => $vals->{version} );
1615 3 50       2021 if( $opts->{encrypt} )
1616             {
1617 0         0 $raw .= 'Set-Cookie3: ' . $hv->as_string . "\n";
1618             }
1619             else
1620             {
1621 3 50       27 $io->print( 'Set-Cookie3: ', $hv->as_string, "\n" ) || return( $self->error( "Unable to write to cookie file \"$file\": $!" ) );
1622             }
1623 1         1047 });
1624 1 50       67 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         27 $io->close;
1632 1         244 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   27 }
  2 0 0     5  
  2 0 0     5294  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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 34 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 79 my $self = shift( @_ );
2121 4         16 my $c = shift( @_ );
2122 4         25 my $opts = $self->_get_args_as_hash( @_ );
2123 4 50       667 return( $self->error( "No cookie name was provided to set." ) ) if( !$c->name->length );
2124 4 50       165024 return( $self->error( "Cookie value should be an object." ) ) if( !Scalar::Util::blessed( $c ) );
2125 4 50       647 return( $self->error( "Cookie object does not have any as_string method." ) ) if( !$c->can( 'as_string' ) );
2126 4   50     116 $opts->{response} //= '';
2127 4         46 my $r = $self->request;
2128 4 50 33     248 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         235 $opts->{response}->header( 'Set-Cookie' => $c->as_string );
2135             }
2136             else
2137             {
2138 0         0 return( "Set-Cookie: " . $c->as_string );
2139             }
2140 4         527 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   302 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   18 }
  2 0 0     4  
  2 0 0     1018  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  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   144 sub _index { return( shift->_set_get_hash_as_mix_object( '_index', @_ ) ); }
2189              
2190             # For cookies file encryption
2191 4     4   75 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   31810 {
2211 8         53 my $self = shift( @_ );
2212 8 50 33     7179 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 length
2697              
2698             Read-only. Returns the size of the Cookie repository as a L<number object|Module::Generic::Number>
2699              
2700             =head2 load
2701              
2702             $jar->load( '/home/joe/cookies.json' ) || die( $jar->error );
2703              
2704             # or loading cookies from encrypted file
2705             $jar->load( '/home/joe/cookies_encrypted.json',
2706             {
2707             decrypt => 1,
2708             key => $key,
2709             iv => $iv,
2710             algo => 'AES'
2711             }) || die( $jar->error );
2712              
2713             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.
2714              
2715             Supported options are:
2716              
2717             =over 4
2718              
2719             =item I<algo> string
2720              
2721             Algorithm to use to decrypt the cookie file.
2722              
2723             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>
2724              
2725             =item I<decrypt> boolean
2726              
2727             Must be set to true to enable decryption.
2728              
2729             =item I<iv> string
2730              
2731             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>
2732              
2733             =item I<key> string
2734              
2735             Set the encryption key used to decrypt the cookies file.
2736              
2737             The key must be the same one used to encrypt the file. See L</save>
2738              
2739             =back
2740              
2741             L</load> returns the current object upon success and C<undef> and sets an L<error|Module::Generic/error> upon error.
2742              
2743             =head2 load_as_lwp
2744              
2745             $jar->load_as_lwp( '/home/joe/cookies_lwp.txt' ) ||
2746             die( "Unable to load cookies from file: ", $jar->error );
2747              
2748             # or loading an encrypted file
2749             $jar->load_as_lwp( '/home/joe/cookies_encrypted_lwp.txt',
2750             {
2751             encrypt => 1,
2752             key => $key,
2753             iv => $iv,
2754             algo => 'AES',
2755             }) || die( $jar->error );
2756              
2757             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.
2758              
2759             The supported options are the same as for L</load>
2760              
2761             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:
2762              
2763             #LWP-Cookies-1.0
2764             Set-Cookie3: cookie1=value1; domain=example.com; path=; path_spec; secure; version=2
2765             Set-Cookie3: cookie2=value2; domain=api.example.com; path=; path_spec; secure; version=2
2766             Set-Cookie3: cookie3=value3; domain=img.example.com; path=; path_spec; secure; version=2
2767              
2768             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2769              
2770             =head2 load_as_mozilla
2771              
2772             $jar->load_as_mozilla( '/home/joe/cookies.sqlite' ) ||
2773             die( "Unable to load cookies from mozilla cookies.sqlite file: ", $jar->error );
2774              
2775             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.
2776              
2777             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>
2778              
2779             If none of those 2 methods succeeded, it will return C<undef> with an L<error|Module::Generic/error>
2780              
2781             Note that contrary to other loading method, this method does not support encryption.
2782              
2783             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2784              
2785             Supported options are:
2786              
2787             =over 4
2788              
2789             =item * C<use_dbi>
2790              
2791             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.
2792              
2793             =item * C<sqlite>
2794              
2795             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.
2796              
2797             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>
2798              
2799             =back
2800              
2801             =head2 load_as_netscape
2802              
2803             $jar->save_as_netscape( '/home/joe/cookies_netscape.txt' ) ||
2804             die( "Unable to save cookies file: ", $jar->error );
2805              
2806             # or saving as an encrypted file
2807             $jar->save_as_netscape( '/home/joe/cookies_encrypted_netscape.txt',
2808             {
2809             encrypt => 1,
2810             key => $key,
2811             iv => $iv,
2812             algo => 'AES',
2813             }) || die( $jar->error );
2814              
2815             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.
2816              
2817             It returns the current object upon success, or C<undef> and sets an L<error|Module::Generic/error> upon error.
2818              
2819             =head2 make
2820              
2821             Provided with some parameters and this will instantiate a new L<Cookie> object with those parameters and return the new object.
2822              
2823             This does not add the newly created cookie object to the cookies repository.
2824              
2825             For a list of supported parameters, refer to the L<Cookie documentation|Cookie>
2826              
2827             # Make an encrypted cookie
2828             use Bytes::Random::Secure ();
2829             my $c = $jar->make(
2830             name => 'session',
2831             value => $secret_value,
2832             path => '/',
2833             secure => 1,
2834             http_only => 1,
2835             same_site => 'Lax',
2836             key => Bytes::Random::Secure::random_bytes(32),
2837             algo => $algo,
2838             encrypt => 1,
2839             ) || die( $jar->error );
2840             # or as an hash reference of parameters
2841             my $c = $jar->make({
2842             name => 'session',
2843             value => $secret_value,
2844             path => '/',
2845             secure => 1,
2846             http_only => 1,
2847             same_site => 'Lax',
2848             key => Bytes::Random::Secure::random_bytes(32),
2849             algo => $algo,
2850             encrypt => 1,
2851             }) || die( $jar->error );
2852              
2853             =head2 merge
2854              
2855             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.
2856              
2857             $jar->merge( $other_jar ) || die( $jar->error );
2858              
2859             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>
2860              
2861             This method also takes an hash or hash reference of options:
2862              
2863             =over 4
2864              
2865             =item I<die> boolean
2866              
2867             If true, the anonymous code passed to the C<do> method called, will die upon error. Default to false.
2868              
2869             By default, if an error occurs, C<undef> is returned and the L<error|Module::Generic/error> is set.
2870              
2871             =item I<overwrite> boolean
2872              
2873             If true, when an existing cookie is found it will be overwritten by the new one. Default to false.
2874              
2875             =back
2876              
2877             use Nice::Try;
2878             try
2879             {
2880             $jar->merge( $other_jar, die => 1, overwrite => 1 );
2881             }
2882             catch( $e )
2883             {
2884             die( "Failed to merge cookies repository: $e\n" );
2885             }
2886              
2887             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>
2888              
2889             =head2 parse
2890              
2891             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.
2892              
2893             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.
2894              
2895             my $cookies = $jar->parse( 'foo=bar; site_prefs=lang%3Den-GB' );
2896             # You can safely do as well:
2897             my $cookies = $jar->parse( '' );
2898              
2899             =head2 purge
2900              
2901             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.
2902              
2903             This returns an L<array object|Module::Generic::Array> of all the cookies thus removed.
2904              
2905             my $all = $jar->purge;
2906             printf( "Cookie(s) removed were: %s\n", $all->map(sub{ $_->name })->join( ',' ) );
2907             # or
2908             printf( "%d cookie(s) removed from our repository.\n", $jar->purge->length );
2909              
2910             =head2 replace
2911              
2912             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.
2913              
2914             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>
2915              
2916             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.
2917              
2918             =head2 repo
2919              
2920             Set or get the L<array object|Module::Generic::Array> used as the cookie jar repository.
2921              
2922             printf( "%d cookies found\n", $jar->repo->length );
2923              
2924             =head2 request
2925              
2926             Set or get the L<Apache2::RequestRec> object. This object is used to set the C<Set-Cookie> header within modperl.
2927              
2928             =head2 save
2929              
2930             $jar->save( '/home/joe/cookies.json' ) ||
2931             die( "Failed to save cookies: ", $jar->error );
2932              
2933             # or saving the cookies file encrypted
2934             $jar->save( '/home/joe/cookies_encrypted.json',
2935             {
2936             encrypt => 1,
2937             key => $key,
2938             iv => $iv,
2939             algo => 'AES',
2940             }) || die( $jar->error );
2941              
2942             Provided with a file, and an hash or hash reference of options, and this will save the repository of cookies as json data.
2943              
2944             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.
2945              
2946             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
2947              
2948             Supported options are:
2949              
2950             =over 4
2951              
2952             =item I<algo> string
2953              
2954             Algorithm to use to encrypt the cookie file.
2955              
2956             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>
2957              
2958             =item I<encrypt> boolean
2959              
2960             Must be set to true to enable encryption.
2961              
2962             =item I<iv> string
2963              
2964             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.
2965              
2966             To find the right size for the Initialisation Vector, for example for algorithm C<AES>, you could do:
2967              
2968             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->blocksize'
2969              
2970             which would yield C<16>
2971              
2972             =item I<key> string
2973              
2974             Set the encryption key used to encrypt the cookies file.
2975              
2976             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>:
2977              
2978             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->keysize'
2979              
2980             In this case, it will yield C<32>. Replace above C<AES>, by whatever algorithm you have chosen.
2981              
2982             perl -MCrypt::Cipher::Blowfish -lE 'say Crypt::Cipher::Blowfish->keysize'
2983              
2984             would yield C<56> for C<Blowfish>
2985              
2986             You can use L<Bytes::Random::Secure/random_bytes> to generate a random key:
2987              
2988             # will generate a 32 bytes-long key
2989             my $key = Bytes::Random::Secure::random_bytes(32);
2990              
2991             =back
2992              
2993             When encrypting the cookies file, this method will encode the encrypted data in base64 before saving it to file.
2994              
2995             =head2 save_as_lwp
2996              
2997             $jar->save_as_lwp( '/home/joe/cookies_lwp.txt' ) ||
2998             die( "Unable to save cookies file: ", $jar->error );
2999              
3000             # or saving as an encrypted file
3001             $jar->save_as_lwp( '/home/joe/cookies_encrypted_lwp.txt',
3002             {
3003             encrypt => 1,
3004             key => $key,
3005             iv => $iv,
3006             algo => 'AES',
3007             }) || die( $jar->error );
3008              
3009             Provided with a file, and an hash or hash reference of options, and this save the cookies repository as a LWP-style data.
3010              
3011             The supported options are the same as for L</save>
3012              
3013             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3014              
3015             =head2 save_as_mozilla
3016              
3017             $jar->save_as_mozilla( '/home/joe/cookies.sqlite' ) ||
3018             die( "Unable to save cookies as mozilla SQLite database: ", $jar->error );
3019              
3020             # or
3021             $jar->save_as_mozilla( '/home/joe/cookies.sqlite',
3022             {
3023             # force use of DBI/DBD::SQLite
3024             use_dbi => 1,
3025             # or specify the path of the sqlite3 binary
3026             # sqlite => '/some/where/sqlite3',
3027             # Enable logging of SQL queries maybe?
3028             # log_sql => '/some/where/sql.log',
3029             # Overwrite previous data
3030             overwrite => 1,
3031             # abort if an error occurred
3032             rollback => 1,
3033             }) || die( "Unable to save cookies as mozilla SQLite database: ", $jar->error );
3034              
3035             Provided with a file path to a SQLite database and this saves the cookies repository as a mozilla SQLite database.
3036              
3037             The structure of the L<mozilla SQLite database|http://kb.mozillazine.org/Cookies> is:
3038              
3039             CREATE TABLE moz_cookies(
3040             id INTEGER PRIMARY KEY,
3041             originAttributes TEXT NOT NULL DEFAULT '',
3042             name TEXT,
3043             value TEXT,
3044             host TEXT,
3045             path TEXT,
3046             expiry INTEGER,
3047             lastAccessed INTEGER,
3048             creationTime INTEGER,
3049             isSecure INTEGER,
3050             isHttpOnly INTEGER,
3051             inBrowserElement INTEGER DEFAULT 0,
3052             sameSite INTEGER DEFAULT 0,
3053             rawSameSite INTEGER DEFAULT 0,
3054             schemeMap INTEGER DEFAULT 0,
3055             CONSTRAINT moz_uniqueid UNIQUE(name, host, path, originAttributes)
3056             );
3057              
3058             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.
3059              
3060             Note that, contrary to other save methods, this method does not allow encrypting the SQLite database.
3061              
3062             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3063              
3064             Supported options are:
3065              
3066             =over 4
3067              
3068             =item * C<log_sql>
3069              
3070             String. This specifies a file name that will be opened in append mode and to which the SQL statements issued will be logged.
3071              
3072             =item * C<overwrite>
3073              
3074             Boolean. If true, this will overwrite any existing data if the specified SQLite database file already exists.
3075              
3076             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.
3077              
3078             Default false.
3079              
3080             =item * C<rollback>
3081              
3082             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.
3083              
3084             =item * C<skip_discard>
3085              
3086             Boolean. If true, this will not save cookies that have been marked as being discarded, such as session cookies. Default false.
3087              
3088             =item * C<skip_expired>
3089              
3090             Boolean. If true, this will not save the cookies that have already expired. Default false.
3091              
3092             =item * C<sqlite>
3093              
3094             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.
3095              
3096             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>
3097              
3098             =item * C<use_dbi>
3099              
3100             Boolean. Requires the use of L<DBI> and L<DBD::SQLite> and it will return an error if those are not installed.
3101              
3102             If you want to let this method try also to use C<sqlite3> binary if necessary, then do not set this option.
3103              
3104             =back
3105              
3106             =head2 save_as_netscape
3107              
3108             Provided with a file and this saves the cookies repository as a Netscape-style data.
3109              
3110             It returns the current object. If an error occurred, it will return C<undef> and set an L<error|Module::Generic/error>
3111              
3112             =head2 scan
3113              
3114             This is an alias for L</do>
3115              
3116             =head2 secret
3117              
3118             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>
3119              
3120             =head2 set
3121              
3122             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.
3123              
3124             $jar->set( $c, response => $http_response_object ) ||
3125             die( $jar->error );
3126              
3127             Ultimately if none of those two are provided it returns the C<Set-Cookie> header as a string.
3128              
3129             # Returns something like:
3130             # Set-Cookie: my-cookie=somevalue
3131             print( STDOUT $jar->set( $c ), "\015\012" );
3132              
3133             Unless the latter, this method returns the current object.
3134              
3135             =head2 type
3136              
3137             String. Sets or gets the cookie jar file format type. The supported formats are: C<json>, C<lwp> and C<netscape>
3138              
3139             =head1 IMPORTING COOKIES
3140              
3141             To import cookies, you can either use the methods L<scan|HTTP::Cookies/scan> from L<HTTP::Cookies>, such as:
3142              
3143             use Cookie::Jar;
3144             use HTTP::Cookies;
3145             my $jar = Cookie::Jar->new;
3146             my $old = HTTP::Cookies->new;
3147             $old->load( '/home/joe/old_cookies_file.txt' );
3148             my @keys = qw( version key val path domain port path_spec secure expires discard hash );
3149             $old->scan(sub
3150             {
3151             my @values = @_;
3152             my $ref = {};
3153             @$ref{ @keys } = @values;
3154             my $c = Cookie->new;
3155             $c->apply( $ref ) || die( $c->error );
3156             $jar->add( $c );
3157             });
3158             printf( "%d cookies now in our repository.\n", $jar->repo->length );
3159              
3160             or you could also load a cookie file. L<Cookie::Jar> supports L<LWP> format and old Netscape format:
3161              
3162             $jar->load_as_lwp( '/home/joe/lwp_cookies.txt' );
3163             $jar->load_as_netscape( '/home/joe/netscape_cookies.txt' );
3164              
3165             And of course, if you are using L<Cookie::Jar> json cookies file, you can import them with:
3166              
3167             $jar->load( '/home/joe/cookies.json' );
3168              
3169             =head1 ENCRYPTION
3170              
3171             This package supports encryption and decryption of cookies file, and also the cookies values themselve.
3172              
3173             See methods L</save> and L</load> for encryption options and the L<Cookie> package for options to encrypt or sign cookies value.
3174              
3175             =head1 INSTALLATION
3176              
3177             As usual, to install this module, you can do:
3178              
3179             perl Makefile.PL
3180             make
3181             make test
3182             sudo make install
3183              
3184             If you have Apache/modperl2 installed, this will also prepare the Makefile and run test under modperl.
3185              
3186             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:
3187              
3188             perl -MApache::TestConfig -le 'Apache::TestConfig::usage()'
3189              
3190             For example:
3191              
3192             perl Makefile.PL -apxs /usr/bin/apxs -port 1234
3193             # which will also set the path to httpd_conf, otherwise
3194             perl Makefile.PL -httpd_conf /etc/apache2/apache2.conf
3195              
3196             # then
3197             make
3198             make test
3199             sudo make install
3200              
3201             See also L<modperl testing documentation|https://perl.apache.org/docs/general/testing/testing.html>
3202              
3203             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:
3204              
3205             NO_MOD_PERL=1 perl Makefile.PL
3206             make
3207             make test
3208             sudo make install
3209              
3210             =head1 AUTHOR
3211              
3212             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
3213              
3214             =head1 SEE ALSO
3215              
3216             L<Cookie>, L<Cookie::Domain>, L<Apache2::Cookies>, L<APR::Request::Cookie>, L<Cookie::Baker>
3217              
3218             L<Latest tentative version of the cookie standard|https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09>
3219              
3220             L<Mozilla documentation on Set-Cookie|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
3221              
3222             L<Information on double submit cookies|https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie>
3223              
3224             =head1 COPYRIGHT & LICENSE
3225              
3226             Copyright (c) 2019-2019 DEGUEST Pte. Ltd.
3227              
3228             You can use, copy, modify and redistribute this package and associated
3229             files under the same terms as Perl itself.
3230              
3231             =cut