Commit f8680446 authored by Jacques's avatar Jacques
Browse files

Initial release

parents
Pipeline #507756245 failed with stages
Revision history for Perl module Cookies
v0.1.0 2021-11-09T14:40:13+0900
- Added classes Cookies and Cookie
## How to contribute
Thank you for considering contributing to this distribution.
I welcome any contribution to Cookies, including, but not limited to bug reports, feature requests, general comments, and patches.
Feel free to clone it from its gitlab repository at <https://git.deguest.jp/jack/Cookies> and issue a pull request.
I am very flexible to collaboration and always eager to learn from others.
All contributions are assumed to be provided under the terms of [perl5 license](http://dev.perl.org/licenses/).
### Issues
File [an issue on CPAN bug tracker](https://git.deguest.jp/jack/Cookies/issues) if you think you've found a bug. Please describe
1. How can it be reproduced
1. What was expected
1. What actually occurred
1. What version of the involved component
## Coding Style
I personally stick to the [Allman](https://en.wikipedia.org/wiki/Indentation_style#Allman_style) coding style. If you want to provide a patch to an existing piece of code, for clarity and consistency, it is best to stick to the existing coding style, whatever that may be. However, if you want to contribute a new module, you should feel free to use your coding style with comments to ensure readability and clarity.
Otherwise, I think it is reasonable to heed to [perl style recommendations](https://metacpan.org/pod/perlstyle).
## Versioning
The versioning style used is dotted decimal, such as `v0.1.1`
Please see [version](https://metacpan.org/pod/version) for more information.
## How to contact the author
You can reach me via e-mail <jdeguest@cpan.org>, or via [Telegram](https://t.me/jackdeguest), or [LinkedIn](https://www.linkedin.com/in/jackdeguest/)
## Issue Tracking
Issues are currently reported using CPAN [bug tracker](https://git.deguest.jp/jack/Cookies/issues)
## More information
Please refer to the [README](https://metacpan.org/source/JDEGUEST/Cookies-v0.1.0/README.md)
## Author
Jacques Deguest
This diff is collapsed.
CHANGES
CONTRIBUTING.md
lib/Cookie.pm
lib/Cookies.pm
LICENSE
Makefile.PL
MANIFEST
README
README.md
t/001_load.t
t/002_cookie.t
t/003_cookies.t
t/090_coverage.t
t/091_pod.t
t/099_kwalitee.t
use ExtUtils::MakeMaker;
use strict;
use warnings;
WriteMakefile(
NAME => 'Cookies',
AUTHOR => 'Jacques Deguest <jack@deguest.jp>',
VERSION_FROM => 'lib/Cookies.pm',
ABSTRACT_FROM => 'lib/Cookies.pm',
PL_FILES => {},
PREREQ_PM => {
'overload' => '1.22',
'overloading' => '0.02',
'parent' => 0,
'strict' => 0,
'version' => 0,
'warnings' => 0,
'DateTime' => '1.52',
'DateTime::Format::Strptime' => '1.77',
'JSON' => '4.03',
'Module::Generic' => 'v0.17.0',
'Nice::Try' => 'v1.1.2',
'Scalar::Util' => '1.50',
'URI::Escape' => '5.09',
},
TEST_REQUIRES =>
{
'Bytes::Random::Secure' => '0.29',
'File::Find' => 0,
'HTTP::Request' => '6.22',
'HTTP::Response'=> '6.22',
'Test::More' => '1.302162',
'Test::Time' => 0,
},
LICENSE => 'perl_5',
MIN_PERL_VERSION => 'v5.16.0',
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Cookies-*' },
( eval { ExtUtils::MakeMaker->VERSION(6.46) } ? ( META_MERGE => {
'meta-spec' => { version => 2 },
dynamic_config => 1,
resources => {
# homepage => undef,
repository => {
url => 'git@git.deguest.jp:jack/Cookies.git',
web => 'https://git.deguest.jp/jack/Cookies',
type => 'git',
},
bugtracker => {
web => 'https://git.deguest.jp/jack/Cookies/issues',
},
},
}) : ()),
);
NAME
Cookies - Cookies API for Server & Client
SYNOPSIS
use Cookies;
my $jar = Cookies->new( request => $r ) ||
return( $self->error( "An error occurred while trying to get the cookie jar." ) );
# set the default host
$jar->host( 'www.example.com' );
$jar->fetch;
# or using a HTTP::Request object
# Retrieve cookies from Cookie header sent from client
$jar->fetch( request => $http_request );
if( $jar->exists( 'my-cookie' ) )
{
# do something
}
# get the cookie
my $sid = $jar->get( 'my-cookie' );
# set a new cookie
$jar->set( 'my-cookie' => $cookie_object );
# Remove cookie from jar
$jar->delete( 'my-cookie' );
# Create and add cookie to jar
$jar->add(
name => 'session',
value => 'lang=en-GB',
path => '/',
secure => 1,
same_site => 'Lax',
) || die( $jar->error );
# or add an existing cookie
$jar->add( $some_cookie_object );
return( $jar->make({
name => 'my-cookie',
domain => 'example.com',
value => 'sid1234567',
path => '/',
expires => '+10D',
# or alternatively
maxage => 864000
# to make it exclusively accessible by regular http request and not ajax
http_only => 1,
# should it be used under ssl only?
secure => 1,
}) );
# Add the Set-Cookie headers
$jar->add_response_header;
# Alternatively, using a HTTP::Response object or equivalent
$jar->add_response_header( $http_response );
$jar->delete( 'some_cookie' );
$jar->do(sub
{
# cookie object is available as $_ or as first argument in @_
});
# For client side
# Takes a HTTP::Response object or equivalent
# Extract cookies from Set-Cookie headers received from server
$jar->extract( $http_response );
# get by domain; by default sort it
my $all = $jar->get_by_domain( 'example.com' );
# Reverse sort
$all = $jar->get_by_domain( 'example.com', sort => 0 );
# Save cookies repository as json
$jar->save( '/some/where/mycookies.json' ) || die( $jar->error );
# Load cookies into jar
$jar->load( '/some/where/mycookies.json' ) || die( $jar->error );
VERSION
v0.1.0
DESCRIPTION
This is a module to handle cookies, according to the latest standard as
set by rfc6265 <https://datatracker.ietf.org/doc/html/rfc6265>, both by
the http server and the client. Most modules out there are either
antiquated, i.e. they do not support latest cookie rfc6265
<https://datatracker.ietf.org/doc/html/rfc6265>, or they focus only on
http client side.
For example, Apache2::Cookie does not work well in decoding cookies, and
Cookie::Baker "Set-Cookie" timestamp format is wrong. They use
Mon-09-Jan 2020 12:17:30 GMT where it should be, as per rfc 6265 Mon, 09
Jan 2020 12:17:30 GMT
Also APR::Request::Cookie and Apache2::Cookie which is a wrapper around
APR::Request::Cookie return a cookie object that returns the value of
the cookie upon stringification instead of the full "Set-Cookie"
parameters. Clearly they designed it with a bias leaned toward
collecting cookies from the browser.
This module supports modperl and uses a Apache2::RequestRec if provided,
or can use package objects that implement similar interface as
HTTP::Request and HTTP::Response, or if none of those above are
available or provided, this module returns its results as a string.
This module does not die upon error, but instead returns "undef" and
sets an error, so you should always check the return value of a method.
METHODS
new
This initiates the package and takes the following parameters:
*request*
This is an optional parameter to provide a Apache2::RequestRec
object. When provided, it will be used in various methods to get or
set cookies from or onto http headers.
package MyApacheHandler;
use Apache2::Request ();
use Cookies;
sub handler : method
{
my( $class, $r ) = @_;
my $jar = Cookies->new( $r );
# Load cookies;
$jar->fetch;
$r->log_error( "$class: Found ", $jar->repo->length, " cookies." );
$jar->add(
name => 'session',
value => 'lang=en-GB',
path => '/',
secure => 1,
same_site => 'Lax',
);
# Will use Apache2::RequestRec object to set the Set-Cookie headers
$jar->add_response_header || do
{
$r->log_reason( "Unable to add Set-Cookie to response header: ", $jar->error );
return( Apache2::Const::HTTP_INTERNAL_SERVER_ERROR );
};
# Do some more computing
return( Apache2::Const::OK );
}
*debug*
Optional. If set with a positive integer, this will activate verbose
debugging message
add
Provided with an hash or hash reference of cookie parameters (see
Cookie) and this will create a new cookie and add it to the cookie
repository.
Alternatively, you can also provide directly an existing cookie object
my $c = $jar->add( $cookie_object ) || die( $jar->error );
add_request_header
Provided with a request object, such as, but not limited to
HTTP::Request and this will add all relevant cookies in the repository
into the "Cookie" http request header.
As long as the object provided supports the "uri" and "header" method,
you can provide any class of object you want.
Please refer to the rfc6265
<https://datatracker.ietf.org/doc/html/rfc6265> for more information on
the applicable rule when adding cookies to the outgoing request header.
add_response_header
This is the alter ego to "add_request_header", in that it performs the
equivalent function, but for the server side.
You can optionally provide, as unique argument, an object, such as but
not limited to, HTTP::Response, as long as that class supports the
"header" method
Alternatively, if an Apache object has been set upon object
instantiation or later using the "request" method, then it will be used
to set the outgoing "Set-Cookie" headers (there is one for every cookie
sent).
If no response, nor Apache2 object were set, then this will simply
return a list of "Set-Cookie" in list context, or a string of possibly
multiline "Set-Cookie" headers, or an empty string if there is no cookie
found to be sent.
delete
Given a cookie name, this will remove it from the cookie jar.
However, this will NOT remove it from the web browser by sending a
Set-Cookie header. For that, you might want to look at the "elapse" in
Cookie method.
It returns the cookie object removed.
my $c = $jar->delete( 'my-cookie' );
print( "Cookie value removed was: ", $c->value, "\n" );
If you are interested in telling the http client to remove all your
cookies, you can set the "Clear-Site-Data" header:
Clear-Site-Data: "cookies"
You can instruct the http client to remove other data like local
storage:
Clear-Site-Data: "cookies", "cache", "storage", "executionContexts"
Although this is widely supported, there is no guarantee the http client
will actually comply with this request.
See Mozilla documentation
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Da
ta> for more information.
do
Provided with an anonymous code or reference to a subroutine, and this
will call that code for every cookie in the repository, passing it the
cookie object as the sole argument. Also, that cookie object is
accessible using $_.
If the code return "undef", it will end the loop, and it the code
returns true, this will have the current cookie object added to an array
object returned upon completion of the loop.
my $found = $jar->do(sub
{
# Part of the path
if( index( $path, $_->path ) == 0 )
{
return(1);
}
return(0);
});
print( "Found cookies: ", $found->map(sub{$_->name})->join( ',' ), "\n" );
exists
Given a cookie name, this will check if it exists.
It returns 1 if it does, or 0 if it does not.
extract
Provided with a response object, such as, but not limited to
HTTP::Response, and this will retrieve any cookie sent from the remote
server, parse them and add their respective to the repository.
As per the rfc6265 specifications
<https://datatracker.ietf.org/doc/html/rfc6265> if there are duplicate
cookies for the same domain, only the last one will be retained.
If the cookie received does not contain any "Domain" specification,
then, in line with rfc6265 specifications, it will take the root of the
current domain as the default domain value. Since finding out what is
the root for a domain name is a non-trivial exercise, this method relies
on IO::Socket::SSL::PublicSuffix, which is loaded only when this method
and "add_request_header" are called. So to use this method, you need to
make sure you have the package IO::Socket::SSL::PublicSuffix installed
first.
fetch
This method does the equivalent of "extract", but for the server.
It retrieves all possible cookies from the http request received from
the web browser.
It takes an optional hash or hash reference of parameters, such as
"host". If it is not provided, the value set with "host" is used
instead.
If the parameter "request" containing an http request object, such as,
but not limited to HTTP::Request, is provided, it will use it to get the
"Cookie" header value.
Alternatively, if a value for "request" has been set, it will use it to
get the "Cookie" header value from Apache modperl.
You can also provide the "Cookie" string to parse by providing the
"string" option to this method.
$jar->fetch( string => q{foo=bar; site_prefs=lang%3Den-GB} ) ||
die( $jar->error );
Ultimately, if none of those are available, it will use the environment
variable "HTTP_COOKIE"
In void context, this method, will add the fetched cookies to its
repository.
It returns an hash reference of cookie key => cookie object
A cookie key is made of the host (possibly empty) and the cookie name
separated by ";"
# Cookies added to the repository
$jar->fetch || die( $jar->error );
# Cookies returned, but NOT added to the repository
my $cookies = $jar->fetch || die( $jar->error );
get
Given a cookie name and an optional host, this will retrieve its value
and return it.
If not found, it will try to return a value with just the cookie name.
If nothing is found, this will return "undef", so do not try to check
for error if "undef" is returned.
This method cannot produce an error, thus if nothing is found, it is
guaranteed to mean the cookie does not exist.
# Wrong, an undefined returned value here only means there is no such cookie
my $c = $jar->get( 'my-cookie' );
die( $jar->error ) if( !defined( $c ) );
# Correct
my $c = $jar->get( 'my-cookie' ) || die( "No cookie my-cookie found\n" );
get_by_domain
Provided with a host and an optional hash or hash reference of
parameters, and this returns an array object of cookie objects matching
the domain specified.
If a "sort" parameter has been provided and its value is true, this will
sort the cookies by path alphabetically. If the sort value exists, but
is false, this will sort the cookies by path but in a reverse
alphabetical order.
By default, the cookies are sorted.
host
Sets or gets the default host. This is especially useful for cookies
repository used on the server side.
key
Provided with a cookie name and an optional host and this returns a key
used to add an entry in the hash repository.
If no host is provided, the key is just the cookie, otherwise the
resulting key is the cookie name and host separated just by ";"
You should not need to use this method as it is used internally only.
load
Give a json cookie file and this will load its data into the repository.
If there are duplicates (same cookie name and host), the latest one
added takes precedence, as per te rfc6265 specifications.
load_as_lwp
Given a file path to an LWP-style cookie file (see below a snapshot of
what it looks like), and this method will read the cookies from the file
and add them to our repository, possibly overwriting previous cookies
with the same name and domain name.
LWP-style cookie files are ancient, and barely used anymore, but no
matter; if you need to load cookies from such file, it looks like this:
#LWP-Cookies-1.0
Set-Cookie3: cookie1=value1; domain=example.com; path=; path_spec; secure; version=2
Set-Cookie3: cookie2=value2; domain=api.example.com; path=; path_spec; secure; version=2
Set-Cookie3: cookie3=value3; domain=img.example.com; path=; path_spec; secure; version=2
It returns the current object.
load_as_netscape
Given a file path to a Netscape-style cookie file, and this method will
read cookies from the file and add them to our repository, possibly
overwriting previous cookies with the same name and domain name.
It returns the current object.
make
Provided with some parameters and this will instantiate a new Cookie
object with those parameters and return the new object.
This does not add the newly created cookie object to the cookies
repository.
For a list of supported parameters, refer to the Cookie documentation
# Make an encrypted cookie
use Bytes::Random::Secure ();
my $c = $jar->make(
name => 'session',
value => $secret_value,
path => '/',
secure => 1,
http_only => 1,
same_site => 'Lax',
key => Bytes::Random::Secure::random_bytes(32),
algo => $algo,
encrypt => 1,
) || die( $jar->error );
# or as an hash reference of parameters
my $c = $jar->make({
name => 'session',
value => $secret_value,
path => '/',
secure => 1,
http_only => 1,
same_site => 'Lax',
key => Bytes::Random::Secure::random_bytes(32),
algo => $algo,
encrypt => 1,
}) || die( $jar->error );
parse
This method is used by "fetch" to parse cookies sent by http client.
Parsing is much simpler than for http client receiving cookies from
server.
It takes the raw "Cookie" string sent by the http client, and returns an
hash reference (possibly empty) of cookie name to cookie value pairs.
my $cookies = $jar->parse( 'foo=bar; site_prefs=lang%3Den-GB' );
# You can safely do as well:
my $cookies = $jar->parse( '' );
repo
Set or get the hash object used as the cookie jar repository.
printf( "%d cookies found\n", $jar->repo->length );
request
Set or get the Apache2::RequestRec object. This object is used to set
the "Set-Cookie" header within modperl.
save
Provided with a file and this will save the repository of cookies as
json data.
The hash saved to file contains 2 top properties: "updated_on"
containing the last update date and "cookies" containing an hash of
cookie name to cookie properties pairs.
It returns the current object. If an error occurred, it will return
"undef" and set an error
$jar->save( '/home/joe/cookies.json' ) ||
die( "Failed to save cookies: ", $jar->error );
save_as_lwp
Provided with a file and this save the cookies repository as a LWP-style
data.
It returns the current object. If an error occurred, it will return
"undef" and set an error
save_as_netscape
Provided with a file and this save the cookies repository as a
Netscape-style data.
It returns the current object. If an error occurred, it will return
"undef" and set an error
scan
This is an alias for "do"
set
Given a cookie object, and a cookie object, this adds it or replace the
previous one if any.
This will also add the cookie to the outgoing http headers using the
"Set-Cookie" http header. To do so, it uses the value set in "request"
or a response object provided with the "response" parameter.
$jar->set( $c, response => $http_response_object ) ||
die( $jar->error );
Ultimately if none of those two are provided it returns the "Set-Cookie"
header as a string.
# Returns something like:
# Set-Cookie: my-cookie=somevalue
print( STDOUT $jar->set( $c ), "\015\012" );
Unless the latter, this method returns the current object.
AUTHOR
Jacques Deguest <jack