File Coverage

blib/lib/CGI/Cookie/Splitter.pm
Criterion Covered Total %
statement 90 90 100.0
branch 22 30 73.3
condition 1 3 33.3
subroutine 21 21 100.0
pod 7 15 46.6
total 141 159 88.6


line stmt bran cond sub pod time code
1             package CGI::Cookie::Splitter;
2             BEGIN {
3 1     1   34229 $CGI::Cookie::Splitter::AUTHORITY = 'cpan:NUFFIN';
4             }
5             # git description: v0.03-5-gd375e1a
6             $CGI::Cookie::Splitter::VERSION = '0.04';
7             # ABSTRACT: Split big cookies into smaller ones.
8              
9 1     1   11 use strict;
  1         2  
  1         34  
10 1     1   6 use warnings;
  1         11  
  1         48  
11              
12 1     1   6 use Scalar::Util qw/blessed/;
  1         3  
  1         159  
13 1     1   1450 use CGI::Simple::Util qw/escape unescape/;
  1         5575  
  1         131  
14 1     1   14 use Carp qw/croak/;
  1         3  
  1         1430  
15              
16             sub new {
17 14     14 1 174406 my ( $class, %params ) = @_;
18              
19 14 100       81 $params{size} = 4096 unless exists $params{size};
20              
21 14 50 33     198 croak "size has to be a positive integer ($params{size} is invalid)"
22             unless $params{size} =~ /^\d+$/ and $params{size} > 1;
23              
24 14         70 bless \%params, $class;
25             }
26              
27 308     308 0 298813 sub size { $_[0]{size} }
28              
29             sub split {
30 14     14 1 15232 my ( $self, @cookies ) = @_;
31 14         36 map { $self->split_cookie($_) } @cookies;
  24         73  
32             }
33              
34             sub split_cookie {
35 24     24 0 38 my ( $self, $cookie ) = @_;
36 24 100       60 return $cookie unless $self->should_split( $cookie );
37 4052         31383 return $self->do_split_cookie(
38             $self->new_cookie( $cookie,
39             name => $self->mangle_name( $cookie->name, 0 ),
40 14         79 value => CORE::join("&",map { escape($_) } $cookie->value) # simplifies the string splitting
41             )
42             );
43             }
44              
45             sub do_split_cookie {
46 100     100 0 2157 my ( $self, $head ) = @_;
47              
48 100         404 my $tail = $self->new_cookie( $head, value => '', name => $self->mangle_name_next( $head->name ) );
49              
50 100         15554 my $max_value_size = $self->size - ( $self->cookie_size( $head ) - length( escape($head->value) ) );
51 100         131515 $max_value_size -= 30; # account for overhead the cookie serializer might add
52              
53 100 50       317 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?)"
54             unless ( $max_value_size > 0 );
55              
56 100         320 my ( $head_v, $tail_v ) = $self->split_value( $max_value_size, $head->value );
57              
58 100         344 $head->value( $head_v );
59 100         1418 $tail->value( $tail_v );
60              
61 100 50       1048 die "Internal math error, please file a bug for CGI::Cookie::Splitter"
62             unless $self->cookie_size( $head ) <= $self->size; # 10 is not enough overhead
63              
64 100 100       891 return $head unless $tail_v;
65 86         263 return ( $head, $self->do_split_cookie( $tail ) );
66             }
67              
68             sub split_value {
69 100     100 0 864 my ( $self, $max_size, $value ) = @_;
70              
71 100         147 my $adjusted_size = $max_size;
72              
73 100         124 my ( $head, $tail );
74              
75 100 100       275 return ( $value, '' ) if length($value) <= $adjusted_size;
76              
77 244 50       498 split_value: {
78 86         113 croak "Can't reduce the size of the cookie anymore (adjusted = $adjusted_size, max = $max_size)" unless $adjusted_size > 0;
79              
80 244         799 $head = substr( $value, 0, $adjusted_size );
81 244         1110 $tail = substr( $value, $adjusted_size );
82              
83 244 100       717 if ( length(my $escaped = escape($head)) > $max_size ) {
84 158         36394 my $adjustment = int( ( length($escaped) - length($head) ) / 3 ) + 1;
85              
86 158 50       327 die "Internal math error, please file a bug for CGI::Cookie::Splitter"
87             unless $adjustment;
88              
89 158         201 $adjusted_size -= $adjustment;
90 158         364 redo split_value;
91             }
92             }
93              
94 86         11828 return ( $head, $tail );
95             }
96              
97             sub cookie_size {
98 308     308 0 449 my ( $self, $cookie ) = @_;
99 308         941 length( $cookie->as_string );
100             }
101              
102             sub new_cookie {
103 128     128 0 2544 my ( $self, $cookie, %params ) = @_;
104              
105 128         168 my %out_params;
106 128         366 for (qw/name secure path domain expires value/) {
107 768 100       5541 $out_params{"-$_"} = (exists($params{$_})
108             ? $params{$_} : $cookie->$_
109             );
110             }
111              
112 128         966 blessed($cookie)->new( %out_params );
113             }
114              
115             sub should_split {
116 108     108 1 152632 my ( $self, $cookie ) = @_;
117 108         332 $self->cookie_size( $cookie ) > $self->size;
118             }
119              
120             sub join {
121 14     14 1 21796 my ( $self, @cookies ) = @_;
122              
123 14         32 my %split;
124             my @ret;
125              
126 14         36 foreach my $cookie ( @cookies ) {
127 110         335 my ( $name, $index ) = $self->demangle_name( $cookie->name );
128 110 100       209 if ( $name ) {
129 100         280 $split{$name}[$index] = $cookie;
130             } else {
131 10         28 push @ret, $cookie;
132             }
133             }
134              
135 14         73 foreach my $name ( sort { $a cmp $b } keys %split ) {
  5         14  
136 14         535 my $split_cookie = $split{$name};
137 14 50       31 croak "The cookie $name is missing some chunks" if grep { !defined } @$split_cookie;
  100         182  
138 14         111 push @ret, $self->join_cookie( $name => @$split_cookie );
139             }
140              
141 14         3235 return @ret;
142             }
143              
144             sub join_cookie {
145 14     14 0 44 my ( $self, $name, @cookies ) = @_;
146 14         27 $self->new_cookie( $cookies[0], name => $name, value => $self->join_value( map { $_->value } @cookies ) );
  100         649  
147             }
148              
149             sub join_value {
150 14     14 0 125 my ( $self, @values ) = @_;
151 14         875 return [ map { unescape($_) } split('&', CORE::join("", @values)) ];
  4052         34151  
152             }
153              
154             sub mangle_name_next {
155 100     100 1 613 my ( $self, $mangled ) = @_;
156 100         253 my ( $name, $index ) = $self->demangle_name( $mangled );
157 100 50       448 $self->mangle_name( $name, 1 + ((defined($index) ? $index : 0)) ); # can't trust magic incr because it might overflow and fudge 'chunk'
158             }
159              
160             sub mangle_name {
161 114     114 1 625 my ( $self, $name, $index ) = @_;
162 114 50       770 return sprintf '_bigcookie_%s_chunk%d', +(defined($name) ? $name : ''), $index;
163             }
164              
165             sub demangle_name {
166 290     290 1 1628 my ( $self, $mangled_name ) = @_;
167 290         1651 my ( $name, $index ) = ( $mangled_name =~ /^_bigcookie_(.+?)_chunk(\d+)$/ );
168              
169 290         1039 return ( $name, $index );
170             }
171              
172             __PACKAGE__;
173              
174             __END__