File Coverage

blib/lib/Eidolon/Core/CGI.pm
Criterion Covered Total %
statement 104 193 53.8
branch 49 98 50.0
condition 17 39 43.5
subroutine 13 24 54.1
pod 20 20 100.0
total 203 374 54.2


line stmt bran cond sub pod time code
1             package Eidolon::Core::CGI;
2             # ==============================================================================
3             #
4             # Eidolon
5             # Copyright (c) 2009, Atma 7
6             # ---
7             # Eidolon/Core/CGI.pm - common gateway interface functions
8             #
9             # ==============================================================================
10              
11 2     2   1307 use warnings;
  2         5  
  2         112  
12 2     2   13 use strict;
  2         4  
  2         6672  
13              
14             our $VERSION = "0.02"; # 2009-05-14 04:54:47
15              
16             # ------------------------------------------------------------------------------
17             # \% new($postmax, $tmpdir)
18             # constructor
19             # ------------------------------------------------------------------------------
20             sub new
21             {
22 3     3 1 77 my ($class, $self, $cfg);
23              
24 3         4 $class = shift;
25 3         14 $cfg = Eidolon::Core::Config->get_instance;
26              
27             # class attributes
28 3   50     97 $self =
      50        
      50        
      50        
      50        
      50        
29             {
30             # base parameters
31             "max_post_size" => $cfg->{"cgi"}->{"max_post_size"} || 6553600,
32             "tmp_dir" => $cfg->{"cgi"}->{"tmp_dir"} || "/tmp/",
33             "session_cookie" => $cfg->{"cgi"}->{"session_cookie"} || "SESSIONID",
34             "session_time" => $cfg->{"cgi"}->{"session_time"} || 3600,
35            
36             # input data
37             "cookie" => {},
38             "get" => {},
39             "post" => {},
40             "file" => {},
41              
42             # session data
43             "session" =>
44             {
45             "id" => undef,
46             "params" => {}
47             },
48              
49             # HTTP-header for reply
50             "header" =>
51             {
52             "content_type" => $cfg->{"cgi"}->{"content_type"} || "text/html",
53             "charset" => $cfg->{"cgi"}->{"charset"} || "UTF-8",
54             "cookie" => [],
55             "user_defined" => "",
56             "is_sent" => 0
57             }
58             };
59              
60 3         10 bless $self, $class;
61 3         9 $self->_init;
62              
63 3         9 return $self;
64             }
65              
66             # ------------------------------------------------------------------------------
67             # _init()
68             # class initialization
69             # ------------------------------------------------------------------------------
70             sub _init
71             {
72 3     3   4 my ($self, $key, $value, $boundary, $buffer, $stage, $fh, $filename, $sid);
73              
74 3         6 $self = shift;
75              
76             # GET
77 3 100       10 if ($ENV{"QUERY_STRING"})
78             {
79 1         5 foreach (split /&/, $ENV{"QUERY_STRING"})
80             {
81 5         11 ($key, $value) = map { $self->decode_string($_) } split /=/;
  10         21  
82 5 50       13 next unless (defined $key);
83 5         17 $self->{"get"}->{$key} = $value;
84             }
85             }
86              
87             # POST
88 3 100 66     26 if ($ENV{"REQUEST_METHOD"} && uc($ENV{"REQUEST_METHOD"}) eq "POST")
89             {
90             # check POST size
91 2 50       10 if ($ENV{"CONTENT_LENGTH"} > $self->{"max_post_size"}) { throw CoreError::CGI::MaxPost }
  0         0  
92              
93             # multipart/form-data
94 2 100       10 if ($ENV{"CONTENT_TYPE"} =~ /^multipart\/form-data/)
95             {
96             # get boundary marker
97 1         7 ($boundary) = $ENV{"CONTENT_TYPE"} =~ /boundary="?(\S[^"]+)"?$/;
98 1 50       57 throw CoreError::CGI::InvalidPOST unless ($boundary);
99              
100 1         3 $buffer = "";
101 1         2 $stage = 0;
102              
103             # each line
104 1         8 while ()
105             {
106             # new parameter
107             ($stage == 0) && do
108 10 100       180 {
109 5 100       44 if (/^--$boundary(--)?\r\n$/o)
110             {
111 3 100       9 $stage = $1 ? 4 : 1;
112 3 100       10 substr($buffer, -2) = "" if ($buffer);
113             }
114              
115 5 100       12 if ($buffer)
116             {
117 2 100       5 if ($fh) { print $fh $buffer } else { $self->{"post"}->{$key} .= $buffer }
  1         23  
  1         3  
118 2         4 undef $buffer;
119             }
120              
121 5 100 100     89 $stage && $fh && close $fh;
122 5 100       15 ($stage == 4) && last;
123 4 100       8 $buffer = $stage ? "" : $_;
124              
125 4         13 next;
126             };
127              
128             # parameter's header
129             ($stage == 1) && /^Content-Disposition: form-data; name="?([^"\s;]+)"?(?:; (filename)="?([^"\s;]*)"?)?\r\n$/o && do
130 5 100 66     30 {
131 2         6 $key = $self->decode_string($1);
132              
133 2 100       6 if ($2)
134             {
135 1         2 $stage++;
136              
137 1 50       5 if ($3)
138             {
139             # file upload
140 1         5 $filename = $self->{"tmp_dir"}.$self->generate_string;
141 1   33     194 open $fh, ">$filename" || throw CoreError::CGI::FileSave;
142              
143             # file variables
144 1 50       17 $self->{"file"}->{$key} =
145             {
146             "name" => $3,
147             "tmp" => $filename,
148             "ext" => (rindex($3, ".") != -1) ? substr($3, rindex($3, ".") + 1) : undef
149             };
150             }
151              
152 1         3 undef $filename;
153             }
154             else
155             {
156             # usual field
157 1         2 $stage += 2;
158 1         3 $self->{"post"}->{$key} = undef;
159 1         2 undef $fh;
160             }
161              
162 2         8 next;
163             };
164              
165             # content type
166             ($stage == 2) && /^Content-Type: ([^\r\n]+)\r\n$/o && do
167 3 100 66     17 {
168 1 50       7 $self->{"file"}->{$key}->{"type"} = $1 if ($self->{"file"}->{$key});
169 1         1 $stage++;
170              
171 1         4 next;
172             };
173              
174             # empty line
175 2 50       6 ($stage == 3) && do { $stage = 0; next };
  2         3  
  2         7  
176              
177 0         0 throw CoreError::CGI::InvalidPOST($_.$stage);
178             }
179             }
180             else
181             {
182             # application/x-www-form-urlencoded
183 1         17 read STDIN, $buffer, $ENV{"CONTENT_LENGTH"};
184            
185 1         24 foreach (split /&/, $buffer)
186             {
187 5         15 ($key, $value) = map { $self->decode_string($_) } split /=/;
  10         21  
188 5 50       17 next unless (defined $key);
189 5         15 $self->{"post"}->{$key} = $value;
190             }
191             }
192             }
193              
194             # cookies
195 3 50       12 if ($ENV{"HTTP_COOKIE"})
196             {
197 0         0 foreach (split /;\s*/, $ENV{"HTTP_COOKIE"})
198             {
199 0         0 ($key, $value) = map { $self->decode_string($_) } split /=/;
  0         0  
200 0         0 $self->{"cookie"}->{$key} = $value;
201             }
202             }
203              
204             # session
205 3 50       13 if ($sid = $self->get_cookie($self->{"session_cookie"}))
206             {
207 0         0 $filename = $self->{"tmp_dir"}.$sid;
208              
209 0 0 0     0 if ((-f $filename) && (time - (stat $filename)[9] < $self->{"session_time"}))
210             {
211 0         0 open FILE, $filename;
212              
213 0         0 while ()
214             {
215 0         0 chomp;
216 0         0 ($key, $value) = split /\t/;
217 0         0 $self->{"session"}->{"params"}->{$key} = $value;
218             }
219              
220 0         0 close FILE;
221 0         0 $self->{"session"}->{"id"} = $sid;
222              
223             # touch file
224 0         0 utime undef, undef, ($filename);
225             }
226             else
227             {
228 0         0 $self->destroy_session;
229             }
230             }
231              
232             # cleanup
233 3         5 undef $_;
234 3         9 undef $buffer;
235             }
236              
237             # ------------------------------------------------------------------------------
238             # $ decode_string($string)
239             # decode string
240             # ------------------------------------------------------------------------------
241             sub decode_string
242             {
243 22     22 1 34 my ($self, $string) = @_;
244            
245 22         27 $string =~ tr/+/ /;
246 22         29 $string =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/ge;
  0         0  
247              
248 22         66 return $string;
249             }
250              
251             # ------------------------------------------------------------------------------
252             # $ \% get()
253             # GET parameters
254             # ------------------------------------------------------------------------------
255             sub get
256             {
257 3     3 1 8 my ($self, $name) = @_;
258              
259 3 50       21 return defined $name ? $self->{"get"}->{$name} : $self->{"get"};
260             }
261              
262             # ------------------------------------------------------------------------------
263             # $ \% post()
264             # POST parameters
265             # ------------------------------------------------------------------------------
266             sub post
267             {
268 4     4 1 14 my ($self, $name) = @_;
269            
270 4 50       27 return defined $name ? $self->{"post"}->{$name} : $self->{"post"};
271             }
272              
273             # ------------------------------------------------------------------------------
274             # $ get_query()
275             # get query
276             # ------------------------------------------------------------------------------
277             sub get_query
278             {
279 0     0 1 0 my ($self, $query, $query_len);
280              
281 0         0 $self = shift;
282 0         0 $query = $self->get("query");
283            
284             # query may be empty
285 0 0       0 if ($query)
286             {
287 0         0 $query_len = length($query);
288              
289 0 0       0 if ( index($query, "/") == 0) { substr($query, 0, 1) = "" }
  0         0  
290 0 0       0 if (rindex($query, "/") == $query_len - 1) { substr($query, $query_len - 1, 1) = "" }
  0         0  
291             }
292              
293 0         0 return $query;
294             }
295              
296             # ------------------------------------------------------------------------------
297             # \% get_file()
298             # file parameters
299             # ------------------------------------------------------------------------------
300             sub get_file
301             {
302 6     6 1 11 my ($self, $name) = @_;
303              
304 6 50       126 return defined $name ? $self->{"file"}->{$name} : $self->{"file"};
305             }
306              
307             # ------------------------------------------------------------------------------
308             # \$ receive_file($name)
309             # get file contents
310             # ------------------------------------------------------------------------------
311             sub receive_file
312             {
313 1     1 1 4 my ($self, $name, $buffer) = @_;
314              
315 1 50 33     9 if ($name && $self->get_file($name))
316             {
317 1         4 open FILE, "<".$self->get_file($name)->{"tmp"};
318 1         5 binmode FILE;
319 1         3 read FILE, $buffer, -s $self->get_file($name)->{"tmp"};
320 1         12 close FILE;
321             }
322              
323 1         7 return \$buffer;
324             }
325              
326             # ------------------------------------------------------------------------------
327             # $ get_param($name)
328             # get GET or POST parameter
329             # ------------------------------------------------------------------------------
330             sub get_param
331             {
332 2     2 1 7 my ($self, $name) = @_;
333              
334 2 50       23 return exists $self->{"get"}->{$name} ? $self->get($name) :
    100          
335             exists $self->{"post"}->{$name} ? $self->post($name) : undef;
336             }
337              
338             # ------------------------------------------------------------------------------
339             # $ get_cookie($name)
340             # get cookie value
341             # ------------------------------------------------------------------------------
342             sub get_cookie
343             {
344 3     3 1 6 my ($self, $name) = @_;
345              
346 3 50       16 return exists $self->{"cookie"}->{$name} ?
347             $self->{"cookie"}->{$name} : undef;
348             }
349              
350             # ------------------------------------------------------------------------------
351             # set_cookie($name, $value, $expires, $path, $domain)
352             # set cookie value
353             # ------------------------------------------------------------------------------
354             sub set_cookie
355             {
356 0     0 1 0 my ($self, $name, $value, $expires, $path, $domain) = @_;
357              
358 0 0       0 if ($self->header_sent)
359             {
360 0         0 warn "Cannot set cookie - header is already sent";
361 0         0 return;
362             }
363              
364 0   0     0 $expires ||= 0;
365 0   0     0 $path ||= "/";
366 0   0     0 $domain ||= "";
367              
368             # compute cookie's expiry date
369 0 0       0 if ($expires)
370             {
371 0         0 $expires = gmtime(time + $expires);
372 0         0 $expires =~ s/^(\w+)\s+(\w+)\s+(\d+)\s+([\d:]+)\s+\d\d(\d\d)$/$1, $3-$2-$5 $4 GMT/;
373             }
374              
375             # create a cookie
376 0         0 push @{ $self->{"header"}->{"cookie"} },
  0         0  
377             {
378             "name" => $name,
379             "value" => $value,
380             "expires" => $expires,
381             "path" => $path,
382             "domain" => $domain
383             };
384             }
385              
386             # ------------------------------------------------------------------------------
387             # $ generate_string($len)
388             # random string generation
389             # ------------------------------------------------------------------------------
390             sub generate_string
391             {
392 1     1 1 3 my ($self, $len, $symtable, $symlen, $str, $i);
393              
394 1         2 ($self, $len) = @_;
395              
396 1 50       5 $len = 32 if (!$len);
397 1         11 $symtable = join "", (0..9, "a".."z", "A".."Z");
398 1         2 $symlen = length($symtable);
399 1         2 $str = "";
400              
401 1         5 for ($i = 0; $i < $len; $i++)
402             {
403 32         117 $str .= substr($symtable, int rand($symlen), 1);
404             }
405              
406 1         5 return $str;
407             }
408              
409             # ------------------------------------------------------------------------------
410             # start_session()
411             # session start
412             # ------------------------------------------------------------------------------
413             sub start_session
414             {
415 0     0 1 0 my $self = shift;
416              
417 0 0       0 unless ($self->get_cookie($self->{"session_cookie"}))
418             {
419 0         0 $self->{"session"}->{"id"} = $self->generate_string(64);
420 0         0 $self->set_cookie($self->{"session_cookie"}, $self->{"session"}->{"id"});
421             }
422             }
423              
424             # ------------------------------------------------------------------------------
425             # destroy_session()
426             # delete session params & destroy session's cookie
427             # ------------------------------------------------------------------------------
428             sub destroy_session
429             {
430 0     0 1 0 my ($self, $sid);
431              
432 0         0 $self = shift;
433 0         0 $sid = $self->{"session"}->{"id"};
434              
435 0 0       0 return unless ($sid);
436              
437 0         0 $self->set_cookie($self->{"session_cookie"}, "");
438 0         0 unlink $self->{"tmp_dir"}.$sid;
439             }
440              
441             # ------------------------------------------------------------------------------
442             # session_started()
443             # check if session exists
444             # ------------------------------------------------------------------------------
445             sub session_started()
446             {
447 0     0 1 0 return defined $_[0]->{"session"}->{"id"};
448             }
449              
450             # ------------------------------------------------------------------------------
451             # set_session($key, $value)
452             # set session parameter
453             # ------------------------------------------------------------------------------
454             sub set_session
455             {
456 0     0 1 0 my ($self, $key, $value) = @_;
457              
458 0 0       0 $self->{"session"}->{"params"}->{$key} = $value if $self->session_started;
459             }
460              
461             # ------------------------------------------------------------------------------
462             # get_session($key)
463             # get session parameter
464             # ------------------------------------------------------------------------------
465             sub get_session
466             {
467 0     0 1 0 my ($self, $key) = @_;
468              
469 0 0       0 return $self->session_started ? $self->{"session"}->{"params"}->{$key} : undef;
470             }
471              
472             # ------------------------------------------------------------------------------
473             # header_sent()
474             # check if header is sent
475             # ------------------------------------------------------------------------------
476             sub header_sent
477             {
478 0     0 1 0 return $_[0]->{"header"}->{"is_sent"};
479             }
480              
481             # ------------------------------------------------------------------------------
482             # add_header($header)
483             # add user-defined header
484             # ------------------------------------------------------------------------------
485             sub add_header
486             {
487 0     0 1 0 $_[0]->{"header"}->{"user_defined"} .= $_[1]."\n";
488             }
489              
490             # ------------------------------------------------------------------------------
491             # redirect($to)
492             # redirect to new location
493             # ------------------------------------------------------------------------------
494             sub redirect
495             {
496 0     0 1 0 my ($self, $to) = @_;
497              
498 0 0       0 if ($to)
499             {
500 0         0 $self->add_header("Status: 302 Found");
501 0         0 $self->add_header("Location: $to");
502             }
503 0         0 $self->send_header;
504             }
505              
506             # ------------------------------------------------------------------------------
507             # send_header()
508             # send header
509             # ------------------------------------------------------------------------------
510             sub send_header
511             {
512 0     0 1 0 my ($self, $buffer);
513              
514 0         0 $self = shift;
515              
516 0 0       0 if ($self->header_sent)
517             {
518 0         0 warn "Cannot send header - header is already sent";
519 0         0 return;
520             }
521              
522             # add user-defined header parts
523 0         0 $buffer = $self->{"header"}->{"user_defined"};
524            
525             # content type and charset
526 0         0 $buffer .= sprintf
527             (
528             "Content-Type: %s; charset=%s\n",
529             $self->{"header"}->{"content_type"},
530             $self->{"header"}->{"charset"}
531             );
532              
533             # cookies
534 0         0 foreach (@{ $self->{"header"}->{"cookie"} })
  0         0  
535             {
536 0         0 $buffer .= "Set-Cookie: $_->{'name'}=$_->{'value'};";
537              
538 0 0       0 if ( $_->{"path"} ) { $buffer .= " path=$_->{'path'};" }
  0         0  
539 0 0       0 if ( $_->{"expires"} ) { $buffer .= " expires=$_->{'expires'};" }
  0         0  
540 0 0       0 if ( $_->{"domain"} ) { $buffer .= " domain=$_->{'domain'};" }
  0         0  
541              
542 0         0 $buffer .= "\n";
543             }
544              
545 0         0 $buffer .= "\n";
546              
547             # save session data
548 0 0       0 if ($self->{"session"}->{"id"})
549             {
550 0         0 open FILE, ">", $self->{"tmp_dir"}.$self->{"session"}->{"id"};
551              
552 0         0 foreach (keys %{ $self->{"session"}->{"params"} })
  0         0  
553             {
554 0         0 print FILE "$_\t".$self->{"session"}->{"params"}->{$_}."\n";
555             }
556              
557 0         0 close FILE;
558             }
559              
560 0         0 $self->{"header"}->{"is_sent"} = 1;
561              
562 0         0 print $buffer;
563 0         0 undef $buffer;
564             }
565              
566             # ------------------------------------------------------------------------------
567             # DESTROY()
568             # destructor
569             # ------------------------------------------------------------------------------
570             sub DESTROY
571             {
572 3     3   664 unlink $_[0]->{"file"}->{$_}->{"tmp"} foreach (keys %{ $_[0]->{"file"} });
  3         306  
573             }
574              
575             1;
576              
577             __END__