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 5     5   244978 use strict;
  5         14  
  5         203  
3 5     5   28 use warnings;
  5         9  
  5         144  
4 5     5   26 use utf8;
  5         11  
  5         35  
5 5     5   596 use 5.008_001;
  5         20  
  5         317  
6              
7 5     5   7694 use Storable ();
  5         27485  
  5         183  
8 5     5   50 use Carp ();
  5         11  
  5         86  
9 5     5   16164 use Cookie::Baker;
  5         29713  
  5         489  
10 5     5   8034 use MIME::Base64;
  5         5993  
  5         6150  
11 5     5   7262 use HTTP::Session2::Random;
  5         22  
  5         194  
12 5     5   61081 use Digest::HMAC;
  5         3722  
  5         3458  
13 5     5   4744 use HTTP::Session2::Expired;
  5         22  
  5         407  
14              
15 5     5   41 use Mouse;
  5         10  
  5         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 5     5   2939 no Mouse;
  5         11  
  5         25  
46              
47             # HMAC timing attack
48             sub _compare {
49 7     7   179 my ( $s1, $s2 ) = @_;
50              
51 7 100       20 return unless defined $s2;
52 6 50       21 return if length $s1 != length $s2;
53 6         10 my $r = 0;
54 6         19 for my $i ( 0 .. length($s1) - 1 ) {
55 480         736 $r |= ord( substr $s1, $i ) ^ ord( substr $s2, $i );
56             }
57              
58 6         25 return $r == 0;
59             }
60              
61             sub sig {
62 13     13 0 23 my($self, $b64) = @_;
63 13 50       56 $self->secret or die "Missing secret. ABORT";
64 13         134 Digest::HMAC::hmac_hex($b64, $self->secret, $self->hmac_function);
65             }
66              
67             sub load_session {
68 12     12 0 17 my $self = shift;
69              
70             # Load from cookie.
71 12         94 my $cookies = Cookie::Baker::crush_cookie($self->env->{HTTP_COOKIE});
72 12         502 my $session_cookie = $cookies->{$self->session_cookie->{name}};
73 12 100       71 if (defined $session_cookie) {
74 7         27 my ($time, $id, $serialized, $sig) = split /:/, $session_cookie, 4;
75 7 100       23 _compare($self->sig($serialized), $sig) or do {
76 1         3 return;
77             };
78              
79 6 100       39 if (defined $self->ignore_old) {
80 2 100       18 if ($time < $self->ignore_old()) {
81 1         10 return;
82             }
83             }
84              
85 5         47 my $data = $self->deserializer->($serialized);
86 5         144 $self->{id} = $id;
87 5         9 $self->{_data} = $data;
88 5         29 return 1;
89             }
90             }
91              
92             sub create_session {
93 6     6 0 11 my $self = shift;
94              
95 6         34 $self->{id} = HTTP::Session2::Random::generate_session_id();
96 6         27 $self->{_data} = +{};
97             }
98              
99             sub regenerate_id {
100 1     1 0 516 my ($self) = @_;
101              
102             # Load original session first.
103 1         38 $self->load_session();
104              
105             # Create new session.
106 1         3 $self->{id} = HTTP::Session2::Random::generate_session_id();
107 1         4 $self->is_dirty(1);
108 1         4 $self->necessary_to_send(1);
109             }
110              
111             sub xsrf_token {
112 2     2 1 4 my $self = shift;
113 2         7 return $self->id;
114             }
115              
116             sub expire {
117 1     1 0 413 my $self = shift;
118              
119             # Load original session first.
120 1         4 $self->load_session();
121              
122             # Rebless to expired object.
123 1         4 bless $self, 'HTTP::Session2::Expired';
124              
125 1         3 return;
126             }
127              
128             sub finalize {
129 7     7 0 13 my ($self) = @_;
130              
131 7 100 100     73 return () unless $self->necessary_to_send || $self->is_dirty;
132              
133 6         11 my @cookies;
134              
135             # Finalize session cookie
136             {
137 6         10 my %cookie = %{$self->session_cookie};
  6         12  
  6         60  
138 6         18 my $name = delete $cookie{name};
139 6         33 my $value = $self->_serialize($self->id, $self->_data);
140 6         293 push @cookies, $name => +{
141             %cookie,
142             value => $value,
143             };
144             }
145              
146             # Finalize XSRF cookie
147             {
148 6         11 my %cookie = %{$self->xsrf_cookie};
  6         10  
  6         50  
149 6         43 my $name = delete $cookie{name};
150 6         26 push @cookies, $name => +{
151             %cookie,
152             value => $self->id,
153             };
154             }
155              
156 6         26 return @cookies;
157             }
158              
159             sub _serialize {
160 6     6   13 my ($self, $id, $data) = @_;
161              
162 6         25 my $serialized = $self->serializer->($data);
163 6         457 join ":", time(), $id, $serialized, $self->sig($serialized);
164             }
165              
166             1;
167             __END__