Cookie
view release on metacpan or search on metacpan
lib/Cookie/Jar.pm view on Meta::CPAN
}
my $ref = $self->_cookies;
my $idx = $self->_index;
$this->name or return( $self->error( "No cookie name was set in this cookie." ) );
my $key = $self->key( $this ) || return( $self->pass_error );
$ref->push( $this );
$idx->{ $key } = [] if( !CORE::exists( $idx->{ $key } ) );
push( @{$idx->{ $key }}, $this );
return( $this );
}
sub add_cookie_header { return( shift->add_request_header( @_ ) ); }
sub add_request_header
{
my $self = shift( @_ );
my $req = shift( @_ ) || return( $self->error( "No request object was provided." ) );
return( $self->error( "Request object provided is not an object." ) ) if( !Scalar::Util::blessed( $req ) );
return( $self->error( "Request object provided does not support the uri or header methods." ) ) if( !$req->can( 'uri' ) || !$req->can( 'header' ) );
my $uri = $req->uri || return( $self->error( "No uri set in the request object." ) );
my $scheme = $uri->scheme;
unless( $scheme =~ /^https?\z/ )
{
return( '' );
}
my( $host, $port, $path );
if( $host = $req->header( 'Host' ) )
{
$host =~ s/:(\d+)$//;
$host = lc( $host );
$port = $1;
}
else
{
$host = lc( $uri->host );
}
my $is_secure = ( $scheme eq 'https' ? 1 : 0 );
# URI::URL method
if( $uri->can( 'epath' ) )
{
$path = $uri->epath;
}
else
{
# URI::_generic method
$path = $uri->path;
}
$path = '/' unless( CORE::length( $path ) );
$port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
# my $now = time();
my $now = DateTime->now;
$path = $self->_normalize_path( $path ) if( CORE::index( $path, '%' ) != -1 );
my $root;
if( $self->_is_ip( $host ) )
{
$root = $host;
}
else
{
my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
my $res = $dom->stat( $host );
return( $self->pass_error( $dom->error ) ) if( !defined( $res ) );
if( !CORE::length( $res ) || ( $res && !$res->domain->length ) )
{
return( $self->error( "No root domain found for host \"$host\"." ) );
}
$root = $res->domain;
}
# rfc6265, section 5.4
# "Either:
# The cookie's host-only-flag is true and the canonicalized request-host is identical to the cookie's domain.
# Or:
# The cookie's host-only-flag is false and the canonicalized request-host domain-matches the cookie's domain."
# 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
# cookie domain was explicitly set to www.example.or.jp and matches www.example.or.jp
# <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
# cookie values for the "Cookie" header
my @values = ();
my @ok_cookies = ();
# 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
my $cookies = $self->get_by_domain( $root, with_subdomain => 1 );
# Ref: rfc6265, section 5.4
# <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
foreach my $c ( @$cookies )
{
unless( ( $c->host_only && $root eq $c->domain ) ||
( !$c->host_only && $self->_domain_match( $host, $c->domain ) ) )
{
next;
}
if( index( $path, $c->path ) != 0 )
{
next;
}
elsif( !$is_secure && $c->secure )
{
next;
}
# elsif( $c->expires && $c->expires->epoch < $now )
elsif( $c->expires && $c->expires < $now )
{
next;
}
elsif( $c->port && $c->port != $port )
{
next;
}
push( @ok_cookies, $c );
}
# sort cookies by path and by creation date.
# Ref: rfc6265, section 5.4.2:
# "Cookies with longer paths are listed before cookies with shorter paths."
# "Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times."
# <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4>
# The OR here actually means AND, since the <=> comparison returns false when 2 elements are equal
# So when 2 path are the same, we differentiate them by their creation date
foreach my $c ( sort{ $b->path->length <=> $a->path->length || $a->created_on <=> $b->created_on } @ok_cookies )
{
push( @values, $c->as_string({ is_request => 1 }) );
# rfc6265, section 5.4.3
lib/Cookie/Jar.pm view on Meta::CPAN
# From http client point of view
sub extract
{
my $self = shift( @_ );
my $resp = shift( @_ ) || return( $self->error( "No response object was provided." ) );
return( $self->error( "Response object provided is not an object." ) ) if( !Scalar::Util::blessed( $resp ) );
my $uri;
if( $self->_is_a( $resp, 'HTTP::Response' ) )
{
my $req = $resp->request;
return( $self->error( "No HTTP::Request object is set in this HTTP::Response." ) ) if( !$resp->request );
$uri = $resp->request->uri;
}
elsif( $resp->can( 'uri' ) && $resp->can( 'header' ) )
{
$uri = $resp->uri;
}
else
{
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." ) );
}
my $all = Module::Generic::HeaderValue->new_from_multi( [$resp->header( 'Set-Cookie' )], debug => $self->debug, decode => 1 ) ||
return( $self->pass_error( Module::Generic::HeaderValue->error ) );
return( $resp ) unless( $all->length );
$uri || return( $self->error( "No uri set in the response object." ) );
my( $host, $port, $path );
if( $host = $resp->header( 'Host' ) ||
( $resp->request && ( $host = $resp->request->header( 'Host' ) ) ) )
{
if( $host =~ s/:(\d+)$// )
{
$port = $1;
}
$host = lc( $host );
}
else
{
$host = lc( $uri->host );
}
# URI::URL method
if( $uri->can( 'epath' ) )
{
$path = $uri->epath;
}
else
{
# URI::_generic method
$path = $uri->path;
}
$path = '/' unless( CORE::length( $path ) );
$port = $uri->port if( !defined( $port ) || !CORE::length( $port ) );
my $root;
if( $self->_is_ip( $host ) )
{
$root = $host;
}
else
{
my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
my $res = $dom->stat( $host );
if( !defined( $res ) )
{
return( $self->pass_error( $dom->error ) );
}
# Possibly empty
$root = $res ? $res->domain : '';
}
foreach my $o ( @$all )
{
my( $name, $value ) = $o->value->list;
my $c = Cookie->new( name => $name, value => $value ) ||
return( $self->pass_error( Cookie->error ) );
if( CORE::length( $o->param( 'expires' ) ) )
{
my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
if( $dt )
{
$c->expires( $dt );
}
else
{
$c->expires( $o->param( 'expires' ) );
}
}
elsif( CORE::length( $o->param( 'max-age' ) ) )
{
$c->max_age( $o->param( 'max-age' ) );
}
if( $o->param( 'domain' ) )
{
# rfc6265, section 5.2.3:
# "If the first character of the attribute-value string is %x2E ("."): Let cookie-domain be the attribute-value without the leading %x2E (".") character."
# Ref: <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3>
my $c_dom = $o->param( 'domain' );
# Remove leading dot as per rfc specifications
$c_dom =~ s/^\.//g;
# "Convert the cookie-domain to lower case."
$c_dom = lc( $c_dom );
# Check the domain name is legitimate, i.e. sent from a host that has authority
# "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...
# <https://tools.ietf.org/html/rfc6265#section-4.1.2.3>
if( CORE::length( $c_dom ) >= CORE::length( $root ) &&
( $c_dom eq $host || $host =~ /\.$c_dom$/ ) )
{
$c->domain( $c_dom );
}
else
{
next;
}
}
# "If omitted, defaults to the host of the current document URL, not including subdomains."
# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
else
{
if( $root )
{
$c->domain( $root );
lib/Cookie/Jar.pm view on Meta::CPAN
else
{
my $frag = $self->new_array( [split( /\//, $path )] );
# Not perfect
if( $path eq '/' || substr( $path, -1, 1 ) eq '/' )
{
$c->path( $path );
}
else
{
$frag->pop;
$c->path( $frag->join( '/' )->scalar );
}
}
$c->port( $port ) if( defined( $port ) );
$c->http_only(1) if( $o->param( 'httponly' ) );
$c->secure(1) if( $o->param( 'secure' ) );
$c->same_site(1) if( $o->param( 'samesite' ) );
my @old = $self->get({ name => $c->name, host => $c->domain, path => $c->path });
if( scalar( @old ) )
{
$c->created_on( $old[0]->created_on ) if( $old[0]->created_on );
# $self->replace( $c );
for( @old )
{
my $arr;
$arr = $self->delete( $_ ) || do
{
# Error trying to remove cookie
};
}
}
$self->message( 3, "Adding cookie name '", $c->name, "'." ) if( $COOKIES_DEBUG );
$self->add( $c ) || return( $self->pass_error );
}
return( $self );
}
sub extract_cookies { return( shift->extract( @_ ) ); }
sub extract_one
{
my $self = shift( @_ );
my $str = shift( @_ );
my $opts = $self->_get_args_as_hash( @_ );
$opts->{path} //= '/';
return( $self->error( "No cookie data was provided." ) ) if( !length( "$str" ) );
my( $host, $root );
if( defined( $opts->{host} ) && CORE::length( $opts->{host} ) )
{
$host = $opts->{host};
if( $self->_is_ip( $host ) )
{
$root = $host;
}
else
{
my $dom = Cookie::Domain->new || return( $self->pass_error( Cookie::Domain->error ) );
my $res = $dom->stat( $host );
if( !defined( $res ) )
{
return( $self->pass_error( $dom->error ) );
}
# Possibly empty
$root = $res ? $res->domain : '';
}
}
my $o = Module::Generic::HeaderValue->new_from_header( "$str" ) ||
return( $self->pass_error( Module::Generic::HeaderValue->error ) );
my( $name, $value ) = $o->value->list;
my $c = Cookie->new( name => $name, value => $value ) ||
return( $self->pass_error( Cookie->error ) );
if( CORE::length( $o->param( 'expires' ) ) )
{
my $dt = $self->_parse_timestamp( $o->param( 'expire' ) );
if( $dt )
{
$c->expires( $dt );
}
else
{
$c->expires( $o->param( 'expires' ) );
}
}
elsif( CORE::length( $o->param( 'max-age' ) ) )
{
$c->max_age( $o->param( 'max-age' ) );
}
if( $o->param( 'domain' ) )
{
# rfc6265, section 5.2.3:
# "If the first character of the attribute-value string is %x2E ("."): Let cookie-domain be the attribute-value without the leading %x2E (".") character."
# Ref: <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3>
my $c_dom = $o->param( 'domain' );
# Remove leading dot as per rfc specifications
$c_dom =~ s/^\.//g;
# "Convert the cookie-domain to lower case."
$c_dom = lc( $c_dom );
$c->domain( $c_dom );
}
# "If omitted, defaults to the host of the current document URL, not including subdomains."
# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
else
{
if( $root )
{
$c->domain( $root );
$c->implicit(1);
}
else
{
}
}
# 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."
if( defined( $o->param( 'path' ) ) && CORE::length( $o->param( 'path' ) ) )
{
( run in 1.766 second using v1.01-cache-2.11-cpan-5b529ec07f3 )