| 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/</g; | ||||
| 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/>/</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__ |