Hyperman

 view release on metacpan or  search on metacpan

t/72-tls-edges.t  view on Meta::CPAN

#!perl
use strict;
use warnings;
use lib 't/lib';
use Test::More;
use HMTest qw(free_ports quiet_child server_reap);
use Time::HiRes ();
use File::Temp ();
use Hyperman;

# The TLS edges: a certificate that does not match its key, a CA that cannot
# be loaded, SNI entries without a certificate or with a NUL in the host, a
# client whose ALPN list names nothing we speak, a detach on a TLS
# connection, a TLS write that fills the socket and one whose peer has gone,
# a stream handle told it is full, and the pre-0.57 writer pair closed and
# dropped by hand.

plan skip_all => 'OpenSSL support not built' unless Hyperman->has_tls;
plan skip_all => 'fork is not Windows' if $^O eq 'MSWin32';
my $openssl = `which openssl`; chomp $openssl;
plan skip_all => 'openssl CLI not found' unless $openssl;
plan skip_all => 'IO::Socket::SSL is not installed' unless eval { require IO::Socket::SSL; 1 };

my $dir = File::Temp::tempdir(CLEANUP => 1);
my $made = 1;
sub sh { $made &&= system("$_[0] >/dev/null 2>&1") == 0 }
sh(qq{openssl req -x509 -newkey rsa:2048 -nodes -keyout $dir/a.key -out $dir/a.pem -days 1 -subj "/CN=localhost"});
sh(qq{openssl req -x509 -newkey rsa:2048 -nodes -keyout $dir/b.key -out $dir/b.pem -days 1 -subj "/CN=other"});
plan skip_all => 'could not create the test certificates'
    unless $made && !grep { !-s "$dir/$_" } qw(a.pem a.key b.pem b.key);

my ($port) = free_ports(1);
plan skip_all => 'no free loopback port' unless $port;
sub slurp { my $f = shift; -f $f ? do { local (@ARGV, $/) = ($f); <> } // '' : '' }
sub wait_for { my ($file, $re) = @_; for (1 .. 50) { my $t = slurp($file); return $t if $t =~ $re; Time::HiRes::sleep(0.1) } return slurp($file) }

# A run() that must refuse, in a child with stderr captured, since the TLS
# layer explains itself there before the croak.
sub refused_in_child {
    my ($name, %opts) = @_;
    my $pid = fork // die "fork: $!";
    if (!$pid) {
        quiet_child(stderr => "$dir/$name.stderr");
        my $err = eval { Hyperman->run(app => sub { [ 200, [], [] ] }, host => '127.0.0.1', port => 0, workers => 1, %opts); 'none' } // $@;
        open my $fh, '>', "$dir/$name.err" or exit 1; print $fh $err; close $fh;
        exit 0;
    }
    server_reap($pid);
    return (slurp("$dir/$name.err"), slurp("$dir/$name.stderr"));
}

{
    my ($err, $log) = refused_in_child('mismatch', tls_cert => "$dir/a.pem", tls_key => "$dir/b.key");
    like($err, qr/TLS: could not initialise/, 'a certificate with the wrong key is refused');
    like($log, qr/cannot load private key/, 'and the reason is logged');
    ($err, $log) = refused_in_child('badca', tls_cert => "$dir/a.pem", tls_key => "$dir/a.key", tls_verify => 'require', tls_ca => "$dir/missing.pem");
    like($err, qr/TLS: could not initialise/, 'a CA that does not exist is refused');
    like($log, qr/cannot load CA/, 'and named');
}

my $result = "$dir/result";
my $app = sub {
    my $env = shift;
    my $p = $env->{PATH_INFO};
    if ($p eq '/big') { return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'b' x (8 * 1024 * 1024) ] ] }
    if ($p eq '/detach') {
        my $err = eval { Hyperman::detach($env); 'none' } // $@;
        return [ 200, [ 'Content-Type' => 'text/plain' ], [ $err ] ];
    }
    if ($p eq '/stream_full') {
        return sub {
            my $w = Hyperman::stream($env, 200, [ 'Content-Type' => 'text/plain' ]);
            my @rc;
            for (1 .. 64) { my $rc = $w->write('s' x (1024 * 1024)); push @rc, $rc; last if $rc != 1 }
            open my $fh, '>', $result or die; print $fh join ',', @rc; close $fh;
            $w->close;
        };
    }
    if ($p eq '/hand_close') {
        return sub {
            my $w = shift->([ 200, [ 'Content-Type' => 'text/plain' ] ]);
            my ($fd, $id) = @{ $env->{'psgix.hyperman.conn'} };
            my $h = bless [ $fd, $id ], 'Hyperman::Writer';
            $h->write('hand-closed');
            $h->close;
        };
    }
    if ($p eq '/hand_drop') {
        return sub {
            my $w = shift->([ 200, [ 'Content-Type' => 'text/plain' ] ]);
            my ($fd, $id) = @{ $env->{'psgix.hyperman.conn'} };
            { my $h = bless [ $fd, $id ], 'Hyperman::Writer'; $h->write('hand-dropped') }
        };
    }



( run in 1.263 second using v1.01-cache-2.11-cpan-145b269f2f3 )