| #!/usr/bin/perl | |
| # (C) Sergey Kandaurov | |
| # (C) Nginx, Inc. | |
| # Tests for HTTP/2 protocol with headers. | |
| # various HEADERS compression/encoding, see hpack() for mode details. | |
| ############################################################################### | |
| use warnings; | |
| use strict; | |
| use Test::More; | |
| use Config; | |
| BEGIN { use FindBin; chdir($FindBin::Bin); } | |
| use lib 'lib'; | |
| use Test::Nginx; | |
| use Test::Nginx::HTTP2; | |
| ############################################################################### | |
| select STDERR; $| = 1; | |
| select STDOUT; $| = 1; | |
| my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(93) | |
| ->write_file_expand('nginx.conf', <<'EOF'); | |
| %%TEST_GLOBALS%% | |
| daemon off; | |
| events { | |
| } | |
| http { | |
| %%TEST_GLOBALS_HTTP%% | |
| server { | |
| listen 127.0.0.1:8080 http2; | |
| listen 127.0.0.1:8081; | |
| listen 127.0.0.1:8082 http2 sndbuf=128; | |
| server_name localhost; | |
| http2_max_field_size 128k; | |
| http2_max_header_size 128k; | |
| location / { | |
| add_header X-Sent-Foo $http_x_foo; | |
| add_header X-Referer $http_referer; | |
| return 200; | |
| } | |
| location /frame_size { | |
| add_header X-LongHeader $arg_h; | |
| add_header X-LongHeader $arg_h; | |
| add_header X-LongHeader $arg_h; | |
| alias %%TESTDIR%%/t2.html; | |
| } | |
| location /continuation { | |
| add_header X-LongHeader $arg_h; | |
| add_header X-LongHeader $arg_h; | |
| add_header X-LongHeader $arg_h; | |
| return 200 body; | |
| location /continuation/204 { | |
| return 204; | |
| } | |
| } | |
| location /proxy/ { | |
| add_header X-UC-a $upstream_cookie_a; | |
| add_header X-UC-c $upstream_cookie_c; | |
| proxy_pass http://127.0.0.1:8083/; | |
| proxy_set_header X-Cookie-a $cookie_a; | |
| proxy_set_header X-Cookie-c $cookie_c; | |
| } | |
| location /proxy2/ { | |
| proxy_pass http://127.0.0.1:8081/; | |
| } | |
| location /set-cookie { | |
| add_header Set-Cookie a=b; | |
| add_header Set-Cookie c=d; | |
| return 200; | |
| } | |
| location /cookie { | |
| add_header X-Cookie $http_cookie; | |
| add_header X-Cookie-a $cookie_a; | |
| add_header X-Cookie-c $cookie_c; | |
| return 200; | |
| } | |
| } | |
| server { | |
| listen 127.0.0.1:8084 http2; | |
| server_name localhost; | |
| http2_max_field_size 22; | |
| } | |
| server { | |
| listen 127.0.0.1:8085 http2; | |
| server_name localhost; | |
| http2_max_header_size 64; | |
| } | |
| } | |
| EOF | |
| $t->run_daemon(\&http_daemon); | |
| $t->run()->waitforsocket('127.0.0.1:' . port(8083)); | |
| # file size is slightly beyond initial window size: 2**16 + 80 bytes | |
| $t->write_file('t1.html', | |
| join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202))); | |
| $t->write_file('t2.html', 'SEE-THIS'); | |
| ############################################################################### | |
| # 6.1. Indexed Header Field Representation | |
| my $s = Test::Nginx::HTTP2->new(); | |
| my $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }]}); | |
| my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'indexed header field'); | |
| # 6.2.1. Literal Header Field with Incremental Indexing | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 1, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 1, huff => 0 }, | |
| { name => ':path', value => '/', mode => 1, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal with indexing'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 1, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 1, huff => 1 }, | |
| { name => ':path', value => '/', mode => 1, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman'); | |
| # 6.2.1. Literal Header Field with Incremental Indexing -- New Name | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 2, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 2, huff => 0 }, | |
| { name => ':path', value => '/', mode => 2, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 2, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal with indexing - new'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 2, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 2, huff => 1 }, | |
| { name => ':path', value => '/', mode => 2, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 2, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman'); | |
| # 6.2.2. Literal Header Field without Indexing | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
| { name => ':path', value => '/', mode => 3, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 3, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal without indexing'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 3, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 3, huff => 1 }, | |
| { name => ':path', value => '/', mode => 3, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 3, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
| { name => ':path', value => '/', mode => 3, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 3, huff => 0 }, | |
| { name => 'referer', value => 'foo', mode => 3, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, | |
| 'literal without indexing - multibyte index'); | |
| is($frame->{headers}->{'x-referer'}, 'foo', | |
| 'literal without indexing - multibyte index value'); | |
| # 6.2.2. Literal Header Field without Indexing -- New Name | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 4, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 4, huff => 0 }, | |
| { name => ':path', value => '/', mode => 4, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 4, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal without indexing - new'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 4, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 4, huff => 1 }, | |
| { name => ':path', value => '/', mode => 4, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 4, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, | |
| 'literal without indexing - new huffman'); | |
| # 6.2.3. Literal Header Field Never Indexed | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
| { name => ':path', value => '/', mode => 5, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 5, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal never indexed'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 5, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 5, huff => 1 }, | |
| { name => ':path', value => '/', mode => 5, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 5, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
| { name => ':path', value => '/', mode => 5, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 5, huff => 0 }, | |
| { name => 'referer', value => 'foo', mode => 5, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, | |
| 'literal never indexed - multibyte index'); | |
| is($frame->{headers}->{'x-referer'}, 'foo', | |
| 'literal never indexed - multibyte index value'); | |
| # 6.2.3. Literal Header Field Never Indexed -- New Name | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 6, huff => 0 }, | |
| { name => ':scheme', value => 'http', mode => 6, huff => 0 }, | |
| { name => ':path', value => '/', mode => 6, huff => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 6, huff => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal never indexed - new'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 6, huff => 1 }, | |
| { name => ':scheme', value => 'http', mode => 6, huff => 1 }, | |
| { name => ':path', value => '/', mode => 6, huff => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 6, huff => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman'); | |
| # reuse literal with multibyte indexing | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'referer', value => 'foo', mode => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 0 }, | |
| { name => 'referer', value => 'foo', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new'); | |
| # reuse literal with multibyte indexing - reused name | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 0 }, | |
| { name => 'x-foo', value => 'X-Bar', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed'); | |
| # reuse literal with multibyte indexing - reused name only | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 0 }, | |
| { name => 'x-foo', value => 'X-Baz', mode => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', | |
| 'name with indexing - indexed name'); | |
| # response header field with characters not suitable for huffman encoding | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x-foo', value => '{{{{{', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars'); | |
| like($s->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding'); | |
| # response header field with huffman encoding | |
| # NB: implementation detail, not obligated | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x-foo', value => 'aaaaa', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars'); | |
| unlike($s->{headers}, qr/aaaaa/, 'well known chars - huffman encoding'); | |
| # response header field with huffman encoding - complete table mod \0, CR, LF | |
| # first saturate with short-encoded characters (NB: implementation detail) | |
| my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x-foo', value => $field, mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars'); | |
| unlike($s->{headers}, qr/abcde/, 'all chars - huffman encoding'); | |
| # 6.3. Dynamic Table Size Update | |
| # remove some indexed headers from the dynamic table | |
| # by maintaining dynamic table space only for index 0 | |
| # 'x-foo' has index 0, and 'referer' has index 1 | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'referer', value => 'foo', mode => 1 }, | |
| { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| $sid = $s->new_stream({ table_size => 61, headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| isnt($frame, undef, 'updated table size - remaining index'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'referer', value => 'foo', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame, undef, 'invalid index'); | |
| # 5.4.1. Connection Error Handling | |
| # An endpoint that encounters a connection error SHOULD first send a | |
| # GOAWAY frame <..> | |
| ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
| ok($frame, 'invalid index - GOAWAY'); | |
| # RFC 7541, 2.3.3. Index Address Space | |
| # Indices strictly greater than the sum of the lengths of both tables | |
| # MUST be treated as a decoding error. | |
| # 4.3. Header Compression and Decompression | |
| # A decoding error in a header block MUST be treated | |
| # as a connection error of type COMPRESSION_ERROR. | |
| is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream'); | |
| is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR'); | |
| # HPACK zero index | |
| # RFC 7541, 6.1 Indexed Header Field Representation | |
| # The index value of 0 is not used. It MUST be treated as a decoding | |
| # error if found in an indexed header field representation. | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => '', value => '', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ok($frame, 'zero index - GOAWAY'); | |
| is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR'); | |
| # invalid table size update | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ table_size => 4097, headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
| ok($frame, 'invalid table size - GOAWAY'); | |
| is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream'); | |
| is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR'); | |
| # request header field with multiple values | |
| # 8.1.2.5. Compressing the Cookie Header Field | |
| # To allow for better compression efficiency, the Cookie header field | |
| # MAY be split into separate header fields <..>. | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/cookie', mode => 2 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'cookie', value => 'a=b', mode => 2}, | |
| { name => 'cookie', value => 'c=d', mode => 2}]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-cookie-a'}, 'b', | |
| 'multiple request header fields - cookie'); | |
| is($frame->{headers}->{'x-cookie-c'}, 'd', | |
| 'multiple request header fields - cookie 2'); | |
| is($frame->{headers}->{'x-cookie'}, 'a=b; c=d', | |
| 'multiple request header fields - semi-colon'); | |
| # request header field with multiple values to HTTP backend | |
| # 8.1.2.5. Compressing the Cookie Header Field | |
| # these MUST be concatenated into a single octet string | |
| # using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") | |
| # before being passed into a non-HTTP/2 context, such as an HTTP/1.1 | |
| # connection <..> | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/proxy/cookie', mode => 2 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'cookie', value => 'a=b', mode => 2 }, | |
| { name => 'cookie', value => 'c=d', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d', | |
| 'multiple request header fields proxied - semi-colon'); | |
| is($frame->{headers}->{'x-sent-cookie2'}, '', | |
| 'multiple request header fields proxied - dublicate cookie'); | |
| is($frame->{headers}->{'x-sent-cookie-a'}, 'b', | |
| 'multiple request header fields proxied - cookie 1'); | |
| is($frame->{headers}->{'x-sent-cookie-c'}, 'd', | |
| 'multiple request header fields proxied - cookie 2'); | |
| # response header field with multiple values | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ path => '/set-cookie' }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
| 'multiple response header fields - cookie'); | |
| is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
| 'multiple response header fields - cookie 2'); | |
| # response header field with multiple values from HTTP backend | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ path => '/proxy/set-cookie' }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
| 'multiple response header proxied - cookie'); | |
| is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
| 'multiple response header proxied - cookie 2'); | |
| is($frame->{headers}->{'x-uc-a'}, 'b', | |
| 'multiple response header proxied - upstream cookie'); | |
| is($frame->{headers}->{'x-uc-c'}, 'd', | |
| 'multiple response header proxied - upstream cookie 2'); | |
| # CONTINUATION in response | |
| # put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2) | |
| # to break header block into separate frames, one such field per frame | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**13 }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]); | |
| my @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
| my $data = $data[-1]; | |
| is(@{$data->{headers}{'x-longheader'}}, 3, | |
| 'response CONTINUATION - headers'); | |
| is($data->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
| 'response CONTINUATION - header 1'); | |
| is($data->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
| 'response CONTINUATION - header 2'); | |
| is($data->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
| 'response CONTINUATION - header 3'); | |
| @data = sort { $a <=> $b } map { $_->{length} } @data; | |
| cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size'); | |
| # same but without response DATA frames | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ path => '/continuation/204?h=' . 'x' x 2**13 }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]); | |
| @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
| $data = $data[-1]; | |
| is(@{$data->{headers}{'x-longheader'}}, 3, | |
| 'no body CONTINUATION - headers'); | |
| is($data->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
| 'no body CONTINUATION - header 1'); | |
| is($data->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
| 'no body CONTINUATION - header 2'); | |
| is($data->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
| 'no body CONTINUATION - header 3'); | |
| @data = sort { $a <=> $b } map { $_->{length} } @data; | |
| cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size'); | |
| # response header block is always split by SETTINGS_MAX_FRAME_SIZE | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]); | |
| @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
| @data = sort { $a <=> $b } map { $_->{length} } @data; | |
| cmp_ok($data[-1], '<=', 2**14, 'response header frames limited'); | |
| # response header frame sent in parts | |
| $s = Test::Nginx::HTTP2->new(port(8082)); | |
| $s->h2_settings(0, 0x5 => 2**17); | |
| $sid = $s->new_stream({ path => '/frame_size?h=' . 'x' x 2**15 }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| ok($frame, 'response header - parts'); | |
| SKIP: { | |
| skip 'response header failed', 1 unless $frame; | |
| skip 'broken sendfile', 1 if $^O eq 'freebsd' and | |
| $Config{osvers} =~ '11.0-release' and | |
| $t->read_file('nginx.conf') =~ /sendfile on/; | |
| is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304, | |
| 'response header - headers'); | |
| } | |
| # response header block split and sent in parts | |
| $s = Test::Nginx::HTTP2->new(port(8082)); | |
| $sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]); | |
| @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
| ok(@data, 'response header split'); | |
| SKIP: { | |
| skip 'response header split failed', 2 unless @data; | |
| my ($lengths) = sort { $b <=> $a } map { $_->{length} } @data; | |
| cmp_ok($lengths, '<=', 16384, 'response header split - max size'); | |
| is(length join('', @{$data[-1]->{headers}->{'x-longheader'}}), 98304, | |
| 'response header split - headers'); | |
| } | |
| # max_field_size - header field name | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'field name size less'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'field name size second'); | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'field name size equal'); | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| is($frame, undef, 'field name size greater'); | |
| # max_field_size - header field value | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'field value size less'); | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'field value size equal'); | |
| $s = Test::Nginx::HTTP2->new(port(8084)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| is($frame, undef, 'field value size greater'); | |
| # max_header_size | |
| $s = Test::Nginx::HTTP2->new(port(8085)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'x', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'header size less'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'x', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'header size second'); | |
| $s = Test::Nginx::HTTP2->new(port(8085)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'xx', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'header size equal'); | |
| $s = Test::Nginx::HTTP2->new(port(8085)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'xxx', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| is($frame, undef, 'header size greater'); | |
| # header size is based on (decompressed) header list | |
| # two extra 1-byte indices would otherwise fit in max_header_size | |
| $s = Test::Nginx::HTTP2->new(port(8085)); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'x', mode => 2 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'header size new index'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'x', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
| ok($frame, 'header size indexed'); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/t2.html', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'longname9', value => 'x', mode => 0 }, | |
| { name => 'longname9', value => 'x', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; | |
| is($frame->{code}, 0xb, 'header size indexed greater'); | |
| # HPACK table boundary | |
| $s = Test::Nginx::HTTP2->new(); | |
| $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]); | |
| $frames = $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| ok($frame, 'HPACK table boundary'); | |
| $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]); | |
| $frames = $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| ok($frame, 'HPACK table boundary - header field name'); | |
| $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]); | |
| $frames = $s->read(all => [{ sid => $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }, | |
| { name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| ok($frame, 'HPACK table boundary - header field value'); | |
| # ensure that request header field value with newline doesn't get split | |
| # | |
| # 10.3. Intermediary Encapsulation Attacks | |
| # Any request or response that contains a character not permitted | |
| # in a header field value MUST be treated as malformed. | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/proxy2/', mode => 1 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]}); | |
| $frames = $s->read(all => [{ type => 'RST_STREAM' }]); | |
| # 10.3. Intermediary Encapsulation Attacks | |
| # An intermediary therefore cannot translate an HTTP/2 request or response | |
| # containing an invalid field name into an HTTP/1.1 message. | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header'); | |
| # 8.1.2.6. Malformed Requests and Responses | |
| # Malformed requests or responses that are detected MUST be treated | |
| # as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. | |
| ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
| is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid'); | |
| is($frame->{length}, 4, 'newline in request header - RST_STREAM length'); | |
| is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags'); | |
| is($frame->{code}, 1, 'newline in request header - RST_STREAM code'); | |
| # invalid header name as seen with underscore should not lead to ignoring rest | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }, | |
| { name => 'x_foo', value => "x-bar", mode => 2 }, | |
| { name => 'referer', value => "see-this", mode => 1 }]}); | |
| $frames = $s->read(all => [{ type => 'HEADERS' }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name'); | |
| # missing mandatory request header ':scheme' | |
| TODO: { | |
| local $TODO = 'not yet' unless $t->has_version('1.13.2'); | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => 'localhost', mode => 1 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 400, 'incomplete headers'); | |
| } | |
| # empty request header ':authority' | |
| $s = Test::Nginx::HTTP2->new(); | |
| $sid = $s->new_stream({ headers => [ | |
| { name => ':method', value => 'GET', mode => 0 }, | |
| { name => ':scheme', value => 'http', mode => 0 }, | |
| { name => ':path', value => '/', mode => 0 }, | |
| { name => ':authority', value => '', mode => 0 }]}); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 400, 'empty authority'); | |
| # client sent invalid :path header | |
| TODO: { | |
| local $TODO = 'not yet' unless $t->has_version('1.13.9'); | |
| $sid = $s->new_stream({ path => 't1.html' }); | |
| $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
| ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
| is($frame->{headers}->{':status'}, 400, 'invalid path'); | |
| } | |
| ############################################################################### | |
| sub http_daemon { | |
| my $server = IO::Socket::INET->new( | |
| Proto => 'tcp', | |
| LocalHost => '127.0.0.1', | |
| LocalPort => port(8083), | |
| Listen => 5, | |
| Reuse => 1 | |
| ) | |
| or die "Can't create listening socket: $!\n"; | |
| local $SIG{PIPE} = 'IGNORE'; | |
| while (my $client = $server->accept()) { | |
| $client->autoflush(1); | |
| my $headers = ''; | |
| my $uri = ''; | |
| while (<$client>) { | |
| $headers .= $_; | |
| last if (/^\x0d?\x0a?$/); | |
| } | |
| next if $headers eq ''; | |
| $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; | |
| if ($uri eq '/cookie') { | |
| my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig; | |
| $cookie2 = '' unless defined $cookie2; | |
| my ($cookie_a, $cookie_c) = ('', ''); | |
| $cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i; | |
| $cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i; | |
| print $client <<EOF; | |
| HTTP/1.1 200 OK | |
| Connection: close | |
| X-Sent-Cookie: $cookie | |
| X-Sent-Cookie2: $cookie2 | |
| X-Sent-Cookie-a: $cookie_a | |
| X-Sent-Cookie-c: $cookie_c | |
| EOF | |
| } elsif ($uri eq '/set-cookie') { | |
| print $client <<EOF; | |
| HTTP/1.1 200 OK | |
| Connection: close | |
| Set-Cookie: a=b | |
| Set-Cookie: c=d | |
| EOF | |
| } | |
| } continue { | |
| close $client; | |
| } | |
| } | |
| ############################################################################### |