Cookies-Roundtrip
view release on metacpan or search on metacpan
lib/Cookies/Roundtrip.pm view on Meta::CPAN
package Cookies::Roundtrip;
# NOTE: HTTP::Cookies and HTTP::CookieJar and
# Firefox::Marionette::Cookie
# have expires or expiry fields.
# some can be undef which has a special meaning:
# they are session cookies (which are deleted when session/browser/tab is closed).
# THEY ALL store expiry as unix epoch seconds.
# BUT SetCookie needs a "YYYY-MM-DD hh:mm:ssZ"-formatted string representing Universal Time.
# use this: HTTP::Date::time2isoz(epochsecs)
use strict;
use warnings;
our $VERSION = '0.01';
use HTTP::Cookies;
use HTTP::CookieJar;
use Firefox::Marionette::Cookie;
use HTTP::Date qw(str2time parse_date time2str);
use HTTP::Response;
use HTTP::Request;
use DateTime;
use HTTP::Headers::Util qw/join_header_words/;
use Data::Compare;
use Devel::StackTrace; # until the module is stable
use Data::Roundtrip qw/perl2dump no-unicode-escape-permanently/;
use Exporter; # you need to import 'import' if you don't define it further down
# the EXPORT_OK and EXPORT_TAGS is code by [kcott] @ Perlmongs.org, thanks!
# see https://perlmonks.org/?node_id=11115288
our (@EXPORT_OK, %EXPORT_TAGS);
# For all the EXPORT subs and tags see the BEGIN{} block at the end
sub lwpuseragent_get_cookies {
my ($ua, $verbosity) = @_;
#$verbosity //= 0;
#my $parent = ( caller(1) )[3] || "N/A";
#my $whoami = ( caller(0) )[3];
return $ua->cookie_jar
}
# Save cookies of LWP::UserAgent (they are HTTP::CookieJar)
# into a file. The file will be empty if no cookies.
# It returns 1 on failure, 0 on success.
sub lwpuseragent_save_cookies_to_file {
my ($ua, $filename, $skip_discard, $verbosity) = @_;
$verbosity //= 0;
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
# LWP::UserAgent supports both HTTP::Cookies and HTTP::CookieJar
# cookie jars (depending on the 'cookie_jar_class' param to their constructor)
# we need to find the class of the cookie jar of mech
# this is probably unsupported but there is no other way:
my $cookie_jar_class = $ua->{'cookie_jar_class'};
if( $cookie_jar_class eq 'HTTP::Cookies' ){
if( httpcookies2file(
defined($ua->cookie_jar) ? $ua->cookie_jar : $cookie_jar_class->new(),
$filename, $skip_discard, $verbosity
) ){ print STDERR "${whoami}, line ".__LINE__." (via $parent): error, call to ".'httpcookies2file()'." has failed.\n"; return 1 }
} elsif( ($cookie_jar_class eq 'HTTP::CookieJar') || ($cookie_jar_class eq 'HTTP::CookieJar::LWP') ){
if( httpcookiejar2file(
defined($ua->cookie_jar) ? $ua->cookie_jar : $cookie_jar_class->new(),
$filename, $skip_discard, $verbosity
) ){ print STDERR "${whoami}, line ".__LINE__." (via $parent): error, call to ".'httpcookies2file()'." has failed.\n"; return 1 }
} else {
print STDERR "${whoami}, line ".__LINE__." (via $parent): error, input cookies type '$cookie_jar_class' is not known. Known cookies types are 'HTTP::Cookies' and 'HTTP::CookieJar'.\n";
return 0
}
return 0 # success
}
# Convenience method to load any kind of cookies
# or a cookies file, into the LWP::UserAgent
# it returns the $ua's cookie_jar on success or undef on failure
sub lwpuseragent_load_cookies {
my ($ua, $cookies_or_file_etc, $verbosity) = @_;
$verbosity //= 0;
lib/Cookies/Roundtrip.pm view on Meta::CPAN
# from HTTP/Cookies.pm, set_cookie signature:
# my($version,
# $key, $val, $path, $domain, $port,
# $path_spec, $secure, $maxage, $discard, $rest) = @_;
# it expects a max-age but we could have expires
# and expires can be an epoch or a string date
if( exists $parsed_setcookie->{'expires'} ){
if( ! defined $parsed_setcookie->{'expires'} ){ $parsed_setcookie->{'max-age'} = undef }
elsif( $parsed_setcookie->{'expires'}=~ /^\d+$/ ){
$parsed_setcookie->{'max-age'} = $parsed_setcookie->{'expires'} - DateTime->now()->epoch();
} else {
$parsed_setcookie->{'max-age'} = HTTP::Date::str2time($parsed_setcookie->{'expires'}) - DateTime->now()->epoch();
}
delete $parsed_setcookie->{'expires'}; # does not care if you do
}
if( ! exists($parsed_setcookie->{'secure'}) || ! defined($parsed_setcookie->{'secure'}) ){
$parsed_setcookie->{'secure'} = 0;
}
return $parsed_setcookie;
}
# Convert an array of Set-Cookie strings into a fresh
# or user-supplied (this is appending) HTTP::Cookies object
# and return it. Optionally specifying $verbosity (as integer).
# $httpcookiejar is optional, if given, then cookies
# will be appended in it and return it. Else
# a fresh HTTP::Cookies will be created, loaded and returned.
# It returns undef on failure.
sub setcookies2httpcookies {
my ($setcookies, $httpcookies, $verbosity) = @_;
$verbosity //= 0;
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
if( ! defined $httpcookies ){
if( ! defined($httpcookies = HTTP::Cookies->new()) ){ print STDERR "${whoami}, line ".__LINE__." (via $parent): error, call to ".'HTTP::Cookies->new()'." has failed.\n"; return undef }
}
for my $asc (@$setcookies){
if( $verbosity > 1 ){
my $parsed_setcookie_hash = setcookie2hash($asc, $verbosity);
if( ! defined $parsed_setcookie_hash ){ print STDERR "--begin SetCookie:\n${asc}\n--end cookie.\n"."${whoami}, line ".__LINE__." (via $parent): error, call to ".'setcookie2hash()'." has failed for above SetCookie string.\n"; return undef }
print STDOUT perl2dump($parsed_setcookie_hash)."${whoami}, line ".__LINE__." (via $parent): parse this SetCookie into the above hash: ${asc}\n";
}
my $parsed_setcookie_array = setcookie2httpcookies_set_cookie_array($asc, $verbosity);
if( ! defined $parsed_setcookie_array ){ print STDERR "--begin SetCookie:\n${asc}\n--end cookie.\n"."${whoami}, line ".__LINE__." (via $parent): error, call to ".'setcookie2httpcookies_set_cookie_array()'." has failed for above SetCookie string.\n"...
# from HTTP/Cookies.pm, set_cookie signature:
# my($version,
# $key, $val, $path, $domain, $port,
# $path_spec, $secure, $maxage, $discard, $rest) = @_;
$httpcookies->set_cookie(@$parsed_setcookie_array);
if( $verbosity > 0 ){ print STDOUT "${whoami}, line ".__LINE__." (via $parent): inserting 1 Set-Cookies into HTTP::Cookies which now has ".count_httpcookies($httpcookies, undef, $verbosity)." cookies.\n" }
# this way is bogus, what with response and request and url and all
# my $response = HTTP::Response->new(200);
# my $request = HTTP::Request->new(GET => $url);
# $response->request($request);
# $response->header('Set-Cookie2', $newasc); # TODO: does it push multiple Cookie headers or overrides?
# if( ! defined $httpcookies->extract_cookies($response) ){ print STDERR "--begin cookies:\n".as_string_setcookies($setcookies)."\n--end cookies.\n\n"."${whoami}, line ".__LINE__." (via $parent): error, call to ".'httpcookies->extract_cookies()'." h...
# if( $verbosity > 0 ){ print STDOUT "${whoami}, line ".__LINE__." (via $parent): inserting 1 Set-Cookies into HTTP::Cookies which now has ".count_httpcookies($httpcookies, undef, $verbosity)." cookies.\n" }
}
if( $verbosity > 0 ){ print STDOUT "${whoami}, line ".__LINE__." (via $parent): done all, inserted ".count_setcookies($setcookies)." Set-Cookies into HTTP::Cookies which now has ".count_httpcookies($httpcookies, undef, $verbosity)." cookies.\n" }
return $httpcookies;
}
# Array of Firefox::Marionette::Cookie to array of cookies.
# Each of the output cookie resembles a Set-Cookie header and,
# logically, it can be loaded by any cookies container.
# It converts the specified Array of Firefox::Marionette::Cookie object into
# an array of cookies which can then be loaded into other exotic
# cookie containers.
# It returns the cookies as ARRAY_REF.
# for Set-Cookie format see:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie
sub firefoxmarionettecookies2setcookies {
my ($firefoxmarionettecookies, $setcookies, $skip_discard, $verbosity) = @_;
$skip_discard //= 0;
$verbosity //= 0;
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
if( ! defined $setcookies ){ $setcookies = [] }
for my $afc (@$firefoxmarionettecookies){
my @h = ($afc->name, $afc->value);
push @h, 'path', $afc->path;
push @h, 'domain', $afc->domain;
# no port in F:M:C
# boolean, it can be present or not present, it has no value like secure=...
push(@h, 'secure') if $afc->secure;
if( defined $afc->expiry ){
# we can have expires as Date or max-age as epoch
push @h, 'expires', HTTP::Date::time2isoz($afc->expiry)
}
# SameSite can be one of: Lax, None, Strict
if( $afc->same_site !~ /^(None|Lax|Strict)$/ ){ print STDERR "${whoami}, line ".__LINE__." (via $parent): error, field 'same_site' (".$afc->same_site.") is not one of: 'Lax', 'None', 'Strict'.\n"; return undef }
push @h, 'SameSite', $afc->same_site;
# boolean, it can be present or not present, it has no value like secure=...
push(@h, 'HttpOnly') if $afc->http_only;
my $cookstr = HTTP::Headers::Util::join_header_words(\@h);
# and yet again we suffer from the quotes, perhaps HTTP::CookieJar::load_cookies()
# assumes no quotes?!
$cookstr =~ s/\bpath="([^"]*)";/path=$1;/;
$cookstr =~ s/\bexpires="([^"]*)";/expires=$1;/;
#push(@$setcookies, "Set-Cookie3: " . $cookstr); # this is for a header?
push(@$setcookies, $cookstr);
}
return $setcookies;
}
# Convert an array of Firefox::Marionette::Cookie objects into
# a fresh or user-supplied (this is appending)
# HTTP::CookieJar and return it. Optionally specifying $verbosity (as integer).
( run in 0.923 second using v1.01-cache-2.11-cpan-3c2a17b8caa )