File Coverage

blib/lib/Cookie/Baker.pm
Criterion Covered Total %
statement 72 76 94.7
branch 36 44 81.8
condition 8 8 100.0
subroutine 10 10 100.0
pod 1 2 50.0
total 127 140 90.7


line stmt bran cond sub pod time code
1             package Cookie::Baker;
2              
3 3     3   454125 use 5.008001;
  3         10  
4 3     3   16 use strict;
  3         14  
  3         127  
5 3     3   20 use warnings;
  3         6  
  3         194  
6 3     3   16 use base qw/Exporter/;
  3         20  
  3         418  
7 3     3   1592 use URI::Escape;
  3         5784  
  3         712  
8              
9             BEGIN {
10 3     3   12 our $VERSION = "0.12";
11 3         20 our @EXPORT = qw/bake_cookie crush_cookie/;
12 3         10 my $use_pp = $ENV{COOKIE_BAKER_PP};
13 3 50       10 if (!$use_pp) {
14 3         4 eval {
15 3         390 require Cookie::Baker::XS;
16 0 0       0 if ( $Cookie::Baker::XS::VERSION < $VERSION ) {
17 0         0 warn "Cookie::Baker::XS $VERSION is require. fallback to PP version";
18 0         0 die;
19             }
20             };
21 3         13 $use_pp = !!$@;
22             }
23 3 50       15 if ($use_pp) {
24 3         2006 *crush_cookie = \&pp_crush_cookie;
25             }
26             else {
27 0         0 *crush_cookie = \&Cookie::Baker::XS::crush_cookie;
28             }
29             }
30              
31             sub bake_cookie {
32 30     30 1 171139 my ($name,$val) = @_;
33              
34 30 50       116 return '' unless defined $val;
35 30 100       81 my %args = ref $val ? %{$val} : (value => $val);
  29         151  
36 30 100       99 if ($args{partitioned}) {
37             # enforce SameSite=None; and secure; on CHIPS (Cookies Having Independent Partitioned State)
38 2         6 $args{samesite} = 'none';
39 2         17 $args{secure} = 1;
40             }
41 30 50       117 $name = URI::Escape::uri_escape($name) if $name =~ m![^a-zA-Z\-\._~]!;
42 30         118 my $cookie = "$name=" . URI::Escape::uri_escape($args{value}) . '; ';
43 30 50       729 $cookie .= 'domain=' . $args{domain} . '; ' if $args{domain};
44 30 100       77 $cookie .= 'path='. $args{path} . '; ' if $args{path};
45 30 100 100     159 $cookie .= 'expires=' . _date($args{expires}) . '; ' if exists $args{expires} && defined $args{expires};
46 30 100       79 $cookie .= 'max-age=' . $args{"max-age"} . '; ' if exists $args{"max-age"};
47 30 100 100     150 if (exists $args{samesite} && $args{samesite} =~ m/^(?:lax|strict|none)/i) {
48 6         26 $cookie .= 'SameSite=' . ucfirst(lc($args{samesite})) . '; ';
49             # secure flag must be set when SameSite=None
50 6 100       27 $args{secure} = 1 if $cookie =~ m/SameSite=None; /;
51             }
52 30 100       81 $cookie .= 'secure; ' if $args{secure};
53 30 100       62 $cookie .= 'HttpOnly; ' if $args{httponly};
54 30 100       68 $cookie .= 'Partitioned; ' if $args{partitioned};
55              
56 30         65 substr($cookie,-2,2,'');
57 30         179 $cookie;
58             }
59              
60             my @MON = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
61             my @WDAY = qw( Sun Mon Tue Wed Thu Fri Sat );
62              
63             my %term = (
64             's' => 1,
65             'm' => 60,
66             'h' => 3600,
67             'd' => 86400,
68             'M' => 86400 * 30,
69             'y' => 86400 * 365,
70             );
71              
72             sub _date {
73 13     13   31 my $expires = shift;
74              
75 13         19 my $expires_at;
76 13 100       127 if ($expires =~ /^\d+$/) {
    100          
    100          
77             # all numbers -> epoch date
78 2         7 $expires_at = $expires;
79             }
80             elsif ( $expires =~ /^([-+]?(?:\d+|\d*\.\d*))([smhdMy]?)/ ) {
81 3     3   23 no warnings;
  3         5  
  3         1382  
82 9   100     82 my $offset = ($term{$2} || 1) * $1;
83 9         31 $expires_at = time + $offset;
84             }
85             elsif ( $expires eq 'now' ) {
86 1         6 $expires_at = time;
87             }
88             else {
89 1         4 return $expires;
90             }
91 12         133 my($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($expires_at);
92 12         32 $year += 1900;
93             # (cookies use '-' as date separator, HTTP uses ' ')
94 12         99 return sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT",
95             $WDAY[$wday], $mday, $MON[$mon], $year, $hour, $min, $sec);
96             }
97              
98             sub pp_crush_cookie {
99 44     44 0 248398 my $cookie_string = shift;
100 44 100       96 return {} unless $cookie_string;
101 40         43 my %results;
102 40         302 my @pairs = grep m/=/, split /; ?/, $cookie_string;
103 40         66 for my $pair ( @pairs ) {
104             # trim leading trailing whitespace
105 114         187 $pair =~ s/^\s+//; $pair =~ s/\s+$//;
  114         181  
106              
107 114         250 my ($key, $value) = split( "=", $pair, 2 );
108              
109 114         182 $key = URI::Escape::uri_unescape($key);
110              
111             # Values can be quoted
112 114 50       632 $value = "" unless defined $value;
113 114         139 $value =~ s/\A"(.*)"\z/$1/;
114 114         135 $value = URI::Escape::uri_unescape($value);
115              
116             # Take the first one like CGI.pm or rack do
117 114 100       929 $results{$key} = $value unless exists $results{$key};
118             }
119 40         126 return \%results;
120             }
121              
122             1;
123             __END__