my
$has_xs_parser
;
BEGIN {
$has_xs_parser
=
$ENV
{
'USE_XS_PARSER'
} &&
eval
{
require
HTTP::Parser::XS } };
sub
net_server_type { __PACKAGE__ }
sub
options {
my
$self
=
shift
;
my
$ref
=
$self
->SUPER::options(
@_
);
my
$prop
=
$self
->{
'server'
};
$ref
->{
$_
} = \
$prop
->{
$_
}
for
qw(timeout_header timeout_idle server_revision max_header_size
access_log_format access_log_file access_log_function enable_dispatch
default_content_type allow_body_on_all_statuses)
;
return
$ref
;
}
sub
timeout_header {
shift
->{
'server'
}->{
'timeout_header'
} }
sub
timeout_idle {
shift
->{
'server'
}->{
'timeout_idle'
} }
sub
server_revision {
shift
->{
'server'
}->{
'server_revision'
} }
sub
max_header_size {
shift
->{
'server'
}->{
'max_header_size'
} }
sub
default_port { 80 }
sub
default_server_type {
'PreFork'
}
sub
initialize_logging {
my
$self
=
shift
;
$self
->SUPER::initialize_logging(
@_
);
my
$prop
=
$self
->{
'server'
};
my
$d
= {
access_log_format
=>
'%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"'
,
};
$prop
->{
$_
} =
$d
->{
$_
}
foreach
grep
{!
defined
(
$prop
->{
$_
})}
keys
%$d
;
$self
->_init_access_log;
}
sub
post_configure {
my
$self
=
shift
;
$self
->SUPER::post_configure(
@_
);
my
$prop
=
$self
->{
'server'
};
my
$d
= {
timeout_header
=> 15,
timeout_idle
=> 60,
server_revision
=> __PACKAGE__.
"/$Net::Server::VERSION"
,
max_header_size
=> 100_000,
};
$prop
->{
$_
} =
$d
->{
$_
}
foreach
grep
{!
defined
(
$prop
->{
$_
})}
keys
%$d
;
$self
->_tie_client_stdout;
}
sub
post_bind {
my
$self
=
shift
;
$self
->SUPER::post_bind(
@_
);
$self
->_check_dispatch;
}
sub
_init_access_log {
my
$self
=
shift
;
my
$prop
=
$self
->{
'server'
};
my
$log
=
$prop
->{
'access_log_file'
};
return
if
(!
$log
||
$log
eq
'/dev/null'
) && !
$prop
->{
'access_log_function'
};
return
if
!
$prop
->{
'access_log_format'
};
$prop
->{
'access_log_format'
} =~ s/\\([\\\
"nt])/$1 eq 'n' ? "
\n
" : $1 eq 't' ? "
\t" : $1/eg;
if
(
my
$code
=
$prop
->{
'access_log_function'
}) {
if
(
ref
$code
ne
'CODE'
) {
die
"Passed access_log_function $code was not a valid method of server, or was not a code object\n"
if
!
$self
->can(
$code
);
my
$copy
=
$self
;
$prop
->{
'access_log_function'
} =
sub
{
$copy
->
$code
(
@_
) };
weaken
$copy
;
}
}
elsif
(
$log
eq
'STDOUT'
||
$log
eq
'/dev/stdout'
) {
open
my
$fh
,
'>&'
, \
*STDOUT
or
die
"Could not dup STDOUT: $!"
;
$fh
->autoflush(1);
$prop
->{
'access_log_function'
} =
sub
{
print
$fh
@_
,
"\n"
};
}
elsif
(
$log
eq
'STDERR'
||
$log
eq
'/dev/stderr'
) {
$prop
->{
'access_log_function'
} =
sub
{
print
STDERR
@_
,
"\n"
};
}
else
{
open
my
$fh
,
'>>'
,
$log
or
die
"Could not open access_log_file \"$log\": $!"
;
$fh
->autoflush(1);
push
@{
$prop
->{
'chown_files'
} },
$log
;
$prop
->{
'access_log_function'
} =
sub
{
print
$fh
@_
,
"\n"
};
}
}
sub
_tie_client_stdout {
my
$self
=
shift
;
my
$prop
=
$self
->{
'server'
};
my
$copy
=
$self
;
$prop
->{
'tie_client_stdout'
} = 1;
$prop
->{
'tied_stdout_callback'
} =
sub
{
my
$client
=
shift
;
my
$method
=
shift
;
alarm
(
$copy
->timeout_idle);
my
$request_info
=
$copy
->{
'request_info'
};
if
(
$request_info
->{
'headers_sent'
}) {
my
(
$resp
,
$len
);
if
(
$method
eq
'print'
) {
$resp
=
$client
->
print
(
my
$str
=
join
''
,
@_
);
$len
=
length
$str
;
}
elsif
(
$method
eq
'printf'
) {
$resp
=
$client
->
print
(
my
$str
=
sprintf
(
shift
,
@_
));
$len
=
length
$str
;
}
elsif
(
$method
eq
'say'
) {
$resp
=
$client
->
print
(
my
$str
=
join
''
,
@_
,
"\n"
);
$len
=
length
$str
;
}
elsif
(
$method
eq
'write'
) {
my
$buf
=
shift
;
$buf
=
substr
(
$buf
,
$_
[1] || 0,
$_
[0])
if
@_
;
$resp
=
$client
->
print
(
$buf
);
$len
=
length
$buf
;
}
elsif
(
$method
eq
'syswrite'
) {
$len
=
$resp
=
$client
->
syswrite
(
@_
);
}
else
{
return
$client
->
$method
(
@_
);
}
$request_info
->{
'response_size'
} = (
$request_info
->{
'response_size'
} || 0) +
$len
if
defined
$len
;
return
$resp
;
}
die
"All headers must only be sent via print ($method)\n"
if
$method
ne
'print'
;
my
$headers
= ${
*$client
}{
'headers'
} ||= {
buffer
=>
''
,
status
=>
undef
,
msg
=>
undef
,
headers
=> []};
$headers
->{
'buffer'
} .=
join
(
''
,
@_
);
while
(
$headers
->{
'buffer'
} =~ s/^(.*?)\015?\012//) {
my
$line
= $1;
if
(
$line
=~ m{^HTTP/(1.[01]) \s+ (\d+) (?: | \s+ (.+?)) \s* $ }x) {
die
"Found HTTP/ line after other headers were sent\n"
if
@{
$headers
->{
'headers'
} };
@$headers
{
qw(version status msg)
} = ($1, $2, $3);
}
elsif
(!
length
$line
) {
if
(!
$headers
->{
'status'
} && ! @{
$headers
->{
'headers'
} }) {
die
"Premature end of script headers\n"
;
}
delete
${
*$client
}{
'headers'
};
$copy
->send_status(
$headers
);
if
(
my
$n
=
length
$headers
->{
'buffer'
}) {
$request_info
->{
'response_size'
} =
$n
;
$client
->
print
(
$headers
->{
'buffer'
});
}
return
;
}
elsif
(
$line
!~ s/^(\w+(?:-(?:\w+))*):\s*//) {
my
$invalid
= (
$line
=~ /(.{0,120})/) ?
"$1..."
:
''
;
$invalid
=~ s/</
<
;/g;
die
"Premature end of script headers: $invalid<br>\n"
;
}
else
{
my
$key
= $1;
push
@{
$request_info
->{
'response_headers'
} }, [
$key
,
$line
];
if
(
lc
(
$key
) eq
'status'
&&
$line
=~ /^(\d+) (?:|\s+(.+?))$/ix) {
@$headers
{
qw(status msg)
} = ($1, $2)
if
!
$headers
->{
'status'
};
}
push
@{
$headers
->{
'headers'
} }, [
$key
,
$line
];
}
}
};
weaken
$copy
;
}
sub
_check_dispatch {
my
$self
=
shift
;
if
(!
$self
->{
'server'
}->{
'enable_dispatch'
}) {
return
if
__PACKAGE__->can(
'process_request'
) ne
$self
->can(
'process_request'
);
return
if
__PACKAGE__->can(
'process_http_request'
) ne
$self
->can(
'process_http_request'
);
}
my
$app
=
$self
->{
'server'
}->{
'app'
};
if
(!
$app
|| (
ref
(
$app
) eq
'ARRAY'
&& !
@$app
)) {
$app
= [];
$self
->configure({
app
=>
$app
});
}
my
%dispatch
;
my
$first
;
my
@dispatch
;
foreach
my
$a
(
ref
(
$app
) eq
'ARRAY'
?
@$app
:
$app
) {
next
if
!
$a
;
my
@pairs
=
ref
(
$a
) eq
'ARRAY'
?
@$a
:
ref
(
$a
) eq
'HASH'
?
%$a
:
ref
(
$a
) eq
'CODE'
? (
'/'
,
$a
)
:
$a
=~ m{^(.+?)\s+(.+)$} ? ($1, $2)
:
$a
=~ m{^(.+?)=(.+)$} ? ($1, $2)
: (
$a
,
$a
);
for
(
my
$i
= 0;
$i
<
@pairs
;
$i
+=2) {
my
(
$key
,
$val
) = (
"/$pairs[$i]"
,
$pairs
[
$i
+1]);
$key
=~ s{/\./}{/}g;
$key
=~ s{(?:/[^/]+|)/\../}{/}g;
$key
=~ s{//+}{/}g;
if
(
$dispatch
{
$key
}) {
$self
->
log
(2,
"Already found a path matching \"$key\" - skipping."
);
next
;
}
$dispatch
{
$key
} =
$val
;
push
@dispatch
,
$key
;
$first
||=
$key
;
$self
->
log
(2,
" Dispatch: $key => $val"
);
}
}
if
(
@dispatch
) {
if
(!
$dispatch
{
'/'
} &&
$first
) {
$dispatch
{
'/'
} =
$dispatch
{
$first
};
push
@dispatch
,
'/'
;
$self
->
log
(2,
" Dispatch: / => $dispatch{$first} (default)"
);
}
$self
->{
'dispatch_qr'
} =
join
"|"
,
map
{
"\Q$_\E"
}
@dispatch
;
$self
->{
'dispatch'
} = \
%dispatch
;
}
}
sub
http_base_headers {
my
$self
=
shift
;
return
[
[
Date
=>
gmtime
().
" GMT"
],
[
Connection
=>
'close'
],
[
Server
=>
$self
->server_revision],
];
}
sub
default_content_type {
shift
->{
'server'
}->{
'default_content_type'
} ||
'text/html'
}
our
%status_msg
= (
200
=>
'OK'
,
201
=>
'Created'
,
202
=>
'Accepted'
,
204
=>
'No Content'
,
301
=>
'Moved Permanently'
,
302
=>
'Found'
,
304
=>
'Not Modified'
,
400
=>
'Bad Request'
,
401
=>
'Unauthorized'
,
403
=>
'Forbidden'
,
404
=>
'Not Found'
,
418
=>
"I'm a teapot"
,
500
=>
'Internal Server Error'
,
501
=>
'Not Implemented'
,
503
=>
'Service Unavailable'
,
);
sub
send_status {
my
(
$self
,
$status
,
$msg
,
$body
,
$gen_body
) =
@_
;
my
(
$version
,
$headers
);
if
(
ref
(
$status
) eq
'HASH'
) {
(
$version
,
$status
,
$msg
,
$headers
) =
@$status
{
qw(version status msg headers)
};
}
$version
||=
'1.0'
;
my
@hdrs
= @{
$self
->http_base_headers };
push
@hdrs
,
@$headers
if
$headers
;
foreach
my
$hdr
(
@hdrs
) {
$hdr
->[0] =~ y/_/-/;
$hdr
->[0] =
ucfirst
lc
$hdr
->[0];
if
(!
$status
) {
if
(
$hdr
->[0] eq
'Content-type'
) {
$status
= 200;
}
elsif
(
$hdr
->[0] eq
'Location'
) {
$status
= 302;
}
}
}
$status
||= 500;
$msg
||=
$status_msg
{
$status
} ||
'-'
;
if
(!
$body
&&
$gen_body
) {
my
$_msg
= (
$msg
eq
'-'
) ?
"Status $status"
:
$msg
;
$gen_body
= []
if
ref
$gen_body
ne
'ARRAY'
;
for
(
$_msg
,
@$gen_body
) { s/</
<
;/g; s/>/
<
;/g; s/&/
&alt
;/g }
$body
=
"<html>\n<body>\n<h1>$_msg</h1>"
.
join
(
"\n"
,
map
{
"<p>$_</p>"
}
@$gen_body
).
"</body>\n</html>\n"
;
}
my
$out
=
"HTTP/$version $status $msg\015\012"
;
my
$no_body
;
if
((
$status
== 204 ||
$status
== 304 || (
$status
>= 100 &&
$status
<= 199))
&& !
$self
->{
'server'
}->{
'allow_body_on_all_statuses'
}) {
$no_body
= 1;
}
else
{
my
$ct
= (
grep
{
lc
(
$_
->[0]) eq
'content-type'
}
@hdrs
)[0];
push
@hdrs
,
$ct
= [
'Content-type'
,
$self
->default_content_type]
if
!
$ct
;
}
my
$request_info
=
$self
->{
'request_info'
};
foreach
my
$hdr
(
@hdrs
) {
$out
.=
"$hdr->[0]: $hdr->[1]\015\012"
;
push
@{
$request_info
->{
'response_headers'
} },
$hdr
;
}
$out
.=
"\015\012"
;
$self
->{
'server'
}->{
'client'
}->
print
(
$out
);
@$request_info
{
qw(http_version response_status response_header_size headers_sent)
}
= (
$version
,
$status
,
length
(
$out
), 1);
if
(
$no_body
) {
}
elsif
(
defined
(
$body
) &&
length
(
$body
)) {
$self
->{
'server'
}->{
'client'
}->
print
(
$body
);
$request_info
->{
'response_size'
} +=
length
$body
;
}
}
sub
send_400 {
my
(
$self
,
@err
) =
@_
;
$self
->send_status(400,
undef
,
undef
, \
@err
) }
sub
send_500 {
my
(
$self
,
@err
) =
@_
;
$self
->send_status(500,
undef
,
undef
, \
@err
) }
sub
run_client_connection {
my
$self
=
shift
;
local
$self
->{
'request_info'
} = {};
return
$self
->SUPER::run_client_connection(
@_
);
}
sub
get_client_info {
my
$self
=
shift
;
$self
->SUPER::get_client_info(
@_
);
$self
->clear_http_env;
}
sub
clear_http_env {
my
$self
=
shift
;
%ENV
= ();
}
sub
process_request {
my
$self
=
shift
;
my
$client
=
shift
||
$self
->{
'server'
}->{
'client'
};
my
$ok
=
eval
{
local
$SIG
{
'ALRM'
} =
sub
{
die
"Server Timeout on headers\n"
};
alarm
(
$self
->timeout_header);
$self
->process_headers(
$client
);
$SIG
{
'ALRM'
} =
sub
{
die
"Server Timeout on process\n"
};
alarm
(
$self
->timeout_idle);
$self
->process_http_request(
$client
);
alarm
(0);
1;
};
alarm
(0);
if
(!
$ok
) {
my
$err
=
"$@"
||
"Something happened"
;
$self
->
log
(1,
$err
);
$self
->send_500(
$err
);
}
}
sub
request_denied_hook {
my
(
$self
,
$client
) =
@_
;
$self
->send_400();
}
sub
script_name {
shift
->{
'script_name'
} ||
''
}
sub
process_headers {
my
$self
=
shift
;
my
$client
=
shift
||
$self
->{
'server'
}->{
'client'
};
$ENV
{
'REMOTE_PORT'
} =
$self
->{
'server'
}->{
'peerport'
};
$ENV
{
'REMOTE_ADDR'
} =
$self
->{
'server'
}->{
'peeraddr'
};
$ENV
{
'SERVER_PORT'
} =
$self
->{
'server'
}->{
'sockport'
};
$ENV
{
'SERVER_ADDR'
} =
$self
->{
'server'
}->{
'sockaddr'
};
$ENV
{
$_
} =~ s/^::ffff:(?=\d+(?:\.\d+){3}$)//
for
qw(REMOTE_ADDR SERVER_ADDR)
;
$ENV
{
'HTTPS'
} =
'on'
if
$self
->{
'server'
}->{
'client'
}->NS_proto =~ /SSL/;
my
(
$ok
,
$headers
) =
$client
->read_until(
$self
->max_header_size,
qr{\n\r?\n}
);
my
(
$req
,
$len
,
@parsed
);
die
"Could not parse http headers successfully\n"
if
$ok
!= 1;
if
(
$has_xs_parser
) {
$len
= HTTP::Parser::XS::parse_http_request(
$headers
, \
%ENV
);
die
"Corrupt request"
if
$len
== -1;
die
"Incomplete request"
if
$len
== -2;
$req
=
"$ENV{'REQUEST_METHOD'} $ENV{'REQUEST_URI'} $ENV{'SERVER_PROTOCOL'}"
;
}
else
{
(
$req
,
my
@lines
) =
split
/\r?\n/,
$headers
;
die
"Missing request\n"
if
!
defined
$req
;
if
(!
defined
(
$req
) ||
$req
!~ m{ ^\s*(GET|POST|PUT|PATCH|DELETE|PUSH|HEAD|OPTIONS)\s+(.+)\s+(HTTP/1\.[01])\s*$ }ix) {
die
"Invalid request\n"
;
}
$ENV
{
'REQUEST_METHOD'
} =
uc
$1;
$ENV
{
'REQUEST_URI'
} = $2;
$ENV
{
'SERVER_PROTOCOL'
} = $3;
$ENV
{
'QUERY_STRING'
} = $1
if
$ENV
{
'REQUEST_URI'
} =~ m{ \?(.*)$ }x;
$ENV
{
'PATH_INFO'
} = $1
if
$ENV
{
'REQUEST_URI'
} =~ m{^([^\?]+)};
foreach
my
$l
(
@lines
) {
my
(
$key
,
$val
) =
split
/\s*:\s*/,
$l
, 2;
push
@parsed
, [
"\u\L$key"
,
$val
];
$key
=
uc
(
$key
);
$key
=
'COOKIE'
if
$key
eq
'COOKIES'
;
$key
=~ y/-/_/;
$key
=~ s/^\s+//;
$key
=
"HTTP_$key"
if
$key
!~ /^CONTENT_(?:LENGTH|TYPE)$/;
$val
=~ s/\s+$//;
if
(
exists
$ENV
{
$key
}) {
$ENV
{
$key
} .=
", $val"
;
}
else
{
$ENV
{
$key
} =
$val
;
}
}
$len
=
length
$headers
;
}
$ENV
{
'SCRIPT_NAME'
} =
$self
->script_name(
$ENV
{
'PATH_INFO'
}) ||
''
;
my
$type
=
$Net::Server::HTTP::ISA
[0];
$type
=
$Net::Server::MultiType::ISA
[0]
if
$type
eq
'Net::Server::MultiType'
;
$ENV
{
'NET_SERVER_TYPE'
} =
$type
;
$ENV
{
'NET_SERVER_SOFTWARE'
} =
$self
->server_revision;
$self
->_init_http_request_info(
$req
, \
@parsed
,
$len
);
}
sub
http_request_info {
shift
->{
'request_info'
} }
sub
_init_http_request_info {
my
(
$self
,
$req
,
$parsed
,
$len
) =
@_
;
my
$prop
=
$self
->{
'server'
};
my
$info
=
$self
->{
'request_info'
};
@$info
{
qw(sockaddr sockport peeraddr peerport)
} =
@$prop
{
qw(sockaddr sockport peeraddr peerport)
};
$info
->{
'peerhost'
} =
$prop
->{
'peerhost'
} ||
$info
->{
'peeraddr'
};
$info
->{
'begin'
} =
time
;
$info
->{
'request'
} =
$req
;
$info
->{
'request_headers'
} =
$parsed
;
$info
->{
'query_string'
} =
"?$ENV{'QUERY_STRING'}"
if
defined
$ENV
{
'QUERY_STRING'
};
$info
->{
'request_protocol'
} =
$ENV
{
'HTTPS'
} ?
'https'
:
'http'
;
$info
->{
'request_method'
} =
$ENV
{
'REQUEST_METHOD'
};
$info
->{
'request_path'
} =
$ENV
{
'PATH_INFO'
};
$info
->{
'request_header_size'
} =
$len
;
$info
->{
'request_size'
} =
$ENV
{
'CONTENT_LENGTH'
} || 0;
$info
->{
'remote_user'
} =
'-'
;
}
sub
http_note {
my
(
$self
,
$key
,
$val
) =
@_
;
return
$self
->{
'request_info'
}->{
'notes'
}->{
$key
} =
$val
if
@_
>= 3;
return
$self
->{
'request_info'
}->{
'notes'
}->{
$key
};
}
sub
http_dispatch {
my
(
$self
,
$dispatch_qr
,
$dispatch_table
) =
@_
;
$ENV
{
'PATH_INFO'
} =~ s{^(
$dispatch_qr
)(?=/|$|(?<=/))}{} or
die
"Dispatch not found\n"
;
$ENV
{
'SCRIPT_NAME'
} = $1;
if
(
$ENV
{
'PATH_INFO'
}) {
$ENV
{
'PATH_INFO'
} =
"/$ENV{'PATH_INFO'}"
if
$ENV
{
'PATH_INFO'
} !~ m{^/};
$ENV
{
'PATH_INFO'
} =~ s/%([a-fA-F0-9]{2})/
chr
(
hex
$1)/eg;
}
my
$code
=
$self
->{
'dispatch'
}->{$1};
return
$self
->
$code
()
if
ref
$code
;
$self
->exec_cgi(
$code
);
}
sub
process_http_request {
my
(
$self
,
$client
) =
@_
;
if
(
my
$table
=
$self
->{
'dispatch'
}) {
my
$qr
=
$self
->{
'dispatch_qr'
} or
die
"Dispatch was not correctly setup\n"
;
return
$self
->http_dispatch(
$qr
,
$table
)
}
return
$self
->http_echo;
}
sub
http_echo {
my
$self
=
shift
;
print
"Content-type: text/html\n\n"
;
if
(
$ENV
{
'PATH_INFO'
} &&
$ENV
{
'PATH_INFO'
} eq
'/simple'
) {
print
"Simple"
;
return
;
}
print
"<form method=post action=/bam><input type=text name=foo><input type=submit></form>\n"
;
local
$Data::Dumper::Sortkeys
= 1;
my
$form
= {};
if
(
eval
{
require
CGI }) {
my
$q
= CGI->new;
$form
->{
$_
} =
$q
->param(
$_
)
for
$q
->param; }
print
"<pre>"
.Data::Dumper->Dump([\
%ENV
,
$form
], [
'*ENV'
,
'form'
]).
"</pre>"
;
}
}
sub
post_process_request {
my
$self
=
shift
;
my
$info
=
$self
->{
'request_info'
};
$info
->{
'begin'
} =
time
unless
defined
$info
->{
'begin'
};
$info
->{
'elapsed'
} =
time
-
$info
->{
'begin'
};
$self
->SUPER::post_process_request(
@_
);
$self
->log_http_request(
$info
);
}
sub
log_http_request {
my
(
$self
,
$info
) =
@_
;
my
$prop
=
$self
->{
'server'
};
my
$fmt
=
$prop
->{
'access_log_format'
} ||
return
;
my
$log
=
$prop
->{
'access_log_function'
} ||
return
;
$log
->(
$self
->http_log_format(
$fmt
,
$info
));
}
my
%fmt_map
=
qw(
a peeraddr
A sockaddr
B response_size
f filename
h peerhost
H request_protocol
l remote_logname
m request_method
p sockport
q query_string
r request
s response_status
u remote_user
U request_path
)
;
my
%fmt_code
=
qw(
C http_log_cookie
e http_log_env
i http_log_header_in
n http_log_note
o http_log_header_out
P http_log_pid
t http_log_time
v http_log_vhost
V http_log_vhost
X http_log_constat
)
;
sub
http_log_format {
my
(
$self
,
$fmt
,
$info
,
$orig
) =
@_
;
$fmt
=~ s{ % ([<>])?
(!? \d\d\d (?:,\d\d\d)* )?
(?: \{ ([^\}]+) \} )?
([aABDfhHmpqrsTuUvVhblPtIOCeinoPtX%])
}{
$info
=
$orig
if
$1 &&
$orig
&& $1 eq
'<'
;
my
$v
= $2 && (
substr
($2,0,1) eq
'!'
?
index
($2,
$info
->{
'response_status'
})!=-1 :
index
($2,
$info
->{
'response_status'
})==-1) ?
'-'
:
$fmt_map
{$4} ?
$info
->{
$fmt_map
{$4}}
:
$fmt_code
{$4} ?
do
{
my
$m
=
$fmt_code
{$4};
$self
->
$m
(
$info
, $3, $1, $4) }
: $4 eq
'b'
?
$info
->{
'response_size'
} ||
'-'
: $4 eq
'I'
?
$info
->{
'request_size'
} +
$info
->{
'request_header_size'
}
: $4 eq
'O'
?
$info
->{
'response_size'
} +
$info
->{
'response_header_size'
}
: $4 eq
'T'
?
sprintf
(
'%d'
,
$info
->{
'elapsed'
})
: $4 eq
'D'
?
sprintf
(
'%d'
,
$info
->{
'elapsed'
}/.000_001)
: $4 eq
'%'
?
'%'
:
'-'
;
$v
=
'-'
if
!
defined
(
$v
) || !
length
(
$v
);
$v
=~ s/([^\ -\!\
$v
;
}gxe;
return
$fmt
;
}
sub
http_log_time {
my
(
$self
,
$info
,
$fmt
) =
@_
;
return
'['
.POSIX::strftime(
$fmt
||
'%d/%b/%Y:%T %z'
,
localtime
(
$info
->{
'begin'
})).
']'
;
}
sub
http_log_env {
$ENV
{
$_
[2]} }
sub
http_log_cookie {
my
(
$self
,
$info
,
$var
) =
@_
;
my
@c
;
for
my
$cookie
(
map
{
$_
->[1]}
grep
{
$_
->[0] eq
'Cookie'
} @{
$info
->{
'request_headers'
} || [] }) {
push
@c
, $1
if
$cookie
=~ /^\Q
$var
\E=(.*)/;
}
return
join
', '
,
@c
;
}
sub
http_log_header_in {
my
(
$self
,
$info
,
$var
) =
@_
;
$var
=
"\u\L$var"
;
return
join
', '
,
map
{
$_
->[1]}
grep
{
$_
->[0] eq
$var
} @{
$info
->{
'request_headers'
} || [] };
}
sub
http_log_note {
my
(
$self
,
$info
,
$var
) =
@_
;
return
$self
->http_note(
$var
);
}
sub
http_log_header_out {
my
(
$self
,
$info
,
$var
) =
@_
;
$var
=
"\u\L$var"
;
return
join
', '
,
map
{
$_
->[1]}
grep
{
$_
->[0] eq
$var
} @{
$info
->{
'response_headers'
} || [] };
}
sub
http_log_pid {
$_
[1]->{
'pid'
} || $$ }
sub
http_log_vhost {
my
(
$self
,
$info
,
$fmt
,
$f_l
,
$type
) =
@_
;
return
$self
->http_log_header_in(
$info
,
'Host'
) ||
$self
->{
'server'
}->{
'client'
}->NS_host ||
$self
->{
'server'
}->{
'sockaddr'
};
}
sub
http_log_constat {
my
(
$self
,
$info
) =
@_
;
return
$info
->{
'headers_sent'
} ?
'-'
:
'X'
;
}
sub
exec_fork_hook {}
sub
exec_trusted_perl {
my
(
$self
,
$file
) =
@_
;
die
"File $file is not executable\n"
if
! -x
$file
;
local
$!;
my
$pid
=
fork
;
die
"Could not spawn child process: $!\n"
if
!
defined
$pid
;
$self
->exec_fork_hook(
$pid
,
$file
, 1);
if
(!
$pid
) {
if
(!
eval
{
require
$file
}) {
my
$err
=
"$@"
||
"Error while running trusted perl script\n"
;
$err
=~ s{\s
*Compilation
failed in
require
at lib/Net/Server/HTTP\.pm line \d+\.\s*\z}{\n};
die
$err
if
!
$self
->{
'request_info'
}->{
'headers_sent'
};
warn
$err
;
}
exit
;
}
else
{
waitpid
$pid
, 0;
return
;
}
}
sub
exec_cgi {
my
(
$self
,
$file
) =
@_
;
my
$done
= 0;
my
$pid
;
Net::Server::SIG::register_sig(
CHLD
=>
sub
{
while
(
defined
(
my
$chld
=
waitpid
(-1, POSIX::WNOHANG()))) {
$done
= ($? >> 8) || -1
if
$pid
==
$chld
;
last
unless
$chld
> 0;
}
});
my
$in
;
my
$out
;
my
$err
= Symbol::gensym();
local
$!;
$pid
=
eval
{ IPC::Open3::open3(
$in
,
$out
,
$err
,
$file
) } or
die
"Could not run external script $file: $!\n"
;
$self
->exec_fork_hook(
$pid
,
$file
);
my
$len
=
$ENV
{
'CONTENT_LENGTH'
} || 0;
my
$s_in
=
$len
? IO::Select->new(
$in
) :
undef
;
my
$s_out
= IO::Select->new(
$out
,
$err
);
my
$printed
;
while
(!
$done
) {
my
(
$o
,
$i
,
$e
) = IO::Select->
select
(
$s_out
,
$s_in
,
undef
);
Net::Server::SIG::check_sigs();
for
my
$fh
(
@$o
) {
read
(
$fh
,
my
$buf
, 4096) ||
next
;
if
(
$fh
==
$out
) {
print
$buf
;
$printed
||= 1;
}
else
{
print
STDERR
$buf
;
}
}
if
(
@$i
) {
my
$bytes
=
read
(STDIN,
my
$buf
,
$len
);
print
$in
$buf
if
$bytes
;
$len
-=
$bytes
;
$s_in
=
undef
if
$len
<= 0;
}
}
if
(!
$self
->{
'request_info'
}->{
'headers_sent'
}) {
if
(!
$printed
) {
$self
->send_500(
"Premature end of script headers"
);
}
elsif
(
$done
> 0) {
$self
->send_500(
"Script exited unsuccessfully"
);
}
}
Net::Server::SIG::unregister_sig(
'CHLD'
);
}
1;