File Coverage

blib/lib/HTTP/Session2/ClientStore.pm
Criterion Covered Total %
statement 94 94 100.0
branch 14 16 87.5
condition 3 3 100.0
subroutine 22 22 100.0
pod 1 7 14.2
total 134 142 94.3


line stmt bran cond sub pod time code
1             package HTTP::Session2::ClientStore;
2 6     6   225131 use strict;
  6         46  
  6         136  
3 6     6   25 use warnings;
  6         10  
  6         117  
4 6     6   22 use utf8;
  6         14  
  6         27  
5 6     6   168 use 5.008_001;
  6         17  
6              
7 6     6   2578 use Storable ();
  6         12196  
  6         110  
8 6     6   29 use Carp ();
  6         10  
  6         76  
9 6     6   1558 use Cookie::Baker;
  6         10177  
  6         290  
10 6     6   1865 use MIME::Base64;
  6         2359  
  6         265  
11 6     6   1825 use HTTP::Session2::Random;
  6         11  
  6         146  
12 6     6   1693 use Digest::HMAC;
  6         2278  
  6         207  
13 6     6   1677 use HTTP::Session2::Expired;
  6         14  
  6         155  
14              
15 6     6   31 use Mouse;
  6         11  
  6         37  
16              
17             extends 'HTTP::Session2::Base';
18              
19             # Backward compatibility.
20              
21             has 'serializer' => (
22             is => 'ro',
23             default => sub {
24             sub {
25             warn("Do not use HTTP::Session2::ClientStore. Use HTTP::Session2::ServerStore or HTTP::Session2::ClientStore2 instead.");
26             MIME::Base64::encode(Storable::nfreeze($_[0]), '' )
27             }
28             },
29             );
30              
31             has 'deserializer' => (
32             is => 'ro',
33             default => sub {
34             sub {
35             warn("Do not use HTTP::Session2::ClientStore. Use HTTP::Session2::ServerStore or HTTP::Session2::ClientStore2 instead.");
36             Storable::thaw(MIME::Base64::decode($_[0]))
37             }
38             },
39             );
40              
41             has ignore_old => (
42             is => 'ro',
43             );
44              
45 6     6   2627 no Mouse;
  6         10  
  6         21  
46              
47             # HMAC timing attack
48             sub _compare {
49 20     20   416 my ( $s1, $s2 ) = @_;
50              
51 20 100       52 return unless defined $s2;
52 19 50       39 return if length $s1 != length $s2;
53 19         26 my $r = 0;
54 19         52 for my $i ( 0 .. length($s1) - 1 ) {
55 1520         1981 $r |= ord( substr $s1, $i ) ^ ord( substr $s2, $i );
56             }
57              
58 19         55 return $r == 0;
59             }
60              
61             sub sig {
62 31     31 0 66 my($self, $b64) = @_;
63 31 50       107 $self->secret or die "Missing secret. ABORT";
64 31         116 Digest::HMAC::hmac_hex($b64, $self->secret, $self->hmac_function);
65             }
66              
67             sub load_session {
68 29     29 0 42 my $self = shift;
69              
70             # Load from cookie.
71 29         136 my $cookies = Cookie::Baker::crush_cookie($self->env->{HTTP_COOKIE});
72 29         1486 my $session_cookie = $cookies->{$self->session_cookie->{name}};
73 29 100       109 if (defined $session_cookie) {
74 20         65 my ($time, $id, $serialized, $sig) = split /:/, $session_cookie, 4;
75 20 100       48 _compare($self->sig($serialized), $sig) or do {
76 1         3 return;
77             };
78              
79 19 100       63 if (defined $self->ignore_old) {
80 2 100       8 if ($time < $self->ignore_old()) {
81 1         5 return;
82             }
83             }
84              
85 18         52 my $data = $self->deserializer->($serialized);
86 18         428 $self->{id} = $id;
87 18         34 $self->{_data} = $data;
88 18         78 return 1;
89             }
90             }
91              
92             sub create_session {
93 9     9 0 25 my $self = shift;
94              
95 9         27 $self->{id} = HTTP::Session2::Random::generate_session_id();
96 9         32 $self->{_data} = +{};
97             }
98              
99             sub regenerate_id {
100 2     2 0 549 my ($self) = @_;
101              
102             # Load original session first.
103 2         6 $self->load_session();
104              
105             # Create new session.
106 2         8 $self->{id} = HTTP::Session2::Random::generate_session_id();
107 2         9 $self->is_dirty(1);
108 2         7 $self->necessary_to_send(1);
109             }
110              
111             sub xsrf_token {
112 7     7 1 11 my $self = shift;
113 7         16 return $self->id;
114             }
115              
116             sub expire {
117 3     3 0 536 my $self = shift;
118              
119             # Load original session first.
120 3         9 $self->load_session();
121              
122             # Rebless to expired object.
123 3         13 bless $self, 'HTTP::Session2::Expired';
124              
125 3         7 return;
126             }
127              
128             sub finalize {
129 18     18 0 48 my ($self) = @_;
130              
131 18 100 100     130 return () unless $self->necessary_to_send || $self->is_dirty;
132              
133 11         34 my @cookies;
134              
135             # Finalize session cookie
136             {
137 11         15 my %cookie = %{$self->session_cookie};
  11         59  
138 11         46 my $name = delete $cookie{name};
139 11         90 my $value = $self->_serialize($self->id, $self->_data);
140 11         314 push @cookies, $name => +{
141             %cookie,
142             value => $value,
143             };
144             }
145              
146             # Finalize XSRF cookie
147             {
148 11         19 my %cookie = %{$self->xsrf_cookie};
  11         21  
  11         16  
  11         67  
149 11         25 my $name = delete $cookie{name};
150 11         41 push @cookies, $name => +{
151             %cookie,
152             value => $self->id,
153             };
154             }
155              
156 11         36 return @cookies;
157             }
158              
159             sub _serialize {
160 11     11   37 my ($self, $id, $data) = @_;
161              
162 11         35 my $serialized = $self->serializer->($data);
163 11         877 join ":", time(), $id, $serialized, $self->sig($serialized);
164             }
165              
166             1;
167             __END__