File Coverage

blib/lib/Cookie.pm
Criterion Covered Total %
statement 329 1204 27.3
branch 115 1222 9.4
condition 73 533 13.7
subroutine 57 89 64.0
pod 46 46 100.0
total 620 3094 20.0


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Cookies API for Server & Client - ~/lib/Cookie.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;
12             BEGIN
13 0         0 {
14 3     3   13033274 use strict;
  3         22  
  3         93  
15 3     3   14 use warnings;
  3         5  
  3         76  
16 3     3   14 use warnings::register;
  3         3  
  3         468  
17 3     3   479 use parent qw( Module::Generic );
  3         291  
  3         16  
18 3     3   23840299 use vars qw( $VERSION $SUBS $COOKIE_DEBUG );
  3         9  
  3         185  
19 3     3   1779 use DateTime;
  3         1038553  
  3         138  
20 3     3   1420 use DateTime::Format::Strptime;
  3         422952  
  3         20  
21 3     3   2431 use Module::Generic::DateTime;
  3         10078835  
  3         58  
22 3     3   1293 use Nice::Try;
  3         7  
  3         24  
23 3     3   4864136 use URI::Escape ();
  3         1960  
  3         279  
24             use overload (
25             '""' => \&as_string,
26 59     59   3099 bool => sub{ return( $_[0] ) },
27             # '""' => sub{ $_[0]->as_string },
28 3         51 'eq' => \&same_as,
29             '==' => \&same_as,
30             fallback => 1,
31 3     3   37 );
  3         9  
32 3     3   11 our $VERSION = 'v0.3.0';
33 3         5 our $SUBS;
34 3         77 our $COOKIE_DEBUG = 0;
35 3     3   545 use constant CRYPTX_VERSION => '0.074';
  3         8  
  3         256  
36             };
37              
38 3     3   18 use strict;
  3         5  
  3         77  
39 3     3   14 use warnings;
  3         7  
  3         161  
40              
41             sub init
42             {
43 46     46 1 71982 my $self = shift( @_ );
44 3     3   21 no overloading;
  3         8  
  3         3961  
45 46         155 $self->{name} = undef;
46 46         174 $self->{value} = undef;
47 46         148 $self->{comment} = undef;
48 46         141 $self->{commentURL} = undef;
49 46         150 $self->{discard} = 0;
50 46         123 $self->{domain} = undef;
51 46         141 $self->{expires} = undef;
52 46         179 $self->{http_only} = 0;
53             # In the case of cookie sent from the server and no domain was set
54             # This domain, which we need anyway, was provided implicitly or explicitly
55 46         153 $self->{implicit} = 0;
56 46         87 $self->{max_age} = undef;
57 46         114 $self->{path} = undef;
58 46         135 $self->{port} = undef;
59 46         150 $self->{same_site} = undef;
60 46         95 $self->{secure} = 0;
61 46         269 $self->{accessed} = time();
62 46         426 $self->{created} = time();
63             # Ref: <https://stackoverflow.com/questions/41467012/what-is-the-difference-between-signed-and-encrypted-cookies-in-rails>
64             # Integrity protection with Message Authentication Code (MAC)
65             # e.g. Crypt::Mac::HMAC::hmac("SHA256","plop","Oh boy, this is cool")
66 46         369 $self->{sign} = 0;
67             # Crypt::Cipher::AES
68             # Crypt::Cipher
69             # one of 'AES', 'Anubis', 'Blowfish', 'CAST5', 'Camellia', 'DES', 'DES_EDE',
70             # 'KASUMI', 'Khazad', 'MULTI2', 'Noekeon', 'RC2', 'RC5', 'RC6',
71             # 'SAFERP', 'SAFER_K128', 'SAFER_K64', 'SAFER_SK128', 'SAFER_SK64',
72             # 'SEED', 'Skipjack', 'Twofish', 'XTEA', 'IDEA', 'Serpent'
73             # simply any <NAME> for which there exists Crypt::Cipher::<NAME>
74             # Encryption algorithm
75             # Ref: <https://stackoverflow.com/questions/4147451/aes-vs-blowfish-for-file-encryption>
76 46         190 $self->{algo} = 'AES';
77 46         114 $self->{encrypt} = 0;
78 46         135 $self->{initialisation_vector} = undef;
79 46         154 $self->{key} = undef;
80             # Should this API be strict about the cookie names?
81             # When true, this will reject cookie names with invalid characters.
82 46         140 $self->{strict} = 0;
83             # Needs to be an empty string or it would be overriden by Module::Generic who would put here the package version instead
84 46         147 $self->{version} = '';
85 46         135 $self->{_init_strict_use_sub} = 1;
86 46 100       228 $self->SUPER::init( @_ ) || return( $self->pass_error );
87 45         29795 $self->{fields} = [qw( name value comment commentURL discard domain expires http_only implicit max_age path port same_site secure version )];
88 45         160 return( $self );
89             }
90              
91 19     19 1 718 sub accessed_on { return( shift->_set_get_datetime( 'accessed', @_ ) ); }
92              
93             sub algo
94             {
95 0     0 1 0 my $self = shift( @_ );
96 0 0       0 if( @_ )
97             {
98 0         0 my $algo = shift( @_ );
99 0 0 0     0 if( defined( $algo ) && CORE::length( $algo ) )
100             {
101 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
102 0     0   0 {
103 0 0       0 $self->_load_class( 'Crypt::Mode::CBC', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
104             # Crypt::Mode::CBC dies when it is unhappy, but we catch a null return
105             # value anyway just in case
106 0   0     0 my $o = Crypt::Mode::CBC->new( $algo ) ||
107             return( $self->error( "Unsupported algorithm \"$algo\"" ) );
108 0         0 $self->_set_get_scalar_as_object( 'algo', $algo );
109 0         0 $self->reset(1);
110             }
111 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  
112 0     0   0 {
113 0         0 return( $self->error( "Unsupported algorithm \"$algo\": $e" ) );
114 3 0 0 3   25 }
  3 0 0     8  
  3 0 0     5050  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
115             }
116             else
117             {
118 0         0 $self->{algo} = undef;
119             }
120             }
121 0         0 return( $self->_set_get_scalar_as_object( 'algo' ) );
122             }
123              
124             sub apply
125             {
126 15     15 1 43 my $self = shift( @_ );
127 15         56 my $hash = $self->_get_args_as_hash( @_ );
128 15 50       1977 return( $self ) if( !scalar( keys( %$hash ) ) );
129 15 50 66     285 if( !defined( $SUBS ) ||
      66        
130             ref( $SUBS ) ne 'ARRAY' ||
131             !scalar( @$SUBS ) )
132             {
133 1         58 $SUBS = [grep( /^(?!apply|as_hash|as_string|can|fields|import|init|reset)(?:[a-z][a-z\_]+)$/, keys( %Cookie:: ) )];
134             }
135            
136 15         68 foreach( @$SUBS )
137             {
138             # Value could be undef
139             # Passing an empty string to Module::Generic::Number will trigger an error (undef)
140             # So if the value is empty, we simply set it directly.
141 600 100 100     695681 if( $_ eq 'version' && !CORE::length( $hash->{ $_ } ) )
142             {
143 12         28 $self->{ $_ } = $hash->{ $_ };
144 12         48 next;
145             }
146            
147 588 100       1222 if( CORE::exists( $hash->{ $_ } ) )
148             {
149 107 100       258 if( !defined( $hash->{ $_ } ) )
150             {
151 30         75 $self->{ $_ } = undef;
152             }
153             else
154             {
155 77         457 $self->$_( $hash->{ $_ } );
156             }
157             }
158             }
159 15         104 return( $self );
160             }
161              
162             sub as_hash
163             {
164 6     6 1 45 my $self = shift( @_ );
165 6         24 my $ref = {};
166 6         44 foreach my $m ( qw( name value comment commentURL domain expires http_only implicit max_age path port same_site secure version created_on accessed_on ) )
167             {
168 96         105312 $ref->{ $m } = $self->$m;
169             }
170 6         24243 return( $ref );
171             }
172              
173             # sub as_string { return( shift->APR::Request::Cookie::as_string ); }
174             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
175             sub as_string
176             {
177 38     38 1 10947 my $self = shift( @_ );
178             # If is_request is true, we only send the name and value
179 38         206 my $opts = $self->_get_args_as_hash( @_ );
180 38   100     1792 $opts->{is_request} //= 0;
181 38 0 33     176 return( $self->{_cache_value} ) if( $self->{_cache_value} && !CORE::length( $self->{_reset} ) && !$opts->{is_request} );
      33        
182 38         155 my $name = $self->name;
183 38 50 33     35013 return( $self->error( "No cookie is name in this cookie object." ) ) if( !defined( $name ) || !length( $name ) );
184 38 50       436 if( $name =~ m/[^a-zA-Z\-\.\_\~]/ )
185             {
186 0         0 $name = URI::Escape::uri_escape( $name );
187             }
188 38         472 my $value = $self->value;
189            
190 38 50 33     34767 if( $self->sign || $self->encrypt )
191             {
192 0   0     0 my $key = $self->key ||
193             return( $self->error( "Signature or encryption has been enabled, but no key was provided." ) );
194 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
195 0     0   0 {
196 0 0       0 if( $self->sign->is_true )
    0          
197             {
198 0 0       0 $self->_load_class( 'Crypt::Mac::HMAC', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
199 0         0 my $signature = Crypt::Mac::HMAC::hmac_b64( "SHA256", "$key", "$value" );
200 0         0 $value = "$value.$signature";
201             }
202             elsif( $self->encrypt )
203             {
204 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
205 0         0 my $algo = $self->algo;
206 0   0     0 my $p = $self->_encrypt_objects( $key => $algo ) || return( $self->pass_error );
207 0         0 my $crypt = $p->{crypt};
208             # $value = Crypt::Misc::encode_b64( $crypt->encrypt( "$value", $p->{key}, $p->{iv} ) );
209 0         0 my $encrypted = $crypt->encrypt( "$value", $p->{key}, $p->{iv} );
210 0         0 $value = Crypt::Misc::encode_b64( $encrypted );
211             }
212             }
213 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  
214 0     0   0 {
215 0 0       0 return( $self->error( "An error occurred while trying to ", ( $self->sign ? 'sign' : 'encrypt' ), " cookie value: $e" ) );
216 3 0 0 3   62 }
  3 0 0     8  
  3 0 0     5126  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
217             }
218            
219             # Not necessary to encode, but customary and practical
220 38 50       29395 if( CORE::length( $value ) )
221             {
222 38         303 my $wrapped_in_double_quotes = 0;
223 38 50       109 if( $value =~ /^\"([^\"]+)\"$/ )
224             {
225 0         0 $value = $1;
226 0         0 $wrapped_in_double_quotes = 1;
227             }
228 38         411 $value = URI::Escape::uri_escape( $value );
229 38 50       1064 $value = sprintf( '"%s"', $value ) if( $wrapped_in_double_quotes );
230             }
231 38         130 my @parts = ( "${name}=${value}" );
232 38 100       525 return( $parts[0] ) if( $opts->{is_request} );
233 28 50       116 push( @parts, sprintf( 'Domain=%s', $self->domain ) ) if( $self->domain );
234 28 50       21821 push( @parts, sprintf( 'Port=%d', $self->port ) ) if( $self->port );
235 28 100       21174 push( @parts, sprintf( 'Path=%s', $self->path ) ) if( $self->path );
236             # Could be empty. If not specified, it would be a session cookie
237 28 100 100     20055 if( ( my $t = $self->expires ) && !$self->max_age->length )
238             {
239 14         9161 ( my $dt_str = "$t" ) =~ s/\bUTC\b/GMT/;
240 14         3871 push( @parts, sprintf( 'Expires=%s', $dt_str ) );
241             }
242             # Number of seconds until the cookie expires
243             # A zero or negative number will expire the cookie immediately.
244             # If both Expires and Max-Age are set, Max-Age has precedence.
245 28 100       515930 push( @parts, sprintf( 'Max-Age=%d', $self->max_age ) ) if( CORE::length( $self->max_age ) );
246 28 100 100     23484 if( $self->same_site->defined && $self->same_site =~ /^(?:lax|strict|none)/i )
247             {
248 3         2786 push( @parts, sprintf( 'SameSite=%s', ucfirst( lc( $self->same_site ) ) ) );
249             }
250 28 100       18559 push( @parts, 'Secure' ) if( $self->secure );
251 28 100       21575 push( @parts, 'HttpOnly' ) if( $self->http_only );
252 28         21342 my $c = join( '; ', @parts );
253 28         81 $self->{_cache_value} = $c;
254 28         57 CORE::delete( $self->{_reset} );
255 28         190 return( $c );
256             }
257              
258             # A Version 2 cookie, which has been deprecated by protocol
259             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie2
260 12     12 1 65 sub comment { return( shift->_set_get_scalar_as_object( 'comment', @_ ) ); }
261              
262             # A Version 2 cookie, which has been deprecated by protocol
263             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie2
264 12     12 1 111 sub commentURL { return( shift->_set_get_uri( 'commentURL', @_ ) ); }
265              
266 18     18 1 138921 sub created_on { return( shift->_set_get_datetime( 'created', @_ ) ); }
267              
268             sub decrypt
269             {
270 0     0 1 0 my $self = shift( @_ );
271 0         0 my $opts = $self->_get_args_as_hash( @_ );
272 0         0 my $value = $self->value;
273 0 0       0 return( $value ) if( !$value->length );
274 0   0     0 $opts->{key} //= '';
275 0   0     0 $opts->{algo} //= '';
276 0   0     0 $opts->{iv} //= '';
277 0   0     0 my $key = $opts->{key} || $self->key;
278 0   0     0 my $algo = $opts->{algo} || $self->algo;
279 0 0 0     0 return( $self->error( "Cookie encryption was enabled, but no key was set to decrypt it." ) ) if( !defined( $key ) || !CORE::length( "$key" ) );
280 0 0 0     0 return( $self->error( "Cookie encryption was enabled, but no algorithm was set to decrypt it." ) ) if( !defined( $algo ) || !CORE::length( "$algo" ) );
281 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
282 0     0   0 {
283 0 0       0 $self->_load_class( 'Crypt::Misc', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
284             # If IV is not provided, _encrypt_objects will generate one and save it for next time
285 0   0     0 my $p = $self->_encrypt_objects( $key => $algo, $opts->{iv} ) || return( $self->pass_error );
286 0         0 my $crypt = $p->{crypt};
287 0         0 my $bin = Crypt::Misc::decode_b64( "$value" );
288 0         0 return( $crypt->decrypt( "$bin", $p->{key}, $p->{iv} ) );
289             }
290 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  
291 0     0   0 {
292 0         0 return( $self->error( "An error occurred while trying to decrypt cookie value: $e" ) );
293 3 0 0 3   25 }
  3 0 0     37  
  3 0 0     3612  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
294             }
295              
296 9     9 1 51 sub discard { return( shift->_set_get_boolean( 'discard', @_ ) ); }
297              
298 121     121 1 16471 sub domain { return( shift->reset(@_)->_set_get_scalar_as_object( 'domain', @_ ) ); }
299              
300             # To expire a cookie, the domain and path must match that was previously set
301             # <https://datatracker.ietf.org/doc/html/rfc6265#section-3.1>
302             sub elapse
303             {
304 1     1 1 694 my $self = shift( @_ );
305 1         21 $self->expires(0);
306 1         1128 return( $self );
307             }
308              
309 38     38 1 29973 sub encrypt { return( shift->reset(@_)->_set_get_boolean( 'encrypt', @_ ) ); }
310              
311             # sub expires { return( shift->APR::Request::Cookie::expires( @_ ) ); }
312             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
313             # Example: Fri, 13 Dec 2019 02:27:28 GMT
314             sub expires
315             {
316 82     82 1 32662 my $self = shift( @_ );
317 82 100       286 if( @_ )
318             {
319 21         85 $self->reset(1);
320 21         55 my $exp = shift( @_ );
321 21         38 my $tz;
322             # DateTime::TimeZone::Local will die ungracefully if the local timezone is not set with the error:
323             # "Cannot determine local time zone"
324 21 50 33     80 try
  21         39  
  21         59  
  21         104  
  0         0  
  21         51  
  21         95  
  21         63  
325 21     21   38 {
326 21         136 $tz = DateTime::TimeZone->new( name => 'local' );
327             }
328 21 50 100     179 catch( $e )
  21 0 33     23362  
  14 0       44  
  21 0       50  
  21 0       33  
  21 0       30  
  21 0       35  
  21 0       117  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 100       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  21         77  
  0         0  
  14         20  
  7         22  
  7         58  
  21         89  
  21         125  
  21         61  
  21         79  
  0         0  
  0         0  
  0         0  
  0         0  
329 0     0   0 {
330 0         0 $tz = DateTime::TimeZone->new( name => 'UTC' );
331 3 0 0 3   23 }
  3 0 0     6  
  3 0 66     3334  
  0 0 33     0  
  0 0 66     0  
  0 0 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 66     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  
  21 0       82  
  0 0       0  
  21 0       543  
  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  
  21         91  
  0         0  
  0         0  
  0         0  
  0         0  
  21         67  
332 21         54 my $dt;
333             # unsets the value
334 21 100 33     216 if( !defined( $exp ) )
    100 66        
    100 33        
    100          
    100          
    50          
335             {
336 1         3 $self->{expires} = undef;
337             }
338             elsif( $exp =~ /^\d{1,10}$/ )
339             {
340 3 50 33     17 try
  3         10  
  3         15  
  3         34  
  0         0  
  3         8  
  3         10  
  3         11  
341 3     3   6 {
342             # Unexpectedly, DateTime sets the time zone ONLY after having instantiated the
343             # object and set its time zone to UTC.
344             # Thus, here setting to 'local' (e.g. corresponding to Asia/Tokyo) would
345             # actually set the epoch to GMT+9 instead of treating the epoch time provided
346             # to being in Asia/Tokyo time zone!
347             # Issue #126
348             # <https://github.com/houseabsolute/DateTime.pm/issues/126>
349 3         31 $dt = DateTime->from_epoch( epoch => $exp, time_zone => $tz );
350             }
351 3 50 100     29 catch( $e )
  3 0 33     1163  
  2 0       7  
  3 0       13  
  3 0       7  
  3 0       6  
  3 0       6  
  3 0       17  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 100       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  3         53  
  0         0  
  2         5  
  1         11  
  1         20  
  3         24  
  3         27  
  3         14  
  3         19  
  0         0  
  0         0  
  0         0  
  0         0  
352 0     0   0 {
353 0         0 return( $self->error( "An error occurred while setting the cookie expiration date time based on the unix timestamp '$exp'." ) );
354 3 0 0 3   25 }
  3 0 0     7  
  3 0 66     6054  
  0 0 33     0  
  0 0 66     0  
  0 0 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 66     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  
  3 0       10  
  0 0       0  
  3 0       103  
  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  
  3         16  
  0         0  
  0         0  
  0         0  
  0         0  
  3         24  
355             }
356             elsif( $self->_is_object( $exp ) && ( $exp->isa( 'DateTime' ) || $exp->isa( 'Module::Generic::Datetime' ) ) )
357             {
358 2         167 $dt = $exp;
359             }
360             elsif( $exp =~ /^([\+\-]?\d+)([YyMDdhms])?$/ )
361             {
362 9         145 my( $num, $unit ) = ( $1, $2 );
363 9 100       30 $unit = 's' if( !length( $unit ) );
364 9         55 my $interval =
365             {
366             's' => 1,
367             'm' => 60,
368             'h' => 3600,
369             'D' => 86400,
370             'd' => 86400,
371             'M' => 86400 * 30,
372             'Y' => 86400 * 365,
373             'y' => 86400 * 365,
374             };
375 9   50     37 my $offset = ( $interval->{ $unit } || 1 ) * int( $num );
376 9         24 my $ts = time() + $offset;
377 9         90 $dt = DateTime->from_epoch( epoch => $ts, time_zone => $tz );
378             }
379             elsif( lc( $exp ) eq 'now' )
380             {
381 1         24 $dt = DateTime->now( time_zone => $tz );
382             }
383             elsif( defined( $exp ) && CORE::length( $exp ) )
384             {
385 5         325 $dt = $self->_parse_timestamp( $exp );
386 5 50       8093827 return( $self->pass_error ) if( !defined( $dt ) );
387 5 100       41 return( $self->error( "Provided expires value '$exp' is an invalid expression." ) ) if( !CORE::length( $dt ) );
388             }
389             else
390             {
391             }
392            
393 20 100       5919 if( defined( $dt ) )
394             {
395 19 50       139 $dt = $self->_header_datetime( $dt ) if( $self->_is_a( $dt, 'DateTime' ) );
396 19 50       298 $self->{expires} = $dt->isa( 'Module::Generic::DateTime' ) ? $dt : Module::Generic::DateTime->new( $dt );
397             }
398             }
399 81         10517 return( $self->_set_get_datetime( 'expires' ) );
400             }
401              
402 3     3 1 33 sub fields { return( shift->_set_get_array_as_object( 'fields', @_ ) ); }
403              
404 0     0 1 0 sub host { return( shift->domain( @_ ) ); }
405              
406 12     12 1 56 sub host_only { return( shift->implicit( @_ ) ); }
407              
408 46     46 1 1971 sub http_only { return( shift->reset(@_)->_set_get_boolean( 'http_only', @_ ) ); }
409              
410 0     0 1 0 sub httponly { return( shift->http_only( @_ ) ); }
411              
412 32     32 1 155 sub implicit { return( shift->reset(@_)->_set_get_boolean( 'implicit', @_ ) ); }
413              
414             # For cookie encryption
415 0     0 1 0 sub initialisation_vector { return( shift->_set_get_scalar_as_object( 'initialisation_vector', @_ ) ); }
416              
417             sub is_expired
418             {
419 0     0 1 0 my $self = shift( @_ );
420 0         0 my $exp = $self->expires;
421 0         0 my $max_age = $self->max_age;
422 0 0 0     0 return( $self->false ) if( !defined( $exp ) && !defined( $max_age ) );
423 0 0 0     0 if( ( defined( $exp ) && !$self->_is_a( $exp, 'Module::Generic::DateTime' ) && !$self->_is_a( $exp, 'DateTime' ) ) ||
      0        
      0        
      0        
424             ( defined( $max_age ) && $max_age !~ /\-?\d+$/ ) )
425             {
426 0         0 return( $self->false );
427             }
428 0         0 my $now = DateTime->now;
429 0 0 0     0 if( ( defined( $max_age ) && $max_age <= 0 ) ||
      0        
      0        
430             ( defined( $exp ) && $exp < $now ) )
431             {
432 0         0 return( $self->true );
433             }
434             else
435             {
436 0         0 return( $self->false );
437             }
438             }
439              
440 0     0 1 0 sub is_persistent { return( !shift->is_session ); }
441              
442             sub is_session
443             {
444 0     0 1 0 my $self = shift( @_ );
445 0 0 0     0 return( defined( $self->expires ) || defined( $self->max_age ) ? $self->false : $self->true );
446             }
447              
448 0     0 1 0 sub is_tainted { return( shift->_set_get_boolean( 'is_tainted', @_ ) ); }
449              
450             sub is_valid
451             {
452 0     0 1 0 my $self = shift( @_ );
453 0         0 my $opts = $self->_get_args_as_hash( @_ );
454 0   0     0 $opts->{key} ||= $self->key || '';
      0        
455 0 0 0     0 return( $self->true ) if( !$self->sign && !CORE::length( $opts->{key} ) );
456 0 0 0     0 return( $self->error( "Signature validation is required, but no key has been set." ) ) if( !$self->key->length && !CORE::exists( $opts->{key} ) || ( CORE::exists( $opts->{key} ) && !CORE::length( $opts->{key} ) ) );
      0        
      0        
457 0         0 my $value = $self->value;
458 0 0       0 return( $self->true ) if( !$value->length );
459 0 0       0 if( $value->index( '.' ) == -1 )
460             {
461             # Not an error, so we only issue a warning if warnings are enabled
462 0 0       0 warnings::warn( "The cookie does not have a signature attached to it." ) if( warnings::enabled() );
463 0         0 return( $self->false );
464             }
465 0         0 my @parts = $value->split( '.' );
466             # We take the last one, because the cookie name, itself, could potentially contain dots.
467             # The value must be an uri unescaped value
468 0         0 my $sig = pop( @parts );
469 0         0 my $orig = join( '.', @parts );
470 0         0 my $key = $opts->{key};
471 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
472 0     0   0 {
473 0 0       0 $self->_load_class( 'Crypt::Mac::HMAC', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
474 0         0 my $check = Crypt::Mac::HMAC::hmac_b64( 'SHA256', "$key", "$orig" );
475 0         0 return( "$check" eq "$sig" );
476             }
477 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  
478 0     0   0 {
479 0         0 return( $self->error( "An error occurred while trying to check the cookie signature validation: $e" ) );
480 3 0 0 3   24 }
  3 0 0     6  
  3 0 0     4045  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
481             }
482              
483 0     0 1 0 sub iv { return( shift->initialisation_vector( @_ ) ); }
484              
485 0     0 1 0 sub key { return( shift->_set_get_scalar_as_object( 'key', @_ ) ); }
486              
487             # Check if the cookie domain is within the host provided, i.e.
488             # wether this cookie should be sent as part of the request
489             sub match_host
490             {
491 0     0 1 0 my $self = shift( @_ );
492             # e.g. www.example.com
493 0   0     0 my $host = shift( @_ ) || return(0);
494 0         0 $host = lc( $host );
495             # and ours could be just example.com
496 0         0 my $dom = $self->domain;
497 0 0       0 return(1) if( $host eq $dom );
498             # if our domain is longer than $host, then we are not a match as we should be a subset
499             # e.g. ours www.ja.example.com vs $host ja.example.com
500 0 0       0 return(0) if( CORE::length( $dom ) > CORE::length( $host ) );
501             # our cookie domain has been set implicitly and since we are not an exact match,
502             # no need to go further.
503 0 0       0 unless( $self->implicit )
504             {
505 0 0       0 return( $host =~ /\.${dom}$/ ? 1 : 0 );
506             }
507 0         0 return(0);
508             }
509              
510             # sub max_age { return( shift->reset(@_)->_set_get_scalar( 'max_age', @_ ) ); }
511             sub max_age
512             {
513 60     60 1 19138 my $self = shift( @_ );
514 60 100       193 if( @_ )
515             {
516 2         23 $self->reset( @_ );
517 2         4 my $v = shift( @_ );
518 2 50       12 if( !defined( $v ) )
519             {
520 0         0 $self->{max_age} = undef;
521             }
522             else
523             {
524 2 50       19 return( $self->error( "Invalid max-age value '$v'" ) ) if( $v !~ /^\-?\d+$/ );
525 2         8 $v = int( $v );
526             # "If both Expires and Max-Age are set, Max-Age has precedence"
527             # <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
528 2         4 my $exp;
529 2 100       19 if( $v <= 0 )
530             {
531 1         10 $exp = DateTime->new(
532             year => 1970,
533             month => 1,
534             day => 1,
535             hour => 0,
536             minute => 0,
537             second => 0,
538             time_zone => 'GMT',
539             );
540             }
541             else
542             {
543 1         8 my $tz;
544             # DateTime::TimeZone::Local will die ungracefully if the local timezeon is not set with the error:
545             # "Cannot determine local time zone"
546 1 50 33     4 try
  1         2  
  1         3  
  1         7  
  0         0  
  1         2  
  1         5  
  1         2  
547 1     1   4 {
548 1         8 $tz = DateTime::TimeZone->new( name => 'local' );
549             }
550 1 0 50     9 catch( $e )
  1 0 33     856  
  1 0       5  
  1 0       2  
  1 0       2  
  1 0       3  
  1 0       10  
  1 0       8  
  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         5  
  0         0  
  1         3  
  0         0  
  0         0  
  1         6  
  1         15  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
551 0     0   0 {
552 0         0 $tz = DateTime::TimeZone->new( name => 'UTC' );
553 3 0 0 3   24 }
  3 0 0     8  
  3 0 33     6400  
  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       5  
  0 0       0  
  1 0       65  
  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         5  
  0         0  
  0         0  
  0         0  
  0         0  
  1         4  
554 1         10 $exp = DateTime->now( time_zone => $tz );
555 1         620 $exp->add( seconds => $v );
556             }
557 2         2176 $self->expires( $exp );
558 2         2465 return( $self->_set_get_number( 'max_age' => $v ) );
559             }
560             }
561 58         205 return( $self->_set_get_number( 'max_age' ) );
562             }
563              
564 0     0 1 0 sub maxage { return( shift->max_age( @_ ) ); }
565              
566             sub name
567             {
568 160     160 1 17459 my $self = shift( @_ );
569 160 100       527 if( @_ )
570             {
571 44         193 $self->reset( @_ );
572 44         116 my $name = shift( @_ );
573             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
574 44 50 33     273 if( $name =~ /[\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=\{\}]/ && $self->strict )
575             {
576 0         0 return( $self->error( "A cookie name can only contain US ascii characters. Cookie name provided was '$name'." ) );
577             }
578 44 50       272 if( $name =~ s/^__Secure\-// )
    50          
579             {
580 0         0 $self->secure(1);
581             }
582             elsif( $name =~ s/^__Host\-// )
583             {
584 0         0 $self->secure(1);
585 0 0       0 $self->path( '/' ) if( !$self->path->length );
586             }
587 44         165 $self->_set_get_scalar_as_object( 'name' => $name );
588             }
589 160         3453732 return( $self->_set_get_scalar_as_object( 'name' ) );
590             }
591              
592 148     148 1 232633 sub path { return( shift->reset(@_)->_set_get_scalar_as_object( 'path', @_ ) ); }
593              
594 66     66 1 22295 sub port { return( shift->reset(@_)->_set_get_number( 'port', @_ ) ); }
595              
596             sub reset
597             {
598 754     754 1 1418 my $self = shift( @_ );
599 754 50 100     2615 $self->{_reset} = scalar( @_ ) if( !CORE::length( $self->{_reset} ) && scalar( @_ ) );
600 754         2580 return( $self );
601             }
602              
603             sub same_as
604             {
605 3     3 1 25 my $self = shift( @_ );
606 3         9 my $this = shift( @_ );
607 3 50 33     10 return(0) if( !$this || !$self->_is_object( $this ) );
608 3         48 my $fields = $self->fields;
609 3         2788 foreach my $f ( @$fields )
610             {
611 45         868 my $v = $self->$f;
612 45         41114 my $code = $this->can( $f );
613 45 50       132 return(0) if( !$code );
614 45         81 my $v2 = $code->( $this );
615 45 50 66     40468 if( ( !defined( $v ) && defined( $v2 ) ) ||
      66        
      33        
      66        
      33        
      66        
      66        
      33        
616             ( defined( $v ) && !defined( $v2 ) ) ||
617             ( defined( $v ) && length( "$v" ) != length( "$v2" ) ) ||
618             ( defined( $v ) && defined( $v2 ) && "$v" ne "$v2" ) )
619             {
620 0         0 return(0);
621             }
622             }
623 3         25 return(1);
624             }
625              
626 51     51 1 5694 sub same_site { return( shift->reset(@_)->_set_get_scalar_as_object( 'same_site', @_ ) ); }
627              
628 0     0 1 0 sub samesite { return( shift->same_site( @_ ) ); }
629              
630 51     51 1 2185 sub secure { return( shift->reset(@_)->_set_get_boolean( 'secure', @_ ) ); }
631              
632 38     38 1 156 sub sign { return( shift->reset(@_)->_set_get_boolean( 'sign', @_ ) ); }
633              
634 0     0 1 0 sub strict { return( shift->reset(@_)->_set_get_boolean( 'strict', @_ ) ); }
635              
636             sub uri
637             {
638 0     0 1 0 my $self = shift( @_ );
639 0 0       0 if( @_ )
    0          
640             {
641 0         0 $self->reset( @_ );
642 0   0     0 my $uri = $self->_set_get_uri( 'uri', @_ ) || return;
643 0         0 $self->port( $uri->port );
644 0         0 $self->path( $uri->path );
645 0         0 $self->domain( $uri->host );
646             }
647             elsif( $self->domain )
648             {
649 0 0       0 my $uri =
    0          
    0          
650             ( $self->secure ? 'https' : 'http' ) . '://' .
651             $self->domain .
652             ( $self->port ? ':' . $self->port : '' ) .
653             ( $self->path ? $self->path : '/' );
654 0         0 return( $self->_set_get_uri( 'uri' => $uri ) );
655             }
656 0         0 return( $self->_set_get_uri( 'uri' ) );
657             }
658              
659 96     96 1 124971 sub value { return( shift->reset(@_)->_set_get_scalar_as_object( 'value', @_ ) ); }
660              
661             # Deprecated. Was a version 2 cookie spec: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie2
662 15     15 1 82 sub version { return( shift->_set_get_number( 'version', @_ ) ); }
663              
664             sub _encrypt_objects
665             {
666 0     0   0 my $self = shift( @_ );
667 0         0 my( $key, $algo, $iv ) = @_;
668 0 0 0     0 return( $self->error( "Key provided is empty!" ) ) if( !defined( $key ) || !CORE::length( "$key" ) );
669 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" ) );
670 0   0     0 $iv //= '';
671 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
672 0     0   0 {
673 0 0       0 $self->_load_class( 'Crypt::Mode::CBC', { version => CRYPTX_VERSION } ) || return( $self->pass_error );
674 0 0       0 $self->_load_class( 'Bytes::Random::Secure' ) || return( $self->pass_error );
675 0   0     0 my $crypt = Crypt::Mode::CBC->new( "$algo" ) || return( $self->error( "Unable to create a Crypt::Mode::CBC object." ) );
676 0         0 my $class = "Crypt::Cipher::${algo}";
677 0 0       0 $self->_load_class( $class ) || return( $self->pass_error );
678 0         0 my $key_len = $class->keysize;
679 0         0 my $block_len = $class->blocksize;
680 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 );
681             # Generate an "IV", i.e. Initialisation Vector based on the required block size
682 0   0     0 $iv ||= $self->initialisation_vector;
683 0 0 0     0 if( defined( $iv ) && CORE::length( "$iv" ) )
684             {
685 0 0       0 if( CORE::length( "$iv" ) != $block_len )
686             {
687 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 documentation Cookie" ) );
688             }
689             }
690             else
691             {
692 0         0 $iv = Bytes::Random::Secure::random_bytes( $block_len );
693             # Save it for decryption
694 0         0 $self->initialisation_vector( $iv );
695             }
696 0         0 my $key_pack = pack( 'H' x $key_len, $key );
697 0         0 my $iv_pack = pack( 'H' x $block_len, $iv );
698 0         0 return({ 'crypt' => $crypt, key => $key_pack, iv => $iv_pack });
699             }
700 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  
701 0     0   0 {
702 0         0 return( $self->error( "Error getting the encryption objects for algorithm \"$algo\": $e" ) );
703 3 0 0 3   24 }
  3 0 0     6  
  3 0 0     1145  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
704             }
705              
706             sub _header_datetime
707             {
708 19     19   948 my $self = shift( @_ );
709 19         32 my $dt;
710 19 50       56 if( @_ )
711             {
712 19 50       72 return( $self->error( "Date time provided ($dt) is not an object." ) ) if( !$self->_is_object( $_[0] ) );
713 19 50       211 return( $self->error( "Object provided (", ref( $_[0] ), ") is not a DateTime object." ) ) if( !$_[0]->isa( 'DateTime' ) );
714 19         45 $dt = shift( @_ );
715             }
716 19 50       51 $dt = DateTime->now if( !defined( $dt ) );
717 19         56 $dt->set_time_zone( 'GMT' );
718 19         4556 my $fmt = DateTime::Format::Strptime->new(
719             pattern => '%a, %d %b %Y %H:%M:%S GMT',
720             locale => 'en_GB',
721             time_zone => 'GMT',
722             );
723 19         32421 $dt->set_formatter( $fmt );
724 19         1338 return( $dt );
725             }
726              
727             sub TO_JSON
728             {
729 0     0 1   my $self = shift( @_ );
730 0           my $fields = $self->fields;
731 0           my $ref = {};
732 0           foreach my $m ( @$fields )
733             {
734 0           $ref->{ $m } = $self->$m;
735             }
736 0           return( $ref );
737             }
738              
739             1;
740             # NOTE: POD
741             __END__
742              
743             =encoding utf8
744              
745             =head1 NAME
746              
747             Cookie - Cookie Object with Encryption or Signature
748              
749             =head1 SYNOPSIS
750              
751             use Cookie;
752             my $c = Cookie->new(
753             name => 'my-cookie',
754             domain => 'example.com',
755             value => 'sid1234567',
756             path => '/',
757             expires => '+10D',
758             # or alternatively
759             maxage => 864000
760             # to make it exclusively accessible by regular http request and not javascript
761             http_only => 1,
762             same_site => 'Lax',
763             # should it be used under ssl only?
764             secure => 1,
765             );
766             # make the cookie expired
767             # Sets the expiration datetime to Thu, 01 Jan 1970 09:00:00 GMT
768             $c->elapse;
769             # Get cookie as an hash reference
770             my $hash = $c->as_hash;
771             print $c->as_string, "\n";
772             # or
773             print "$c\n";
774             # If expires is set, we can use its underlying DateTime object
775             my $now = DateTime->now;
776             if( $c->expires && $c->expires > $now )
777             {
778             # ok, we're good
779             }
780             # Unset expiration, effectively transforming it into a session cookie
781             $c->expires( undef );
782             print "Is session cookie? ", $c->is_session ? 'yes' : 'no', "\n";
783             $c->match_host( 'www.example.com' );
784             # Set max-age (in seconds) that takes precedence over expiration
785             $c->max_age( 86400 );
786             # Make it expired to tell the http client to remove it:
787             $c->max_age(0) # or $c->max_age(-1)
788             # Unset max-age
789             $c->max_age( undef );
790             print "Is it same? ", $c->same_as( $other ) ? 'yes' : 'no', "\n";
791             # Conveniently set port, path and domain in one go, but not the secure flag
792             $c->uri( 'https://www.example.com:8080/some/where' );
793              
794             # Create encrypted cookie
795             # You can generate a key or type one as long as it meets the size requirement
796             use Bytes::Random::Secure ();
797             my $c = Cookie->new(
798             name => 'my-cookie',
799             domain => 'example.com',
800             value => 'sid1234567',
801             path => '/',
802             expires => '+10D',
803             # or alternatively
804             maxage => 864000
805             # to make it exclusively accessible by regular http request and not ajax
806             http_only => 1,
807             same_site => 'Lax',
808             # should it be used under ssl only?
809             secure => 1,
810             # Encryption parameters
811             key => Bytes::Random::Secure::random_bytes(32),
812             algo => 'AES',
813             encrypt => 1,
814             );
815             print( "My encrypted cookie: $c\n" );
816              
817             # Sign cookie only
818             my $c = Cookie->new(
819             name => 'my-cookie',
820             domain => 'example.com',
821             value => 'sid1234567',
822             path => '/',
823             expires => '+10D',
824             # or alternatively
825             maxage => 864000
826             # to make it exclusively accessible by regular http request and not ajax
827             http_only => 1,
828             same_site => 'Lax',
829             # should it be used under ssl only?
830             secure => 1,
831             # Encryption parameters
832             # No size constraint for signature, but obviously the longer the better
833             key => Bytes::Random::Secure::random_bytes(32),
834             sign => 1,
835             );
836             print( "My signed cookie: $c\n" );
837              
838             =head1 VERSION
839              
840             v0.3.0
841              
842             =head1 DESCRIPTION
843              
844             This is a powerful and versatile package to create and represent a cookie compliant with the latest standard as set by L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265>. This can be used as a standalone module, or can be managed as part of the cookie jar L<Cookie::Jar>
845              
846             The object is overloaded and will call L</as_string> upon stringification and can also be used in comparison with other cookie object:
847              
848             if( $cookie1 eq $cookie2 )
849             {
850             # do something
851             }
852              
853             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.
854              
855             See also the L<Cookie::Jar> package to manage server and client side handling of cookies:
856              
857             use Cookie::Jar;
858             # Possibly passing the cookie repository the Apache2::RequestRec object
859             my $jar = Cookie::Jar->new( $r );
860             my $c = $jar->make(
861             name => 'my_cookie',
862             value => 'some value',
863             domain => 'example.org',
864             path => '/',
865             secure => 1,
866             http_only => 1,
867             ) || die( $jar->error );
868             # Set it in the server response C<Set-Cookie> header:
869             $jar->set( $c ) || die( $jar->error );
870              
871             =head1 METHODS
872              
873             =head2 new
874              
875             Provided with an hash or hash reference of parameters, and this initiates a new cookie object and return it. Each of the following parameters has a corresponding method.
876              
877             =over 4
878              
879             =item * C<debug>
880              
881             Optional. If set with a positive integer, this will activate verbose debugging message
882              
883             =item * C<name>
884              
885             String.
886              
887             See also L</name>
888              
889             =item * C<value>
890              
891             String.
892              
893             See also L</value>
894              
895             =item * C<comment>
896              
897             String.
898              
899             See also L</comment>
900              
901             =item * C<commentURL>
902              
903             URI string or object.
904              
905             See also L</commentURL>
906              
907             =item * C<discard>
908              
909             Boolean.
910              
911             See also L</discard>
912              
913             =item * C<domain>
914              
915             String.
916              
917             See also L</domain>
918              
919             =item * C<expires>
920              
921             Datetime str | DateTime object | integer
922              
923             See also L</expires>
924              
925             =item * C<http_only>
926              
927             Boolean,
928              
929             See also L</http_only>
930              
931             =item * C<implicit>
932              
933             Boolean.
934              
935             See also L</implicit>
936              
937             =item * C<max_age>
938              
939             Integer.
940              
941             See also L</max_age>
942              
943             =item * C<path>
944              
945             String.
946              
947             See also L</path>
948              
949             =item * C<port>
950              
951             Integer.
952              
953             See also L</port>
954              
955             =item * C<same_site>
956              
957             String.
958              
959             See also L</same_site>
960              
961             =item * C<secure>
962              
963             Boolean.
964              
965             See also L</secure>
966              
967             =item * C<version>
968              
969             Integer.
970              
971             See also L</version>
972              
973             =back
974              
975             Other extra parameters not directly related to the cookie standard:
976              
977             =over 4
978              
979             =item * C<accessed_on>
980              
981             Datetime.
982              
983             See also L</accessed_on>
984              
985             =item * C<algo>
986              
987             String.
988              
989             See also L</algo>
990              
991             =item * C<created_on>
992              
993             Datetime.
994              
995             See also L</created_on>
996              
997             =item * C<encrypt>
998              
999             Boolean.
1000              
1001             See also L</encrypt>
1002              
1003             =item * C<key>
1004              
1005             String.
1006              
1007             See also L</key>
1008              
1009             =item * C<sign>
1010              
1011             Boolean.
1012              
1013             See also L</sign>
1014              
1015             =back
1016              
1017             =head2 accessed_on
1018              
1019             Set or get the datetime of the cookie object last accessed.
1020              
1021             According to L<rfc6265, section 5.3.12.3|https://datatracker.ietf.org/doc/html/rfc6265#section-5.3>, when deciding which cookies to remove, for those who have equal removal priority:
1022              
1023             "If two cookies have the same removal priority, the user agent MUST evict the cookie with the earliest last-access date first."
1024              
1025             =head2 algo
1026              
1027             This set or get the the algorithm used to encrypt the cookie value.
1028              
1029             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>
1030              
1031             See also L<Stackoverflow on the choice of encryption algorithm|https://stackoverflow.com/questions/4147451/aes-vs-blowfish-for-file-encryption>
1032              
1033             By default, the algorithm is set to C<AES>
1034              
1035             If the algorithm set is unsupported, this method returns an L<error|Module::Generic/error>
1036              
1037             It returns the current value as a L<scalar object|Module::Generic::Scalar>
1038              
1039             =head2 apply
1040              
1041             Provided with an hash ore hash reference of cookie parameter, and this will apply them to each of their equivalent method.
1042              
1043             $c->apply(
1044             expires => 'now',
1045             secure => 1,
1046             http_only => 1,
1047             );
1048              
1049             In the example above, this will call methods L</expires>, L</secure> and L</http_only> passing them the relevant values.
1050              
1051             It returns the current object.
1052              
1053             =head2 as_hash
1054              
1055             Returns an hash reference of the cookie value.
1056              
1057             The hash reference returned will contain the following keys: C<name> C<value> C<comment> C<commentURL> C<domain> C<expires> C<http_only> C<implicit> C<max_age> C<path> C<port> C<same_site> C<secure> C<version>
1058              
1059             =head2 as_string
1060              
1061             Returns a string representation of the object.
1062              
1063             my $cookie_string = $cookie->as_string;
1064             # or
1065             my $cookie_string = "$cookie";
1066             my-cookie="sid1234567"; Domain=example.com; Path=/; Expires=Mon, 09 Jan 2020 12:17:30 GMT; Secure; HttpOnly
1067              
1068             If encryption is enabled with L</encrypt>, the cookie value will be encrypted using the key provided with L</key> and the L<Initialisation Vector|/initialisation_vector>. If the latter was not provided, it will be generated automatically. The resulting encrypted value is then encoded in base64 and escaped. For example:
1069              
1070             my $cookie_value = "toc_ok=1";
1071             my $key = Bytes::Random::Secure::random_bytes(32);
1072             # result:
1073             # session=PyJTlRJniAYVJJF6%2FswuPw%3D%3D; Path=/; SameSite=Lax; Secure; HttpOnly
1074              
1075             If cookie signature is enabled for integrity protection with L</sign>, an sha256 hmac will be generated using the key provided with L</key> and the resulting hash appended to the cookie value separated by a dot. For example:
1076              
1077             my $cookie_value = "toc_ok=1";
1078             my $key = "hard to guess key";
1079             # I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
1080             my $signature = Crypt::Mac::HMAC::hmac_b64( $key, $cookie_value );
1081             # result: toc_ok=1.I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
1082             # ultimately the cookie value sent will be:
1083             # toc_ok%3D1.I2M4%2Frh%2FTiNV5RZDSBJkhLblBvrN5k9448G6w%2Fgp%2Fjg%3D
1084              
1085             The returned value is cached so the next time, it simply return the cached version and not re-process it. You can reset it by calling L</reset>.
1086              
1087             =head2 comment
1088              
1089             $cookie->comment( 'Some comment' );
1090             my $comment = $cookie->comment;
1091              
1092             Sets or gets the optional comment for this cookie. This was used in version 2 of cookies but has since been deprecated.
1093              
1094             Returns a L<Module::Generic::Scalar> object.
1095              
1096             =head2 commentURL
1097              
1098             $cookie->commentURL( 'https://example.com/some/where.html' );
1099             my $comment = $cookie->commentURL;
1100              
1101             Sets or gets the optional comment URL for this cookie. This was used in version 2 of cookies but has since been deprecated.
1102              
1103             Returns an L<URI> object.
1104              
1105             =head2 created_on
1106              
1107             Set or get the datetime of the cookie object created. This value is primarily used by L<Cookie::Jar>, as per the rfc6265, when setting the http request header C<Cookie> to differentiate two cookies that share the same domain and path. The cookie that has their creation datetime earlier are set first:
1108              
1109             "Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times." (L<rfc6265, section 5.4.2|https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>)
1110              
1111             =head2 decrypt
1112              
1113             This returns the cookie decrypted value. If it used on a non-encrypted cookie, this would return C<undef> and set an L<error|Module::Generic/error>
1114              
1115             It takes an optional hash or hash reference of parameters:
1116              
1117             =over 4
1118              
1119             =item I<algo> string
1120              
1121             The algorithm to use for encryption. Defaults to the value set with L</algo>. See this method for more information on acceptable values.
1122              
1123             =item I<iv> string
1124              
1125             The Initialisation Vector used for encryption and decryption. Default to the value set with L</initialisation_vector>
1126              
1127             =item I<key> string
1128              
1129             The encryption key. Defaults to the value set with L</key>
1130              
1131             =back
1132              
1133             =head2 discard
1134              
1135             Boolean. Set or get this value to true to flag this cookie to be discarded, whatever that means to you the user. This is not a standard protocol property.
1136              
1137             This method is used in L<Cookie::Jar/save_as_lwp> and L<Cookie::Jar/save_as_netscape> with the option C<skip_discard>
1138              
1139             It returns the current value as a L<Module::Generic::Boolean> object.
1140              
1141             =head2 domain
1142              
1143             $cookie->domain( 'example.com' );
1144             my $dom = $cookie->domain;
1145              
1146             Sets or gets the domain for this cookie.
1147              
1148             Returns the current value as a L<Module::Generic::Scalar> object.
1149              
1150             Note that you can also call it using the alias method C<host>
1151              
1152             =head2 elapse
1153              
1154             Set the C<expires> value for this cookie to C<0>, which, in turn, will set it to C<Thu, 01 Jan 1970 09:00:00 GMT>
1155              
1156             When sent to the http client, this will have the effect of removing the cookie.
1157              
1158             See L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265#section-3.1> for more information.
1159              
1160             =head2 encrypt
1161              
1162             Set or get the boolean value. If true, the this will tell L</as_string> to encrypt the cookie value.
1163              
1164             To use this feature, an encryption L<key|/key> must be set and the module L<Crypt::Cipher> must be installed.
1165              
1166             You can read more about the differences between L<sign and encryption at Stackoverflow|https://stackoverflow.com/questions/41467012/what-is-the-difference-between-signed-and-encrypted-cookies-in-rails>
1167              
1168             =head2 expires
1169              
1170             Sets or gets the expiration date and time for this cookie.
1171              
1172             The value provided can be one of:
1173              
1174             =over 4
1175              
1176             =item A date compliant with L<rfc7231|https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1>
1177              
1178             For example: C<01 Nov 2021 08:42:17 GMT>
1179              
1180             =item unix timestamp.
1181              
1182             For example: C<1631099228>
1183              
1184             =item variable time.
1185              
1186             For example: C<30s> (30 seconds), C<5m> (5 minutes), C<12h> (12 hours), C<30D> (30 days), C<2M> (2 months), C<1Y> (1 year)
1187              
1188             However, this is not sprintf, so you cannot combine them, thus B<you cannot do this>: C<5m1D>
1189              
1190             =item C<now>
1191              
1192             Special keyword
1193              
1194             =item In last resort, the value provided will be parsed using L<Module::Generic/_parse_timestamp>. If parsing fails, it will return C<undef> and set an error.
1195              
1196             =back
1197              
1198             Ultimately, a L<DateTime> will be derived from those values, or C<undef> will be returned and an error will be set.
1199              
1200             The L<DateTime> object will be set with a formatter to allow a stringification that is compliant with rfc6265.
1201              
1202             And you can use L</max_age> alternatively.
1203              
1204             See also L<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date>
1205              
1206             Note that a cookie without an expiration datetime is referred as a C<session cookie>, so setting the cookie expiration change a cookie from being a session cookie to being a more permanent cookie.
1207              
1208             As L<documented|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>, if expiration is "unspecified, the cookie becomes a session cookie. A session finishes when the client shuts down, after which the session cookie is removed."
1209              
1210             =head2 fields
1211              
1212             Returns an L<array object|Module::Generic::Array> of cookie fields available. This is essentially used by L</apply>
1213              
1214             =head2 host
1215              
1216             Alias for L</domain>
1217              
1218             =head2 host_only
1219              
1220             This is an alias for L</implicit>. It has been added to comply with the language of L<rfc6265, section 5.3.6|https://datatracker.ietf.org/doc/html/rfc6265#section-5.3>
1221              
1222             If the domain attribute was not provided by the server for this cookie, then:
1223             "set the cookie's host-only-flag to true." and "set the cookie's domain to the canonicalized request-host"
1224              
1225             Returns the current value as a L<Module::Generic::Boolean> object (that is stringifyable).
1226              
1227             =head2 http_only
1228              
1229             Sets or gets the boolean for C<httpOnly>
1230              
1231             Returns a L<Module::Generic::Boolean> object.
1232              
1233             =head2 httponly
1234              
1235             Alias for L</http_only>
1236              
1237             =head2 implicit
1238              
1239             This boolean is set to true if the L<domain|/domain> was not initially set and has been derived from the current host.
1240              
1241             Returns a L<Module::Generic::Boolean> object.
1242              
1243             =head2 initialisation_vector
1244              
1245             Set or get the L<Initialisation Vector|https://en.wikipedia.org/wiki/Initialization_vector> used for cookie 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.
1246              
1247             To find the right size for the Initialisation Vector, for example for algorithm C<AES>, you could do:
1248              
1249             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->blocksize'
1250              
1251             which would yield C<16>
1252              
1253             =head2 is_expired
1254              
1255             Returns true if this cookie has an expiration datetime set and it has expired, i.e. the expiration datetime is in the past. Otherwise, it returns false.
1256              
1257             Return value is in the form of a L<Module::Generic::Boolean> object that stringifies to 1 or 0;
1258              
1259             =head2 is_persistent
1260              
1261             Boolean. This returns true if the cookie sent from the server is not a session cookie, i.e. it has an L</expires> value set.
1262              
1263             See L<rfc62655, section 5.3.3|https://datatracker.ietf.org/doc/html/rfc6265#section-5.3>
1264              
1265             =head2 is_session
1266              
1267             Returns true if this is a session cookie, i.e. it has no expiration datetime nor any L</max_age> set, otherwise, it returns false.
1268              
1269             Return value is in the form of a L<Module::Generic::Boolean> object that stringifies to 1 or 0;
1270              
1271             =head2 is_tainted
1272              
1273             Sets or gets the boolean value. This is a legacy method of old cookie module, but not used anymore.
1274              
1275             Returns a L<Module::Generic::Boolean> object.
1276              
1277             =head2 is_valid
1278              
1279             This takes an optional hash or hash reference of parameters.
1280              
1281             It returns true if the cookie was signed and the signature is valid, or false otherwise.
1282              
1283             If an error occurred, this method returns C<undef> and sets an L<error|Module::Generic/error> instead, so check the return value.
1284              
1285             my $rv = $c->is_valid;
1286             die( $c->error ) if( !defined( $rv ) );
1287             print( "Cookie is valid? ", $rv ? 'yes' : 'no', "\n" );
1288              
1289             Return value is in the form of a L<Module::Generic::Boolean> object that stringifies to 1 or 0;
1290              
1291             Possible parameters are:
1292              
1293             =over 4
1294              
1295             =item I<key> string
1296              
1297             The encryption key to use to sign and verify the cookie signature. Defaults to the value set with L</key>
1298              
1299             =back
1300              
1301             =head2 iv
1302              
1303             This is an alias for L</initialisation_vector>
1304              
1305             =head2 key
1306              
1307             Set or get the encryption key used to encrypt the cookie value. This is used when L</encrypt> or L</sign> are set to true.
1308              
1309             When used for cookie encryption, make sure the key size is big enough to satisfy the encryption algorithm requirement, which you can check with, say for C<AES>:
1310              
1311             perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->keysize'
1312              
1313             In this case, it will yield C<32>. Replace above C<AES>, byt whatever algorithm you have chosen.
1314              
1315             perl -MCrypt::Cipher::Blowfish -lE 'say Crypt::Cipher::Blowfish->keysize'
1316              
1317             would yield C<56> for C<Blowfish>
1318              
1319             You can use L<Bytes::Random::Secure/random_bytes> to generate a random key:
1320              
1321             # will generate a 32 bytes-long key
1322             my $key = Bytes::Random::Secure::random_bytes(32);
1323              
1324             =head2 match_host
1325              
1326             Provided with an host name and this returns true if this cookie domain either is a perfect match or if the L</implicit> flag is on and the cookie domain is a subset of the host provided.
1327              
1328             Otherwise this returns false.
1329              
1330             =head2 max_age
1331              
1332             Sets or gets the integer value for C<Max-Age>
1333              
1334             This value should be an integer representing the number of seconds until this cookie expires.
1335              
1336             As per the rfc6265, C<Max-Age> takes precedence over C<Expires> when set, so if you set this, any value set with L</expires> will be discarded.
1337              
1338             Returns a L<Module::Generic::Number> object.
1339              
1340             =head2 maxage
1341              
1342             Alias for L</max_age>
1343              
1344             =head2 name
1345              
1346             Sets or gets the cookie name.
1347              
1348             As per the L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>, a cookie name cannot contain any of the following charadcters:
1349              
1350             \(\)\<\>\@\,\;\:\\\"\/\[\]\?\=\{\}
1351              
1352             Returns a L<Module::Generic::Scalar> object.
1353              
1354             =head2 path
1355              
1356             Sets or gets the path.
1357              
1358             Returns a L<Module::Generic::Scalar> object.
1359              
1360             =head2 port
1361              
1362             Sets or gets the port number.
1363              
1364             Returns a L<Module::Generic::Number> object.
1365              
1366             =head2 reset
1367              
1368             Set the reset flag to true, which will force L</as_string> to recompute the string value of the cookie.
1369              
1370             =head2 same_as
1371              
1372             Provided with another object and this returns true if it has the same property values, false otherwise.
1373              
1374             This is used in overloaded object comparison, such as:
1375              
1376             print( "Same cookie\n" ) if( $c1 eq $c2 );
1377             # or
1378             print( "Same cookie\n" ) if( $c1 == $c2 );
1379              
1380             =head2 same_site
1381              
1382             Sets or gets the boolean value for C<Same-Site>.
1383              
1384             The proper values should be C<Relaxed>, C<Strict> or C<None>, but this module does not enforce the value you set. Setting a proper value is your responsibility.
1385              
1386             See L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite> for more information.
1387              
1388             If set to C<None>, L<secure> should be set to true.
1389              
1390             See L<rfc 6265|https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies-07> for more information.
1391              
1392             Returns a L<Module::Generic::Scalar> object.
1393              
1394             =head2 samesite
1395              
1396             Alias for L</same_site>.
1397              
1398             =head2 secure
1399              
1400             Sets or gets the boolean value for C<Secure>.
1401              
1402             Returns a L<Module::Generic::Boolean> object.
1403              
1404             =head2 sign
1405              
1406             Set or get the boolean value. If true, then the cookie value will be signed. The way this works, is that L<Crypt::Mac::HMAC/hmac_b64> will create a C<SHA256> encrypted digest using the encryption key you provided with L</key> and attach the signature to the cookie value separated by a dot. For example:
1407              
1408             my $cookie_value = "toc_ok=1";
1409             my $key = "hard to guess key";
1410             my $signature = Crypt::Mac::HMAC::hmac_b64( $key, $cookie_value );
1411             # signature is I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
1412             # cookie resulting value before uri encoding:
1413             # toc_ok%3D1.I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
1414              
1415             So, you need to have the module L<Crypt::Mac> installed to be able to use this feature.
1416              
1417             Signature are used to ensure data integrity protection for content that are not secret.
1418              
1419             For more secret content, use L</encrypt>.
1420              
1421             You can read more about the difference between L<sign and encryption at Stackoverflow|https://stackoverflow.com/questions/41467012/what-is-the-difference-between-signed-and-encrypted-cookies-in-rails>
1422              
1423             =head2 strict
1424              
1425             Boolean. Should this API be strict about the cookie names?
1426             When true, this will reject cookie names with invalid characters.
1427              
1428             Cookie name can contain only US ASCII characters and exclude any separators such as C<< ( ) < > @ , ; : \ " / [ ] ? = { } >>
1429              
1430             =head2 uri
1431              
1432             If a value is provided, it will be transformed into a L<URI> object, and its C<port>, C<path> and C<host> components will be used to set the values for L</port>, L</path> and L</domain> respectively.
1433              
1434             Otherwise, with no value provided, this will form an L<URI> object based on the cookie secure flag, C<domain>, C<port>, and C<path>
1435              
1436             $c->uri( 'https://www.example.com:8080/some/where?q=find+me' );
1437             # sets host to www.example.com, port to 8080 and path to /some/where
1438             my $uri = $c->uri;
1439             # get an uri based on cookie properties value, such as:
1440             # https://www.example.com:8080/some/where
1441              
1442             =head2 value
1443              
1444             Sets or gets the value for this cookie.
1445              
1446             Returns a L<Module::Generic::Scalar> object.
1447              
1448             =head2 version
1449              
1450             Sets or gets the cookie version. This was used in version 2 of the cookie standard, but has since been deprecated by L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265>.
1451              
1452             Returns a L<Module::Generic::Number> object.
1453              
1454             =head2 _header_datetime
1455              
1456             Given a L<DateTime> object, or by default will instantiate a new one, and this will set its formatter to L<DateTime::Format::Strptime> with the appropriate format to ensure the stringification produces a rfc6265 compliant datetime string.
1457              
1458             =head2 TO_JSON
1459              
1460             This method is used so that if the cookie object is part of some data encoded into json, this will convert the cookie data properly to be used by L<JSON>
1461              
1462             =head1 SIGNED COOKIES
1463              
1464             As shown in the L</SYNOPSIS> you can sign cookies effortlessly. This package has taken all the hassle of doing it for you.
1465              
1466             To use this feature you need to have installed L<Crypt::Mode::CBC> which is part of L<CryptX>
1467              
1468             The methods available to use for cookie integrity protection are: L</key>, L</sign> to enable cookie signature, L</is_valid> to check if the signature is valid.
1469              
1470             Cookie signature is performed by L<CryptX>, which is an XS module, and thus very fast.
1471              
1472             =head1 ENCRYPTED COOKIES
1473              
1474             As shown in the L</SYNOPSIS> you can encrypt cookies effortlessly. This package has taken all the hassle of doing it for you.
1475              
1476             To use this feature you need to have installed L<Crypt::Mode::CBC> which is part of L<CryptX>
1477              
1478             The methods available to use for cookie encryption are: L</algo> to set the desired algorithm, L</key>, L</encrypt> to enable encryption, L</decrypt> to decrypt the cookie value, and optionally L</initialisation_vector>.
1479              
1480             Cookie encryption is performed by L<CryptX>, which is an XS module, and thus very fast.
1481              
1482             =head1 INSTALLATION
1483              
1484             As usual, to install this module, you can do:
1485              
1486             perl Makefile.PL
1487             make
1488             make test
1489             sudo make install
1490              
1491             If you have Apache/modperl2 installed, this will also prepare the Makefile and run test under modperl.
1492              
1493             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:
1494              
1495             perl -MApache::TestConfig -le 'Apache::TestConfig::usage()'
1496              
1497             For example:
1498              
1499             perl Makefile.PL -apxs /usr/bin/apxs -port 1234
1500             # which will also set the path to httpd_conf, otherwise
1501             perl Makefile.PL -httpd_conf /etc/apache2/apache2.conf
1502              
1503             # then
1504             make
1505             make test
1506             sudo make install
1507              
1508             See also L<modperl testing documentation|https://perl.apache.org/docs/general/testing/testing.html>
1509              
1510             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:
1511              
1512             NO_MOD_PERL=1 perl Makefile.PL
1513             make
1514             make test
1515             sudo make install
1516              
1517             =head1 AUTHOR
1518              
1519             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1520              
1521             =head1 SEE ALSO
1522              
1523             L<Cookie::Jar>, L<Apache2::Cookies>, L<APR::Request::Cookie>
1524              
1525             L<rfc6265|https://datatracker.ietf.org/doc/html/rfc6265>
1526              
1527             L<Latest tentative version of the cookie standard|https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09>
1528              
1529             =head1 COPYRIGHT & LICENSE
1530              
1531             Copyright (c) 2019-2021 DEGUEST Pte. Ltd.
1532              
1533             You can use, copy, modify and redistribute this package and associated
1534             files under the same terms as Perl itself.
1535              
1536             =cut