From af65e06b3ba65952f1223e09b9df0736965bdaeb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Aug 2016 23:36:34 +0000 Subject: http: do not allow bad getline+close responses to kill us PSGI applications (like our WWW :P) can fail unpredictability, but lets try to avoid bringing the entire process down when this happens. --- t/httpd-corner.psgi | 12 ++++++++++++ t/httpd-corner.t | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) (limited to 't') diff --git a/t/httpd-corner.psgi b/t/httpd-corner.psgi index 222b9e01..ed1f92c0 100644 --- a/t/httpd-corner.psgi +++ b/t/httpd-corner.psgi @@ -60,6 +60,18 @@ my $app = sub { } } elsif ($path eq '/empty') { $code = 200; + } elsif ($path eq '/getline-die') { + $code = 200; + $body = Plack::Util::inline_object( + getline => sub { die 'GETLINE FAIL' }, + close => sub { die 'CLOSE FAIL' }, + ); + } elsif ($path eq '/close-die') { + $code = 200; + $body = Plack::Util::inline_object( + getline => sub { undef }, + close => sub { die 'CLOSE FAIL' }, + ); } [ $code, $h, $body ] diff --git a/t/httpd-corner.t b/t/httpd-corner.t index 5ecc69b5..1e8465c2 100644 --- a/t/httpd-corner.t +++ b/t/httpd-corner.t @@ -85,6 +85,30 @@ my $spawn_httpd = sub { is($body, "hello world\n", 'callback body matches expected'); } +{ + my $conn = conn_for($sock, 'getline-die'); + $conn->write("GET /getline-die HTTP/1.1\r\nHost: example.com\r\n\r\n"); + ok($conn->read(my $buf, 8192), 'read some response'); + like($buf, qr!HTTP/1\.1 200\b[^\r]*\r\n!, 'got some sort of header'); + is($conn->read(my $nil, 8192), 0, 'read EOF'); + $conn = undef; + my $after = capture($err); + is(scalar(grep(/GETLINE FAIL/, @$after)), 1, 'failure logged'); + is(scalar(grep(/CLOSE FAIL/, @$after)), 1, 'body->close not called'); +} + +{ + my $conn = conn_for($sock, 'close-die'); + $conn->write("GET /close-die HTTP/1.1\r\nHost: example.com\r\n\r\n"); + ok($conn->read(my $buf, 8192), 'read some response'); + like($buf, qr!HTTP/1\.1 200\b[^\r]*\r\n!, 'got some sort of header'); + is($conn->read(my $nil, 8192), 0, 'read EOF'); + $conn = undef; + my $after = capture($err); + is(scalar(grep(/GETLINE FAIL/, @$after)), 0, 'getline not failed'); + is(scalar(grep(/CLOSE FAIL/, @$after)), 1, 'body->close not called'); +} + { my $conn = conn_for($sock, 'excessive header'); $SIG{PIPE} = 'IGNORE'; @@ -489,4 +513,13 @@ SKIP: { done_testing(); +sub capture { + my ($f) = @_; + open my $fh, '+<', $f or die "failed to open $f: $!\n"; + local $/ = "\n"; + my @r = <$fh>; + truncate($fh, 0) or die "truncate failed on $f: $!\n"; + \@r +} + 1; -- cgit v1.2.3-24-ge0c7