File Coverage

blib/lib/Net/Server/HTTP.pm
Criterion Covered Total %
statement 220 409 53.7
branch 42 208 20.1
condition 22 94 23.4
subroutine 34 59 57.6
pod 30 40 75.0
total 348 810 42.9


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

Internal Server Error

$err

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