File Coverage

blib/lib/CGI/Cookie/Splitter.pm
Criterion Covered Total %
statement 92 92 100.0
branch 22 30 73.3
condition 1 3 33.3
subroutine 21 21 100.0
pod 7 15 46.6
total 143 161 88.8


line stmt bran cond sub pod time code
1             package CGI::Cookie::Splitter; # git description: v0.04-15-g9f9f932
2             # ABSTRACT: Split big cookies into smaller ones.
3              
4             our $VERSION = '0.05';
5              
6 1     1   25289 use strict;
  1         2  
  1         36  
7 1     1   7 use warnings;
  1         2  
  1         60  
8              
9 1     1   7 use Scalar::Util qw/blessed/;
  1         2  
  1         142  
10 1     1   789 use CGI::Simple::Util qw/escape unescape/;
  1         4279  
  1         94  
11 1     1   12 use Carp qw/croak/;
  1         2  
  1         64  
12 1     1   693 use namespace::clean 0.19;
  1         33846  
  1         8  
13              
14             sub new {
15 14     14 1 200496 my ( $class, %params ) = @_;
16              
17 14 100       411 $params{size} = 4096 unless exists $params{size};
18              
19             croak "size has to be a positive integer ($params{size} is invalid)"
20 14 50 33     200 unless $params{size} =~ /^\d+$/ and $params{size} > 1;
21              
22 14         57 bless \%params, $class;
23             }
24              
25 308     308 0 397470 sub size { $_[0]{size} }
26              
27             sub split {
28 14     14 1 25339 my ( $self, @cookies ) = @_;
29 14         122 map { $self->split_cookie($_) } @cookies;
  24         178  
30             }
31              
32             sub split_cookie {
33 24     24 0 35 my ( $self, $cookie ) = @_;
34 24 100       74 return $cookie unless $self->should_split( $cookie );
35             return $self->do_split_cookie(
36             $self->new_cookie( $cookie,
37             name => $self->mangle_name( $cookie->name, 0 ),
38 14         78 value => CORE::join("&",map { escape($_) } $cookie->value) # simplifies the string splitting
  4052         60334  
39             )
40             );
41             }
42              
43             sub do_split_cookie {
44 100     100 0 4754 my ( $self, $head ) = @_;
45              
46 100         386 my $tail = $self->new_cookie( $head, value => '', name => $self->mangle_name_next( $head->name ) );
47              
48 100         21170 my $max_value_size = $self->size - ( $self->cookie_size( $head ) - length( escape($head->value) ) );
49 100         221863 $max_value_size -= 30; # account for overhead the cookie serializer might add
50              
51 100 50       622 die "Internal math error, please file a bug for CGI::Cookie::Splitter: max size should be > 0, but is $max_value_size (perhaps other attrs are too big?)"
52             unless ( $max_value_size > 0 );
53              
54 100         450 my ( $head_v, $tail_v ) = $self->split_value( $max_value_size, $head->value );
55              
56 100         438 $head->value( $head_v );
57 100         1539 $tail->value( $tail_v );
58              
59 100 50       1963 die "Internal math error, please file a bug for CGI::Cookie::Splitter"
60             unless $self->cookie_size( $head ) <= $self->size; # 10 is not enough overhead
61              
62 100 100       2094 return $head unless $tail_v;
63 86         323 return ( $head, $self->do_split_cookie( $tail ) );
64             }
65              
66             sub split_value {
67 100     100 0 1165 my ( $self, $max_size, $value ) = @_;
68              
69 100         139 my $adjusted_size = $max_size;
70              
71 100         103 my ( $head, $tail );
72              
73 100 100       303 return ( $value, '' ) if length($value) <= $adjusted_size;
74              
75             split_value: {
76 86 50       95 croak "Can't reduce the size of the cookie anymore (adjusted = $adjusted_size, max = $max_size)" unless $adjusted_size > 0;
  244         915  
77              
78 244         702 $head = substr( $value, 0, $adjusted_size );
79 244         1154 $tail = substr( $value, $adjusted_size );
80              
81 244 100       1342 if ( length(my $escaped = escape($head)) > $max_size ) {
82 158         55023 my $adjustment = int( ( length($escaped) - length($head) ) / 3 ) + 1;
83              
84 158 50       612 die "Internal math error, please file a bug for CGI::Cookie::Splitter"
85             unless $adjustment;
86              
87 158         194 $adjusted_size -= $adjustment;
88 158         344 redo split_value;
89             }
90             }
91              
92 86         19393 return ( $head, $tail );
93             }
94              
95             sub cookie_size {
96 308     308 0 744 my ( $self, $cookie ) = @_;
97 308         1963 length( $cookie->as_string );
98             }
99              
100             sub new_cookie {
101 128     128 0 3401 my ( $self, $cookie, %params ) = @_;
102              
103 128         298 my %out_params;
104 128         350 for (qw/name secure path domain expires value/) {
105             $out_params{"-$_"} = (exists($params{$_})
106 768 100       6423 ? $params{$_} : $cookie->$_
107             );
108             }
109              
110 128         1191 blessed($cookie)->new( %out_params );
111             }
112              
113             sub should_split {
114 108     108 1 227078 my ( $self, $cookie ) = @_;
115 108         578 $self->cookie_size( $cookie ) > $self->size;
116             }
117              
118             sub join {
119 14     14 1 74520 my ( $self, @cookies ) = @_;
120              
121 14         28 my %split;
122             my @ret;
123              
124 14         40 foreach my $cookie ( @cookies ) {
125 110         282 my ( $name, $index ) = $self->demangle_name( $cookie->name );
126 110 100       232 if ( $name ) {
127 100         286 $split{$name}[$index] = $cookie;
128             } else {
129 10         23 push @ret, $cookie;
130             }
131             }
132              
133 14         68 foreach my $name ( sort { $a cmp $b } keys %split ) {
  6         15  
134 14         897 my $split_cookie = $split{$name};
135 14 50       37 croak "The cookie $name is missing some chunks" if grep { !defined } @$split_cookie;
  100         199  
136 14         53 push @ret, $self->join_cookie( $name => @$split_cookie );
137             }
138              
139 14         4580 return @ret;
140             }
141              
142             sub join_cookie {
143 14     14 0 44 my ( $self, $name, @cookies ) = @_;
144 14         47 $self->new_cookie( $cookies[0], name => $name, value => $self->join_value( map { $_->value } @cookies ) );
  100         659  
145             }
146              
147             sub join_value {
148 14     14 0 132 my ( $self, @values ) = @_;
149 14         1344 return [ map { unescape($_) } split('&', CORE::join("", @values)) ];
  4052         35487  
150             }
151              
152             sub mangle_name_next {
153 100     100 1 736 my ( $self, $mangled ) = @_;
154 100         305 my ( $name, $index ) = $self->demangle_name( $mangled );
155 100 50       710 $self->mangle_name( $name, 1 + ((defined($index) ? $index : 0)) ); # can't trust magic incr because it might overflow and fudge 'chunk'
156             }
157              
158             sub mangle_name {
159 114     114 1 281 my ( $self, $name, $index ) = @_;
160 114 50       1131 return sprintf '_bigcookie_%s_chunk%d', +(defined($name) ? $name : ''), $index;
161             }
162              
163             sub demangle_name {
164 290     290 1 2084 my ( $self, $mangled_name ) = @_;
165 290         3126 my ( $name, $index ) = ( $mangled_name =~ /^_bigcookie_(.+?)_chunk(\d+)$/ );
166              
167 290         1160 return ( $name, $index );
168             }
169              
170             __PACKAGE__;
171              
172             __END__