File Coverage

blib/lib/Net/Server/HTTP.pm
Criterion Covered Total %
statement 214 399 53.6
branch 42 204 20.5
condition 22 88 25.0
subroutine 33 57 57.8
pod 30 39 76.9
total 341 787 43.3


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

Internal Server Error

$err

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