File Coverage

blib/lib/Net/Server/HTTP.pm
Criterion Covered Total %
statement 261 446 58.5
branch 68 240 28.3
condition 37 112 33.0
subroutine 37 63 58.7
pod 33 43 76.7
total 436 904 48.2


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::HTTP - Extensible Perl HTTP base server
4             #
5             # Copyright (C) 2010-2022
6             #
7             # Paul Seamons
8             #
9             # This package may be distributed under the terms of either the
10             # GNU General Public License
11             # or the
12             # Perl Artistic License
13             #
14             ################################################################
15              
16             package Net::Server::HTTP;
17              
18 4     4   14150 use strict;
  4         8  
  4         146  
19 4     4   28 use base qw(Net::Server::MultiType);
  4         92  
  4         1832  
20 4     4   22 use Scalar::Util qw(weaken blessed);
  4         8  
  4         566  
21 4     4   26 use IO::Handle ();
  4         6  
  4         112  
22 4     4   22 use re 'taint'; # most of our regular expressions setting ENV should not be clearing taint
  4         8  
  4         280  
23 4     4   24 use POSIX ();
  4         8  
  4         58  
24 4     4   1892 use Time::HiRes qw(time);
  4         5060  
  4         14  
25             my $has_xs_parser;
26 4   33 4   22514 BEGIN {$has_xs_parser = $ENV{'USE_XS_PARSER'} && eval { require HTTP::Parser::XS } };
27              
28 1     1 0 3 sub net_server_type { __PACKAGE__ }
29              
30             sub options {
31 4     4 0 9 my $self = shift;
32 4         71 my $ref = $self->SUPER::options(@_);
33 4         8 my $prop = $self->{'server'};
34 4         128 $ref->{$_} = \$prop->{$_} for qw(timeout_header timeout_idle server_revision max_header_size
35             access_log_format access_log_file access_log_function enable_dispatch
36             default_content_type allow_body_on_all_statuses);
37 4         22 return $ref;
38             }
39              
40 2     2 1 17 sub timeout_header { shift->{'server'}->{'timeout_header'} }
41 5     5 1 37 sub timeout_idle { shift->{'server'}->{'timeout_idle'} }
42 4     4 1 38 sub server_revision { shift->{'server'}->{'server_revision'} }
43 2     2 1 37 sub max_header_size { shift->{'server'}->{'max_header_size'} }
44              
45 0     0 0 0 sub default_port { 80 }
46              
47 0     0 0 0 sub default_server_type { 'PreFork' }
48              
49             sub initialize_logging {
50 2     2 0 4 my $self = shift;
51 2         22 $self->SUPER::initialize_logging(@_);
52 2         4 my $prop = $self->{'server'};
53              
54 2         17 my $d = {
55             access_log_format => '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"',
56             };
57 2         10 $prop->{$_} = $d->{$_} foreach grep {!defined($prop->{$_})} keys %$d;
  2         11  
58              
59 2         41 $self->_init_access_log;
60             }
61              
62             sub post_configure {
63 2     2 1 5 my $self = shift;
64 2         14 $self->SUPER::post_configure(@_);
65 2         4 my $prop = $self->{'server'};
66              
67             # set other defaults
68 2         12 my $d = {
69             timeout_header => 15,
70             timeout_idle => 60,
71             server_revision => __PACKAGE__."/$Net::Server::VERSION",
72             max_header_size => 100_000,
73             };
74 2         7 $prop->{$_} = $d->{$_} foreach grep {!defined($prop->{$_})} keys %$d;
  8         18  
75              
76 2         26 $self->_tie_client_stdout;
77             }
78              
79             sub post_bind {
80 2     2 1 4 my $self = shift;
81 2         81 $self->SUPER::post_bind(@_);
82              
83 2         33 $self->_check_dispatch;
84             }
85              
86             sub _init_access_log {
87 2     2   10 my $self = shift;
88 2         4 my $prop = $self->{'server'};
89 2         4 my $log = $prop->{'access_log_file'};
90 2 100 33     39 return if (! $log || $log eq '/dev/null') && ! $prop->{'access_log_function'};
      66        
91 1 50       3 return if ! $prop->{'access_log_format'};
92 1 50       5 $prop->{'access_log_format'} =~ s/\\([\\\"nt])/$1 eq 'n' ? "\n" : $1 eq 't' ? "\t" : $1/eg;
  6 50       23  
93 1 50 0     5 if (my $code = $prop->{'access_log_function'}) {
    0 0        
    0          
94 1 50       5 if (ref $code ne 'CODE') {
95 0 0       0 die "Passed access_log_function $code was not a valid method of server, or was not a code object\n" if ! $self->can($code);
96 0         0 my $copy = $self;
97 0     0   0 $prop->{'access_log_function'} = sub { $copy->$code(@_) };
  0         0  
98 0         0 weaken $copy;
99             }
100             } elsif ($log eq 'STDOUT' || $log eq '/dev/stdout') {
101 0 0       0 open my $fh, '>&', \*STDOUT or die "Could not dup STDOUT: $!";
102 0         0 $fh->autoflush(1);
103 0     0   0 $prop->{'access_log_function'} = sub { print $fh @_,"\n" };
  0         0  
104             } elsif ($log eq 'STDERR' || $log eq '/dev/stderr') {
105 0     0   0 $prop->{'access_log_function'} = sub { print STDERR @_,"\n" };
  0         0  
106             } else {
107 0 0       0 open my $fh, '>>', $log or die "Could not open access_log_file \"$log\": $!";
108 0         0 $fh->autoflush(1);
109 0         0 push @{ $prop->{'chown_files'} }, $log;
  0         0  
110 0     0   0 $prop->{'access_log_function'} = sub { print $fh @_,"\n" };
  0         0  
111             }
112             }
113              
114             sub _tie_client_stdout {
115 1     1   2 my $self = shift;
116 1         1 my $prop = $self->{'server'};
117              
118             # install a callback that will handle our outbound header negotiation for the clients similar to what apache does for us
119 1         2 my $copy = $self;
120 1         1 $prop->{'tie_client_stdout'} = 1;
121             $prop->{'tied_stdout_callback'} = sub {
122 3     3   13 my $client = shift;
123 3         12 my $method = shift;
124 3         10 alarm($copy->timeout_idle); # reset timeout
125              
126 3         10 my $request_info = $copy->{'request_info'};
127 3 100       12 if ($request_info->{'headers_sent'}) { # keep track of how much has been printed
128 2         5 my ($resp, $len);
129 2 50       5 if ($method eq 'print') {
    0          
    0          
    0          
    0          
130 2         15 $resp = $client->print(my $str = join '', @_);
131 2         158 $len = length $str;
132             } elsif ($method eq 'printf') {
133 0         0 $resp = $client->print(my $str = sprintf(shift, @_));
134 0         0 $len = length $str;
135             } elsif ($method eq 'say') {
136 0         0 $resp = $client->print(my $str = join '', @_, "\n");
137 0         0 $len = length $str;
138             } elsif ($method eq 'write') {
139 0         0 my $buf = shift;
140 0 0 0     0 $buf = substr($buf, $_[1] || 0, $_[0]) if @_;
141 0         0 $resp = $client->print($buf);
142 0         0 $len = length $buf;
143             } elsif ($method eq 'syswrite') {
144 0         0 $len = $resp = $client->syswrite(@_);
145             } else {
146 0         0 return $client->$method(@_);
147             }
148 2 50 100     25 $request_info->{'response_size'} = ($request_info->{'response_size'} || 0) + $len if defined $len;
149 2         10 return $resp;
150             }
151              
152 1 50       3 die "All headers must only be sent via print ($method)\n" if $method ne 'print';
153              
154 1   50     2 my $headers = ${*$client}{'headers'} ||= {buffer => '', status => undef, msg => undef, headers => []};
  1         30  
155 1         5 $headers->{'buffer'} .= join('', @_);
156 1         10 while ($headers->{'buffer'} =~ s/^(.*?)\015?\012//) {
157 2         5 my $line = $1;
158              
159 2 50       19 if ($line =~ m{^HTTP/(1.[01]) \s+ (\d+) (?: | \s+ (.+?)) \s* $ }x) {
    100          
    50          
160 0 0       0 die "Found HTTP/ line after other headers were sent\n" if @{ $headers->{'headers'} };
  0         0  
161 0         0 @$headers{qw(version status msg)} = ($1, $2, $3);
162             }
163             elsif (! length $line) {
164 1 50 33     9 if (! $headers->{'status'} && ! @{ $headers->{'headers'} }) {
  1         4  
165 0         0 die "Premature end of script headers\n";
166             }
167 1         2 delete ${*$client}{'headers'};
  1         3  
168 1         6 $copy->send_status($headers);
169 1 50       9 if (my $n = length $headers->{'buffer'}) {
170 0         0 $request_info->{'response_size'} = $n;
171 0         0 $client->print($headers->{'buffer'});
172             }
173 1         6 return;
174             } elsif ($line !~ s/^(\w+(?:-(?:\w+))*):\s*//) {
175 0 0       0 my $invalid = ($line =~ /(.{0,120})/) ? "$1..." : '';
176 0         0 $invalid =~ s/
177 0         0 die "Premature end of script headers: $invalid
\n";
178             } else {
179 1         4 my $key = $1;
180 1         7 push @{ $request_info->{'response_headers'} }, [$key, $line];
  1         4  
181 1 50 33     10 if (lc($key) eq 'status' && $line =~ /^(\d+) (?:|\s+(.+?))$/ix) {
182 0 0       0 @$headers{qw(status msg)} = ($1, $2) if ! $headers->{'status'};
183             # not sure if it should also still be setting a header
184             }
185 1         3 push @{ $headers->{'headers'} }, [$key, $line];
  1         7  
186             }
187             }
188 1         32 };
189 1         14 weaken $copy;
190             }
191              
192             sub _check_dispatch {
193 2     2   5 my $self = shift;
194 2 50       13 if (! $self->{'server'}->{'enable_dispatch'}) {
195 2 100       54 return if __PACKAGE__->can('process_request') ne $self->can('process_request');
196 1 50       15 return if __PACKAGE__->can('process_http_request') ne $self->can('process_http_request');
197             }
198              
199 1         3 my $app = $self->{'server'}->{'app'};
200 1 50 0     4 if (! $app || (ref($app) eq 'ARRAY' && !@$app)) {
      33        
201 1         2 $app = [];
202 1         4 $self->configure({app => $app});
203             }
204              
205 1         8 my %dispatch;
206             my $first;
207 1         0 my @dispatch;
208 1 50       16 foreach my $a (ref($app) eq 'ARRAY' ? @$app : $app) {
209 0 0       0 next if ! $a;
210 0 0       0 my @pairs = ref($a) eq 'ARRAY' ? @$a
    0          
    0          
    0          
    0          
211             : ref($a) eq 'HASH' ? %$a
212             : ref($a) eq 'CODE' ? ('/', $a)
213             : $a =~ m{^(.+?)\s+(.+)$} ? ($1, $2)
214             : $a =~ m{^(.+?)=(.+)$} ? ($1, $2)
215             : ($a, $a);
216 0         0 for (my $i = 0; $i < @pairs; $i+=2) {
217 0         0 my ($key, $val) = ("/$pairs[$i]", $pairs[$i+1]);
218 0         0 $key =~ s{/\./}{/}g;
219 0         0 $key =~ s{(?:/[^/]+|)/\../}{/}g;
220 0         0 $key =~ s{//+}{/}g;
221 0 0       0 if ($dispatch{$key}) {
222 0         0 $self->log(2, "Already found a path matching \"$key\" - skipping.");
223 0         0 next;
224             }
225 0         0 $dispatch{$key} = $val;
226 0         0 push @dispatch, $key;
227 0   0     0 $first ||= $key;
228 0         0 $self->log(2, " Dispatch: $key => $val");
229             }
230             }
231 1 50       6 if (@dispatch) {
232 0 0 0     0 if (! $dispatch{'/'} && $first) {
233 0         0 $dispatch{'/'} = $dispatch{$first};
234 0         0 push @dispatch, '/';
235 0         0 $self->log(2, " Dispatch: / => $dispatch{$first} (default)");
236             }
237 0         0 $self->{'dispatch_qr'} = join "|", map {"\Q$_\E"} @dispatch;
  0         0  
238 0         0 $self->{'dispatch'} = \%dispatch;
239             }
240             }
241              
242             sub http_base_headers {
243 2     2 0 4 my $self = shift;
244             return [
245 2         85 [Date => gmtime()." GMT"],
246             [Connection => 'close'],
247             [Server => $self->server_revision],
248             ];
249             }
250              
251 0 0   0 1 0 sub default_content_type { shift->{'server'}->{'default_content_type'} || 'text/html' }
252              
253             our %status_msg = (
254             200 => 'OK',
255             201 => 'Created',
256             202 => 'Accepted',
257             204 => 'No Content',
258             301 => 'Moved Permanently',
259             302 => 'Found',
260             304 => 'Not Modified',
261             400 => 'Bad Request',
262             401 => 'Unauthorized',
263             403 => 'Forbidden',
264             404 => 'Not Found',
265             418 => "I'm a teapot",
266             500 => 'Internal Server Error',
267             501 => 'Not Implemented',
268             503 => 'Service Unavailable',
269             );
270              
271             sub send_status {
272 2     2 1 6 my ($self, $status, $msg, $body, $gen_body) = @_;
273              
274 2         5 my ($version, $headers);
275 2 50       9 if (ref($status) eq 'HASH') {
276 2         8 ($version, $status, $msg, $headers) = @$status{qw(version status msg headers)};
277             }
278 2   50     32 $version ||= '1.0';
279              
280 2         3 my @hdrs = @{ $self->http_base_headers };
  2         13  
281 2 50       11 push @hdrs, @$headers if $headers;
282 2         18 foreach my $hdr (@hdrs) {
283 8         13 $hdr->[0] =~ y/_/-/;
284 8         17 $hdr->[0] = ucfirst lc $hdr->[0];
285 8 100       15 if (! $status) {
286 4 100       9 if ($hdr->[0] eq 'Content-type') {
    50          
287 1         2 $status = 200;
288             } elsif ($hdr->[0] eq 'Location') {
289 0         0 $status = 302;
290             }
291             }
292             }
293 2   50     6 $status ||= 500;
294 2   50     22 $msg ||= $status_msg{$status} || '-';
      33        
295 2 50 33     17 if (! $body && $gen_body) {
296 0 0       0 my $_msg = ($msg eq '-') ? "Status $status" : $msg;
297 0 0       0 $gen_body = [] if ref $gen_body ne 'ARRAY';
298 0         0 for ($_msg, @$gen_body) { s//</g; s/&/&alt;/g }
  0         0  
  0         0  
  0         0  
299 0         0 $body = "\n\n

$_msg

".join("\n", map {"

$_

"} @$gen_body)."\n\n";
  0         0  
300             }
301              
302 2         9 my $out = "HTTP/$version $status $msg\015\012";
303 2         15 my $no_body;
304 2 50 33     39 if (($status == 204 || $status == 304 || ($status >= 100 && $status <= 199))
      33        
305             && ! $self->{'server'}->{'allow_body_on_all_statuses'}) {
306             # no content-type and or body
307 0         0 $no_body = 1;
308             } else {
309 2         6 my $ct = (grep { lc($_->[0]) eq 'content-type' } @hdrs)[0];
  8         18  
310 2 50       8 push @hdrs, $ct = ['Content-type', $self->default_content_type] if ! $ct;
311             }
312              
313 2         4 my $request_info = $self->{'request_info'};
314 2         6 foreach my $hdr (@hdrs) {
315 8         18 $out .= "$hdr->[0]: $hdr->[1]\015\012";
316 8         9 push @{ $request_info->{'response_headers'} }, $hdr;
  8         23  
317             }
318 2         4 $out .= "\015\012";
319              
320 2         41 $self->{'server'}->{'client'}->print($out);
321 2         190 @$request_info{qw(http_version response_status response_header_size headers_sent)}
322             = ($version, $status, length($out), 1);
323              
324 2 50 33     16 if ($no_body) {
    50          
325             # no content-type and or body
326             } elsif (defined($body) && length($body)) {
327 0         0 $self->{'server'}->{'client'}->print($body);
328 0         0 $request_info->{'response_size'} += length $body;
329             }
330             }
331              
332 0     0 1 0 sub send_400 { my ($self, @err) = @_; $self->send_status(400, undef, undef, \@err) }
  0         0  
333 0     0 1 0 sub send_500 { my ($self, @err) = @_; $self->send_status(500, undef, undef, \@err) }
  0         0  
334              
335             ###----------------------------------------------------------------###
336              
337             sub run_client_connection {
338 2     2 1 6 my $self = shift;
339 2         12 local $self->{'request_info'} = {};
340 2         18 return $self->SUPER::run_client_connection(@_);
341             }
342              
343             sub get_client_info {
344 2     2 1 6 my $self = shift;
345 2         19 $self->SUPER::get_client_info(@_);
346 2         38 $self->clear_http_env;
347             }
348              
349             sub clear_http_env {
350 2     2 0 4 my $self = shift;
351 2         346 %ENV = ();
352             }
353              
354             sub process_request {
355 1     1 1 2 my $self = shift;
356 1   33     2 my $client = shift || $self->{'server'}->{'client'};
357              
358 1         3 my $ok = eval {
359 1     0   34 local $SIG{'ALRM'} = sub { die "Server Timeout on headers\n" };
  0         0  
360 1         7 alarm($self->timeout_header);
361 1         13 $self->process_headers($client);
362              
363 1     0   27 $SIG{'ALRM'} = sub { die "Server Timeout on process\n" };
  0         0  
364 1         14 alarm($self->timeout_idle);
365 1         5 $self->process_http_request($client);
366              
367 1         10 alarm(0);
368 1         19 1;
369             };
370 1         7 alarm(0);
371              
372 1 50       6 if (! $ok) {
373 0   0     0 my $err = "$@" || "Something happened";
374 0         0 $self->log(1, $err);
375 0         0 $self->send_500($err);
376             }
377             }
378              
379             sub request_denied_hook {
380 0     0 1 0 my ($self, $client) = @_;
381 0         0 $self->send_400();
382             }
383              
384 2 50   2 0 19 sub script_name { shift->{'script_name'} || '' }
385              
386             sub process_headers {
387 2     2 1 4 my $self = shift;
388 2   66     15 my $client = shift || $self->{'server'}->{'client'};
389              
390 2         14 $ENV{'REMOTE_PORT'} = $self->{'server'}->{'peerport'};
391 2         7 $ENV{'REMOTE_ADDR'} = $self->{'server'}->{'peeraddr'};
392 2         6 $ENV{'SERVER_PORT'} = $self->{'server'}->{'sockport'};
393 2         6 $ENV{'SERVER_ADDR'} = $self->{'server'}->{'sockaddr'};
394 2         9 $ENV{$_} =~ s/^::ffff:(?=\d+(?:\.\d+){3}$)// for qw(REMOTE_ADDR SERVER_ADDR);
395 2 50       8 $ENV{'HTTPS'} = 'on' if $self->{'server'}->{'client'}->NS_proto =~ /SSL/;
396              
397 2         12 my ($ok, $headers) = $client->read_until($self->max_header_size, qr{\n\r?\n});
398 2         9 my ($req, $len, @parsed);
399 2 50       7 die "Could not parse http headers successfully\n" if $ok != 1;
400 2 50       14 if ($has_xs_parser) {
401 0         0 $len = HTTP::Parser::XS::parse_http_request($headers, \%ENV);
402 0 0       0 die "Corrupt request" if $len == -1;
403 0 0       0 die "Incomplete request" if $len == -2;
404 0         0 $req = "$ENV{'REQUEST_METHOD'} $ENV{'REQUEST_URI'} $ENV{'SERVER_PROTOCOL'}";
405             } else {
406 2         18 ($req, my @lines) = split /\r?\n/, $headers;
407 2 50       7 die "Missing request\n" if ! defined $req;
408              
409 2 50 33     44 if (!defined($req) || $req !~ m{ ^\s*(GET|POST|PUT|PATCH|DELETE|PUSH|HEAD|OPTIONS)\s+(.+)\s+(HTTP/1\.[01])\s*$ }ix) {
410 0         0 die "Invalid request\n";
411             }
412 2         33 $ENV{'REQUEST_METHOD'} = uc $1;
413 2         21 $ENV{'REQUEST_URI'} = $2;
414 2         16 $ENV{'SERVER_PROTOCOL'} = $3;
415 2 50       9 $ENV{'QUERY_STRING'} = $1 if $ENV{'REQUEST_URI'} =~ m{ \?(.*)$ }x;
416 2 50       21 $ENV{'PATH_INFO'} = $1 if $ENV{'REQUEST_URI'} =~ m{^([^\?]+)};
417              
418 2         7 foreach my $l (@lines) {
419 4         19 my ($key, $val) = split /\s*:\s*/, $l, 2;
420 4         16 push @parsed, ["\u\L$key", $val];
421 4         18 $key = uc($key);
422 4 50       11 $key = 'COOKIE' if $key eq 'COOKIES';
423 4         8 $key =~ y/-/_/;
424 4         8 $key =~ s/^\s+//;
425 4 50       13 $key = "HTTP_$key" if $key !~ /^CONTENT_(?:LENGTH|TYPE)$/;
426 4         7 $val =~ s/\s+$//;
427 4 50       25 if (exists $ENV{$key}) {
428 0         0 $ENV{$key} .= ", $val";
429             } else {
430 4         17 $ENV{$key} = $val;
431             }
432             }
433 2         5 $len = length $headers;
434             }
435 2   50     20 $ENV{'SCRIPT_NAME'} = $self->script_name($ENV{'PATH_INFO'}) || '';
436              
437 2         6 my $type = $Net::Server::HTTP::ISA[0];
438 2 50       13 $type = $Net::Server::MultiType::ISA[0] if $type eq 'Net::Server::MultiType';
439 2         7 $ENV{'NET_SERVER_TYPE'} = $type;
440 2         9 $ENV{'NET_SERVER_SOFTWARE'} = $self->server_revision;
441              
442 2         16 $self->_init_http_request_info($req, \@parsed, $len);
443             }
444              
445 0     0 1 0 sub http_request_info { shift->{'request_info'} }
446              
447             sub _init_http_request_info {
448 2     2   8 my ($self, $req, $parsed, $len) = @_;
449 2         4 my $prop = $self->{'server'};
450 2         4 my $info = $self->{'request_info'};
451 2         11 @$info{qw(sockaddr sockport peeraddr peerport)} = @$prop{qw(sockaddr sockport peeraddr peerport)};
452 2   33     17 $info->{'peerhost'} = $prop->{'peerhost'} || $info->{'peeraddr'};
453 2         29 $info->{'begin'} = time;
454 2         4 $info->{'request'} = $req;
455 2         6 $info->{'request_headers'} = $parsed;
456 2 50       7 $info->{'query_string'} = "?$ENV{'QUERY_STRING'}" if defined $ENV{'QUERY_STRING'};
457 2 50       8 $info->{'request_protocol'} = $ENV{'HTTPS'} ? 'https' : 'http';
458 2         4 $info->{'request_method'} = $ENV{'REQUEST_METHOD'};
459 2         4 $info->{'request_path'} = $ENV{'PATH_INFO'};
460 2         5 $info->{'request_header_size'} = $len;
461 2   50     15 $info->{'request_size'} = $ENV{'CONTENT_LENGTH'} || 0; # we might not actually read entire request
462 2         19 $info->{'remote_user'} = '-';
463             }
464              
465             sub http_note {
466 0     0 1 0 my ($self, $key, $val) = @_;
467 0 0       0 return $self->{'request_info'}->{'notes'}->{$key} = $val if @_ >= 3;
468 0         0 return $self->{'request_info'}->{'notes'}->{$key};
469             }
470              
471             sub http_dispatch {
472 0     0 1 0 my ($self, $dispatch_qr, $dispatch_table) = @_;
473              
474 0 0       0 $ENV{'PATH_INFO'} =~ s{^($dispatch_qr)(?=/|$|(?<=/))}{} or die "Dispatch not found\n";
475 0         0 $ENV{'SCRIPT_NAME'} = $1;
476 0 0       0 if ($ENV{'PATH_INFO'}) {
477 0 0       0 $ENV{'PATH_INFO'} = "/$ENV{'PATH_INFO'}" if $ENV{'PATH_INFO'} !~ m{^/};
478 0         0 $ENV{'PATH_INFO'} =~ s/%([a-fA-F0-9]{2})/chr(hex $1)/eg;
  0         0  
479             }
480 0         0 my $code = $self->{'dispatch'}->{$1};
481 0 0       0 return $self->$code() if ref $code;
482 0         0 $self->exec_cgi($code);
483             }
484              
485             sub process_http_request {
486 1     1 1 3 my ($self, $client) = @_;
487              
488 1 50       3 if (my $table = $self->{'dispatch'}) {
489 0 0       0 my $qr = $self->{'dispatch_qr'} or die "Dispatch was not correctly setup\n";
490 0         0 return $self->http_dispatch($qr, $table)
491             }
492              
493 1         12 return $self->http_echo;
494             }
495              
496             sub http_echo {
497 1     1 0 2 my $self = shift;
498 1         7 print "Content-type: text/html\n\n";
499 1 50 33     11 if ($ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} eq '/simple') {
500 0         0 print "Simple";
501 0         0 return;
502             }
503 1         5 print "
\n";
504 1 50       2 if (eval { require Data::Dumper }) {
  1         604  
505 1         6252 local $Data::Dumper::Sortkeys = 1;
506 1         3 my $form = {};
507 1 50       2 if (eval { require CGI }) { my $q = CGI->new; $form->{$_} = $q->param($_) for $q->param; }
  1         788  
  1         26311  
  1         289  
508 1         27 print "
".Data::Dumper->Dump([\%ENV, $form], ['*ENV', 'form'])."
";
509             }
510             }
511              
512             sub post_process_request {
513 2     2 1 6 my $self = shift;
514 2         5 my $info = $self->{'request_info'};
515 2 50       9 $info->{'begin'} = time unless defined $info->{'begin'};
516 2         15 $info->{'elapsed'} = time - $info->{'begin'};
517 2         28 $self->SUPER::post_process_request(@_);
518 2         175 $self->log_http_request($info);
519             }
520              
521             ###----------------------------------------------------------------###
522              
523             sub log_http_request {
524 2     2 0 7 my ($self, $info) = @_;
525 2         4 my $prop = $self->{'server'};
526 2   50     11 my $fmt = $prop->{'access_log_format'} || return;
527 2   100     9 my $log = $prop->{'access_log_function'} || return;
528 1         18 $log->($self->http_log_format($fmt, $info));
529             }
530              
531             my %fmt_map = qw(
532             a peeraddr
533             A sockaddr
534             B response_size
535             f filename
536             h peerhost
537             H request_protocol
538             l remote_logname
539             m request_method
540             p sockport
541             q query_string
542             r request
543             s response_status
544             u remote_user
545             U request_path
546             );
547             my %fmt_code = qw(
548             C http_log_cookie
549             e http_log_env
550             i http_log_header_in
551             n http_log_note
552             o http_log_header_out
553             P http_log_pid
554             t http_log_time
555             v http_log_vhost
556             V http_log_vhost
557             X http_log_constat
558             );
559              
560             sub http_log_format {
561 1     1 1 5 my ($self, $fmt, $info, $orig) = @_;
562 1         11 $fmt =~ s{ % ([<>])? # 1
563             (!? \d\d\d (?:,\d\d\d)* )? # 2
564             (?: \{ ([^\}]+) \} )? # 3
565             ([aABDfhHmpqrsTuUvVhblPtIOCeinoPtX%]) # 4
566             }{
567 9 50 66     29 $info = $orig if $1 && $orig && $1 eq '<';
      33        
568             my $v = $2 && (substr($2,0,1) eq '!' ? index($2, $info->{'response_status'})!=-1 : index($2, $info->{'response_status'})==-1) ? '-'
569             : $fmt_map{$4} ? $info->{$fmt_map{$4}}
570 3         6 : $fmt_code{$4} ? do { my $m = $fmt_code{$4}; $self->$m($info, $3, $1, $4) }
  3         24  
571             : $4 eq 'b' ? $info->{'response_size'} || '-' # B can be 0, b cannot
572             : $4 eq 'I' ? $info->{'request_size'} + $info->{'request_header_size'}
573             : $4 eq 'O' ? $info->{'response_size'} + $info->{'response_header_size'}
574             : $4 eq 'T' ? sprintf('%d', $info->{'elapsed'})
575 9 0 33     61 : $4 eq 'D' ? sprintf('%d', $info->{'elapsed'}/.000_001)
    0 50        
    0          
    0          
    0          
    50          
    100          
    100          
    50          
576             : $4 eq '%' ? '%'
577             : '-';
578 9 100 66     30 $v = '-' if !defined($v) || !length($v);
579 9 0       17 $v =~ s/([^\ -\!\#-\[\]-\~])/$1 eq "\n" ? '\n' : $1 eq "\t" ? '\t' : sprintf('\x%02X', ord($1))/eg; # escape non-printable or " or \
  0 0       0  
580 9         34 $v;
581             }gxe;
582 1         7 return $fmt;
583             }
584             sub http_log_time {
585 1     1 1 5 my ($self, $info, $fmt) = @_;
586 1   50     77 return '['.POSIX::strftime($fmt || '%d/%b/%Y:%T %z', localtime($info->{'begin'})).']';
587             }
588 0     0 1 0 sub http_log_env { $ENV{$_[2]} }
589             sub http_log_cookie {
590 0     0 1 0 my ($self, $info, $var) = @_;
591 0         0 my @c;
592 0 0       0 for my $cookie (map {$_->[1]} grep {$_->[0] eq 'Cookie' } @{ $info->{'request_headers'} || [] }) {
  0         0  
  0         0  
  0         0  
593 0 0       0 push @c, $1 if $cookie =~ /^\Q$var\E=(.*)/;
594             }
595 0         0 return join ', ', @c;
596             }
597             sub http_log_header_in {
598 2     2 1 5 my ($self, $info, $var) = @_;
599 2         6 $var = "\u\L$var";
600 2 50       5 return join ', ', map {$_->[1]} grep {$_->[0] eq $var} @{ $info->{'request_headers'} || [] };
  2         16  
  6         13  
  2         8  
601             }
602             sub http_log_note {
603 0     0 1   my ($self, $info, $var) = @_;
604 0           return $self->http_note($var);
605             }
606             sub http_log_header_out {
607 0     0 1   my ($self, $info, $var) = @_;
608 0           $var = "\u\L$var";
609 0 0         return join ', ', map {$_->[1]} grep {$_->[0] eq $var} @{ $info->{'response_headers'} || [] };
  0            
  0            
  0            
610             }
611 0 0   0 1   sub http_log_pid { $_[1]->{'pid'} || $$ } # we do not support tid yet
612             sub http_log_vhost {
613 0     0 1   my ($self, $info, $fmt, $f_l, $type) = @_;
614 0   0       return $self->http_log_header_in($info, 'Host') || $self->{'server'}->{'client'}->NS_host || $self->{'server'}->{'sockaddr'};
615             }
616             sub http_log_constat {
617 0     0 1   my ($self, $info) = @_;
618 0 0         return $info->{'headers_sent'} ? '-' : 'X';
619             }
620              
621             ###----------------------------------------------------------------###
622              
623       0 1   sub exec_fork_hook {}
624              
625             sub exec_trusted_perl {
626 0     0 1   my ($self, $file) = @_;
627 0 0         die "File $file is not executable\n" if ! -x $file;
628 0           local $!;
629 0           my $pid = fork;
630 0 0         die "Could not spawn child process: $!\n" if ! defined $pid;
631 0           $self->exec_fork_hook($pid, $file, 1);
632 0 0         if (!$pid) {
633 0 0         if (!eval { require $file }) {
  0            
634 0   0       my $err = "$@" || "Error while running trusted perl script\n";
635 0           $err =~ s{\s*Compilation failed in require at lib/Net/Server/HTTP\.pm line \d+\.\s*\z}{\n};
636 0 0         die $err if !$self->{'request_info'}->{'headers_sent'};
637 0           warn $err;
638             }
639 0           exit;
640             } else {
641 0           waitpid $pid, 0;
642 0           return;
643             }
644             }
645              
646             sub exec_cgi {
647 0     0 1   my ($self, $file) = @_;
648              
649 0           my $done = 0;
650 0           my $pid;
651             Net::Server::SIG::register_sig(CHLD => sub {
652 0     0     while (defined(my $chld = waitpid(-1, POSIX::WNOHANG()))) {
653 0 0 0       $done = ($? >> 8) || -1 if $pid == $chld;
654 0 0         last unless $chld > 0;
655             }
656 0           });
657              
658 0           require IPC::Open3;
659 0           require Symbol;
660 0           my $in;
661             my $out;
662 0           my $err = Symbol::gensym();
663 0           local $!;
664 0 0         $pid = eval { IPC::Open3::open3($in, $out, $err, $file) } or die "Could not run external script $file: $!\n";
  0            
665 0           $self->exec_fork_hook($pid, $file); # won't occur for the child
666 0   0       my $len = $ENV{'CONTENT_LENGTH'} || 0;
667 0 0         my $s_in = $len ? IO::Select->new($in) : undef;
668 0           my $s_out = IO::Select->new($out, $err);
669 0           my $printed;
670 0           while (!$done) {
671 0           my ($o, $i, $e) = IO::Select->select($s_out, $s_in, undef);
672 0           Net::Server::SIG::check_sigs();
673 0           for my $fh (@$o) {
674 0 0         read($fh, my $buf, 4096) || next;
675 0 0         if ($fh == $out) {
676 0           print $buf;
677 0   0       $printed ||= 1;
678             } else {
679 0           print STDERR $buf;
680             }
681             }
682 0 0         if (@$i) {
683 0           my $bytes = read(STDIN, my $buf, $len);
684 0 0         print $in $buf if $bytes;
685 0           $len -= $bytes;
686 0 0         $s_in = undef if $len <= 0;
687             }
688             }
689 0 0         if (!$self->{'request_info'}->{'headers_sent'}) {
690 0 0         if (!$printed) {
    0          
691 0           $self->send_500("Premature end of script headers");
692             } elsif ($done > 0) {
693 0           $self->send_500("Script exited unsuccessfully");
694             }
695             }
696              
697 0           Net::Server::SIG::unregister_sig('CHLD');
698             }
699              
700             1;
701              
702             __END__