File Coverage

blib/lib/Bot/Pastebot/WebUtil.pm
Criterion Covered Total %
statement 12 109 11.0
branch 0 32 0.0
condition 0 19 0.0
subroutine 4 17 23.5
pod 0 12 0.0
total 16 189 8.4


line stmt bran cond sub pod time code
1             # Rocco's POE web server helper functions. Do URL en/decoding. Load
2             # static pages, and do template things with them.
3             #
4             # TODO - We could probably replace them with an actual CPAN library or
5             # two.
6              
7             package Bot::Pastebot::WebUtil;
8              
9 1     1   1132 use warnings;
  1         2  
  1         33  
10 1     1   4 use strict;
  1         2  
  1         29  
11              
12 1     1   846 use CGI::Cookie;
  1         7454  
  1         32  
13              
14 1     1   8 use base qw(Exporter);
  1         2  
  1         1478  
15             our @EXPORT_OK = qw(
16             url_decode url_encode parse_content parse_cookie static_response
17             dump_content dump_query_as_response base64_decode html_encode
18             is_true cookie redirect
19             );
20              
21             #------------------------------------------------------------------------------
22             # Build two URL-encoding maps. Map non-printable characters to
23             # hexified ordinal values, and map hexified ordinal values back to
24             # non-printable characters.
25              
26             my (%raw_to_url, %url_to_raw);
27              
28             # Nonprintable characters
29             for (my $ord = 0; $ord < 256; $ord++) {
30             my $character = chr($ord);
31             my $hex = lc(unpack('H2', $character));
32              
33             # Map characters to their hex values, including the escape.
34             $raw_to_url{ $character } = '%' . $hex;
35              
36             # Map hex codes (lower- and uppercase) to characters.
37             $url_to_raw{ $hex } = $character;
38             $url_to_raw{ uc $hex } = $character;
39             }
40              
41             # Return a cookie string for a Set-Cookie header. The request argument is
42             # used to figure out domain.
43             sub cookie {
44 0     0 0   my ($name, $value, $request) = @_;
45              
46 0           return CGI::Cookie->new(
47             -name => $name,
48             -value => $value,
49             -expires => '+36M',
50             -domain => (split /:/, $request->headers->header('Host'))[0],
51             -path => '/',
52             )->as_string;
53             }
54              
55             # Decode url-encoded data. This code was shamelessly stolen from
56             # Lincoln Stein's CGI.pm module. Translate plusses to spaces, and
57             # then translate %xx sequences into their corresponding characters.
58             # Avoid /e on the regexp because "eval" is close to "evil".
59             sub url_decode {
60 0     0 0   my $data = shift;
61 0 0         return undef unless defined $data;
62 0           $data =~ tr[+][ ];
63 0           $data =~ s/%([0-9a-fA-F]{2})/$url_to_raw{$1}/g;
64 0           return $data;
65             }
66              
67             # Url-encode data. This code was shamelessly stolen from Lincoln
68             # Stein's CGI.pm module. Translate nonprintable characters to %xx
69             # sequences, and spaces to plusses. Avoid /e too.
70             sub url_encode {
71 0     0 0   my $data = shift;
72 0 0         return undef unless defined $data;
73 0           $data =~ s/([^a-zA-Z0-9_.:=\&\#\+\?\/-])/$raw_to_url{$1}/g;
74 0           return $data;
75             }
76              
77             # HTML-encode data. More theft from CGI.pm. Translates the
78             # blatantly "bad" html characters.
79             sub html_encode {
80 0     0 0   my $data = shift;
81 0 0         return undef unless defined $data;
82 0           $data =~ s{&}{&amp;}gso;
83 0           $data =~ s{<}{&lt;}gso;
84 0           $data =~ s{>}{&gt;}gso;
85 0           $data =~ s{\"}{&quot;}gso;
86             # XXX: these bits are necessary for Latin charsets only, which is us.
87 0           $data =~ s{\'}{&#39;}gso;
88 0           $data =~ s{\x8b}{&#139;}gso;
89 0           $data =~ s{\x9b}{&#155;}gso;
90 0           return $data;
91             }
92              
93             # Parse content. This doesn't care where the content comes from; it
94             # may be from the URL, in the case of GET requests, or it may be from
95             # the actual content of a POST. This code was shamelessly stolen from
96             # Lincoln Stein's CGI.pm module.
97             sub parse_content {
98 0     0 0   my $content = shift;
99 0           my %content;
100              
101 0 0 0       return \%content unless defined $content and length $content;
102              
103 0           foreach (split(/[\&\;]/, $content)) {
104 0           my ($param, $value) = split(/=/, $_, 2);
105 0           $param = &url_decode($param);
106 0           $value = &url_decode($value);
107              
108 0 0         if (exists $content{$param}) {
109 0 0         if (ref($content{$param}) eq 'ARRAY') {
110 0           push @{$content{$param}}, $value;
  0            
111             }
112             else {
113 0           $content{$param} = [ $content{$param}, $value ];
114             }
115             }
116             else {
117 0           $content{$param} = $value;
118             }
119             }
120              
121 0           return \%content;
122             }
123              
124             # Parse a cookie string (found usually in the Cookie: header), returning a
125             # hashref containing cookies values, not CGI::Cookie objects.
126             sub parse_cookie {
127 0     0 0   my ($cookie) = @_;
128              
129 0 0         return {} if not defined $cookie;
130 0           return { map url_decode($_), map /([^=]+)=?(.*)/s, split /; ?/, $cookie };
131             }
132              
133             sub _render_template {
134 0     0     my ($template, $filename, $record) = @_;
135              
136 0           my ($content, $error);
137 0 0         if (open(my $template_fh, "<", $filename)) {
138              
139 0           $content = eval { $template->process($template_fh, $record) };
  0            
140              
141 0 0 0       if ($@ || !defined $content || !length $content) {
      0        
142 0   0       my $template_error = $template->error || 'unknown error';
143 0           $error = 1;
144 0           $content = (
145             "<html><head><title>Template Error</title></head>" .
146             "<body>Error processing $filename: $template_error</body></html>"
147             );
148             }
149             } else {
150 0           $error = 1;
151 0           $content = (
152             "<html><head><title>Template Error</title></head>" .
153             "<body>Error opening $filename: $!</body></html>"
154             );
155             }
156              
157             return +{
158 0           content => $content,
159             error => 1,
160             };
161             }
162              
163             # Generate a static response from a file.
164             sub static_response {
165 0     0 0   my ($template, $filename, $record) = @_;
166              
167 0           my $code = 200;
168 0           my $result = _render_template( $template, $filename, $record );
169 0 0         $code = 500 if $result->{error};
170              
171 0           my $response = HTTP::Response->new($code);
172 0           $response->push_header('Content-type', 'text/html');
173 0           $response->content( $result->{content} );
174              
175 0 0         if (wantarray()) {
176 0           return(1, $response);
177             }
178 0           return $response;
179             }
180              
181             # redirect to a paste
182             sub redirect {
183 0     0 0   my ($template, $filename, $record, $response_code) = @_;
184              
185 0   0       my $response = HTTP::Response->new( $response_code || 303 );
186 0           my $paste_link = $record->{paste_link};
187 0           $response->push_header( "Location", $paste_link );
188              
189 0           my $result = _render_template( $template, $filename, $record );
190 0 0         unless( $result->{error} ) {
191 0           $response->push_header( "Content-type", "text/html" );
192 0           $response->content( $result->{content} );
193             }
194              
195 0           return $response;
196             }
197              
198             # Dump a query's content as a table.
199             sub dump_content {
200 0     0 0   my $content = shift;
201 0 0         if (defined $content) {
202 0           my %parsed_content = %{ &parse_content($content) };
  0            
203 0           $content = '<table border=1><tr><th>Field</th><th>Value</th></tr>';
204 0           foreach my $key (sort keys %parsed_content) {
205 0           $content .= "<tr><td>$key</td><td>$parsed_content{$key}</td></tr>";
206             }
207 0           $content .= '</table>';
208             }
209             else {
210 0           $content = (
211             '<html><head><title>No Response</title></head>' .
212             '<body>This query contained no content.</body></html>'
213             );
214             }
215 0           return $content;
216             }
217              
218             # Dump content as a page. This just wraps &dump_content in a page
219             # template.
220             sub dump_query_as_response {
221 0     0 0   my $request = shift;
222 0           my $response = new HTTP::Response(200);
223 0           $response->push_header('Content-Type', 'text/html');
224 0           $response->content(
225             "<html><head><title>Content Dump: /signup-do</title></head><body>" .
226             &dump_content($request->content()) .
227             "</body></html>"
228             );
229 0           return $response;
230             }
231              
232             # Decode base64 stuff. Shamelessly stolen from MIME::Decode::Base64
233             # but no longer needed.
234             sub base64_decode {
235 0     0 0   my $data = shift;
236 0 0 0       if (defined($data) and length($data)) {
237 0           $data =~ tr[A-Za-z0-9+/][]cd;
238 0           $data .= '===';
239 0           $data = substr($data, 0, ((length($data) >> 2) << 2));
240 0           $data =~ tr[A-Za-z0-9+/][ -_];
241 0           $data = unpack 'u', chr(32 + (0.75 * length($data))) . $data;
242             }
243 0           return $data;
244             }
245              
246             # Determine if a checkbox/radio thingy is true.
247              
248             my %bool = (
249             1 => 1, t => 1, y => 1, yes => 1, da => 1, si => 1, on => 1,
250             0 => 0, f => 0, n => 0, no => 0, nyet => 0, off => 0,
251             );
252              
253             sub is_true {
254 0     0 0   my $value = shift;
255 0 0 0       return 0 unless defined $value and length $value;
256 0           $value = lc($value);
257 0 0         return $bool{$value} if exists $bool{$value};
258 0           return 0;
259             }
260              
261             1;