File Coverage

blib/lib/HTTP/Session2/ClientStore.pm
Criterion Covered Total %
statement 95 95 100.0
branch 14 16 87.5
condition 3 3 100.0
subroutine 22 22 100.0
pod 1 7 14.2
total 135 143 94.4


line stmt bran cond sub pod time code
1             package HTTP::Session2::ClientStore;
2 6     6   163187 use strict;
  6         14  
  6         434  
3 6     6   226 use warnings;
  6         12  
  6         178  
4 6     6   33 use utf8;
  6         12  
  6         80  
5 6     6   283 use 5.008_001;
  6         23  
  6         251  
6              
7 6     6   19265 use Storable ();
  6         19848  
  6         185  
8 6     6   56 use Carp ();
  6         13  
  6         105  
9 6     6   5180 use Cookie::Baker;
  6         26611  
  6         468  
10 6     6   16245 use MIME::Base64;
  6         4740  
  6         415  
11 6     6   4157 use HTTP::Session2::Random;
  6         19  
  6         193  
12 6     6   5366 use Digest::HMAC;
  6         3317  
  6         284  
13 6     6   3411 use HTTP::Session2::Expired;
  6         21  
  6         181  
14              
15 6     6   38 use Mouse;
  6         16  
  6         33  
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   2821 no Mouse;
  6         15  
  6         28  
46              
47             # HMAC timing attack
48             sub _compare {
49 20     20   498 my ( $s1, $s2 ) = @_;
50              
51 20 100       53 return unless defined $s2;
52 19 50       58 return if length $s1 != length $s2;
53 19         24 my $r = 0;
54 19         51 for my $i ( 0 .. length($s1) - 1 ) {
55 1520         2352 $r |= ord( substr $s1, $i ) ^ ord( substr $s2, $i );
56             }
57              
58 19         74 return $r == 0;
59             }
60              
61             sub sig {
62 31     31 0 61 my($self, $b64) = @_;
63 31 50       132 $self->secret or die "Missing secret. ABORT";
64 31         187 Digest::HMAC::hmac_hex($b64, $self->secret, $self->hmac_function);
65             }
66              
67             sub load_session {
68 29     29 0 43 my $self = shift;
69              
70             # Load from cookie.
71 29         373 my $cookies = Cookie::Baker::crush_cookie($self->env->{HTTP_COOKIE});
72 29         1493 my $session_cookie = $cookies->{$self->session_cookie->{name}};
73 29 100       273 if (defined $session_cookie) {
74 20         78 my ($time, $id, $serialized, $sig) = split /:/, $session_cookie, 4;
75 20 100       60 _compare($self->sig($serialized), $sig) or do {
76 1         5 return;
77             };
78              
79 19 100       205 if (defined $self->ignore_old) {
80 2 100       14 if ($time < $self->ignore_old()) {
81 1         9 return;
82             }
83             }
84              
85 18         60 my $data = $self->deserializer->($serialized);
86 18         451 $self->{id} = $id;
87 18         31 $self->{_data} = $data;
88 18         109 return 1;
89             }
90             }
91              
92             sub create_session {
93 9     9 0 24 my $self = shift;
94              
95 9         72 $self->{id} = HTTP::Session2::Random::generate_session_id();
96 9         44 $self->{_data} = +{};
97             }
98              
99             sub regenerate_id {
100 2     2 0 704 my ($self) = @_;
101              
102             # Load original session first.
103 2         42 $self->load_session();
104              
105             # Create new session.
106 2         11 $self->{id} = HTTP::Session2::Random::generate_session_id();
107 2         11 $self->is_dirty(1);
108 2         9 $self->necessary_to_send(1);
109             }
110              
111             sub xsrf_token {
112 7     7 1 10 my $self = shift;
113 7         26 return $self->id;
114             }
115              
116             sub expire {
117 3     3 0 690 my $self = shift;
118              
119             # Load original session first.
120 3         14 $self->load_session();
121              
122             # Rebless to expired object.
123 3         46 bless $self, 'HTTP::Session2::Expired';
124              
125 3         73 return;
126             }
127              
128             sub finalize {
129 18     18 0 29 my ($self) = @_;
130              
131 18 100 100     200 return () unless $self->necessary_to_send || $self->is_dirty;
132              
133 11         23 my @cookies;
134              
135             # Finalize session cookie
136             {
137 11         16 my %cookie = %{$self->session_cookie};
  11         16  
  11         98  
138 11         36 my $name = delete $cookie{name};
139 11         64 my $value = $self->_serialize($self->id, $self->_data);
140 11         466 push @cookies, $name => +{
141             %cookie,
142             value => $value,
143             };
144             }
145              
146             # Finalize XSRF cookie
147             {
148 11         22 my %cookie = %{$self->xsrf_cookie};
  11         24  
  11         101  
149 11         67 my $name = delete $cookie{name};
150 11         50 push @cookies, $name => +{
151             %cookie,
152             value => $self->id,
153             };
154             }
155              
156 11         56 return @cookies;
157             }
158              
159             sub _serialize {
160 11     11   27 my ($self, $id, $data) = @_;
161              
162 11         46 my $serialized = $self->serializer->($data);
163 11         1181 join ":", time(), $id, $serialized, $self->sig($serialized);
164             }
165              
166             1;
167             __END__