root/trunk/t/52-chunked-upload.t @ 766

Revision 766, 5.2 kB (checked in by bradfitz, 21 months ago)

SECURITY: patch from Jeremey James <jbj@…> to not crash
on zero byte chunked upload when buffered uploads are enabled.

Line 
1#!/usr/bin/perl
2
3use strict;
4use Perlbal::Test;
5use Perlbal::Test::WebServer;
6use Perlbal::Test::WebClient;
7use IO::Socket::INET;
8use Test::More 'no_plan';
9
10# setup webserver
11my $web_port = start_webserver();
12ok($web_port, 'webserver started');
13
14# setup perlbal
15my $port = new_port();
16my $dir = tempdir();
17
18my $conf = qq{
19SERVER aio_mode = none
20
21CREATE POOL a
22POOL a ADD 127.0.0.1:$web_port
23
24CREATE SERVICE test
25SET role = reverse_proxy
26SET pool = a
27SET connect_ahead = 0
28SET listen = 127.0.0.1:$port
29SET persist_client = 1
30SET buffer_uploads_path = $dir
31SET buffer_uploads = 1
32ENABLE test
33};
34
35my $msock = start_server($conf);
36ok($msock, 'perlbal started');
37
38ok(! buffer_file_exists(), "no files in buffer directory");
39
40# setup data
41my $data = 'x' x 1_000_000;
42my ($curpos, $clen) = (0, 0);
43
44my $req;
45
46# disable all of it
47request("buffer_off", 500_000,
48        "write:500",
49        "write:5",
50        "write:5",
51        "write:5",
52        "sleep:0.25",
53        "exists",
54        "write:100000",
55        "write:60000",
56        "write:1000",
57        "finish",
58        sub {
59            my ($res) = @_;
60            my $cont = $res->content;
61            like($cont, qr/length = 500000/, "backend got right content-length");
62        },
63        "empty");
64
65sub buffer_file_exists {
66    opendir DIR, $dir
67        or die "can't open dir\n";
68    foreach (readdir(DIR)) {
69        next if /^\./;
70        return 1;
71    }
72    return 0;
73}
74
75# cmds can be:
76#    write:<length>     writes <length> bytes
77#    sleep:<duration>   sleeps <duration> seconds, may be fractional
78#    finish             (sends any final writes and/or reads response)
79#    close              close socket
80#    sub {}             coderef to run.  gets passed response object
81#    no-reason          response has no reason
82#    reason:<reason>    did buffering for either "size", "rate", or "time"
83#    empty              No files in temp buffer location
84#    exists             Yes, a temporary file exists
85sub request {
86    my $testname = shift;
87    my $len = shift || 0;
88    my @cmds = @_;
89
90    my $curpos = 0;
91    my $remain = $len;
92
93    my $hdr = "POST /status HTTP/1.0\r\nTransfer-Encoding: chunked\r\nExpect: 100-continue\r\n\r\n";
94    my $sock = IO::Socket::INET->new( PeerAddr => "127.0.0.1:$port" )
95        or return undef;
96    my $rv = syswrite($sock, $hdr);
97    die unless $rv == length($hdr);
98
99    # wanting HTTP/1.1 100 Continue\r\n...\r\n lines
100    {
101        my $contline = <$sock>;
102        die "didn't get 100 Continue line, got: $contline"
103            unless $contline =~ m!^HTTP/1.1 100!;
104        my $gotempty = 0;
105        while (defined(my $line = <$sock>)) {
106            if ($line eq "\r\n") {
107                $gotempty = 1;
108                last;
109            }
110        }
111        die "didn't get empty line after 100 Continue" unless $gotempty;
112    }
113
114    my $res = undef;  # no response yet
115
116    foreach my $cmd (@cmds) {
117        my $writelen;
118
119        if ($cmd =~ /^write:([\d_]+)/) {
120            $writelen = $1;
121            $writelen =~ s/_//g;
122        } elsif ($cmd =~ /^(\d+)/) {
123            $writelen = $1;
124        } elsif ($cmd eq "finish") {
125            $writelen = $remain;
126        }
127
128        if ($cmd =~ /^sleep:([\d\.]+)/) {
129            select undef, undef, undef, $1;
130            next;
131        }
132
133        if ($cmd eq "close") {
134            close($sock);
135            next;
136        }
137
138        if ($cmd eq "exists") {
139            ok(buffer_file_exists(), "$testname: buffer file exists");
140            next;
141        }
142
143        if ($cmd eq "empty") {
144            ok(! buffer_file_exists(), "$testname: no file");
145            next;
146        }
147
148        if ($writelen) {
149            die "Too long" if $writelen > $remain;
150            my $buf = "x" x $writelen;
151            $buf = sprintf("%x\r\n", $writelen) . $buf . "\r\n";
152            $remain -= $writelen;
153            if ($remain == 0) {
154                # one \r\n for chunk ending, one for chunked-body ending,
155                # after (our empty) trailer...
156                $buf .= "0\r\n\r\n";
157            }
158            my $bufsize = length($buf);
159            my $off = 0;
160            while ($off < $bufsize) {
161                my $rv = syswrite($sock, $buf, $bufsize-$off, $off);
162                die "Error writing: $!" unless defined $rv;
163                die "Got rv=0 from syswrite" unless $rv;
164                $off += $rv;
165            }
166
167            next unless $cmd eq "finish";
168        }
169
170        if ($cmd eq "finish") {
171            $res = resp_from_sock($sock);
172            my $clen = $res ? $res->header('Content-Length') : 0;
173            ok($res && length($res->content) == $clen, "$testname: good response");
174            next;
175        }
176
177        if (ref $cmd eq "CODE") {
178            $cmd->($res, $testname);
179            next;
180        }
181
182        die "Invalid command: $cmd\n";
183    }
184}
185
186# Try a 0 length chunked request, as it used to crash server
187{
188    my $hdr = "POST /status HTTP/1.0\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n";
189    my $sock = IO::Socket::INET->new( PeerAddr => "127.0.0.1:$port" )
190        or return undef;
191    my $rv = syswrite($sock, $hdr);
192    die unless $rv == length($hdr);
193
194    # Give it time to crash
195    select undef, undef, undef, 1.0;
196
197    my $sock2 = IO::Socket::INET->new( PeerAddr => "127.0.0.1:$port" );
198    ok ($sock2, 'Server still alive');
199}
200
2011;
Note: See TracBrowser for help on using the browser.