]> mj.ucw.cz Git - libucw.git/blob - lib/perl/CGI.pm
CGI: Added functions for parsing of HTTP headers.
[libucw.git] / lib / perl / CGI.pm
1 #       Poor Man's CGI Module for Perl
2 #
3 #       (c) 2002--2007 Martin Mares <mj@ucw.cz>
4 #       Slightly modified by Tomas Valla <tom@ucw.cz>
5 #
6 #       This software may be freely distributed and used according to the terms
7 #       of the GNU Lesser General Public License.
8
9 package UCW::CGI;
10
11 use strict;
12 use warnings;
13
14 BEGIN {
15         # The somewhat hairy Perl export mechanism
16         use Exporter();
17         our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
18         $VERSION = 1.0;
19         @ISA = qw(Exporter);
20         @EXPORT = qw(&html_escape &url_escape &self_ref &self_form);
21         @EXPORT_OK = qw();
22         %EXPORT_TAGS = ();
23 }
24
25 sub url_escape($) {
26         my $x = shift @_;
27         $x =~ s/([^-\$_.!*'(),0-9A-Za-z\x80-\xff])/"%".unpack('H2',$1)/ge;
28         return $x;
29 }
30
31 sub html_escape($) {
32         my $x = shift @_;
33         $x =~ s/&/&amp;/g;
34         $x =~ s/</&lt;/g;
35         $x =~ s/>/&gt;/g;
36         $x =~ s/"/&quot;/g;
37         return $x;
38 }
39
40 ### Analysing RFC 822 Style Headers ###
41
42 sub rfc822_prepare($) {
43         my $x = shift @_;
44         # Convert all %'s and backslash escapes to %xx escapes
45         $x =~ s/%/%25/g;
46         $x =~ s/\\(.)/"%".unpack("H2",$1)/ge;
47         # Remove all comments, beware, they can be nested (unterminated comments are closed at EOL automatically)
48         while ($x =~ s/^(("[^"]*"|[^"(])*(\([^)]*)*)(\([^()]*(\)|$))/$1 /) { }
49         # Remove quotes and escape dangerous characters inside (again closing at the end automatically)
50         $x =~ s{"([^"]*)("|$)}{my $z=$1; $z =~ s/([^0-9a-zA-Z%_-])/"%".unpack("H2",$1)/ge; $z;}ge;
51         # All control characters are properly escaped, tokens are clearly visible.
52         # Finally remove all unnecessary spaces.
53         $x =~ s/\s+/ /g;
54         $x =~ s/(^ | $)//g;
55         $x =~ s{\s*([()<>@,;:\\"/\[\]?=])\s*}{$1}g;
56         return $x;
57 }
58
59 sub rfc822_deescape($) {
60         my $x = shift @_;
61         $x =~ s/%(..)/pack("H2",$1)/ge;
62         return $x;
63 }
64
65 ### Reading of HTTP headers ###
66
67 sub http_get($) {
68         my $h = shift @_;
69         $h =~ tr/a-z-/A-Z_/;
70         return $ENV{"HTTP_$h"} || $ENV{"$h"};
71 }
72
73 ### Parsing of Arguments ###
74
75 our $arg_table;
76
77 sub parse_arg_string($) {
78         my ($s) = @_;
79         $s =~ s/\s+//;
80         foreach $_ (split /[&:]/,$s) {
81                 (/^([^=]+)=(.*)$/) or next;
82                 my $arg = $arg_table->{$1} or next;
83                 $_ = $2;
84                 s/\+/ /g;
85                 s/%(..)/pack("c",hex $1)/eg;
86                 s/(\r|\n|\t)/ /g;
87                 s/^\s+//;
88                 s/\s+$//;
89                 if (my $rx = $arg->{'check'}) {
90                         if (!/^$rx$/) { $_ = $arg->{'default'}; }
91                 }
92
93                 my $r = ref($arg->{'var'});
94                 if ($r eq 'SCALAR') {
95                         ${$arg->{'var'}} = $_;
96                 } elsif ($r eq 'ARRAY') {
97                         push @{$arg->{'var'}}, $_;
98                 }
99         }
100 }
101
102 sub parse_args($) {
103         $arg_table = shift @_;
104         foreach my $a (values %$arg_table) {
105                 my $r = ref($a->{'var'});
106                 defined($a->{'default'}) or $a->{'default'}="";
107                 if ($r eq 'SCALAR') {
108                         ${$a->{'var'}} = $a->{'default'};
109                 } elsif ($r eq 'ARRAY') {
110                         @{$a->{'var'}} = ();
111                 }
112         }
113         defined $ENV{"GATEWAY_INTERFACE"} or die "Not called as a CGI script";
114         my $method = $ENV{"REQUEST_METHOD"};
115         if ($method eq "GET") {
116                 parse_arg_string($ENV{"QUERY_STRING"});
117         } elsif ($method eq "POST") {
118                 if ($ENV{"CONTENT_TYPE"} =~ /^application\/x-www-form-urlencoded\b/i) {
119                         while (<STDIN>) {
120                                 chomp;
121                                 parse_arg_string($_);
122                         }
123                 } else {
124                         return "Unknown content type for POST data";
125                 }
126         } else {
127                 return "Unknown request method";
128         }
129 }
130
131 sub make_out_args($) {
132         my ($overrides) = @_;
133         my $out = {};
134         foreach my $name (keys %$arg_table) {
135                 my $arg = $arg_table->{$name};
136                 defined $arg->{'pass'} && !$arg->{'pass'} && !exists $overrides->{$name} && next;
137                 my $value;
138                 if (!defined($value = $overrides->{$name})) {
139                         if (exists $overrides->{$name}) {
140                                 $value = $arg->{'default'};
141                         } else {
142                                 $value = ${$arg->{'var'}};
143                         }
144                 }
145                 if ($value ne $arg->{'default'}) {
146                         $out->{$name} = $value;
147                 }
148         }
149         return $out;
150 }
151
152 sub self_ref(@) {
153         my %h = @_;
154         my $out = make_out_args(\%h);
155         return "?" . join(':', map { "$_=" . url_escape($out->{$_}) } sort keys %$out);
156 }
157
158 sub self_form(@) {
159         my %h = @_;
160         my $out = make_out_args(\%h);
161         return join('', map { "<input type=hidden name=$_ value='" . html_escape($out->{$_}) . "'>\n" } sort keys %$out);
162 }
163
164 ### Cookies
165
166 sub cookie_esc($) {
167         my $x = shift @_;
168         if ($x !~ /^[a-zA-Z0-9%]+$/) {
169                 $x =~ s/([\\\"])/\\$1/g;
170                 $x = "\"$x\"";
171         }
172         return $x;
173 }
174
175 sub set_cookie($$@) {
176         my $key = shift @_;
177         my $value = shift @_;
178         my %other = @_;
179         $other{'version'} = 1 unless defined $other{'version'};
180         print "Set-Cookie: $key=", cookie_esc($value);
181         foreach my $k (keys %other) {
182                 print ";$k=", cookie_esc($other{$k});
183         }
184         print "\n";
185 }
186
187 sub parse_cookies() {
188         my $h = http_get("Cookie") or return ();
189         my @cook = ();
190         while (my ($padding,$name,$val,$xx,$rest) = ($h =~ /\s*([,;]\s*)*([^ =]+)=([^ =,;\"]*|\"([^\"\\]|\\.)*\")(\s.*|;.*|$)/)) {
191                 if ($val =~ /^\"/) {
192                         $val =~ s/^\"//;
193                         $val =~ s/\"$//;
194                         $val =~ s/\\(.)/$1/g;
195                 }
196                 push @cook, $name, $val;
197                 $h = $rest;
198         }
199         return @cook;
200 }
201
202 1;  # OK