File Coverage

lib/App/Session/HTMLHidden.pm
Criterion Covered Total %
statement 47 120 39.1
branch 12 54 22.2
condition 0 6 0.0
subroutine 9 10 90.0
pod 2 2 100.0
total 70 192 36.4


line stmt bran cond sub pod time code
1              
2             #############################################################################
3             ## $Id: HTMLHidden.pm 13887 2010-04-06 13:36:42Z spadkins $
4             #############################################################################
5              
6             package App::Session::HTMLHidden;
7             $VERSION = (q$Revision: 13887 $ =~ /(\d[\d\.]*)/)[0]; # VERSION numbers generated by svn
8              
9 1     1   6 use App;
  1         2  
  1         24  
10 1     1   543 use App::Session;
  1         5  
  1         45  
11             @ISA = ( "App::Session" );
12              
13 1     1   6 use strict;
  1         1  
  1         30  
14              
15 1     1   5 use Data::Dumper;
  1         3  
  1         54  
16 1     1   1154 use Storable qw(freeze thaw);
  1         4250  
  1         91  
17 1     1   10 use Compress::Zlib;
  1         2  
  1         391  
18 1     1   1000 use MIME::Base64;
  1         698  
  1         1384  
19              
20             # note: We may want to apply an HMAC (hashed message authentication code)
21             # so that users cannot fiddle with the values.
22             # We may also want to add IP address and timeout for security.
23             # We may also want to add encryption so they can't even decode the data.
24             # use Digest::HMAC_MD5;
25             # use Crypt::CBC;
26              
27             =head1 NAME
28              
29             App::Session::HTMLHidden - a session whose state is maintained across
30             HTML requests by being embedded in an HTML tag.
31              
32             =head1 SYNOPSIS
33              
34             # ... official way to get a Session object ...
35             use App;
36             $session = App->session();
37             $session = $session->session(); # get the session
38              
39             # any of the following named parameters may be specified
40             $session = $session->session(
41             );
42              
43             # ... alternative way (used internally) ...
44             use App::Session::HTMLHidden;
45             $session = App::Session->new();
46              
47             =cut
48              
49             #############################################################################
50             # CONSTANTS
51             #############################################################################
52              
53             =head1 DESCRIPTION
54              
55             A Session class models the sequence of events associated with a
56             use of the system. These events may occur in different processes.
57             Yet the accumulated state of the session needs to be propagated from
58             one process to the next.
59              
60             This Session::HTMLHidden maintains its state across
61             HTML requests by being embedded in an HTML tag.
62             As a result, it requires no server-side storage, so the sessions
63             never need to time out.
64              
65             =cut
66              
67             #############################################################################
68             # CONSTRUCTOR METHODS
69             #############################################################################
70              
71             =head1 Constructor Methods:
72              
73             =cut
74              
75             #############################################################################
76             # new()
77             #############################################################################
78              
79             =head2 new()
80              
81             The constructor is inherited from
82             L|App::Service/"new()">.
83              
84             =cut
85              
86             #############################################################################
87             # PUBLIC METHODS
88             #############################################################################
89              
90             =head1 Public Methods:
91              
92             =cut
93              
94             #############################################################################
95             # get_session_id()
96             #############################################################################
97              
98             =head2 get_session_id()
99              
100             The get_session_id() returns the session_id of this particular session.
101             This session_id is unique for all time. If a session_id does not yet
102             exist, one will be created. The session_id is only created when first
103             requested, and not when the session is instantiated.
104              
105             * Signature: $session_id = $session->get_session_id();
106             * Param: void
107             * Return: $session_id string
108             * Throws:
109             * Since: 0.01
110              
111             Sample Usage:
112              
113             $session->get_session_id();
114              
115             =cut
116              
117             my $seq = 0;
118              
119             sub get_session_id {
120 1 50   1 1 8 &App::sub_entry if ($App::trace);
121 1         4 my $self = shift;
122 1         4 my $session_id = "embedded";
123 1 50       4 &App::sub_exit($session_id) if ($App::trace);
124 1         5 return($session_id);
125             }
126              
127             #############################################################################
128             # html()
129             #############################################################################
130              
131             =head2 html()
132              
133             The html() method ...
134              
135             * Signature: $html = $session->html();
136             * Param: void
137             * Return: $html string
138             * Throws:
139             * Since: 0.01
140              
141             Sample Usage:
142              
143             $session->html();
144              
145             =cut
146              
147             sub html {
148 0 0   0 1 0 &App::sub_entry if ($App::trace);
149 0         0 my ($self) = @_;
150 0         0 my ($sessiontext, $sessiondata, $html, $options);
151              
152 0         0 $sessiondata = $self->{store};
153 0         0 $sessiontext = encode_base64(Compress::Zlib::memGzip(freeze($sessiondata)));
154              
155 0         0 my ($maxvarsize, $maxvarlines);
156 0         0 $maxvarlines = 24;
157 0         0 $maxvarsize = $maxvarlines*77; # length of a MIME/Base64 line is (76 chars + newline)
158              
159 0 0       0 if (length($sessiontext) <= $maxvarsize) {
160 0         0 $html = "";
161             }
162             else {
163 0         0 my (@sessiontext, $i, $startidx, $endidx, $textchunk);
164 0         0 @sessiontext = split(/\n/,$sessiontext);
165 0         0 $i = 1;
166 0         0 $startidx = 0;
167 0         0 $endidx = $startidx+$maxvarlines-1;
168 0         0 $textchunk = join("\n",@sessiontext[$startidx .. $endidx]);
169 0         0 $html = "";
170 0         0 while ($endidx < $#sessiontext) {
171 0         0 $i++;
172 0         0 $startidx += $maxvarlines;
173 0         0 $endidx = $startidx+$maxvarlines-1;
174 0 0       0 $endidx = $#sessiontext if ($endidx > $#sessiontext-1);
175 0         0 $textchunk = join("\n",@sessiontext[$startidx .. $endidx]);
176 0         0 $html .= "\n";
177             }
178             }
179 0         0 $html .= "\n";
180              
181 0         0 $options = $self->{context}->options();
182 0 0 0     0 if ($options && $options->{show_session}) {
183             # Debugging Only
184 0         0 my $d = Data::Dumper->new([ $sessiondata ], [ "session_store" ]);
185 0         0 $d->Indent(1);
186 0         0 $html .= "\n";
189             }
190              
191 0         0 my $app = $options->{"app"};
192 0         0 my $cookie_attribs = $options->{"app.Session.cookie_attribs"};
193 0 0       0 if ($cookie_attribs) {
194 0         0 my $cookiedata = {};
195 0         0 foreach my $cookie_attrib (split(/[ ,;]+/, $cookie_attribs)) {
196 0 0       0 if ($cookie_attrib =~ /^([^-]+)-(.+)$/) {
    0          
197 0         0 $cookiedata->{$1}{$2} = $sessiondata->{SessionObject}{$1}{$2};
198             }
199             elsif ($cookie_attrib) {
200 0         0 $cookiedata->{default}{$cookie_attrib} =
201             $sessiondata->{SessionObject}{default}{$cookie_attrib};
202             }
203             }
204              
205 0         0 my $cgi = $self->{context}->request()->{cgi};
206 0 0       0 my $secure = ($cgi->url() =~ /^https/) ? "; secure" : "";
207              
208 0         0 my $cookietext = MIME::Base64::encode(Compress::Zlib::memGzip(freeze($cookiedata)));
209 0         0 $cookietext =~ s/\n//g; # get rid of newlines (76 char lines)
210 0   0     0 my $cookie_options = $options->{"app.Session.cookie_options"} || "$secure";
211 0         0 my $headers = "Set-Cookie: app_session_${app}_persist=$cookietext$cookie_options\n";
212 0         0 $self->{context}->set_header($headers);
213             }
214              
215 0 0       0 &App::sub_exit($html) if ($App::trace);
216 0         0 $html;
217             }
218              
219             #############################################################################
220             # PROTECTED METHODS
221             #############################################################################
222              
223             =head1 Protected Methods:
224              
225             The following methods are intended to be called by subclasses of the
226             current class.
227              
228             =cut
229              
230             #############################################################################
231             # _init()
232             #############################################################################
233              
234             =head2 _init()
235              
236             The _init() method is called from within the constructor.
237              
238             * Signature: _init($named)
239             * Param: $named {} [in]
240             * Return: void
241             * Throws: App::Exception
242             * Since: 0.01
243              
244             Sample Usage:
245              
246             $ref->_init($args);
247              
248             The _init() method looks at the CGI variables in the request
249             and restores the session state information from the variable
250             named "app.sessiondata" (and "app.sessiondata[2..n]").
251              
252             When the values of these variables are concatenated, they
253             form a Base64-encoded, gzipped, frozen multi-level hash of
254             session state data. To retrieve the state data, the text
255             is therefore decoded, gunzipped, and thawed (a la Storable).
256              
257             TODO: encrypt and MAC
258              
259             =cut
260              
261             sub _init {
262 1 50   1   5 &App::sub_entry if ($App::trace);
263 1         2 my ($self, $args) = @_;
264 1         1 my ($cgi, $sessiontext, $store, $request);
265              
266 1         8 $self->{context} = $args->{context};
267 1         2 $store = {};
268 1 50       5 $cgi = $args->{cgi} if (defined $args);
269              
270 1         2 eval {
271 1         8 $request = $self->{context}->request();
272             };
273             # ignore it if it fails
274              
275 1 50       7 if (!defined $cgi) {
276 1 50       5 $cgi = $request->{cgi} if ($request);
277             }
278              
279 1 50       4 if (defined $cgi) {
280 1         3 $sessiontext = $cgi->param("app.sessiondata");
281 1 50       15 if ($sessiontext) {
282 0         0 my ($i, $textchunk);
283 0         0 $i = 2;
284 0         0 while (1) {
285 0         0 $textchunk = $cgi->param("app.sessiondata${i}");
286 0 0       0 last if (!$textchunk);
287 0         0 $sessiontext .= $textchunk;
288 0         0 $i++;
289             }
290 0         0 $store = thaw(Compress::Zlib::memGunzip(decode_base64($sessiontext)));
291             }
292             }
293              
294 1 50       4 if ($request) {
295 1         3 my $options = $self->{context}{options};
296 1         2 my $cookie_attribs = $options->{"app.Session.cookie_attribs"};
297 1 50       4 if ($cookie_attribs) {
298 0         0 my $cookiedata = {};
299              
300 0         0 my $app = $options->{"app"};
301 0         0 my $cookietext = $cgi->cookie("app_session_${app}_persist");
302 0 0       0 if ($cookietext) {
303 0         0 $cookietext =~ s/ /\+/g;
304 0         0 my $length = length($cookietext);
305 0         0 my $pad = 4 - ($length % 4);
306 0 0       0 $pad = 0 if ($pad == 4);
307 0 0       0 $cookietext .= ("=" x $pad) if ($pad);
308 0         0 $cookietext =~ s/(.{76})/$1\n/g;
309 0         0 $cookietext .= "\n";
310             #print "Session::Cookie->_init(): sessiontext = [\n$sessiontext\n]\n";
311 0         0 $cookiedata = thaw(Compress::Zlib::memGunzip(MIME::Base64::decode($cookietext)));
312             }
313              
314 0         0 foreach my $cookie_attrib (split(/[ ,;]+/, $cookie_attribs)) {
315 0 0       0 if ($cookie_attrib =~ /^([^-]+)-(.+)$/) {
    0          
316 0         0 $store->{SessionObject}{$1}{$2} = $cookiedata->{$1}{$2};
317             }
318             elsif ($cookie_attrib) {
319 0         0 $store->{SessionObject}{default}{$cookie_attrib} =
320             $cookiedata->{default}{$cookie_attrib};
321             }
322             }
323             }
324             }
325              
326 1 50       6 $self->{context} = $args->{context} if (defined $args->{context});
327 1         3 $self->{store} = $store;
328 1         2 $self->{cache} = {};
329 1 50       6 &App::sub_exit() if ($App::trace);
330             }
331              
332             1;
333