|
Packit |
ec7ac3 |
#!/usr/bin/perl
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# to run test with Net::SSL as backend set environment
|
|
Packit |
ec7ac3 |
# PERL_NET_HTTPS_SSL_SOCKET_CLASS=Net::SSL
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
use strict;
|
|
Packit |
ec7ac3 |
use warnings;
|
|
Packit |
ec7ac3 |
use Test::More;
|
|
Packit |
ec7ac3 |
use File::Temp 'tempfile';
|
|
Packit |
ec7ac3 |
use IO::Socket::INET;
|
|
Packit |
ec7ac3 |
use IO::Select;
|
|
Packit |
ec7ac3 |
use Socket 'MSG_PEEK';
|
|
Packit |
ec7ac3 |
use LWP::UserAgent;
|
|
Packit |
ec7ac3 |
use LWP::Protocol::https;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
plan skip_all => "fork not implemented on this platform" if
|
|
Packit |
ec7ac3 |
grep { $^O =~m{$_} } qw( MacOS VOS vmesa riscos amigaos );
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
eval { require IO::Socket::SSL }
|
|
Packit |
ec7ac3 |
and $IO::Socket::SSL::VERSION >= 1.953
|
|
Packit |
ec7ac3 |
and eval { require IO::Socket::SSL::Utils }
|
|
Packit |
ec7ac3 |
or plan skip_all => "no recent version of IO::Socket::SSL::Utils";
|
|
Packit |
ec7ac3 |
IO::Socket::SSL::Utils->import;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# create CA -------------------------------------------------------------
|
|
Packit |
ec7ac3 |
my ($cacert,$cakey) = CERT_create( CA => 1 );
|
|
Packit |
ec7ac3 |
my $cafile = do {
|
|
Packit |
ec7ac3 |
my ($fh,$fname) = tempfile( CLEANUP => 1 );
|
|
Packit |
ec7ac3 |
print $fh PEM_cert2string($cacert);
|
|
Packit |
ec7ac3 |
$fname
|
|
Packit |
ec7ac3 |
};
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# create two web servers ------------------------------------------------
|
|
Packit |
ec7ac3 |
my (@server,@saddr);
|
|
Packit |
ec7ac3 |
for my $i (0,1) {
|
|
Packit |
ec7ac3 |
my $server = IO::Socket::INET->new(
|
|
Packit |
ec7ac3 |
LocalAddr => '127.0.0.1',
|
|
Packit |
ec7ac3 |
LocalPort => 0, # let system pick port
|
|
Packit |
ec7ac3 |
Listen => 10
|
|
Packit |
ec7ac3 |
) or die "failed to create INET listener";
|
|
Packit |
ec7ac3 |
my $saddr = $server->sockhost.':'.$server->sockport;
|
|
Packit |
ec7ac3 |
$server[$i] = $server;
|
|
Packit |
ec7ac3 |
$saddr[$i] = $saddr;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
my @childs;
|
|
Packit |
ec7ac3 |
END { kill 9,@childs if @childs };
|
|
Packit |
ec7ac3 |
defined( my $pid = fork()) or die "fork failed: $!";
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# child process runs _server and exits
|
|
Packit |
ec7ac3 |
if ( ! $pid ) {
|
|
Packit |
ec7ac3 |
@childs = ();
|
|
Packit |
ec7ac3 |
exit( _server());
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# parent continues with closed server sockets
|
|
Packit |
ec7ac3 |
push @childs,$pid;
|
|
Packit |
ec7ac3 |
@server = ();
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# check which SSL implementation Net::HTTPS uses
|
|
Packit |
ec7ac3 |
# Net::SSL behaves different than the default IO::Socket::SSL
|
|
Packit |
ec7ac3 |
my $netssl = $Net::HTTPS::SSL_SOCKET_CLASS eq 'Net::SSL';
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# do some tests ----------------------------------------------------------
|
|
Packit |
ec7ac3 |
my %ua;
|
|
Packit |
ec7ac3 |
$ua{noproxy} = LWP::UserAgent->new(
|
|
Packit |
ec7ac3 |
keep_alive => 10, # size of connection cache
|
|
Packit |
ec7ac3 |
# server does not know the expected name and returns generic certificate
|
|
Packit |
ec7ac3 |
ssl_opts => { verify_hostname => 0 }
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
$ua{proxy} = LWP::UserAgent->new(
|
|
Packit |
ec7ac3 |
keep_alive => 10, # size of connection cache
|
|
Packit |
ec7ac3 |
ssl_opts => {
|
|
Packit |
ec7ac3 |
# Net::SSL cannot verify hostnames :(
|
|
Packit |
ec7ac3 |
verify_hostname => $netssl ? 0: 1,
|
|
Packit |
ec7ac3 |
SSL_ca_file => $cafile
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
$ua{proxy_nokeepalive} = LWP::UserAgent->new(
|
|
Packit |
ec7ac3 |
keep_alive => 0,
|
|
Packit |
ec7ac3 |
ssl_opts => {
|
|
Packit |
ec7ac3 |
# Net::SSL cannot verify hostnames :(
|
|
Packit |
ec7ac3 |
verify_hostname => $netssl ? 0: 1,
|
|
Packit |
ec7ac3 |
SSL_ca_file => $cafile
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
$ENV{http_proxy} = $ENV{https_proxy} = "http://foo:bar\@$saddr[0]";
|
|
Packit |
ec7ac3 |
$ua{proxy}->env_proxy;
|
|
Packit |
ec7ac3 |
$ua{proxy_nokeepalive}->env_proxy;
|
|
Packit |
ec7ac3 |
if ($netssl) {
|
|
Packit |
ec7ac3 |
# Net::SSL cannot get user/pass from proxy url
|
|
Packit |
ec7ac3 |
$ENV{HTTPS_PROXY_USERNAME} = 'foo';
|
|
Packit |
ec7ac3 |
$ENV{HTTPS_PROXY_PASSWORD} = 'bar';
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
my @tests = (
|
|
Packit |
ec7ac3 |
# the expected ids are connid.reqid[tunnel_auth][req_auth]@sslhost
|
|
Packit |
ec7ac3 |
# because we run different sets of test depending on the SSL class
|
|
Packit |
ec7ac3 |
# used by Net::HTTPS we replace connid with a letter and later
|
|
Packit |
ec7ac3 |
# match it to a number
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# keep-alive for non-proxy http
|
|
Packit |
ec7ac3 |
# requests to same target use same connection, even if intermixed
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[0]/foo",'A.1@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[0]/bar",'A.2@nossl' ], # reuse conn#1
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[1]/foo",'B.1@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[1]/bar",'B.2@nossl' ], # reuse conn#2
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[0]/tor",'A.3@nossl' ], # reuse conn#1 again
|
|
Packit |
ec7ac3 |
[ 'noproxy', "http://$saddr[1]/tor",'B.3@nossl' ], # reuse conn#2 again
|
|
Packit |
ec7ac3 |
# keep-alive for proxy http
|
|
Packit |
ec7ac3 |
# use the same proxy connection for all even if the target host differs
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://foo/foo",'C.1.auth@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://foo/bar",'C.2.auth@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://bar/foo",'C.3.auth@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://bar/bar",'C.4.auth@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://foo/tor",'C.5.auth@nossl' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "http://bar/tor",'C.6.auth@nossl' ],
|
|
Packit |
ec7ac3 |
# keep-alive for non-proxy https
|
|
Packit |
ec7ac3 |
# requests to same target use same connection, even if intermixed
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[0]/foo",'D.1@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[0]/bar",'D.2@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[1]/foo",'E.1@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[1]/bar",'E.2@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[0]/tor",'D.3@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
[ 'noproxy', "https://$saddr[1]/tor",'E.3@direct.ssl.access' ],
|
|
Packit |
ec7ac3 |
# keep-alive for proxy https
|
|
Packit |
ec7ac3 |
! $netssl ? (
|
|
Packit |
ec7ac3 |
# note that we reuse proxy conn#C in first request. Although the last id
|
|
Packit |
ec7ac3 |
# from this conn was C.6 the new one is C.8, because request C.7 was the
|
|
Packit |
ec7ac3 |
# socket upgrade via CONNECT request
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/foo",'C.8.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/bar",'C.9.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
# if the target of the tunnel is different we need another connection
|
|
Packit |
ec7ac3 |
# note that it starts with F.2, because F.1 is the CONNECT request which
|
|
Packit |
ec7ac3 |
# established the tunnel
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/foo",'F.2.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/bar",'F.3.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/tor",'C.10.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/tor",'F.4.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
):(
|
|
Packit |
ec7ac3 |
# Net::SSL will cannot reuse socket for CONNECT, but once inside tunnel
|
|
Packit |
ec7ac3 |
# keep-alive is possible
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/foo",'G.2.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/bar",'G.3.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/foo",'F.2.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/bar",'F.3.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://foo/tor",'G.4.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy', "https://bar/tor",'F.4.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
),
|
|
Packit |
ec7ac3 |
# non-keep alive for proxy https
|
|
Packit |
ec7ac3 |
[ 'proxy_nokeepalive', "https://foo/foo",'H.2.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy_nokeepalive', "https://foo/bar",'I.2.Tauth@foo' ],
|
|
Packit |
ec7ac3 |
[ 'proxy_nokeepalive', "https://bar/foo",'J.2.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
[ 'proxy_nokeepalive', "https://bar/bar",'K.2.Tauth@bar' ],
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
plan tests => 2*@tests;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
my (%conn2id,%id2conn);
|
|
Packit |
ec7ac3 |
for my $test (@tests) {
|
|
Packit |
ec7ac3 |
my ($uatype,$url,$expect_id) = @$test;
|
|
Packit |
ec7ac3 |
my $ua = $ua{$uatype} or die "no such ua: $uatype";
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# Net::SSL uses only the environment to decide about proxy, so we need the
|
|
Packit |
ec7ac3 |
# proxy/non-proxy environment for each request
|
|
Packit |
ec7ac3 |
if ( $netssl && $url =~m{^https://} ) {
|
|
Packit |
ec7ac3 |
$ENV{https_proxy} = $uatype =~m{^proxy} ? "http://$saddr[0]":""
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
my $response = $ua->get($url) or die "no response";
|
|
Packit |
ec7ac3 |
if ( $response->is_success
|
|
Packit |
ec7ac3 |
and ( my $body = $response->content()) =~m{^ID: *(\d+)\.(\S+)}m ) {
|
|
Packit |
ec7ac3 |
my $id = [ $1,$2 ];
|
|
Packit |
ec7ac3 |
my $xid = [ $expect_id =~m{(\w+)\.(\S+)} ];
|
|
Packit |
ec7ac3 |
if ( my $x = $id2conn{$id->[0]} ) {
|
|
Packit |
ec7ac3 |
$id->[0] = $x;
|
|
Packit |
ec7ac3 |
} elsif ( ! $conn2id{$xid->[0]} ) {
|
|
Packit |
ec7ac3 |
$conn2id{ $xid->[0] } = $id->[0];
|
|
Packit |
ec7ac3 |
$id2conn{ $id->[0] } = $xid->[0];
|
|
Packit |
ec7ac3 |
$id->[0] = $xid->[0];
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
is("$id->[0].$id->[1]",$expect_id,"$uatype $url -> $expect_id")
|
|
Packit |
ec7ac3 |
or diag($response->as_string);
|
|
Packit |
ec7ac3 |
# inside proxy tunnel and for non-proxy there should be only absolute
|
|
Packit |
ec7ac3 |
# URI in request w/o scheme
|
|
Packit |
ec7ac3 |
my $expect_rqurl = $url;
|
|
Packit |
ec7ac3 |
$expect_rqurl =~s{^\w+://[^/]+}{}
|
|
Packit |
ec7ac3 |
if $uatype eq 'noproxy' or $url =~m{^https://};
|
|
Packit |
ec7ac3 |
my ($rqurl) = $body =~m{^GET (\S+) HTTP/}m;
|
|
Packit |
ec7ac3 |
is($rqurl,$expect_rqurl,"URL in request -> $expect_rqurl");
|
|
Packit |
ec7ac3 |
} else {
|
|
Packit |
ec7ac3 |
die "unexpected response: ".$response->as_string
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# ------------------------------------------------------------------------
|
|
Packit |
ec7ac3 |
# simple web server with keep alive and SSL, which can also simulate proxy
|
|
Packit |
ec7ac3 |
# ------------------------------------------------------------------------
|
|
Packit |
ec7ac3 |
sub _server {
|
|
Packit |
ec7ac3 |
my $connid = 0;
|
|
Packit |
ec7ac3 |
my %certs; # generated certificates
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
ACCEPT:
|
|
Packit |
ec7ac3 |
my ($server) = IO::Select->new(@server)->can_read();
|
|
Packit |
ec7ac3 |
my $cl = $server->accept or goto ACCEPT;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# peek into socket to determine if this is direct SSL or not
|
|
Packit |
ec7ac3 |
# minimal request is "GET / HTTP/1.1\n\n"
|
|
Packit |
ec7ac3 |
my $buf = '';
|
|
Packit |
ec7ac3 |
while (length($buf)<15) {
|
|
Packit |
ec7ac3 |
my $lbuf;
|
|
Packit |
ec7ac3 |
if ( ! IO::Select->new($cl)->can_read(30)
|
|
Packit |
ec7ac3 |
or ! defined recv($cl,$lbuf,20,MSG_PEEK)) {
|
|
Packit |
ec7ac3 |
warn "not enough data for request ($buf): $!";
|
|
Packit |
ec7ac3 |
goto ACCEPT;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$buf .= $lbuf;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
my $ssl_host = '';
|
|
Packit |
ec7ac3 |
if ( $buf !~m{\A[A-Z]{3,} } ) {
|
|
Packit |
ec7ac3 |
# does not look like HTTP, assume direct SSL
|
|
Packit |
ec7ac3 |
$ssl_host = "direct.ssl.access";
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
$connid++;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
defined( my $pid = fork()) or die "failed to fork: $!";
|
|
Packit |
ec7ac3 |
if ( $pid ) {
|
|
Packit |
ec7ac3 |
push @childs,$pid;
|
|
Packit |
ec7ac3 |
goto ACCEPT; # wait for next connection
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# child handles requests
|
|
Packit |
ec7ac3 |
@server = ();
|
|
Packit |
ec7ac3 |
my $reqid = 0;
|
|
Packit |
ec7ac3 |
my $tunnel_auth = '';
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
SSL_UPGRADE:
|
|
Packit |
ec7ac3 |
if ( $ssl_host ) {
|
|
Packit |
ec7ac3 |
my ($cert,$key) = @{
|
|
Packit |
ec7ac3 |
$certs{$ssl_host} ||= do {
|
|
Packit |
ec7ac3 |
diag("creating cert for $ssl_host");
|
|
Packit |
ec7ac3 |
my ($c,$k) = CERT_create(
|
|
Packit |
ec7ac3 |
subject => { commonName => $ssl_host },
|
|
Packit |
ec7ac3 |
issuer_cert => $cacert,
|
|
Packit |
ec7ac3 |
issuer_key => $cakey,
|
|
Packit |
ec7ac3 |
# just reuse cakey as key for certificate
|
|
Packit |
ec7ac3 |
key => $cakey,
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
[ $c,$k ];
|
|
Packit |
ec7ac3 |
};
|
|
Packit |
ec7ac3 |
};
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
IO::Socket::SSL->start_SSL( $cl,
|
|
Packit |
ec7ac3 |
SSL_server => 1,
|
|
Packit |
ec7ac3 |
SSL_cert => $cert,
|
|
Packit |
ec7ac3 |
SSL_key => $key,
|
|
Packit |
ec7ac3 |
) or do {
|
|
Packit |
ec7ac3 |
diag("SSL handshake failed: ".IO::Socket::SSL->errstr);
|
|
Packit |
ec7ac3 |
exit(1);
|
|
Packit |
ec7ac3 |
};
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
REQUEST:
|
|
Packit |
ec7ac3 |
# read header
|
|
Packit |
ec7ac3 |
my $req = '';
|
|
Packit |
ec7ac3 |
while (<$cl>) {
|
|
Packit |
ec7ac3 |
$_ eq "\r\n" and last;
|
|
Packit |
ec7ac3 |
$req .= $_;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$reqid++;
|
|
Packit |
ec7ac3 |
my $req_auth = $req =~m{^Proxy-Authorization:}mi ? '.auth':'';
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
if ( $req =~m{\ACONNECT ([^\s:]+)} ) {
|
|
Packit |
ec7ac3 |
if ( $ssl_host ) {
|
|
Packit |
ec7ac3 |
diag("CONNECT inside SSL tunnel");
|
|
Packit |
ec7ac3 |
exit(1);
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$ssl_host = $1;
|
|
Packit |
ec7ac3 |
$tunnel_auth = $req_auth ? '.Tauth':'';
|
|
Packit |
ec7ac3 |
#diag($req);
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# simulate proxy and establish SSL tunnel
|
|
Packit |
ec7ac3 |
print $cl "HTTP/1.0 200 ok\r\n\r\n";
|
|
Packit |
ec7ac3 |
goto SSL_UPGRADE;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
if ( $req =~m{^Content-length: *(\d+)}mi ) {
|
|
Packit |
ec7ac3 |
read($cl,my $buf,$1) or die "eof while reading request body";
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
my $keep_alive =
|
|
Packit |
ec7ac3 |
$req =~m{^(?:Proxy-)?Connection: *(?:(keep-alive)|close)}mi ? $1 :
|
|
Packit |
ec7ac3 |
$req =~m{\A.*HTTP/1\.1} ? 1 :
|
|
Packit |
ec7ac3 |
0;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# just echo request back, including connid and reqid
|
|
Packit |
ec7ac3 |
my $body = "ID: $connid.$reqid$tunnel_auth$req_auth\@"
|
|
Packit |
ec7ac3 |
. ( $ssl_host || 'nossl' )."\n"
|
|
Packit |
ec7ac3 |
. "---------\n$req";
|
|
Packit |
ec7ac3 |
print $cl "HTTP/1.1 200 ok\r\nContent-type: text/plain\r\n"
|
|
Packit |
ec7ac3 |
. "Connection: ".( $keep_alive ? 'keep-alive':'close' )."\r\n"
|
|
Packit |
ec7ac3 |
. "Content-length: ".length($body)."\r\n"
|
|
Packit |
ec7ac3 |
. "\r\n"
|
|
Packit |
ec7ac3 |
. $body;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
goto REQUEST if $keep_alive;
|
|
Packit |
ec7ac3 |
exit(0); # done handling requests
|
|
Packit |
ec7ac3 |
}
|