Mercurial > hg > nginx-tests
comparison h2.t @ 876:a6abbfed42c0
Tests: split HTTP/2 tests, HTTP2 package introduced.
author | Andrey Zelenkov <zelenkov@nginx.com> |
---|---|
date | Wed, 23 Mar 2016 17:23:08 +0300 |
parents | f693b0aea20f |
children | b06beed07bc8 |
comparison
equal
deleted
inserted
replaced
875:c380b4b7e2e4 | 876:a6abbfed42c0 |
---|---|
10 use warnings; | 10 use warnings; |
11 use strict; | 11 use strict; |
12 | 12 |
13 use Test::More; | 13 use Test::More; |
14 | 14 |
15 use IO::Select; | |
16 use Socket qw/ CRLF /; | 15 use Socket qw/ CRLF /; |
17 | 16 |
18 BEGIN { use FindBin; chdir($FindBin::Bin); } | 17 BEGIN { use FindBin; chdir($FindBin::Bin); } |
19 | 18 |
20 use lib 'lib'; | 19 use lib 'lib'; |
21 use Test::Nginx; | 20 use Test::Nginx; |
21 use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /; | |
22 | 22 |
23 ############################################################################### | 23 ############################################################################### |
24 | 24 |
25 select STDERR; $| = 1; | 25 select STDERR; $| = 1; |
26 select STDOUT; $| = 1; | 26 select STDOUT; $| = 1; |
27 | 27 |
28 eval { require IO::Socket::SSL; }; | 28 my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(137); |
29 plan(skip_all => 'IO::Socket::SSL not installed') if $@; | |
30 eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; | |
31 plan(skip_all => 'IO::Socket::SSL too old') if $@; | |
32 | |
33 my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 proxy cache/) | |
34 ->has(qw/limit_conn rewrite realip shmem/) | |
35 ->has_daemon('openssl')->plan(319); | |
36 | 29 |
37 # Some systems may have also a bug in not treating zero writev iovcnt as EINVAL | 30 # Some systems may have also a bug in not treating zero writev iovcnt as EINVAL |
38 | 31 |
39 $t->todo_alerts(); | 32 $t->todo_alerts(); |
40 | 33 |
47 events { | 40 events { |
48 } | 41 } |
49 | 42 |
50 http { | 43 http { |
51 %%TEST_GLOBALS_HTTP%% | 44 %%TEST_GLOBALS_HTTP%% |
52 | |
53 proxy_cache_path %%TESTDIR%%/cache keys_zone=NAME:1m; | |
54 limit_conn_zone $binary_remote_addr zone=conn:1m; | |
55 limit_req_zone $binary_remote_addr zone=req:1m rate=1r/s; | |
56 | 45 |
57 server { | 46 server { |
58 listen 127.0.0.1:8080 http2; | 47 listen 127.0.0.1:8080 http2; |
59 listen 127.0.0.1:8081; | 48 listen 127.0.0.1:8081; |
60 listen 127.0.0.1:8082 proxy_protocol http2; | |
61 listen 127.0.0.1:8084 http2 ssl; | |
62 listen 127.0.0.1:8092 http2 sndbuf=128; | |
63 listen 127.0.0.1:8094 ssl; | |
64 server_name localhost; | 49 server_name localhost; |
65 | |
66 ssl_certificate_key localhost.key; | |
67 ssl_certificate localhost.crt; | |
68 http2_max_field_size 128k; | |
69 http2_max_header_size 128k; | |
70 | 50 |
71 location / { | 51 location / { |
72 add_header X-Header X-Foo; | 52 add_header X-Header X-Foo; |
73 add_header X-Sent-Foo $http_x_foo; | 53 add_header X-Sent-Foo $http_x_foo; |
74 add_header X-Referer $http_referer; | 54 add_header X-Referer $http_referer; |
75 return 200 'body'; | 55 return 200 'body'; |
76 } | 56 } |
77 location /t { | 57 location /t { |
78 } | 58 } |
79 location /t3.html { | |
80 limit_conn conn 1; | |
81 } | |
82 location /gzip.html { | 59 location /gzip.html { |
83 gzip on; | 60 gzip on; |
84 gzip_min_length 0; | 61 gzip_min_length 0; |
85 gzip_vary on; | 62 gzip_vary on; |
86 alias %%TESTDIR%%/t2.html; | 63 alias %%TESTDIR%%/t2.html; |
87 } | 64 } |
88 location /frame_size { | 65 location /frame_size { |
89 add_header X-LongHeader $arg_h; | |
90 add_header X-LongHeader $arg_h; | |
91 add_header X-LongHeader $arg_h; | |
92 http2_chunk_size 64k; | 66 http2_chunk_size 64k; |
93 alias %%TESTDIR%%/t1.html; | 67 alias %%TESTDIR%%/t1.html; |
94 output_buffers 2 1m; | 68 output_buffers 2 1m; |
95 } | |
96 location /continuation { | |
97 add_header X-LongHeader $arg_h; | |
98 add_header X-LongHeader $arg_h; | |
99 add_header X-LongHeader $arg_h; | |
100 return 200 body; | |
101 | |
102 location /continuation/204 { | |
103 return 204; | |
104 } | |
105 } | |
106 location /pp { | |
107 set_real_ip_from 127.0.0.1/32; | |
108 real_ip_header proxy_protocol; | |
109 alias %%TESTDIR%%/t2.html; | |
110 add_header X-PP $remote_addr; | |
111 } | |
112 location /h2 { | |
113 return 200 $http2; | |
114 } | |
115 location /sp { | |
116 return 200 $server_protocol; | |
117 } | |
118 location /scheme { | |
119 return 200 $scheme; | |
120 } | |
121 location /https { | |
122 return 200 $https; | |
123 } | 69 } |
124 location /chunk_size { | 70 location /chunk_size { |
125 http2_chunk_size 1; | 71 http2_chunk_size 1; |
126 return 200 'body'; | 72 return 200 'body'; |
127 } | 73 } |
136 return 301 text; | 82 return 301 text; |
137 } | 83 } |
138 location /return301_relative { | 84 location /return301_relative { |
139 return 301 /; | 85 return 301 /; |
140 } | 86 } |
141 location /proxy/ { | |
142 add_header X-UC-a $upstream_cookie_a; | |
143 add_header X-UC-c $upstream_cookie_c; | |
144 proxy_pass http://127.0.0.1:8083/; | |
145 proxy_set_header X-Cookie-a $cookie_a; | |
146 proxy_set_header X-Cookie-c $cookie_c; | |
147 } | |
148 location /proxy2/ { | |
149 add_header X-Body "$request_body"; | |
150 add_header X-Body-File $request_body_file; | |
151 client_body_in_file_only on; | |
152 proxy_pass http://127.0.0.1:8081/; | |
153 } | |
154 location /proxy_ssl/ { | |
155 proxy_pass https://127.0.0.1:8094/; | |
156 } | |
157 location /limit_req { | |
158 limit_req zone=req burst=2; | |
159 alias %%TESTDIR%%/t2.html; | |
160 } | |
161 location /proxy_limit_req/ { | |
162 add_header X-Body $request_body; | |
163 add_header X-Body-File $request_body_file; | |
164 client_body_in_file_only on; | |
165 proxy_pass http://127.0.0.1:8081/; | |
166 limit_req zone=req burst=2; | |
167 } | |
168 location /cache/ { | |
169 proxy_pass http://127.0.0.1:8081/; | |
170 proxy_cache NAME; | |
171 proxy_cache_valid 1m; | |
172 } | |
173 location /proxy_buffering_off { | |
174 proxy_pass http://127.0.0.1:8081/; | |
175 proxy_cache NAME; | |
176 proxy_cache_valid 1m; | |
177 proxy_buffering off; | |
178 } | |
179 location /client_max_body_size { | |
180 add_header X-Body $request_body; | |
181 add_header X-Body-File $request_body_file; | |
182 client_body_in_single_buffer on; | |
183 client_body_in_file_only on; | |
184 proxy_pass http://127.0.0.1:8081/; | |
185 client_max_body_size 10; | |
186 } | |
187 location /set-cookie { | |
188 add_header Set-Cookie a=b; | |
189 add_header Set-Cookie c=d; | |
190 return 200; | |
191 } | |
192 location /cookie { | |
193 add_header X-Cookie $http_cookie; | |
194 add_header X-Cookie-a $cookie_a; | |
195 add_header X-Cookie-c $cookie_c; | |
196 return 200; | |
197 } | |
198 location /charset { | 87 location /charset { |
199 charset utf-8; | 88 charset utf-8; |
200 return 200; | 89 return 200; |
201 } | 90 } |
202 } | 91 } |
219 | 108 |
220 http2_max_concurrent_streams 1; | 109 http2_max_concurrent_streams 1; |
221 } | 110 } |
222 | 111 |
223 server { | 112 server { |
224 listen 127.0.0.1:8087 http2; | |
225 server_name localhost; | |
226 | |
227 http2_max_field_size 22; | |
228 } | |
229 | |
230 server { | |
231 listen 127.0.0.1:8088 http2; | |
232 server_name localhost; | |
233 | |
234 http2_max_header_size 64; | |
235 } | |
236 | |
237 server { | |
238 listen 127.0.0.1:8089 http2; | 113 listen 127.0.0.1:8089 http2; |
239 server_name localhost; | 114 server_name localhost; |
240 | 115 |
241 http2_recv_timeout 1s; | 116 http2_recv_timeout 1s; |
242 client_header_timeout 1s; | 117 client_header_timeout 1s; |
249 | 124 |
250 http2_idle_timeout 1s; | 125 http2_idle_timeout 1s; |
251 client_body_timeout 1s; | 126 client_body_timeout 1s; |
252 | 127 |
253 location /proxy2/ { | 128 location /proxy2/ { |
254 add_header X-Body "$request_body"; | 129 add_header X-Body $request_body; |
255 proxy_pass http://127.0.0.1:8081/; | 130 proxy_pass http://127.0.0.1:8081/; |
256 } | 131 } |
257 } | 132 } |
258 | 133 |
259 server { | 134 server { |
276 } | 151 } |
277 } | 152 } |
278 | 153 |
279 EOF | 154 EOF |
280 | 155 |
281 $t->write_file('openssl.conf', <<EOF); | |
282 [ req ] | |
283 default_bits = 2048 | |
284 encrypt_key = no | |
285 distinguished_name = req_distinguished_name | |
286 [ req_distinguished_name ] | |
287 EOF | |
288 | |
289 my $d = $t->testdir(); | |
290 | |
291 foreach my $name ('localhost') { | |
292 system('openssl req -x509 -new ' | |
293 . "-config '$d/openssl.conf' -subj '/CN=$name/' " | |
294 . "-out '$d/$name.crt' -keyout '$d/$name.key' " | |
295 . ">>$d/openssl.out 2>&1") == 0 | |
296 or die "Can't create certificate for $name: $!\n"; | |
297 } | |
298 | |
299 $t->run_daemon(\&http_daemon); | |
300 | |
301 open OLDERR, ">&", \*STDERR; close STDERR; | |
302 $t->run(); | 156 $t->run(); |
303 open STDERR, ">&", \*OLDERR; | |
304 | |
305 $t->waitforsocket('127.0.0.1:8083'); | |
306 | 157 |
307 # file size is slightly beyond initial window size: 2**16 + 80 bytes | 158 # file size is slightly beyond initial window size: 2**16 + 80 bytes |
308 | 159 |
309 $t->write_file('t1.html', | 160 $t->write_file('t1.html', |
310 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202))); | 161 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202))); |
311 $t->write_file('tbig.html', | 162 $t->write_file('tbig.html', |
312 join('', map { sprintf "XX%06dXX", $_ } (1 .. 500000))); | 163 join('', map { sprintf "XX%06dXX", $_ } (1 .. 500000))); |
313 | 164 |
314 $t->write_file('t2.html', 'SEE-THIS'); | 165 $t->write_file('t2.html', 'SEE-THIS'); |
315 $t->write_file('t3.html', 'SEE-THIS'); | |
316 $t->write_file('t4.html', 'SEE-THIS'); | |
317 | |
318 my %cframe = ( | |
319 0 => { name => 'DATA', value => \&data }, | |
320 1 => { name => 'HEADERS', value => \&headers }, | |
321 # 2 => { name => 'PRIORITY', value => \&priority }, | |
322 3 => { name => 'RST_STREAM', value => \&rst_stream }, | |
323 4 => { name => 'SETTINGS', value => \&settings }, | |
324 # 5 => { name => 'PUSH_PROMISE', value => \&push_promise }, | |
325 6 => { name => 'PING', value => \&ping }, | |
326 7 => { name => 'GOAWAY', value => \&goaway }, | |
327 8 => { name => 'WINDOW_UPDATE', value => \&window_update }, | |
328 9 => { name => 'CONTINUATION', value => \&headers }, | |
329 ); | |
330 | 166 |
331 ############################################################################### | 167 ############################################################################### |
332 | 168 |
333 # Upgrade mechanism | 169 # Upgrade mechanism |
334 | 170 |
494 ok($frame, 'DATA frame 2'); | 330 ok($frame, 'DATA frame 2'); |
495 is($frame->{sid}, $sid, 'HEADERS stream 2'); | 331 is($frame->{sid}, $sid, 'HEADERS stream 2'); |
496 is($frame->{length}, length 'body', 'DATA length 2'); | 332 is($frame->{length}, length 'body', 'DATA length 2'); |
497 is($frame->{data}, 'body', 'DATA payload 2'); | 333 is($frame->{data}, 'body', 'DATA payload 2'); |
498 | 334 |
499 # various HEADERS compression/encoding, see hpack() for mode details | |
500 | |
501 # 6.1. Indexed Header Field Representation | |
502 | |
503 $sess = new_session(); | |
504 $sid = new_stream($sess, { headers => [ | |
505 { name => ':method', value => 'GET', mode => 0 }, | |
506 { name => ':scheme', value => 'http', mode => 0 }, | |
507 { name => ':path', value => '/', mode => 0 }, | |
508 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
509 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
510 | |
511 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
512 is($frame->{headers}->{':status'}, 200, 'indexed header field'); | |
513 | |
514 # 6.2.1. Literal Header Field with Incremental Indexing | |
515 | |
516 $sess = new_session(); | |
517 $sid = new_stream($sess, { headers => [ | |
518 { name => ':method', value => 'GET', mode => 1, huff => 0 }, | |
519 { name => ':scheme', value => 'http', mode => 1, huff => 0 }, | |
520 { name => ':path', value => '/', mode => 1, huff => 0 }, | |
521 { name => ':authority', value => 'localhost', mode => 1, huff => 0 }]}); | |
522 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
523 | |
524 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
525 is($frame->{headers}->{':status'}, 200, 'literal with indexing'); | |
526 | |
527 $sess = new_session(); | |
528 $sid = new_stream($sess, { headers => [ | |
529 { name => ':method', value => 'GET', mode => 1, huff => 1 }, | |
530 { name => ':scheme', value => 'http', mode => 1, huff => 1 }, | |
531 { name => ':path', value => '/', mode => 1, huff => 1 }, | |
532 { name => ':authority', value => 'localhost', mode => 1, huff => 1 }]}); | |
533 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
534 | |
535 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
536 is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman'); | |
537 | |
538 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name | |
539 | |
540 $sess = new_session(); | |
541 $sid = new_stream($sess, { headers => [ | |
542 { name => ':method', value => 'GET', mode => 2, huff => 0 }, | |
543 { name => ':scheme', value => 'http', mode => 2, huff => 0 }, | |
544 { name => ':path', value => '/', mode => 2, huff => 0 }, | |
545 { name => ':authority', value => 'localhost', mode => 2, huff => 0 }]}); | |
546 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
547 | |
548 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
549 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new'); | |
550 | |
551 $sess = new_session(); | |
552 $sid = new_stream($sess, { headers => [ | |
553 { name => ':method', value => 'GET', mode => 2, huff => 1 }, | |
554 { name => ':scheme', value => 'http', mode => 2, huff => 1 }, | |
555 { name => ':path', value => '/', mode => 2, huff => 1 }, | |
556 { name => ':authority', value => 'localhost', mode => 2, huff => 1 }]}); | |
557 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
558 | |
559 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
560 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman'); | |
561 | |
562 # 6.2.2. Literal Header Field without Indexing | |
563 | |
564 $sess = new_session(); | |
565 $sid = new_stream($sess, { headers => [ | |
566 { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
567 { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
568 { name => ':path', value => '/', mode => 3, huff => 0 }, | |
569 { name => ':authority', value => 'localhost', mode => 3, huff => 0 }]}); | |
570 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
571 | |
572 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
573 is($frame->{headers}->{':status'}, 200, 'literal without indexing'); | |
574 | |
575 $sess = new_session(); | |
576 $sid = new_stream($sess, { headers => [ | |
577 { name => ':method', value => 'GET', mode => 3, huff => 1 }, | |
578 { name => ':scheme', value => 'http', mode => 3, huff => 1 }, | |
579 { name => ':path', value => '/', mode => 3, huff => 1 }, | |
580 { name => ':authority', value => 'localhost', mode => 3, huff => 1 }]}); | |
581 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
582 | |
583 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
584 is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman'); | |
585 | |
586 $sess = new_session(); | |
587 $sid = new_stream($sess, { headers => [ | |
588 { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
589 { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
590 { name => ':path', value => '/', mode => 3, huff => 0 }, | |
591 { name => ':authority', value => 'localhost', mode => 3, huff => 0 }, | |
592 { name => 'referer', value => 'foo', mode => 3, huff => 0 }]}); | |
593 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
594 | |
595 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
596 is($frame->{headers}->{':status'}, 200, | |
597 'literal without indexing - multibyte index'); | |
598 is($frame->{headers}->{'x-referer'}, 'foo', | |
599 'literal without indexing - multibyte index value'); | |
600 | |
601 # 6.2.2. Literal Header Field without Indexing -- New Name | |
602 | |
603 $sess = new_session(); | |
604 $sid = new_stream($sess, { headers => [ | |
605 { name => ':method', value => 'GET', mode => 4, huff => 0 }, | |
606 { name => ':scheme', value => 'http', mode => 4, huff => 0 }, | |
607 { name => ':path', value => '/', mode => 4, huff => 0 }, | |
608 { name => ':authority', value => 'localhost', mode => 4, huff => 0 }]}); | |
609 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
610 | |
611 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
612 is($frame->{headers}->{':status'}, 200, 'literal without indexing - new'); | |
613 | |
614 $sess = new_session(); | |
615 $sid = new_stream($sess, { headers => [ | |
616 { name => ':method', value => 'GET', mode => 4, huff => 1 }, | |
617 { name => ':scheme', value => 'http', mode => 4, huff => 1 }, | |
618 { name => ':path', value => '/', mode => 4, huff => 1 }, | |
619 { name => ':authority', value => 'localhost', mode => 4, huff => 1 }]}); | |
620 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
621 | |
622 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
623 is($frame->{headers}->{':status'}, 200, | |
624 'literal without indexing - new huffman'); | |
625 | |
626 # 6.2.3. Literal Header Field Never Indexed | |
627 | |
628 $sess = new_session(); | |
629 $sid = new_stream($sess, { headers => [ | |
630 { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
631 { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
632 { name => ':path', value => '/', mode => 5, huff => 0 }, | |
633 { name => ':authority', value => 'localhost', mode => 5, huff => 0 }]}); | |
634 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
635 | |
636 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
637 is($frame->{headers}->{':status'}, 200, 'literal never indexed'); | |
638 | |
639 $sess = new_session(); | |
640 $sid = new_stream($sess, { headers => [ | |
641 { name => ':method', value => 'GET', mode => 5, huff => 1 }, | |
642 { name => ':scheme', value => 'http', mode => 5, huff => 1 }, | |
643 { name => ':path', value => '/', mode => 5, huff => 1 }, | |
644 { name => ':authority', value => 'localhost', mode => 5, huff => 1 }]}); | |
645 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
646 | |
647 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
648 is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman'); | |
649 | |
650 $sess = new_session(); | |
651 $sid = new_stream($sess, { headers => [ | |
652 { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
653 { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
654 { name => ':path', value => '/', mode => 5, huff => 0 }, | |
655 { name => ':authority', value => 'localhost', mode => 5, huff => 0 }, | |
656 { name => 'referer', value => 'foo', mode => 5, huff => 0 }]}); | |
657 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
658 | |
659 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
660 is($frame->{headers}->{':status'}, 200, | |
661 'literal never indexed - multibyte index'); | |
662 is($frame->{headers}->{'x-referer'}, 'foo', | |
663 'literal never indexed - multibyte index value'); | |
664 | |
665 # 6.2.3. Literal Header Field Never Indexed -- New Name | |
666 | |
667 $sess = new_session(); | |
668 $sid = new_stream($sess, { headers => [ | |
669 { name => ':method', value => 'GET', mode => 6, huff => 0 }, | |
670 { name => ':scheme', value => 'http', mode => 6, huff => 0 }, | |
671 { name => ':path', value => '/', mode => 6, huff => 0 }, | |
672 { name => ':authority', value => 'localhost', mode => 6, huff => 0 }]}); | |
673 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
674 | |
675 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
676 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new'); | |
677 | |
678 $sess = new_session(); | |
679 $sid = new_stream($sess, { headers => [ | |
680 { name => ':method', value => 'GET', mode => 6, huff => 1 }, | |
681 { name => ':scheme', value => 'http', mode => 6, huff => 1 }, | |
682 { name => ':path', value => '/', mode => 6, huff => 1 }, | |
683 { name => ':authority', value => 'localhost', mode => 6, huff => 1 }]}); | |
684 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
685 | |
686 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
687 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman'); | |
688 | |
689 # reuse literal with multibyte indexing | |
690 | |
691 $sess = new_session(); | |
692 $sid = new_stream($sess, { headers => [ | |
693 { name => ':method', value => 'GET', mode => 0 }, | |
694 { name => ':scheme', value => 'http', mode => 0 }, | |
695 { name => ':path', value => '/', mode => 0 }, | |
696 { name => ':authority', value => 'localhost', mode => 1 }, | |
697 { name => 'referer', value => 'foo', mode => 1 }]}); | |
698 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
699 | |
700 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
701 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new'); | |
702 | |
703 $sid = new_stream($sess, { headers => [ | |
704 { name => ':method', value => 'GET', mode => 0 }, | |
705 { name => ':scheme', value => 'http', mode => 0 }, | |
706 { name => ':path', value => '/', mode => 0 }, | |
707 { name => ':authority', value => 'localhost', mode => 0 }, | |
708 { name => 'referer', value => 'foo', mode => 0 }]}); | |
709 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
710 | |
711 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
712 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed'); | |
713 | |
714 $sess = new_session(); | |
715 $sid = new_stream($sess, { headers => [ | |
716 { name => ':method', value => 'GET', mode => 0 }, | |
717 { name => ':scheme', value => 'http', mode => 0 }, | |
718 { name => ':path', value => '/', mode => 0 }, | |
719 { name => ':authority', value => 'localhost', mode => 1 }, | |
720 { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
721 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
722 | |
723 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
724 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new'); | |
725 | |
726 # reuse literal with multibyte indexing - reused name | |
727 | |
728 $sid = new_stream($sess, { headers => [ | |
729 { name => ':method', value => 'GET', mode => 0 }, | |
730 { name => ':scheme', value => 'http', mode => 0 }, | |
731 { name => ':path', value => '/', mode => 0 }, | |
732 { name => ':authority', value => 'localhost', mode => 0 }, | |
733 { name => 'x-foo', value => 'X-Bar', mode => 0 }]}); | |
734 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
735 | |
736 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
737 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed'); | |
738 | |
739 # reuse literal with multibyte indexing - reused name only | |
740 | |
741 $sid = new_stream($sess, { headers => [ | |
742 { name => ':method', value => 'GET', mode => 0 }, | |
743 { name => ':scheme', value => 'http', mode => 0 }, | |
744 { name => ':path', value => '/', mode => 0 }, | |
745 { name => ':authority', value => 'localhost', mode => 0 }, | |
746 { name => 'x-foo', value => 'X-Baz', mode => 1 }]}); | |
747 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
748 | |
749 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
750 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', | |
751 'name with indexing - indexed name'); | |
752 | |
753 # response header field with characters not suitable for huffman encoding | |
754 | |
755 $sess = new_session(); | |
756 $sid = new_stream($sess, { headers => [ | |
757 { name => ':method', value => 'GET', mode => 0 }, | |
758 { name => ':scheme', value => 'http', mode => 0 }, | |
759 { name => ':path', value => '/', mode => 0 }, | |
760 { name => ':authority', value => 'localhost', mode => 1 }, | |
761 { name => 'x-foo', value => '{{{{{', mode => 2 }]}); | |
762 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
763 | |
764 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
765 is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars'); | |
766 like($sess->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding'); | |
767 | |
768 # response header field with huffman encoding | |
769 # NB: implementation detail, not obligated | |
770 | |
771 $sess = new_session(); | |
772 $sid = new_stream($sess, { headers => [ | |
773 { name => ':method', value => 'GET', mode => 0 }, | |
774 { name => ':scheme', value => 'http', mode => 0 }, | |
775 { name => ':path', value => '/', mode => 0 }, | |
776 { name => ':authority', value => 'localhost', mode => 1 }, | |
777 { name => 'x-foo', value => 'aaaaa', mode => 2 }]}); | |
778 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
779 | |
780 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
781 is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars'); | |
782 | |
783 TODO: { | |
784 local $TODO = 'not yet' unless $t->has_version('1.9.12'); | |
785 | |
786 unlike($sess->{headers}, qr/aaaaa/, 'well known chars - huffman encoding'); | |
787 | |
788 } | |
789 | |
790 # response header field with huffman encoding - complete table mod \0, CR, LF | |
791 # first saturate with short-encoded characters (NB: implementation detail) | |
792 | |
793 my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255); | |
794 | |
795 $sess = new_session(); | |
796 $sid = new_stream($sess, { headers => [ | |
797 { name => ':method', value => 'GET', mode => 0 }, | |
798 { name => ':scheme', value => 'http', mode => 0 }, | |
799 { name => ':path', value => '/', mode => 0 }, | |
800 { name => ':authority', value => 'localhost', mode => 1 }, | |
801 { name => 'x-foo', value => $field, mode => 2 }]}); | |
802 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
803 | |
804 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
805 is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars'); | |
806 | |
807 TODO: { | |
808 local $TODO = 'not yet' unless $t->has_version('1.9.12'); | |
809 | |
810 unlike($sess->{headers}, qr/abcde/, 'all chars - huffman encoding'); | |
811 | |
812 } | |
813 | |
814 # 6.3. Dynamic Table Size Update | |
815 | |
816 # remove some indexed headers from the dynamic table | |
817 # by maintaining dynamic table space only for index 0 | |
818 # 'x-foo' has index 0, and 'referer' has index 1 | |
819 | |
820 $sess = new_session(); | |
821 $sid = new_stream($sess, { headers => [ | |
822 { name => ':method', value => 'GET', mode => 0 }, | |
823 { name => ':scheme', value => 'http', mode => 0 }, | |
824 { name => ':path', value => '/', mode => 0 }, | |
825 { name => ':authority', value => 'localhost', mode => 1 }, | |
826 { name => 'referer', value => 'foo', mode => 1 }, | |
827 { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
828 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
829 | |
830 $sid = new_stream($sess, { table_size => 61, headers => [ | |
831 { name => ':method', value => 'GET', mode => 0 }, | |
832 { name => ':scheme', value => 'http', mode => 0 }, | |
833 { name => ':path', value => '/', mode => 0 }, | |
834 { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
835 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
836 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
837 | |
838 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
839 isnt($frame, undef, 'updated table size - remaining index'); | |
840 | |
841 $sid = new_stream($sess, { headers => [ | |
842 { name => ':method', value => 'GET', mode => 0 }, | |
843 { name => ':scheme', value => 'http', mode => 0 }, | |
844 { name => ':path', value => '/', mode => 0 }, | |
845 { name => ':authority', value => 'localhost', mode => 1 }, | |
846 { name => 'referer', value => 'foo', mode => 0 }]}); | |
847 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
848 | |
849 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
850 is($frame, undef, 'invalid index'); | |
851 | |
852 # 5.4.1. Connection Error Handling | |
853 # An endpoint that encounters a connection error SHOULD first send a | |
854 # GOAWAY frame <..> | |
855 | |
856 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
857 ok($frame, 'invalid index - GOAWAY'); | |
858 | |
859 # RFC 7541, 2.3.3. Index Address Space | |
860 # Indices strictly greater than the sum of the lengths of both tables | |
861 # MUST be treated as a decoding error. | |
862 | |
863 # 4.3. Header Compression and Decompression | |
864 # A decoding error in a header block MUST be treated | |
865 # as a connection error of type COMPRESSION_ERROR. | |
866 | |
867 is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream'); | |
868 is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR'); | |
869 | |
870 # HPACK zero index | |
871 | |
872 # RFC 7541, 6.1 Indexed Header Field Representation | |
873 # The index value of 0 is not used. It MUST be treated as a decoding | |
874 # error if found in an indexed header field representation. | |
875 | |
876 $sess = new_session(); | |
877 $sid = new_stream($sess, { headers => [ | |
878 { name => ':method', value => 'GET', mode => 0 }, | |
879 { name => ':scheme', value => 'http', mode => 0 }, | |
880 { name => ':path', value => '/', mode => 0 }, | |
881 { name => ':authority', value => 'localhost', mode => 1 }, | |
882 { name => '', value => '', mode => 0 }]}); | |
883 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
884 | |
885 ok($frame, 'zero index - GOAWAY'); | |
886 is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR'); | |
887 | |
888 # invalid table size update | |
889 | |
890 $sess = new_session(); | |
891 $sid = new_stream($sess, { table_size => 4097, headers => [ | |
892 { name => ':method', value => 'GET', mode => 0 }, | |
893 { name => ':scheme', value => 'http', mode => 0 }, | |
894 { name => ':path', value => '/', mode => 0 }, | |
895 { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
896 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
897 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
898 | |
899 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
900 ok($frame, 'invalid table size - GOAWAY'); | |
901 is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream'); | |
902 is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR'); | |
903 | |
904 # HEAD | 335 # HEAD |
905 | 336 |
906 $sess = new_session(); | 337 $sess = new_session(); |
907 $sid = new_stream($sess, { method => 'HEAD' }); | 338 $sid = new_stream($sess, { method => 'HEAD' }); |
908 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | 339 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); |
913 is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEAD - HEADERS header'); | 344 is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEAD - HEADERS header'); |
914 | 345 |
915 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | 346 ($frame) = grep { $_->{type} eq "DATA" } @$frames; |
916 is($frame, undef, 'HEAD - no body'); | 347 is($frame, undef, 'HEAD - no body'); |
917 | 348 |
918 # GET with PROXY protocol | |
919 | |
920 my $proxy = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF; | |
921 $sess = new_session(8082, proxy => $proxy); | |
922 $sid = new_stream($sess, { path => '/pp' }); | |
923 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
924 | |
925 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
926 ok($frame, 'PROXY HEADERS frame'); | |
927 is($frame->{headers}->{'x-pp'}, '192.0.2.1', 'PROXY remote addr'); | |
928 | |
929 # range filter | 349 # range filter |
930 | 350 |
931 $sess = new_session(); | 351 $sess = new_session(); |
932 $sid = new_stream($sess, { headers => [ | 352 $sid = new_stream($sess, { headers => [ |
933 { name => ':method', value => 'GET', mode => 0 }, | 353 { name => ':method', value => 'GET', mode => 0 }, |
941 is($frame->{headers}->{':status'}, 206, 'range - HEADERS status'); | 361 is($frame->{headers}->{':status'}, 206, 'range - HEADERS status'); |
942 | 362 |
943 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | 363 ($frame) = grep { $_->{type} eq "DATA" } @$frames; |
944 is($frame->{length}, 10, 'range - DATA length'); | 364 is($frame->{length}, 10, 'range - DATA length'); |
945 is($frame->{data}, '002XXXX000', 'range - DATA payload'); | 365 is($frame->{data}, '002XXXX000', 'range - DATA payload'); |
946 | |
947 # $http2 | |
948 | |
949 $sess = new_session(); | |
950 $sid = new_stream($sess, { path => '/h2' }); | |
951 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
952 | |
953 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
954 is($frame->{data}, 'h2c', 'http variable - h2c'); | |
955 | |
956 # SSL/TLS connection, NPN | |
957 | |
958 SKIP: { | |
959 eval { IO::Socket::SSL->can_npn() or die; }; | |
960 skip 'OpenSSL NPN support required', 1 if $@; | |
961 | |
962 $sess = new_session(8084, SSL => 1, npn => 'h2'); | |
963 $sid = new_stream($sess, { path => '/h2' }); | |
964 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
965 | |
966 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
967 is($frame->{data}, 'h2', 'http variable - npn'); | |
968 | |
969 } | |
970 | |
971 # SSL/TLS connection, ALPN | |
972 | |
973 SKIP: { | |
974 eval { IO::Socket::SSL->can_alpn() or die; }; | |
975 skip 'OpenSSL ALPN support required', 1 if $@; | |
976 | |
977 $sess = new_session(8084, SSL => 1, alpn => 'h2'); | |
978 $sid = new_stream($sess, { path => '/h2' }); | |
979 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
980 | |
981 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
982 is($frame->{data}, 'h2', 'http variable - alpn'); | |
983 | |
984 } | |
985 | |
986 # $server_protocol | |
987 | |
988 $sess = new_session(); | |
989 $sid = new_stream($sess, { path => '/sp' }); | |
990 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
991 | |
992 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
993 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable'); | |
994 | |
995 # $server_protocol - SSL/TLS connection, NPN | |
996 | |
997 SKIP: { | |
998 eval { IO::Socket::SSL->can_npn() or die; }; | |
999 skip 'OpenSSL NPN support required', 1 if $@; | |
1000 | |
1001 $sess = new_session(8084, SSL => 1, npn => 'h2'); | |
1002 $sid = new_stream($sess, { path => '/sp' }); | |
1003 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1004 | |
1005 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1006 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - npn'); | |
1007 | |
1008 } | |
1009 | |
1010 # $server_protocol - SSL/TLS connection, ALPN | |
1011 | |
1012 SKIP: { | |
1013 eval { IO::Socket::SSL->can_alpn() or die; }; | |
1014 skip 'OpenSSL ALPN support required', 1 if $@; | |
1015 | |
1016 $sess = new_session(8084, SSL => 1, alpn => 'h2'); | |
1017 $sid = new_stream($sess, { path => '/sp' }); | |
1018 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1019 | |
1020 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1021 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - alpn'); | |
1022 | |
1023 } | |
1024 | |
1025 # $scheme | |
1026 | |
1027 $sess = new_session(); | |
1028 $sid = new_stream($sess, { path => '/scheme' }); | |
1029 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1030 | |
1031 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1032 is($frame->{data}, 'http', 'scheme variable'); | |
1033 | |
1034 # $scheme - SSL/TLS connection, NPN | |
1035 | |
1036 SKIP: { | |
1037 eval { IO::Socket::SSL->can_npn() or die; }; | |
1038 skip 'OpenSSL NPN support required', 1 if $@; | |
1039 | |
1040 $sess = new_session(8084, SSL => 1, npn => 'h2'); | |
1041 $sid = new_stream($sess, { path => '/scheme' }); | |
1042 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1043 | |
1044 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1045 is($frame->{data}, 'https', 'scheme variable - npn'); | |
1046 | |
1047 } | |
1048 | |
1049 # $scheme - SSL/TLS connection, ALPN | |
1050 | |
1051 SKIP: { | |
1052 eval { IO::Socket::SSL->can_alpn() or die; }; | |
1053 skip 'OpenSSL ALPN support required', 1 if $@; | |
1054 | |
1055 $sess = new_session(8084, SSL => 1, alpn => 'h2'); | |
1056 $sid = new_stream($sess, { path => '/scheme' }); | |
1057 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1058 | |
1059 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1060 is($frame->{data}, 'https', 'scheme variable - alpn'); | |
1061 | |
1062 } | |
1063 | |
1064 # $https | |
1065 | |
1066 $sess = new_session(); | |
1067 $sid = new_stream($sess, { path => '/https' }); | |
1068 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1069 | |
1070 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1071 is($frame->{data}, '', 'https variable'); | |
1072 | |
1073 # $https - SSL/TLS connection, NPN | |
1074 | |
1075 SKIP: { | |
1076 eval { IO::Socket::SSL->can_npn() or die; }; | |
1077 skip 'OpenSSL NPN support required', 1 if $@; | |
1078 | |
1079 $sess = new_session(8084, SSL => 1, npn => 'h2'); | |
1080 $sid = new_stream($sess, { path => '/https' }); | |
1081 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1082 | |
1083 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1084 is($frame->{data}, 'on', 'https variable - npn'); | |
1085 | |
1086 } | |
1087 | |
1088 # $https - SSL/TLS connection, ALPN | |
1089 | |
1090 SKIP: { | |
1091 eval { IO::Socket::SSL->can_alpn() or die; }; | |
1092 skip 'OpenSSL ALPN support required', 1 if $@; | |
1093 | |
1094 $sess = new_session(8084, SSL => 1, alpn => 'h2'); | |
1095 $sid = new_stream($sess, { path => '/https' }); | |
1096 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1097 | |
1098 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1099 is($frame->{data}, 'on', 'https variable - alpn'); | |
1100 | |
1101 } | |
1102 | 366 |
1103 # http2_chunk_size=1 | 367 # http2_chunk_size=1 |
1104 | 368 |
1105 $sess = new_session(); | 369 $sess = new_session(); |
1106 $sid = new_stream($sess, { path => '/chunk_size' }); | 370 $sid = new_stream($sess, { path => '/chunk_size' }); |
1195 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | 459 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; |
1196 is($frame->{headers}->{':status'}, 200, 'padding - CONTINUATION'); | 460 is($frame->{headers}->{':status'}, 200, 'padding - CONTINUATION'); |
1197 | 461 |
1198 } | 462 } |
1199 | 463 |
1200 # request header field with multiple values | |
1201 | |
1202 # 8.1.2.5. Compressing the Cookie Header Field | |
1203 # To allow for better compression efficiency, the Cookie header field | |
1204 # MAY be split into separate header fields <..>. | |
1205 | |
1206 $sess = new_session(); | |
1207 $sid = new_stream($sess, { headers => [ | |
1208 { name => ':method', value => 'GET', mode => 0 }, | |
1209 { name => ':scheme', value => 'http', mode => 0 }, | |
1210 { name => ':path', value => '/cookie', mode => 2 }, | |
1211 { name => ':authority', value => 'localhost', mode => 1 }, | |
1212 { name => 'cookie', value => 'a=b', mode => 2}, | |
1213 { name => 'cookie', value => 'c=d', mode => 2}]}); | |
1214 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1215 | |
1216 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1217 is($frame->{headers}->{'x-cookie-a'}, 'b', | |
1218 'multiple request header fields - cookie'); | |
1219 is($frame->{headers}->{'x-cookie-c'}, 'd', | |
1220 'multiple request header fields - cookie 2'); | |
1221 is($frame->{headers}->{'x-cookie'}, 'a=b; c=d', | |
1222 'multiple request header fields - semi-colon'); | |
1223 | |
1224 # request header field with multiple values to HTTP backend | |
1225 | |
1226 # 8.1.2.5. Compressing the Cookie Header Field | |
1227 # these MUST be concatenated into a single octet string | |
1228 # using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") | |
1229 # before being passed into a non-HTTP/2 context, such as an HTTP/1.1 | |
1230 # connection <..> | |
1231 | |
1232 $sess = new_session(); | |
1233 $sid = new_stream($sess, { headers => [ | |
1234 { name => ':method', value => 'GET', mode => 0 }, | |
1235 { name => ':scheme', value => 'http', mode => 0 }, | |
1236 { name => ':path', value => '/proxy/cookie', mode => 2 }, | |
1237 { name => ':authority', value => 'localhost', mode => 1 }, | |
1238 { name => 'cookie', value => 'a=b', mode => 2 }, | |
1239 { name => 'cookie', value => 'c=d', mode => 2 }]}); | |
1240 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1241 | |
1242 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1243 is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d', | |
1244 'multiple request header fields proxied - semi-colon'); | |
1245 is($frame->{headers}->{'x-sent-cookie2'}, '', | |
1246 'multiple request header fields proxied - dublicate cookie'); | |
1247 is($frame->{headers}->{'x-sent-cookie-a'}, 'b', | |
1248 'multiple request header fields proxied - cookie 1'); | |
1249 is($frame->{headers}->{'x-sent-cookie-c'}, 'd', | |
1250 'multiple request header fields proxied - cookie 2'); | |
1251 | |
1252 # response header field with multiple values | |
1253 | |
1254 $sess = new_session(); | |
1255 $sid = new_stream($sess, { path => '/set-cookie' }); | |
1256 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1257 | |
1258 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1259 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
1260 'multiple response header fields - cookie'); | |
1261 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
1262 'multiple response header fields - cookie 2'); | |
1263 | |
1264 # response header field with multiple values from HTTP backend | |
1265 | |
1266 $sess = new_session(); | |
1267 $sid = new_stream($sess, { path => '/proxy/set-cookie' }); | |
1268 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1269 | |
1270 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1271 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
1272 'multiple response header proxied - cookie'); | |
1273 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
1274 'multiple response header proxied - cookie 2'); | |
1275 is($frame->{headers}->{'x-uc-a'}, 'b', | |
1276 'multiple response header proxied - upstream cookie'); | |
1277 is($frame->{headers}->{'x-uc-c'}, 'd', | |
1278 'multiple response header proxied - upstream cookie 2'); | |
1279 | |
1280 # internal redirect | 464 # internal redirect |
1281 | 465 |
1282 $sess = new_session(); | 466 $sess = new_session(); |
1283 $sid = new_stream($sess, { path => '/redirect' }); | 467 $sid = new_stream($sess, { path => '/redirect' }); |
1284 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | 468 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); |
1429 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | 613 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); |
1430 | 614 |
1431 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | 615 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; |
1432 is($frame->{headers}->{'content-type'}, 'text/plain; charset=utf-8', 'charset'); | 616 is($frame->{headers}->{'content-type'}, 'text/plain; charset=utf-8', 'charset'); |
1433 | 617 |
1434 # simple proxy cache test | |
1435 | |
1436 $sess = new_session(); | |
1437 $sid = new_stream($sess, { path => '/cache/t4.html' }); | |
1438 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1439 | |
1440 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1441 is($frame->{headers}->{':status'}, '200', 'proxy cache'); | |
1442 | |
1443 my $etag = $frame->{headers}->{'etag'}; | |
1444 | |
1445 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
1446 is($frame->{length}, length 'SEE-THIS', 'proxy cache - DATA'); | |
1447 is($frame->{data}, 'SEE-THIS', 'proxy cache - DATA payload'); | |
1448 | |
1449 $t->write_file('t4.html', 'NOOP'); | |
1450 | |
1451 $sid = new_stream($sess, { headers => [ | |
1452 { name => ':method', value => 'GET', mode => 0 }, | |
1453 { name => ':scheme', value => 'http', mode => 0 }, | |
1454 { name => ':path', value => '/cache/t4.html' }, | |
1455 { name => ':authority', value => 'localhost', mode => 1 }, | |
1456 { name => 'if-none-match', value => $etag }]}); | |
1457 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1458 | |
1459 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1460 is($frame->{headers}->{':status'}, 304, 'proxy cache conditional'); | |
1461 | |
1462 # HEADERS could be received with fin, followed by DATA | |
1463 | |
1464 $sess = new_session(); | |
1465 $sid = new_stream($sess, { path => '/cache/t2.html?1', method => 'HEAD' }); | |
1466 | |
1467 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1468 push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])}; | |
1469 ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body'); | |
1470 | |
1471 # proxy cache - expect no stray empty DATA frame | |
1472 | |
1473 TODO: { | |
1474 local $TODO = 'not yet'; | |
1475 | |
1476 $sess = new_session(); | |
1477 $sid = new_stream($sess, { path => '/cache/t2.html?2' }); | |
1478 | |
1479 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1480 @data = grep ({ $_->{type} eq "DATA" } @$frames); | |
1481 is(@data, 1, 'proxy cache write - data frames'); | |
1482 is(join(' ', map { $_->{data} } @data), 'SEE-THIS', 'proxy cache write - data'); | |
1483 is(join(' ', map { $_->{flags} } @data), '1', 'proxy cache write - flags'); | |
1484 | |
1485 } | |
1486 | |
1487 # HEAD on empty cache with proxy_buffering off | |
1488 | |
1489 $sess = new_session(); | |
1490 $sid = new_stream($sess, | |
1491 { path => '/proxy_buffering_off/t2.html?1', method => 'HEAD' }); | |
1492 | |
1493 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1494 push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])}; | |
1495 ok(!grep ({ $_->{type} eq "DATA" } @$frames), | |
1496 'proxy cache HEAD buffering off - no body'); | |
1497 | |
1498 # request body (uses proxied response) | |
1499 | |
1500 $sess = new_session(); | |
1501 $sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 }); | |
1502 h2_body($sess, 'TEST'); | |
1503 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1504 | |
1505 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1506 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', 'request body'); | |
1507 | |
1508 # request body with padding (uses proxied response) | |
1509 | |
1510 $sess = new_session(); | |
1511 $sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 }); | |
1512 h2_body($sess, 'TEST', { body_padding => 42 }); | |
1513 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1514 | |
1515 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1516 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', | |
1517 'request body with padding'); | |
1518 | |
1519 $sid = new_stream($sess); | |
1520 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1521 | |
1522 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1523 is($frame->{headers}->{':status'}, '200', 'request body with padding - next'); | |
1524 | |
1525 # request body sent in multiple DATA frames in a single packet | |
1526 | |
1527 $sess = new_session(); | |
1528 $sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 }); | |
1529 h2_body($sess, 'TEST', { body_split => [2] }); | |
1530 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1531 | |
1532 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1533 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', | |
1534 'request body in multiple frames'); | |
1535 | |
1536 # request body sent in multiple DATA frames, each in its own packet | |
1537 | |
1538 $sess = new_session(); | |
1539 $sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 }); | |
1540 h2_body($sess, 'TEST', { body_more => 1 }); | |
1541 select undef, undef, undef, 0.1; | |
1542 h2_body($sess, 'MOREDATA'); | |
1543 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1544 | |
1545 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1546 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTMOREDATA', | |
1547 'request body in multiple frames separately'); | |
1548 | |
1549 # request body with an empty DATA frame | |
1550 # "zero size buf in output" alerts seen | |
1551 | |
1552 $sess = new_session(); | |
1553 $sid = new_stream($sess, { path => '/proxy2/', body_more => 1 }); | |
1554 h2_body($sess, ''); | |
1555 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1556 | |
1557 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1558 is($frame->{headers}->{':status'}, 200, 'request body - empty'); | |
1559 | |
1560 TODO: { | |
1561 local $TODO = 'not yet'; | |
1562 | |
1563 ok($frame->{headers}{'x-body-file'}, 'request body - empty body file'); | |
1564 | |
1565 } | |
1566 | |
1567 TODO: { | |
1568 todo_skip 'empty body file', 1 unless $frame->{headers}{'x-body-file'}; | |
1569 | |
1570 is(read_body_file($frame->{headers}{'x-body-file'}), '', | |
1571 'request body - empty content'); | |
1572 | |
1573 } | |
1574 | |
1575 # same as above but proxied to ssl backend | |
1576 | |
1577 TODO: { | |
1578 local $TODO = 'not yet'; | |
1579 | |
1580 $sess = new_session(); | |
1581 $sid = new_stream($sess, { path => '/proxy_ssl/', body_more => 1 }); | |
1582 h2_body($sess, ''); | |
1583 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1584 | |
1585 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1586 is($frame->{headers}->{':status'}, 200, 'request body - empty - proxy ssl'); | |
1587 | |
1588 } | |
1589 | |
1590 # request body delayed in limit_req | |
1591 | |
1592 $sess = new_session(); | |
1593 $sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 }); | |
1594 h2_body($sess, 'TEST'); | |
1595 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1596 | |
1597 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1598 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', | |
1599 'request body - limit req'); | |
1600 | |
1601 # request body delayed in limit_req - with an empty DATA frame | |
1602 | |
1603 $sess = new_session(); | |
1604 $sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 }); | |
1605 h2_body($sess, ''); | |
1606 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1607 | |
1608 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1609 is($frame->{headers}->{':status'}, 200, 'request body - limit req - empty'); | |
1610 | |
1611 # predict send windows | |
1612 | |
1613 $sid = new_stream($sess); | |
1614 my ($maxwin) = sort {$a <=> $b} $sess->{streams}{$sid}, $sess->{conn_window}; | |
1615 | |
1616 SKIP: { | |
1617 skip 'leaves coredump', 1 unless $t->has_version('1.9.7'); | |
1618 skip 'not enough window', 1 if $maxwin < 5; | |
1619 | |
1620 $sess = new_session(); | |
1621 $sid = new_stream($sess, { path => '/proxy_limit_req/', body => 'TEST2' }); | |
1622 select undef, undef, undef, 1.1; | |
1623 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1624 | |
1625 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1626 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST2', | |
1627 'request body - limit req 2'); | |
1628 | |
1629 } | |
1630 | |
1631 # partial request body data frame received (to be discarded) within request | |
1632 # delayed in limit_req, the rest of data frame is received after response | |
1633 | |
1634 $sess = new_session(); | |
1635 | |
1636 SKIP: { | |
1637 skip 'not enough window', 1 if $maxwin < 4; | |
1638 | |
1639 TODO: { | |
1640 todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE} | |
1641 or $t->has_version('1.9.12'); | |
1642 | |
1643 $sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61], | |
1644 split_delay => 1.1 }); | |
1645 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1646 | |
1647 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1648 is($frame->{headers}->{':status'}, '200', 'discard body - limit req - limited'); | |
1649 | |
1650 } | |
1651 | |
1652 } | |
1653 | |
1654 $sid = new_stream($sess, { path => '/' }); | |
1655 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1656 | |
1657 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1658 is($frame->{headers}->{':status'}, '200', 'discard body - limit req - next'); | |
1659 | |
1660 # ditto, but instead of receiving the rest of data frame, connection is closed | |
1661 # 'http request already closed while closing request' alert can be produced | |
1662 | |
1663 SKIP: { | |
1664 skip 'not enough window', 1 if $maxwin < 4; | |
1665 | |
1666 TODO: { | |
1667 todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE} | |
1668 or $t->has_version('1.9.12'); | |
1669 | |
1670 $sess = new_session(); | |
1671 $sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61], | |
1672 abort => 1 }); | |
1673 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1674 | |
1675 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1676 is($frame->{headers}->{':status'}, '200', 'discard body - limit req - eof'); | |
1677 | |
1678 select undef, undef, undef, 1.1; | |
1679 undef $sess; | |
1680 | |
1681 } | |
1682 | |
1683 } | |
1684 | |
1685 # partial request header frame received (field split), | 618 # partial request header frame received (field split), |
1686 # the rest of frame is received after client header timeout | 619 # the rest of frame is received after client header timeout |
1687 | 620 |
1688 TODO: { | 621 TODO: { |
1689 local $TODO = 'not yet' unless $t->has_version('1.9.12'); | 622 local $TODO = 'not yet' unless $t->has_version('1.9.12'); |
1725 $frames = h2_read($sess, all => [{ type => 'PING' }]); | 658 $frames = h2_read($sess, all => [{ type => 'PING' }]); |
1726 | 659 |
1727 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames; | 660 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames; |
1728 ok($frame, 'client body timeout - PING'); | 661 ok($frame, 'client body timeout - PING'); |
1729 | 662 |
1730 # malformed request body length not equal to content-length | |
1731 | |
1732 $sess = new_session(); | |
1733 $sid = new_stream($sess, | |
1734 { body_more => 1, headers => [ | |
1735 { name => ':method', value => 'GET', mode => 0 }, | |
1736 { name => ':scheme', value => 'http', mode => 0 }, | |
1737 { name => ':path', value => '/client_max_body_size', mode => 1 }, | |
1738 { name => ':authority', value => 'localhost', mode => 1 }, | |
1739 { name => 'content-length', value => '5', mode => 1 }]}); | |
1740 h2_body($sess, 'TEST'); | |
1741 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1742 | |
1743 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1744 is($frame->{headers}->{':status'}, 400, 'request body less than content-length'); | |
1745 | |
1746 $sid = new_stream($sess, | |
1747 { body_more => 1, headers => [ | |
1748 { name => ':method', value => 'GET', mode => 0 }, | |
1749 { name => ':scheme', value => 'http', mode => 0 }, | |
1750 { name => ':path', value => '/client_max_body_size', mode => 1 }, | |
1751 { name => ':authority', value => 'localhost', mode => 1 }, | |
1752 { name => 'content-length', value => '3', mode => 1 }]}); | |
1753 h2_body($sess, 'TEST'); | |
1754 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1755 | |
1756 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1757 is($frame->{headers}->{':status'}, 400, 'request body more than content-length'); | |
1758 | |
1759 # client_max_body_size | |
1760 | |
1761 $sess = new_session(); | |
1762 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1763 body_more => 1 }); | |
1764 h2_body($sess, 'TESTTEST12'); | |
1765 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1766 | |
1767 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1768 is($frame->{headers}->{':status'}, 200, 'client_max_body_size - status'); | |
1769 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1770 'client_max_body_size - body'); | |
1771 | |
1772 # client_max_body_size - limited | |
1773 | |
1774 $sess = new_session(); | |
1775 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1776 body_more => 1 }); | |
1777 h2_body($sess, 'TESTTEST123'); | |
1778 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1779 | |
1780 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1781 is($frame->{headers}->{':status'}, 413, 'client_max_body_size - limited'); | |
1782 | |
1783 # client_max_body_size - many DATA frames | |
1784 | |
1785 $sess = new_session(); | |
1786 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1787 body_more => 1 }); | |
1788 h2_body($sess, 'TESTTEST12', { body_split => [2] }); | |
1789 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1790 | |
1791 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1792 is($frame->{headers}->{':status'}, 200, 'client_max_body_size many - status'); | |
1793 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1794 'client_max_body_size many - body'); | |
1795 | |
1796 # client_max_body_size - many DATA frames - limited | |
1797 | |
1798 $sess = new_session(); | |
1799 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1800 body_more => 1 }); | |
1801 h2_body($sess, 'TESTTEST123', { body_split => [2] }); | |
1802 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1803 | |
1804 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1805 is($frame->{headers}->{':status'}, 413, 'client_max_body_size many - limited'); | |
1806 | |
1807 # client_max_body_size - padded DATA | |
1808 | |
1809 $sess = new_session(); | |
1810 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1811 body_more => 1 }); | |
1812 h2_body($sess, 'TESTTEST12', { body_padding => 42 }); | |
1813 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1814 | |
1815 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1816 is($frame->{headers}->{':status'}, 200, 'client_max_body_size pad - status'); | |
1817 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1818 'client_max_body_size pad - body'); | |
1819 | |
1820 # client_max_body_size - padded DATA - limited | |
1821 | |
1822 $sess = new_session(); | |
1823 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1824 body_more => 1 }); | |
1825 h2_body($sess, 'TESTTEST123', { body_padding => 42 }); | |
1826 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1827 | |
1828 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1829 is($frame->{headers}->{':status'}, 413, 'client_max_body_size pad - limited'); | |
1830 | |
1831 # client_max_body_size - many padded DATA frames | |
1832 | |
1833 $sess = new_session(); | |
1834 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1835 body_more => 1 }); | |
1836 h2_body($sess, 'TESTTEST12', { body_padding => 42, body_split => [2] }); | |
1837 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1838 | |
1839 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1840 is($frame->{headers}->{':status'}, 200, | |
1841 'client_max_body_size many pad - status'); | |
1842 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1843 'client_max_body_size many pad - body'); | |
1844 | |
1845 # client_max_body_size - many padded DATA frames - limited | |
1846 | |
1847 $sess = new_session(); | |
1848 $sid = new_stream($sess, { path => '/client_max_body_size/t2.html', | |
1849 body_more => 1 }); | |
1850 h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] }); | |
1851 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1852 | |
1853 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1854 is($frame->{headers}->{':status'}, 413, | |
1855 'client_max_body_size many pad - limited'); | |
1856 | |
1857 # request body without content-length | |
1858 | |
1859 $sess = new_session(); | |
1860 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1861 { name => ':method', value => 'GET', mode => 2 }, | |
1862 { name => ':scheme', value => 'http', mode => 2 }, | |
1863 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1864 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1865 h2_body($sess, 'TESTTEST12'); | |
1866 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1867 | |
1868 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1869 is($frame->{headers}->{':status'}, 200, | |
1870 'request body without content-length - status'); | |
1871 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1872 'request body without content-length - body'); | |
1873 | |
1874 # request body without content-length - limited | |
1875 | |
1876 $sess = new_session(); | |
1877 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1878 { name => ':method', value => 'GET', mode => 2 }, | |
1879 { name => ':scheme', value => 'http', mode => 2 }, | |
1880 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1881 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1882 h2_body($sess, 'TESTTEST123'); | |
1883 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1884 | |
1885 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1886 is($frame->{headers}->{':status'}, 413, | |
1887 'request body without content-length - limited'); | |
1888 | |
1889 # request body without content-length - many DATA frames | |
1890 | |
1891 $sess = new_session(); | |
1892 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1893 { name => ':method', value => 'GET', mode => 2 }, | |
1894 { name => ':scheme', value => 'http', mode => 2 }, | |
1895 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1896 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1897 h2_body($sess, 'TESTTEST12', { body_split => [2] }); | |
1898 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1899 | |
1900 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1901 is($frame->{headers}->{':status'}, 200, | |
1902 'request body without content-length many - status'); | |
1903 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1904 'request body without content-length many - body'); | |
1905 | |
1906 # request body without content-length - many DATA frames - limited | |
1907 | |
1908 $sess = new_session(); | |
1909 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1910 { name => ':method', value => 'GET', mode => 2 }, | |
1911 { name => ':scheme', value => 'http', mode => 2 }, | |
1912 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1913 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1914 h2_body($sess, 'TESTTEST123', { body_split => [2] }); | |
1915 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1916 | |
1917 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1918 is($frame->{headers}->{':status'}, 413, | |
1919 'request body without content-length many - limited'); | |
1920 | |
1921 # request body without content-length - padding | |
1922 | |
1923 $sess = new_session(); | |
1924 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1925 { name => ':method', value => 'GET', mode => 2 }, | |
1926 { name => ':scheme', value => 'http', mode => 2 }, | |
1927 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1928 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1929 h2_body($sess, 'TESTTEST12', { body_padding => 42 }); | |
1930 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1931 | |
1932 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1933 is($frame->{headers}->{':status'}, 200, | |
1934 'request body without content-length pad - status'); | |
1935 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', | |
1936 'request body without content-length pad - body'); | |
1937 | |
1938 # request body without content-length - padding - limited | |
1939 | |
1940 $sess = new_session(); | |
1941 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1942 { name => ':method', value => 'GET', mode => 2 }, | |
1943 { name => ':scheme', value => 'http', mode => 2 }, | |
1944 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1945 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1946 h2_body($sess, 'TESTTEST123', { body_padding => 42 }); | |
1947 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1948 | |
1949 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1950 is($frame->{headers}->{':status'}, 413, | |
1951 'request body without content-length pad - limited'); | |
1952 | |
1953 # request body without content-length - padding with many DATA frames | |
1954 | |
1955 $sess = new_session(); | |
1956 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1957 { name => ':method', value => 'GET', mode => 2 }, | |
1958 { name => ':scheme', value => 'http', mode => 2 }, | |
1959 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1960 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1961 h2_body($sess, 'TESTTEST', { body_padding => 42, body_split => [2] }); | |
1962 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1963 | |
1964 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1965 is($frame->{headers}->{':status'}, 200, | |
1966 'request body without content-length many pad - status'); | |
1967 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST', | |
1968 'request body without content-length many pad - body'); | |
1969 | |
1970 # request body without content-length - padding with many DATA frames - limited | |
1971 | |
1972 $sess = new_session(); | |
1973 $sid = new_stream($sess, { body_more => 1, headers => [ | |
1974 { name => ':method', value => 'GET', mode => 2 }, | |
1975 { name => ':scheme', value => 'http', mode => 2 }, | |
1976 { name => ':path', value => '/client_max_body_size', mode => 2 }, | |
1977 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
1978 h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] }); | |
1979 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
1980 | |
1981 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1982 is($frame->{headers}->{':status'}, 413, | |
1983 'request body without content-length many pad - limited'); | |
1984 | 663 |
1985 # proxied request with logging pristine request header field (e.g., referer) | 664 # proxied request with logging pristine request header field (e.g., referer) |
1986 | 665 |
1987 $sess = new_session(); | 666 $sess = new_session(); |
1988 $sid = new_stream($sess, { headers => [ | 667 $sid = new_stream($sess, { headers => [ |
2195 | 874 |
2196 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | 875 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); |
2197 @data = grep { $_->{type} eq "DATA" } @$frames; | 876 @data = grep { $_->{type} eq "DATA" } @$frames; |
2198 is($data[0]->{length}, 2**15, 'max frame size - custom'); | 877 is($data[0]->{length}, 2**15, 'max frame size - custom'); |
2199 | 878 |
2200 # CONTINUATION in response | |
2201 # put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2) | |
2202 # to break header block into separate frames, one such field per frame | |
2203 | |
2204 $sess = new_session(); | |
2205 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**13 }); | |
2206 | |
2207 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
2208 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
2209 is(@{$data[-1]->{headers}{'x-longheader'}}, 3, | |
2210 'response CONTINUATION - headers'); | |
2211 is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
2212 'response CONTINUATION - header 1'); | |
2213 is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
2214 'response CONTINUATION - header 2'); | |
2215 is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
2216 'response CONTINUATION - header 3'); | |
2217 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
2218 cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size'); | |
2219 | |
2220 # same but without response DATA frames | |
2221 | |
2222 $sess = new_session(); | |
2223 $sid = new_stream($sess, { path => '/continuation/204?h=' . 'x' x 2**13 }); | |
2224 | |
2225 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
2226 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
2227 is(@{$data[-1]->{headers}{'x-longheader'}}, 3, | |
2228 'no body CONTINUATION - headers'); | |
2229 is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
2230 'no body CONTINUATION - header 1'); | |
2231 is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
2232 'no body CONTINUATION - header 2'); | |
2233 is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
2234 'no body CONTINUATION - header 3'); | |
2235 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
2236 cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size'); | |
2237 | |
2238 # response header block is always split by SETTINGS_MAX_FRAME_SIZE | |
2239 | |
2240 $sess = new_session(); | |
2241 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 }); | |
2242 | |
2243 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
2244 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
2245 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
2246 cmp_ok($data[-1], '<=', 2**14, 'response header frames limited'); | |
2247 | |
2248 # response header frame sent in parts | |
2249 | |
2250 TODO: { | |
2251 local $TODO = 'not yet' unless $t->has_version('1.9.7'); | |
2252 | |
2253 $sess = new_session(8092); | |
2254 h2_settings($sess, 0, 0x5 => 2**17); | |
2255 | |
2256 $sid = new_stream($sess, { path => '/frame_size?h=' . 'x' x 2**15 }); | |
2257 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
2258 | |
2259 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
2260 ok($frame, 'response header - parts'); | |
2261 | |
2262 SKIP: { | |
2263 skip 'response header failed', 1 unless $frame; | |
2264 | |
2265 is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304, | |
2266 'response header - headers'); | |
2267 | |
2268 } | |
2269 | |
2270 # response header block split and sent in parts | |
2271 | |
2272 $sess = new_session(8092); | |
2273 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 }); | |
2274 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
2275 | |
2276 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
2277 ($lengths) = sort { $b <=> $a } map { $_->{length} } @data; | |
2278 cmp_ok($lengths, '<=', 16384, 'response header split - max size'); | |
2279 | |
2280 is(length join('', @{@$frames[-1]->{headers}->{'x-longheader'}}), 98304, | |
2281 'response header split - headers'); | |
2282 | |
2283 } | |
2284 | |
2285 # max_field_size - header field name | |
2286 | |
2287 $sess = new_session(8087); | |
2288 $sid = new_stream($sess, { headers => [ | |
2289 { name => ':method', value => 'GET', mode => 0 }, | |
2290 { name => ':scheme', value => 'http', mode => 0 }, | |
2291 { name => ':path', value => '/t2.html', mode => 1 }, | |
2292 { name => ':authority', value => 'localhost', mode => 1 }, | |
2293 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
2294 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2295 | |
2296 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2297 ok($frame, 'field name size less'); | |
2298 | |
2299 $sid = new_stream($sess, { headers => [ | |
2300 { name => ':method', value => 'GET', mode => 0 }, | |
2301 { name => ':scheme', value => 'http', mode => 0 }, | |
2302 { name => ':path', value => '/t2.html', mode => 1 }, | |
2303 { name => ':authority', value => 'localhost', mode => 1 }, | |
2304 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
2305 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2306 | |
2307 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2308 ok($frame, 'field name size second'); | |
2309 | |
2310 $sess = new_session(8087); | |
2311 $sid = new_stream($sess, { headers => [ | |
2312 { name => ':method', value => 'GET', mode => 0 }, | |
2313 { name => ':scheme', value => 'http', mode => 0 }, | |
2314 { name => ':path', value => '/t2.html', mode => 1 }, | |
2315 { name => ':authority', value => 'localhost', mode => 1 }, | |
2316 { name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]}); | |
2317 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2318 | |
2319 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2320 ok($frame, 'field name size equal'); | |
2321 | |
2322 $sess = new_session(8087); | |
2323 $sid = new_stream($sess, { headers => [ | |
2324 { name => ':method', value => 'GET', mode => 0 }, | |
2325 { name => ':scheme', value => 'http', mode => 0 }, | |
2326 { name => ':path', value => '/t2.html', mode => 1 }, | |
2327 { name => ':authority', value => 'localhost', mode => 1 }, | |
2328 { name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]}); | |
2329 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2330 | |
2331 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2332 is($frame, undef, 'field name size greater'); | |
2333 | |
2334 # max_field_size - header field value | |
2335 | |
2336 $sess = new_session(8087); | |
2337 $sid = new_stream($sess, { headers => [ | |
2338 { name => ':method', value => 'GET', mode => 0 }, | |
2339 { name => ':scheme', value => 'http', mode => 0 }, | |
2340 { name => ':path', value => '/t2.html', mode => 1 }, | |
2341 { name => ':authority', value => 'localhost', mode => 1 }, | |
2342 { name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]}); | |
2343 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2344 | |
2345 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2346 ok($frame, 'field value size less'); | |
2347 | |
2348 $sess = new_session(8087); | |
2349 $sid = new_stream($sess, { headers => [ | |
2350 { name => ':method', value => 'GET', mode => 0 }, | |
2351 { name => ':scheme', value => 'http', mode => 0 }, | |
2352 { name => ':path', value => '/t2.html', mode => 1 }, | |
2353 { name => ':authority', value => 'localhost', mode => 1 }, | |
2354 { name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]}); | |
2355 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2356 | |
2357 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2358 ok($frame, 'field value size equal'); | |
2359 | |
2360 $sess = new_session(8087); | |
2361 $sid = new_stream($sess, { headers => [ | |
2362 { name => ':method', value => 'GET', mode => 0 }, | |
2363 { name => ':scheme', value => 'http', mode => 0 }, | |
2364 { name => ':path', value => '/t2.html', mode => 1 }, | |
2365 { name => ':authority', value => 'localhost', mode => 1 }, | |
2366 { name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]}); | |
2367 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2368 | |
2369 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2370 is($frame, undef, 'field value size greater'); | |
2371 | |
2372 # max_header_size | |
2373 | |
2374 $sess = new_session(8088); | |
2375 $sid = new_stream($sess, { headers => [ | |
2376 { name => ':method', value => 'GET', mode => 0 }, | |
2377 { name => ':scheme', value => 'http', mode => 0 }, | |
2378 { name => ':path', value => '/t2.html', mode => 1 }, | |
2379 { name => ':authority', value => 'localhost', mode => 1 }, | |
2380 { name => 'longname9', value => 'x', mode => 2 }]}); | |
2381 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2382 | |
2383 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2384 ok($frame, 'header size less'); | |
2385 | |
2386 $sid = new_stream($sess, { headers => [ | |
2387 { name => ':method', value => 'GET', mode => 0 }, | |
2388 { name => ':scheme', value => 'http', mode => 0 }, | |
2389 { name => ':path', value => '/t2.html', mode => 1 }, | |
2390 { name => ':authority', value => 'localhost', mode => 1 }, | |
2391 { name => 'longname9', value => 'x', mode => 2 }]}); | |
2392 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2393 | |
2394 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2395 ok($frame, 'header size second'); | |
2396 | |
2397 $sess = new_session(8088); | |
2398 $sid = new_stream($sess, { headers => [ | |
2399 { name => ':method', value => 'GET', mode => 0 }, | |
2400 { name => ':scheme', value => 'http', mode => 0 }, | |
2401 { name => ':path', value => '/t2.html', mode => 1 }, | |
2402 { name => ':authority', value => 'localhost', mode => 1 }, | |
2403 { name => 'longname9', value => 'xx', mode => 2 }]}); | |
2404 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2405 | |
2406 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2407 ok($frame, 'header size equal'); | |
2408 | |
2409 $sess = new_session(8088); | |
2410 $sid = new_stream($sess, { headers => [ | |
2411 { name => ':method', value => 'GET', mode => 0 }, | |
2412 { name => ':scheme', value => 'http', mode => 0 }, | |
2413 { name => ':path', value => '/t2.html', mode => 1 }, | |
2414 { name => ':authority', value => 'localhost', mode => 1 }, | |
2415 { name => 'longname9', value => 'xxx', mode => 2 }]}); | |
2416 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2417 | |
2418 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2419 is($frame, undef, 'header size greater'); | |
2420 | |
2421 # header size is based on (decompressed) header list | |
2422 # two extra 1-byte indices would otherwise fit in max_header_size | |
2423 | |
2424 $sess = new_session(8088); | |
2425 $sid = new_stream($sess, { headers => [ | |
2426 { name => ':method', value => 'GET', mode => 0 }, | |
2427 { name => ':scheme', value => 'http', mode => 0 }, | |
2428 { name => ':path', value => '/t2.html', mode => 1 }, | |
2429 { name => ':authority', value => 'localhost', mode => 1 }, | |
2430 { name => 'longname9', value => 'x', mode => 2 }]}); | |
2431 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2432 | |
2433 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2434 ok($frame, 'header size new index'); | |
2435 | |
2436 $sid = new_stream($sess, { headers => [ | |
2437 { name => ':method', value => 'GET', mode => 0 }, | |
2438 { name => ':scheme', value => 'http', mode => 0 }, | |
2439 { name => ':path', value => '/t2.html', mode => 1 }, | |
2440 { name => ':authority', value => 'localhost', mode => 1 }, | |
2441 { name => 'longname9', value => 'x', mode => 0 }]}); | |
2442 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2443 | |
2444 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
2445 ok($frame, 'header size indexed'); | |
2446 | |
2447 $sid = new_stream($sess, { headers => [ | |
2448 { name => ':method', value => 'GET', mode => 0 }, | |
2449 { name => ':scheme', value => 'http', mode => 0 }, | |
2450 { name => ':path', value => '/t2.html', mode => 1 }, | |
2451 { name => ':authority', value => 'localhost', mode => 1 }, | |
2452 { name => 'longname9', value => 'x', mode => 0 }, | |
2453 { name => 'longname9', value => 'x', mode => 0 }]}); | |
2454 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2455 | |
2456 ($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; | |
2457 is($frame->{code}, 0xb, 'header size indexed greater'); | |
2458 | |
2459 # HPACK table boundary | |
2460 | |
2461 $sess = new_session(); | |
2462 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2463 { name => ':method', value => 'GET', mode => 0 }, | |
2464 { name => ':scheme', value => 'http', mode => 0 }, | |
2465 { name => ':path', value => '/', mode => 0 }, | |
2466 { name => ':authority', value => '', mode => 0 }, | |
2467 { name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]); | |
2468 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2469 { name => ':method', value => 'GET', mode => 0 }, | |
2470 { name => ':scheme', value => 'http', mode => 0 }, | |
2471 { name => ':path', value => '/', mode => 0 }, | |
2472 { name => ':authority', value => '', mode => 0 }, | |
2473 { name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]); | |
2474 | |
2475 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
2476 ok($frame, 'HPACK table boundary'); | |
2477 | |
2478 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2479 { name => ':method', value => 'GET', mode => 0 }, | |
2480 { name => ':scheme', value => 'http', mode => 0 }, | |
2481 { name => ':path', value => '/', mode => 0 }, | |
2482 { name => ':authority', value => '', mode => 0 }, | |
2483 { name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]); | |
2484 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2485 { name => ':method', value => 'GET', mode => 0 }, | |
2486 { name => ':scheme', value => 'http', mode => 0 }, | |
2487 { name => ':path', value => '/', mode => 0 }, | |
2488 { name => ':authority', value => '', mode => 0 }, | |
2489 { name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]); | |
2490 | |
2491 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
2492 ok($frame, 'HPACK table boundary - header field name'); | |
2493 | |
2494 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2495 { name => ':method', value => 'GET', mode => 0 }, | |
2496 { name => ':scheme', value => 'http', mode => 0 }, | |
2497 { name => ':path', value => '/', mode => 0 }, | |
2498 { name => ':authority', value => '', mode => 0 }, | |
2499 { name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]); | |
2500 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
2501 { name => ':method', value => 'GET', mode => 0 }, | |
2502 { name => ':scheme', value => 'http', mode => 0 }, | |
2503 { name => ':path', value => '/', mode => 0 }, | |
2504 { name => ':authority', value => '', mode => 0 }, | |
2505 { name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]); | |
2506 | |
2507 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
2508 ok($frame, 'HPACK table boundary - header field value'); | |
2509 | |
2510 # stream multiplexing + WINDOW_UPDATE | 879 # stream multiplexing + WINDOW_UPDATE |
2511 | 880 |
2512 $sess = new_session(); | 881 $sess = new_session(); |
2513 $sid = new_stream($sess, { path => '/t1.html' }); | 882 $sid = new_stream($sess, { path => '/t1.html' }); |
2514 $frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | 883 $frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); |
2537 is($sum, 81, 'multiple - stream1 remain data'); | 906 is($sum, 81, 'multiple - stream1 remain data'); |
2538 | 907 |
2539 @data = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames; | 908 @data = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames; |
2540 $sum = eval join '+', map { $_->{length} } @data; | 909 $sum = eval join '+', map { $_->{length} } @data; |
2541 is($sum, 2**16 + 80, 'multiple - stream2 full data'); | 910 is($sum, 2**16 + 80, 'multiple - stream2 full data'); |
2542 | |
2543 # stream muliplexing + PRIORITY frames | |
2544 | |
2545 $sess = new_session(); | |
2546 $sid = new_stream($sess, { path => '/t1.html' }); | |
2547 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2548 | |
2549 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2550 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2551 | |
2552 h2_priority($sess, 0, $sid); | |
2553 h2_priority($sess, 255, $sid2); | |
2554 | |
2555 h2_window($sess, 2**17, $sid); | |
2556 h2_window($sess, 2**17, $sid2); | |
2557 h2_window($sess, 2**17); | |
2558 | |
2559 $frames = h2_read($sess, all => [ | |
2560 { sid => $sid, fin => 1 }, | |
2561 { sid => $sid2, fin => 1 } | |
2562 ]); | |
2563 | |
2564 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2565 is(join(' ', map { $_->{sid} } @data), "$sid2 $sid", 'weight - PRIORITY 1'); | |
2566 | |
2567 # and vice versa | |
2568 | |
2569 $sess = new_session(); | |
2570 $sid = new_stream($sess, { path => '/t1.html' }); | |
2571 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2572 | |
2573 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2574 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2575 | |
2576 h2_priority($sess, 255, $sid); | |
2577 h2_priority($sess, 0, $sid2); | |
2578 | |
2579 h2_window($sess, 2**17, $sid); | |
2580 h2_window($sess, 2**17, $sid2); | |
2581 h2_window($sess, 2**17); | |
2582 | |
2583 $frames = h2_read($sess, all => [ | |
2584 { sid => $sid, fin => 1 }, | |
2585 { sid => $sid2, fin => 1 } | |
2586 ]); | |
2587 | |
2588 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2589 is(join(' ', map { $_->{sid} } @data), "$sid $sid2", 'weight - PRIORITY 2'); | |
2590 | |
2591 # stream muliplexing + HEADERS PRIORITY flag | |
2592 | |
2593 $sess = new_session(); | |
2594 $sid = new_stream($sess, { path => '/t1.html', prio => 0 }); | |
2595 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2596 | |
2597 $sid2 = new_stream($sess, { path => '/t2.html', prio => 255 }); | |
2598 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2599 | |
2600 h2_window($sess, 2**17, $sid); | |
2601 h2_window($sess, 2**17, $sid2); | |
2602 h2_window($sess, 2**17); | |
2603 | |
2604 $frames = h2_read($sess, all => [ | |
2605 { sid => $sid, fin => 1 }, | |
2606 { sid => $sid2, fin => 1 } | |
2607 ]); | |
2608 | |
2609 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2610 my $sids = join ' ', map { $_->{sid} } @data; | |
2611 is($sids, "$sid2 $sid", 'weight - HEADERS PRIORITY 1'); | |
2612 | |
2613 # and vice versa | |
2614 | |
2615 $sess = new_session(); | |
2616 $sid = new_stream($sess, { path => '/t1.html', prio => 255 }); | |
2617 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2618 | |
2619 $sid2 = new_stream($sess, { path => '/t2.html', prio => 0 }); | |
2620 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2621 | |
2622 h2_window($sess, 2**17, $sid); | |
2623 h2_window($sess, 2**17, $sid2); | |
2624 h2_window($sess, 2**17); | |
2625 | |
2626 $frames = h2_read($sess, all => [ | |
2627 { sid => $sid, fin => 1 }, | |
2628 { sid => $sid2, fin => 1 } | |
2629 ]); | |
2630 | |
2631 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2632 $sids = join ' ', map { $_->{sid} } @data; | |
2633 is($sids, "$sid $sid2", 'weight - HEADERS PRIORITY 2'); | |
2634 | |
2635 # 5.3.1. Stream Dependencies | |
2636 | |
2637 # PRIORITY frame | |
2638 | |
2639 $sess = new_session(); | |
2640 | |
2641 h2_priority($sess, 16, 3, 0); | |
2642 h2_priority($sess, 16, 1, 3); | |
2643 | |
2644 $sid = new_stream($sess, { path => '/t1.html' }); | |
2645 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2646 | |
2647 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2648 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2649 | |
2650 h2_window($sess, 2**17, $sid); | |
2651 h2_window($sess, 2**17, $sid2); | |
2652 h2_window($sess, 2**17); | |
2653 | |
2654 $frames = h2_read($sess, all => [ | |
2655 { sid => $sid, fin => 1 }, | |
2656 { sid => $sid2, fin => 1 }, | |
2657 ]); | |
2658 | |
2659 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2660 $sids = join ' ', map { $_->{sid} } @data; | |
2661 is($sids, "$sid2 $sid", 'dependency - PRIORITY 1'); | |
2662 | |
2663 # and vice versa | |
2664 | |
2665 $sess = new_session(); | |
2666 | |
2667 h2_priority($sess, 16, 1, 0); | |
2668 h2_priority($sess, 16, 3, 1); | |
2669 | |
2670 $sid = new_stream($sess, { path => '/t1.html' }); | |
2671 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2672 | |
2673 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2674 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2675 | |
2676 h2_window($sess, 2**17, $sid); | |
2677 h2_window($sess, 2**17, $sid2); | |
2678 h2_window($sess, 2**17); | |
2679 | |
2680 $frames = h2_read($sess, all => [ | |
2681 { sid => $sid, fin => 1 }, | |
2682 { sid => $sid2, fin => 1 }, | |
2683 ]); | |
2684 | |
2685 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2686 $sids = join ' ', map { $_->{sid} } @data; | |
2687 is($sids, "$sid $sid2", 'dependency - PRIORITY 2'); | |
2688 | |
2689 # PRIORITY - self dependency | |
2690 | |
2691 # 5.3.1. Stream Dependencies | |
2692 # A stream cannot depend on itself. An endpoint MUST treat this as a | |
2693 # stream error of type PROTOCOL_ERROR. | |
2694 | |
2695 $sess = new_session(); | |
2696 $sid = new_stream($sess); | |
2697 h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2698 | |
2699 h2_priority($sess, 0, $sid, $sid); | |
2700 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
2701 | |
2702 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
2703 is($frame->{sid}, $sid, 'dependency - PRIORITY self - RST_STREAM'); | |
2704 is($frame->{code}, 1, 'dependency - PRIORITY self - PROTOCOL_ERROR'); | |
2705 | |
2706 # HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records | |
2707 | |
2708 $sess = new_session(); | |
2709 | |
2710 h2_priority($sess, 16, 1, 0); | |
2711 h2_priority($sess, 16, 3, 0); | |
2712 | |
2713 $sid = new_stream($sess, { path => '/t1.html', dep => 3 }); | |
2714 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2715 | |
2716 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2717 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2718 | |
2719 h2_window($sess, 2**17, $sid); | |
2720 h2_window($sess, 2**17, $sid2); | |
2721 h2_window($sess, 2**17); | |
2722 | |
2723 $frames = h2_read($sess, all => [ | |
2724 { sid => $sid, fin => 1 }, | |
2725 { sid => $sid2, fin => 1 }, | |
2726 ]); | |
2727 | |
2728 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2729 $sids = join ' ', map { $_->{sid} } @data; | |
2730 is($sids, "$sid2 $sid", 'dependency - HEADERS PRIORITY 1'); | |
2731 | |
2732 # and vice versa | |
2733 | |
2734 $sess = new_session(); | |
2735 | |
2736 h2_priority($sess, 16, 1, 0); | |
2737 h2_priority($sess, 16, 3, 0); | |
2738 | |
2739 $sid = new_stream($sess, { path => '/t1.html' }); | |
2740 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2741 | |
2742 $sid2 = new_stream($sess, { path => '/t2.html', dep => 1 }); | |
2743 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2744 | |
2745 h2_window($sess, 2**17, $sid); | |
2746 h2_window($sess, 2**17, $sid2); | |
2747 h2_window($sess, 2**17); | |
2748 | |
2749 $frames = h2_read($sess, all => [ | |
2750 { sid => $sid, fin => 1 }, | |
2751 { sid => $sid2, fin => 1 }, | |
2752 ]); | |
2753 | |
2754 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2755 $sids = join ' ', map { $_->{sid} } @data; | |
2756 is($sids, "$sid $sid2", 'dependency - HEADERS PRIORITY 2'); | |
2757 | |
2758 # HEADERS - self dependency | |
2759 | |
2760 $sess = new_session(); | |
2761 $sid = new_stream($sess, { dep => 1 }); | |
2762 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
2763 | |
2764 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
2765 is($frame->{sid}, $sid, 'dependency - HEADERS self - RST_STREAM'); | |
2766 is($frame->{code}, 1, 'dependency - HEADERS self - PROTOCOL_ERROR'); | |
2767 | |
2768 # PRIORITY frame, weighted dependencies | |
2769 | |
2770 $sess = new_session(); | |
2771 | |
2772 h2_priority($sess, 16, 5, 0); | |
2773 h2_priority($sess, 255, 1, 5); | |
2774 h2_priority($sess, 0, 3, 5); | |
2775 | |
2776 $sid = new_stream($sess, { path => '/t1.html' }); | |
2777 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2778 | |
2779 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2780 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2781 | |
2782 my $sid3 = new_stream($sess, { path => '/t2.html' }); | |
2783 h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]); | |
2784 | |
2785 h2_window($sess, 2**16, 1); | |
2786 h2_window($sess, 2**16, 3); | |
2787 h2_window($sess, 2**16, 5); | |
2788 h2_window($sess, 2**16); | |
2789 | |
2790 $frames = h2_read($sess, all => [ | |
2791 { sid => $sid, fin => 1 }, | |
2792 { sid => $sid2, fin => 1 }, | |
2793 { sid => $sid3, fin => 1 }, | |
2794 ]); | |
2795 | |
2796 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2797 $sids = join ' ', map { $_->{sid} } @data; | |
2798 is($sids, "$sid3 $sid $sid2", 'weighted dependency - PRIORITY 1'); | |
2799 | |
2800 # and vice versa | |
2801 | |
2802 $sess = new_session(); | |
2803 | |
2804 h2_priority($sess, 16, 5, 0); | |
2805 h2_priority($sess, 0, 1, 5); | |
2806 h2_priority($sess, 255, 3, 5); | |
2807 | |
2808 $sid = new_stream($sess, { path => '/t1.html' }); | |
2809 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2810 | |
2811 $sid2 = new_stream($sess, { path => '/t2.html' }); | |
2812 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]); | |
2813 | |
2814 $sid3 = new_stream($sess, { path => '/t2.html' }); | |
2815 h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]); | |
2816 | |
2817 h2_window($sess, 2**16, 1); | |
2818 h2_window($sess, 2**16, 3); | |
2819 h2_window($sess, 2**16, 5); | |
2820 h2_window($sess, 2**16); | |
2821 | |
2822 $frames = h2_read($sess, all => [ | |
2823 { sid => $sid, fin => 1 }, | |
2824 { sid => $sid2, fin => 1 }, | |
2825 { sid => $sid3, fin => 1 }, | |
2826 ]); | |
2827 | |
2828 @data = grep { $_->{type} eq "DATA" } @$frames; | |
2829 $sids = join ' ', map { $_->{sid} } @data; | |
2830 is($sids, "$sid3 $sid2 $sid", 'weighted dependency - PRIORITY 2'); | |
2831 | |
2832 # PRIORITY - reprioritization with circular dependency - after [3] removed | |
2833 # initial dependency tree: | |
2834 # 1 <- [3] <- 5 | |
2835 | |
2836 $sess = new_session(); | |
2837 | |
2838 h2_window($sess, 2**18); | |
2839 | |
2840 h2_priority($sess, 16, 1, 0); | |
2841 h2_priority($sess, 16, 3, 1); | |
2842 h2_priority($sess, 16, 5, 3); | |
2843 | |
2844 $sid = new_stream($sess, { path => '/t1.html' }); | |
2845 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2846 | |
2847 $sid2 = new_stream($sess, { path => '/t1.html' }); | |
2848 h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]); | |
2849 | |
2850 $sid3 = new_stream($sess, { path => '/t1.html' }); | |
2851 h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]); | |
2852 | |
2853 h2_window($sess, 2**16, $sid2); | |
2854 | |
2855 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 1 }]); | |
2856 $sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames; | |
2857 is($sids, $sid2, 'removed dependency'); | |
2858 | |
2859 for (1 .. 40) { | |
2860 h2_read($sess, all => [{ sid => new_stream($sess), fin => 1 }]); | |
2861 } | |
2862 | |
2863 # make circular dependency | |
2864 # 1 <- 5 -- current dependency tree before reprioritization | |
2865 # 5 <- 1 | |
2866 # 1 <- 5 | |
2867 | |
2868 h2_priority($sess, 16, 1, 5); | |
2869 h2_priority($sess, 16, 5, 1); | |
2870 | |
2871 h2_window($sess, 2**16, $sid); | |
2872 h2_window($sess, 2**16, $sid3); | |
2873 | |
2874 $frames = h2_read($sess, all => [ | |
2875 { sid => $sid, fin => 1 }, | |
2876 { sid => $sid3, fin => 1 }, | |
2877 ]); | |
2878 | |
2879 ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames; | |
2880 is($frame->{length}, 81, 'removed dependency - first stream'); | |
2881 | |
2882 ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames; | |
2883 is($frame->{length}, 81, 'removed dependency - last stream'); | |
2884 | |
2885 # PRIORITY - reprioritization with circular dependency - exclusive [5] | |
2886 # 1 <- [5] <- 3 | |
2887 | |
2888 $sess = new_session(); | |
2889 | |
2890 h2_window($sess, 2**18); | |
2891 | |
2892 h2_priority($sess, 16, 1, 0); | |
2893 h2_priority($sess, 16, 3, 1); | |
2894 h2_priority($sess, 16, 5, 1, excl => 1); | |
2895 | |
2896 $sid = new_stream($sess, { path => '/t1.html' }); | |
2897 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]); | |
2898 | |
2899 $sid2 = new_stream($sess, { path => '/t1.html' }); | |
2900 h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]); | |
2901 | |
2902 $sid3 = new_stream($sess, { path => '/t1.html' }); | |
2903 h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]); | |
2904 | |
2905 h2_window($sess, 2**16, $sid); | |
2906 | |
2907 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
2908 $sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames; | |
2909 is($sids, $sid, 'exclusive dependency - parent removed'); | |
2910 | |
2911 # make circular dependency | |
2912 # 5 <- 3 -- current dependency tree before reprioritization | |
2913 # 3 <- 5 | |
2914 | |
2915 h2_priority($sess, 16, 5, 3); | |
2916 | |
2917 h2_window($sess, 2**16, $sid2); | |
2918 h2_window($sess, 2**16, $sid3); | |
2919 | |
2920 $frames = h2_read($sess, all => [ | |
2921 { sid => $sid2, fin => 1 }, | |
2922 { sid => $sid3, fin => 1 }, | |
2923 ]); | |
2924 | |
2925 ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames; | |
2926 is($frame->{length}, 81, 'exclusive dependency - first stream'); | |
2927 | |
2928 ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames; | |
2929 is($frame->{length}, 81, 'exclusive dependency - last stream'); | |
2930 | |
2931 # limit_conn | |
2932 | |
2933 $sess = new_session(); | |
2934 h2_settings($sess, 0, 0x4 => 1); | |
2935 | |
2936 $sid = new_stream($sess, { path => '/t3.html' }); | |
2937 $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]); | |
2938 | |
2939 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames; | |
2940 is($frame->{headers}->{':status'}, 200, 'limit_conn first stream'); | |
2941 | |
2942 $sid2 = new_stream($sess, { path => '/t3.html' }); | |
2943 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]); | |
2944 | |
2945 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames; | |
2946 is($frame->{headers}->{':status'}, 503, 'limit_conn rejected'); | |
2947 | |
2948 h2_settings($sess, 0, 0x4 => 2**16); | |
2949 | |
2950 h2_read($sess, all => [ | |
2951 { sid => $sid, fin => 1 }, | |
2952 { sid => $sid2, fin => 1 } | |
2953 ]); | |
2954 | |
2955 # limit_conn + client's RST_STREAM | |
2956 | |
2957 $sess = new_session(); | |
2958 h2_settings($sess, 0, 0x4 => 1); | |
2959 | |
2960 $sid = new_stream($sess, { path => '/t3.html' }); | |
2961 $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]); | |
2962 h2_rst($sess, $sid, 5); | |
2963 | |
2964 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames; | |
2965 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 1'); | |
2966 | |
2967 $sid2 = new_stream($sess, { path => '/t3.html' }); | |
2968 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]); | |
2969 | |
2970 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames; | |
2971 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 2'); | |
2972 | 911 |
2973 # http2_max_concurrent_streams | 912 # http2_max_concurrent_streams |
2974 | 913 |
2975 $sess = new_session(8086, pure => 1); | 914 $sess = new_session(8086, pure => 1); |
2976 $frames = h2_read($sess, all => [{ type => 'SETTINGS' }]); | 915 $frames = h2_read($sess, all => [{ type => 'SETTINGS' }]); |
3063 | 1002 |
3064 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | 1003 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; |
3065 ok($frame, 'invalid preface 2 - GOAWAY frame'); | 1004 ok($frame, 'invalid preface 2 - GOAWAY frame'); |
3066 is($frame->{code}, 1, 'invalid preface 2 - error code'); | 1005 is($frame->{code}, 1, 'invalid preface 2 - error code'); |
3067 | 1006 |
3068 # invalid PROXY protocol string | |
3069 | |
3070 $sess = new_session(8082, proxy => 'BOGUS TCP4 192.0.2.1 192.0.2.2 1234 5678', | |
3071 pure => 1); | |
3072 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]); | |
3073 | |
3074 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
3075 ok($frame, 'invalid PROXY - GOAWAY frame'); | |
3076 is($frame->{code}, 1, 'invalid PROXY - error code'); | |
3077 | |
3078 # ensure that request header field value with newline doesn't get split | |
3079 # | |
3080 # 10.3. Intermediary Encapsulation Attacks | |
3081 # Any request or response that contains a character not permitted | |
3082 # in a header field value MUST be treated as malformed. | |
3083 | |
3084 $sess = new_session(); | |
3085 $sid = new_stream($sess, { headers => [ | |
3086 { name => ':method', value => 'GET', mode => 0 }, | |
3087 { name => ':scheme', value => 'http', mode => 0 }, | |
3088 { name => ':path', value => '/proxy2/', mode => 1 }, | |
3089 { name => ':authority', value => 'localhost', mode => 1 }, | |
3090 { name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]}); | |
3091 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
3092 | |
3093 # 10.3. Intermediary Encapsulation Attacks | |
3094 # An intermediary therefore cannot translate an HTTP/2 request or response | |
3095 # containing an invalid field name into an HTTP/1.1 message. | |
3096 | |
3097 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
3098 isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header'); | |
3099 | |
3100 # 8.1.2.6. Malformed Requests and Responses | |
3101 # Malformed requests or responses that are detected MUST be treated | |
3102 # as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. | |
3103 | |
3104 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
3105 is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid'); | |
3106 is($frame->{length}, 4, 'newline in request header - RST_STREAM length'); | |
3107 is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags'); | |
3108 is($frame->{code}, 1, 'newline in request header - RST_STREAM code'); | |
3109 | |
3110 # invalid header name as seen with underscore should not lead to ignoring rest | |
3111 | |
3112 TODO: { | |
3113 local $TODO = 'not yet' unless $t->has_version('1.9.7'); | |
3114 | |
3115 $sess = new_session(); | |
3116 $sid = new_stream($sess, { headers => [ | |
3117 { name => ':method', value => 'GET', mode => 0 }, | |
3118 { name => ':scheme', value => 'http', mode => 0 }, | |
3119 { name => ':path', value => '/', mode => 0 }, | |
3120 { name => ':authority', value => 'localhost', mode => 1 }, | |
3121 { name => 'x_foo', value => "x-bar", mode => 2 }, | |
3122 { name => 'referer', value => "see-this", mode => 1 }]}); | |
3123 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
3124 | |
3125 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
3126 is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name'); | |
3127 | |
3128 } | |
3129 | |
3130 # GOAWAY on SYN_STREAM with even StreamID | 1007 # GOAWAY on SYN_STREAM with even StreamID |
3131 | 1008 |
3132 $sess = new_session(); | 1009 $sess = new_session(); |
3133 new_stream($sess, { path => '/' }, 2); | 1010 new_stream($sess, { path => '/' }, 2); |
3134 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]); | 1011 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]); |
3168 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | 1045 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; |
3169 ok($frame, 'dup stream - GOAWAY frame'); | 1046 ok($frame, 'dup stream - GOAWAY frame'); |
3170 is($frame->{code}, 1, 'dup stream - error code'); | 1047 is($frame->{code}, 1, 'dup stream - error code'); |
3171 is($frame->{last_sid}, $sid, 'dup stream - last stream'); | 1048 is($frame->{last_sid}, $sid, 'dup stream - last stream'); |
3172 | 1049 |
3173 # missing mandatory request header ':scheme' | |
3174 | |
3175 TODO: { | |
3176 local $TODO = 'not yet'; | |
3177 | |
3178 $sess = new_session(); | |
3179 $sid = new_stream($sess, { headers => [ | |
3180 { name => ':method', value => 'GET', mode => 0 }, | |
3181 { name => ':path', value => '/', mode => 0 }, | |
3182 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
3183 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
3184 | |
3185 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
3186 is($frame->{headers}->{':status'}, 400, 'incomplete headers'); | |
3187 | |
3188 } | |
3189 | |
3190 # empty request header ':authority' | |
3191 | |
3192 $sess = new_session(); | |
3193 $sid = new_stream($sess, { headers => [ | |
3194 { name => ':method', value => 'GET', mode => 0 }, | |
3195 { name => ':scheme', value => 'http', mode => 0 }, | |
3196 { name => ':path', value => '/', mode => 0 }, | |
3197 { name => ':authority', value => '', mode => 0 }]}); | |
3198 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
3199 | |
3200 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
3201 is($frame->{headers}->{':status'}, 400, 'empty authority'); | |
3202 | |
3203 # aborted stream with zero HEADERS payload followed by client connection close | 1050 # aborted stream with zero HEADERS payload followed by client connection close |
3204 | 1051 |
3205 new_stream(new_session(), { split => [ 9 ], abort => 1 }); | 1052 new_stream(new_session(), { split => [ 9 ], abort => 1 }); |
3206 | 1053 |
3207 # unknown frame type | 1054 # unknown frame type |
3212 $frames = h2_read($sess, all => [{ type => 'PING' }]); | 1059 $frames = h2_read($sess, all => [{ type => 'PING' }]); |
3213 | 1060 |
3214 ($frame) = grep { $_->{type} eq "PING" } @$frames; | 1061 ($frame) = grep { $_->{type} eq "PING" } @$frames; |
3215 is($frame->{value}, 'SEE-THIS', 'unknown frame type'); | 1062 is($frame->{value}, 'SEE-THIS', 'unknown frame type'); |
3216 | 1063 |
3217 # client sent invalid :path header | |
3218 | |
3219 $sid = new_stream($sess, { path => 't1.html' }); | |
3220 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
3221 | |
3222 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
3223 is($frame->{code}, 1, 'invalid path'); | |
3224 | |
3225 # GOAWAY - force closing a connection by server | 1064 # GOAWAY - force closing a connection by server |
3226 | 1065 |
3227 $sid = new_stream($sess); | 1066 $sid = new_stream($sess); |
3228 h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | 1067 h2_read($sess, all => [{ sid => $sid, fin => 1 }]); |
3229 | 1068 |
3259 | 1098 |
3260 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | 1099 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; |
3261 ok($frame, 'GOAWAY on connection close'); | 1100 ok($frame, 'GOAWAY on connection close'); |
3262 | 1101 |
3263 ############################################################################### | 1102 ############################################################################### |
3264 | |
3265 sub h2_ping { | |
3266 my ($sess, $payload) = @_; | |
3267 | |
3268 raw_write($sess->{socket}, pack("x2C2x5a8", 8, 0x6, $payload)); | |
3269 } | |
3270 | |
3271 sub h2_rst { | |
3272 my ($sess, $stream, $error) = @_; | |
3273 | |
3274 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error)); | |
3275 } | |
3276 | |
3277 sub h2_goaway { | |
3278 my ($sess, $stream, $lstream, $err, $debug, %extra) = @_; | |
3279 $debug = '' unless defined $debug; | |
3280 my $len = defined $extra{len} ? $extra{len} : 8 + length($debug); | |
3281 my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug); | |
3282 | |
3283 my @bufs = map { | |
3284 raw_write($sess->{socket}, substr $buf, 0, $_, ""); | |
3285 select undef, undef, undef, 0.4; | |
3286 } @{$extra{split}}; | |
3287 | |
3288 raw_write($sess->{socket}, $buf); | |
3289 } | |
3290 | |
3291 sub h2_priority { | |
3292 my ($sess, $w, $stream, $dep, %extra) = @_; | |
3293 | |
3294 $stream = 0 unless defined $stream; | |
3295 $dep = 0 unless defined $dep; | |
3296 $dep |= $extra{excl} << 31 if exists $extra{excl}; | |
3297 raw_write($sess->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w)); | |
3298 } | |
3299 | |
3300 sub h2_window { | |
3301 my ($sess, $win, $stream) = @_; | |
3302 | |
3303 $stream = 0 unless defined $stream; | |
3304 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win)); | |
3305 } | |
3306 | |
3307 sub h2_settings { | |
3308 my ($sess, $ack, %extra) = @_; | |
3309 | |
3310 my $len = 6 * keys %extra; | |
3311 my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0; | |
3312 $buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra; | |
3313 raw_write($sess->{socket}, $buf); | |
3314 } | |
3315 | |
3316 sub h2_unknown { | |
3317 my ($sess, $payload) = @_; | |
3318 | |
3319 my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload); | |
3320 raw_write($sess->{socket}, $buf); | |
3321 } | |
3322 | |
3323 sub h2_continue { | |
3324 my ($ctx, $stream, $uri) = @_; | |
3325 | |
3326 $uri->{h2_continue} = 1; | |
3327 return new_stream($ctx, $uri, $stream); | |
3328 } | |
3329 | |
3330 sub h2_body { | |
3331 my ($sess, $body, $extra) = @_; | |
3332 $extra = {} unless defined $extra; | |
3333 | |
3334 my $len = length $body; | |
3335 my $sid = $sess->{last_stream}; | |
3336 | |
3337 if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) { | |
3338 h2_read($sess, all => [{ type => 'WINDOW_UPDATE' }]); | |
3339 } | |
3340 | |
3341 if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) { | |
3342 return; | |
3343 } | |
3344 | |
3345 $sess->{conn_window} -= $len; | |
3346 $sess->{streams}{$sid} -= $len; | |
3347 | |
3348 my $buf; | |
3349 | |
3350 my $split = ref $extra->{body_split} && $extra->{body_split} || []; | |
3351 for (@$split) { | |
3352 $buf .= pack_body($sess, substr($body, 0, $_, ""), 0x0, $extra); | |
3353 } | |
3354 | |
3355 $buf .= pack_body($sess, $body, 0x1, $extra) if defined $body; | |
3356 | |
3357 $split = ref $extra->{split} && $extra->{split} || []; | |
3358 for (@$split) { | |
3359 raw_write($sess->{socket}, substr($buf, 0, $_, "")); | |
3360 return if $extra->{abort}; | |
3361 select undef, undef, undef, ($extra->{split_delay} || 0.2); | |
3362 } | |
3363 | |
3364 raw_write($sess->{socket}, $buf); | |
3365 } | |
3366 | |
3367 sub pack_body { | |
3368 my ($ctx, $body, $flags, $extra) = @_; | |
3369 | |
3370 my $pad = defined $extra->{body_padding} ? $extra->{body_padding} : 0; | |
3371 my $padlen = defined $extra->{body_padding} ? 1 : 0; | |
3372 | |
3373 my $buf = pack_length(length($body) + $pad + $padlen); | |
3374 $flags |= 0x8 if $padlen; | |
3375 vec($flags, 0, 1) = 0 if $extra->{body_more}; | |
3376 $buf .= pack 'CC', 0x0, $flags; # DATA, END_STREAM | |
3377 $buf .= pack 'N', $ctx->{last_stream}; | |
3378 $buf .= pack 'C', $pad if $padlen; # DATA Pad Length? | |
3379 $buf .= $body; | |
3380 $buf .= pack "x$pad" if $padlen; # DATA Padding | |
3381 return $buf; | |
3382 } | |
3383 | |
3384 sub new_stream { | |
3385 my ($ctx, $uri, $stream) = @_; | |
3386 my ($input, $buf); | |
3387 my ($d, $status); | |
3388 | |
3389 $ctx->{headers} = ''; | |
3390 | |
3391 my $host = $uri->{host} || '127.0.0.1:8080'; | |
3392 my $method = $uri->{method} || 'GET'; | |
3393 my $scheme = $uri->{scheme} || 'http'; | |
3394 my $path = $uri->{path} || '/'; | |
3395 my $headers = $uri->{headers}; | |
3396 my $body = $uri->{body}; | |
3397 my $prio = $uri->{prio}; | |
3398 my $dep = $uri->{dep}; | |
3399 | |
3400 my $pad = defined $uri->{padding} ? $uri->{padding} : 0; | |
3401 my $padlen = defined $uri->{padding} ? 1 : 0; | |
3402 | |
3403 my $type = defined $uri->{h2_continue} ? 0x9 : 0x1; | |
3404 my $flags = defined $uri->{continuation} ? 0x0 : 0x4; | |
3405 $flags |= 0x1 unless defined $body || defined $uri->{body_more}; | |
3406 $flags |= 0x8 if $padlen; | |
3407 $flags |= 0x20 if defined $dep || defined $prio; | |
3408 | |
3409 if ($stream) { | |
3410 $ctx->{last_stream} = $stream; | |
3411 } else { | |
3412 $ctx->{last_stream} += 2; | |
3413 $ctx->{streams}{$ctx->{last_stream}} = $ctx->{iws}; | |
3414 } | |
3415 | |
3416 $buf = pack("xxx"); # Length stub | |
3417 $buf .= pack("CC", $type, $flags); # END_HEADERS | |
3418 $buf .= pack("N", $ctx->{last_stream}); # Stream-ID | |
3419 | |
3420 $dep = 0 if defined $prio and not defined $dep; | |
3421 $prio = 16 if defined $dep and not defined $prio; | |
3422 | |
3423 unless ($headers) { | |
3424 $input = hpack($ctx, ":method", $method); | |
3425 $input .= hpack($ctx, ":scheme", $scheme); | |
3426 $input .= hpack($ctx, ":path", $path); | |
3427 $input .= hpack($ctx, ":authority", $host); | |
3428 $input .= hpack($ctx, "content-length", length($body)) if $body; | |
3429 | |
3430 } else { | |
3431 $input = join '', map { | |
3432 hpack($ctx, $_->{name}, $_->{value}, | |
3433 mode => $_->{mode}, huff => $_->{huff}) | |
3434 } @$headers if $headers; | |
3435 } | |
3436 | |
3437 $input = pack("B*", '001' . ipack(5, $uri->{table_size})) . $input | |
3438 if defined $uri->{table_size}; | |
3439 | |
3440 my $split = ref $uri->{continuation} && $uri->{continuation} || []; | |
3441 my @input = map { substr $input, 0, $_, "" } @$split; | |
3442 push @input, $input; | |
3443 | |
3444 # set length, attach headers, padding, priority | |
3445 | |
3446 my $hlen = length($input[0]) + $pad + $padlen; | |
3447 $hlen += 5 if $flags & 0x20; | |
3448 $buf |= pack_length($hlen); | |
3449 | |
3450 $buf .= pack 'C', $pad if $padlen; # Pad Length? | |
3451 $buf .= pack 'NC', $dep, $prio if $flags & 0x20; | |
3452 $buf .= $input[0]; | |
3453 $buf .= (pack 'C', 0) x $pad if $padlen; # Padding | |
3454 | |
3455 shift @input; | |
3456 | |
3457 while (@input) { | |
3458 $input = shift @input; | |
3459 $flags = @input ? 0x0 : 0x4; | |
3460 $buf .= pack_length(length($input)); | |
3461 $buf .= pack("CC", 0x9, $flags); | |
3462 $buf .= pack("N", $ctx->{last_stream}); | |
3463 $buf .= $input; | |
3464 } | |
3465 | |
3466 $split = ref $uri->{body_split} && $uri->{body_split} || []; | |
3467 for (@$split) { | |
3468 $buf .= pack_body($ctx, substr($body, 0, $_, ""), 0x0, $uri); | |
3469 } | |
3470 | |
3471 $buf .= pack_body($ctx, $body, 0x1, $uri) if defined $body; | |
3472 | |
3473 $split = ref $uri->{split} && $uri->{split} || []; | |
3474 for (@$split) { | |
3475 raw_write($ctx->{socket}, substr($buf, 0, $_, "")); | |
3476 goto done if $uri->{abort}; | |
3477 select undef, undef, undef, ($uri->{split_delay} || 0.2); | |
3478 } | |
3479 | |
3480 raw_write($ctx->{socket}, $buf); | |
3481 done: | |
3482 return $ctx->{last_stream}; | |
3483 } | |
3484 | |
3485 sub h2_read { | |
3486 my ($sess, %extra) = @_; | |
3487 my (@got); | |
3488 my $s = $sess->{socket}; | |
3489 my $buf = ''; | |
3490 | |
3491 while (1) { | |
3492 $buf = raw_read($s, $buf, 9); | |
3493 last if length $buf < 9; | |
3494 | |
3495 my $length = unpack_length($buf); | |
3496 my $type = unpack('x3C', $buf); | |
3497 my $flags = unpack('x4C', $buf); | |
3498 | |
3499 my $stream = unpack "x5 B32", $buf; | |
3500 substr($stream, 0, 1) = 0; | |
3501 $stream = unpack("N", pack("B32", $stream)); | |
3502 | |
3503 $buf = raw_read($s, $buf, $length + 9); | |
3504 last if length($buf) < $length + 9; | |
3505 | |
3506 $buf = substr($buf, 9); | |
3507 | |
3508 my $frame = $cframe{$type}{value}($sess, $buf, $length, $flags, | |
3509 $stream); | |
3510 $frame->{length} = $length; | |
3511 $frame->{type} = $cframe{$type}{name}; | |
3512 $frame->{flags} = $flags; | |
3513 $frame->{sid} = $stream; | |
3514 push @got, $frame; | |
3515 | |
3516 $buf = substr($buf, $length); | |
3517 | |
3518 last unless $extra{all} && test_fin($got[-1], $extra{all}); | |
3519 }; | |
3520 return \@got; | |
3521 } | |
3522 | |
3523 sub test_fin { | |
3524 my ($frame, $all) = @_; | |
3525 my @test = @{$all}; | |
3526 | |
3527 # wait for the specified DATA length | |
3528 | |
3529 for (@test) { | |
3530 if ($_->{length} && $frame->{type} eq 'DATA') { | |
3531 # check also for StreamID if needed | |
3532 | |
3533 if (!$_->{sid} || $_->{sid} == $frame->{sid}) { | |
3534 $_->{length} -= $frame->{length}; | |
3535 } | |
3536 } | |
3537 } | |
3538 @test = grep { !(defined $_->{length} && $_->{length} == 0) } @test; | |
3539 | |
3540 # wait for the fin flag | |
3541 | |
3542 @test = grep { !(defined $_->{fin} | |
3543 && $_->{sid} == $frame->{sid} && $_->{fin} & $frame->{flags}) | |
3544 } @test if defined $frame->{flags}; | |
3545 | |
3546 # wait for the specified frame | |
3547 | |
3548 @test = grep { !($_->{type} && $_->{type} eq $frame->{type}) } @test; | |
3549 | |
3550 @{$all} = @test; | |
3551 } | |
3552 | |
3553 sub headers { | |
3554 my ($ctx, $buf, $len, $flags) = @_; | |
3555 $ctx->{headers} .= substr($buf, 0, $len); | |
3556 return unless $flags & 0x4; | |
3557 { headers => hunpack($ctx, $ctx->{headers}, length($ctx->{headers})) }; | |
3558 } | |
3559 | |
3560 sub data { | |
3561 my ($ctx, $buf, $len) = @_; | |
3562 return { data => substr($buf, 0, $len) }; | |
3563 } | |
3564 | |
3565 sub settings { | |
3566 my ($ctx, $buf, $len) = @_; | |
3567 my %payload; | |
3568 my $skip = 0; | |
3569 | |
3570 for (1 .. $len / 6) { | |
3571 my $id = hex unpack "\@$skip n", $buf; $skip += 2; | |
3572 $payload{$id} = unpack "\@$skip N", $buf; $skip += 4; | |
3573 | |
3574 $ctx->{iws} = $payload{$id} if $id == 4; | |
3575 } | |
3576 return \%payload; | |
3577 } | |
3578 | |
3579 sub ping { | |
3580 my ($ctx, $buf, $len) = @_; | |
3581 return { value => unpack "A$len", $buf }; | |
3582 } | |
3583 | |
3584 sub rst_stream { | |
3585 my ($ctx, $buf, $len) = @_; | |
3586 return { code => unpack "N", $buf }; | |
3587 } | |
3588 | |
3589 sub goaway { | |
3590 my ($ctx, $buf, $len) = @_; | |
3591 my %payload; | |
3592 | |
3593 my $stream = unpack "B32", $buf; | |
3594 substr($stream, 0, 1) = 0; | |
3595 $stream = unpack("N", pack("B32", $stream)); | |
3596 $payload{last_sid} = $stream; | |
3597 | |
3598 $len -= 4; | |
3599 $payload{code} = unpack "x4 N", $buf; | |
3600 $payload{debug} = unpack "x8 A$len", $buf; | |
3601 return \%payload; | |
3602 } | |
3603 | |
3604 sub window_update { | |
3605 my ($ctx, $buf, $len, $flags, $sid) = @_; | |
3606 my $value = unpack "B32", $buf; | |
3607 substr($value, 0, 1) = 0; | |
3608 $value = unpack("N", pack("B32", $value)); | |
3609 | |
3610 unless ($sid) { | |
3611 $ctx->{conn_window} += $value; | |
3612 | |
3613 } else { | |
3614 $ctx->{streams}{$sid} = $ctx->{iws} | |
3615 unless defined $ctx->{streams}{$sid}; | |
3616 $ctx->{streams}{$sid} += $value; | |
3617 } | |
3618 | |
3619 return { wdelta => $value }; | |
3620 } | |
3621 | |
3622 sub pack_length { | |
3623 pack 'c3', unpack 'xc3', pack 'N', $_[0]; | |
3624 } | |
3625 | |
3626 sub unpack_length { | |
3627 unpack 'N', pack 'xc3', unpack 'c3', $_[0]; | |
3628 } | |
3629 | |
3630 sub raw_read { | |
3631 my ($s, $buf, $len) = @_; | |
3632 my $got = ''; | |
3633 | |
3634 while (length($buf) < $len && IO::Select->new($s)->can_read(1)) { | |
3635 $s->sysread($got, 16384) or last; | |
3636 log_in($got); | |
3637 $buf .= $got; | |
3638 } | |
3639 return $buf; | |
3640 } | |
3641 | |
3642 sub raw_write { | |
3643 my ($s, $message) = @_; | |
3644 | |
3645 local $SIG{PIPE} = 'IGNORE'; | |
3646 | |
3647 while (IO::Select->new($s)->can_write(0.4)) { | |
3648 log_out($message); | |
3649 my $n = $s->syswrite($message); | |
3650 last unless $n; | |
3651 $message = substr($message, $n); | |
3652 last unless length $message; | |
3653 } | |
3654 } | |
3655 | |
3656 sub new_session { | |
3657 my ($port, %extra) = @_; | |
3658 | |
3659 my $s = new_socket($port, %extra); | |
3660 my $preface = $extra{preface} | |
3661 || 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF; | |
3662 | |
3663 if ($extra{proxy}) { | |
3664 raw_write($s, $extra{proxy}); | |
3665 } | |
3666 | |
3667 # preface | |
3668 | |
3669 raw_write($s, $preface); | |
3670 | |
3671 my $ctx = { socket => $s, last_stream => -1, | |
3672 dynamic_encode => [ static_table() ], | |
3673 dynamic_decode => [ static_table() ], | |
3674 static_table_size => scalar @{[static_table()]}, | |
3675 iws => 65535, conn_window => 65535, streams => {}}; | |
3676 | |
3677 return $ctx if $extra{pure}; | |
3678 | |
3679 # update windows, if any | |
3680 | |
3681 h2_read($ctx, all => [ | |
3682 { type => 'WINDOW_UPDATE' }, | |
3683 { type => 'SETTINGS'} | |
3684 ]); | |
3685 | |
3686 return $ctx; | |
3687 } | |
3688 | |
3689 sub new_socket { | |
3690 my ($port, %extra) = @_; | |
3691 my $npn = $extra{'npn'}; | |
3692 my $alpn = $extra{'alpn'}; | |
3693 my $s; | |
3694 | |
3695 $port = 8080 unless defined $port; | |
3696 | |
3697 eval { | |
3698 local $SIG{ALRM} = sub { die "timeout\n" }; | |
3699 local $SIG{PIPE} = sub { die "sigpipe\n" }; | |
3700 alarm(2); | |
3701 $s = IO::Socket::INET->new( | |
3702 Proto => 'tcp', | |
3703 PeerAddr => "127.0.0.1:$port", | |
3704 ); | |
3705 IO::Socket::SSL->start_SSL($s, | |
3706 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), | |
3707 SSL_npn_protocols => $npn ? [ $npn ] : undef, | |
3708 SSL_alpn_protocols => $alpn ? [ $alpn ] : undef, | |
3709 SSL_error_trap => sub { die $_[1] } | |
3710 ) if $extra{'SSL'}; | |
3711 alarm(0); | |
3712 }; | |
3713 alarm(0); | |
3714 | |
3715 if ($@) { | |
3716 log_in("died: $@"); | |
3717 return undef; | |
3718 } | |
3719 | |
3720 return $s; | |
3721 } | |
3722 | |
3723 sub static_table { | |
3724 [ '', '' ], # unused | |
3725 [ ':authority', '' ], | |
3726 [ ':method', 'GET' ], | |
3727 [ ':method', 'POST' ], | |
3728 [ ':path', '/' ], | |
3729 [ ':path', '/index.html' ], | |
3730 [ ':scheme', 'http' ], | |
3731 [ ':scheme', 'https' ], | |
3732 [ ':status', '200' ], | |
3733 [ ':status', '204' ], | |
3734 [ ':status', '206' ], | |
3735 [ ':status', '304' ], | |
3736 [ ':status', '400' ], | |
3737 [ ':status', '404' ], | |
3738 [ ':status', '500' ], | |
3739 [ 'accept-charset', '' ], | |
3740 [ 'accept-encoding', 'gzip, deflate' ], | |
3741 [ 'accept-language', '' ], | |
3742 [ 'accept-ranges', '' ], | |
3743 [ 'accept', '' ], | |
3744 [ 'access-control-allow-origin', | |
3745 '' ], | |
3746 [ 'age', '' ], | |
3747 [ 'allow', '' ], | |
3748 [ 'authorization', '' ], | |
3749 [ 'cache-control', '' ], | |
3750 [ 'content-disposition', | |
3751 '' ], | |
3752 [ 'content-encoding', '' ], | |
3753 [ 'content-language', '' ], | |
3754 [ 'content-length', '' ], | |
3755 [ 'content-location', '' ], | |
3756 [ 'content-range', '' ], | |
3757 [ 'content-type', '' ], | |
3758 [ 'cookie', '' ], | |
3759 [ 'date', '' ], | |
3760 [ 'etag', '' ], | |
3761 [ 'expect', '' ], | |
3762 [ 'expires', '' ], | |
3763 [ 'from', '' ], | |
3764 [ 'host', '' ], | |
3765 [ 'if-match', '' ], | |
3766 [ 'if-modified-since', '' ], | |
3767 [ 'if-none-match', '' ], | |
3768 [ 'if-range', '' ], | |
3769 [ 'if-unmodified-since', | |
3770 '' ], | |
3771 [ 'last-modified', '' ], | |
3772 [ 'link', '' ], | |
3773 [ 'location', '' ], | |
3774 [ 'max-forwards', '' ], | |
3775 [ 'proxy-authenticate', '' ], | |
3776 [ 'proxy-authorization', | |
3777 '' ], | |
3778 [ 'range', '' ], | |
3779 [ 'referer', '' ], | |
3780 [ 'refresh', '' ], | |
3781 [ 'retry-after', '' ], | |
3782 [ 'server', '' ], | |
3783 [ 'set-cookie', '' ], | |
3784 [ 'strict-transport-security', | |
3785 '' ], | |
3786 [ 'transfer-encoding', '' ], | |
3787 [ 'user-agent', '' ], | |
3788 [ 'vary', '' ], | |
3789 [ 'via', '' ], | |
3790 [ 'www-authenticate', '' ], | |
3791 } | |
3792 | |
3793 # RFC 7541, 5.1. Integer Representation | |
3794 | |
3795 sub ipack { | |
3796 my ($base, $d) = @_; | |
3797 return sprintf("%.*b", $base, $d) if $d < 2**$base - 1; | |
3798 | |
3799 my $o = sprintf("%${base}b", 2**$base - 1); | |
3800 $d -= 2**$base - 1; | |
3801 while ($d >= 128) { | |
3802 $o .= sprintf("%8b", $d % 128 + 128); | |
3803 $d /= 128; | |
3804 } | |
3805 $o .= sprintf("%08b", $d); | |
3806 return $o; | |
3807 } | |
3808 | |
3809 sub iunpack { | |
3810 my ($base, $b, $s) = @_; | |
3811 | |
3812 my $len = unpack("\@$s B8", $b); $s++; | |
3813 my $prefix = substr($len, 0, 8 - $base); | |
3814 $len = '0' x (8 - $base) . substr($len, 8 - $base); | |
3815 $len = unpack("C", pack("B8", $len)); | |
3816 | |
3817 return ($len, $s, $prefix) if $len < 2**$base - 1; | |
3818 | |
3819 my $m = 0; | |
3820 my $d; | |
3821 | |
3822 do { | |
3823 $d = unpack("\@$s C", $b); $s++; | |
3824 $len += ($d & 127) * 2**$m; | |
3825 $m += $base; | |
3826 } while (($d & 128) == 128); | |
3827 | |
3828 return ($len, $s, $prefix); | |
3829 } | |
3830 | |
3831 sub hpack { | |
3832 my ($ctx, $name, $value, %extra) = @_; | |
3833 my $table = $ctx->{dynamic_encode}; | |
3834 my $mode = defined $extra{mode} ? $extra{mode} : 1; | |
3835 my $huff = $extra{huff}; | |
3836 | |
3837 my ($index, $buf) = 0; | |
3838 | |
3839 # 6.1. Indexed Header Field Representation | |
3840 | |
3841 if ($mode == 0) { | |
3842 ++$index until $index > $#$table | |
3843 or $table->[$index][0] eq $name | |
3844 and $table->[$index][1] eq $value; | |
3845 $buf = pack('B*', '1' . ipack(7, $index)); | |
3846 } | |
3847 | |
3848 # 6.2.1. Literal Header Field with Incremental Indexing | |
3849 | |
3850 if ($mode == 1) { | |
3851 splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ]; | |
3852 | |
3853 ++$index until $index > $#$table | |
3854 or $table->[$index][0] eq $name; | |
3855 my $value = $huff ? huff($value) : $value; | |
3856 | |
3857 $buf = pack('B*', '01' . ipack(6, $index) | |
3858 . ($huff ? '1' : '0') . ipack(7, length($value))); | |
3859 $buf .= $value; | |
3860 } | |
3861 | |
3862 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name | |
3863 | |
3864 if ($mode == 2) { | |
3865 splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ]; | |
3866 | |
3867 my $name = $huff ? huff($name) : $name; | |
3868 my $value = $huff ? huff($value) : $value; | |
3869 my $hbit = ($huff ? '1' : '0'); | |
3870 | |
3871 $buf = pack('B*', '01000000'); | |
3872 $buf .= pack('B*', $hbit . ipack(7, length($name))); | |
3873 $buf .= $name; | |
3874 $buf .= pack('B*', $hbit . ipack(7, length($value))); | |
3875 $buf .= $value; | |
3876 } | |
3877 | |
3878 # 6.2.2. Literal Header Field without Indexing | |
3879 | |
3880 if ($mode == 3) { | |
3881 ++$index until $index > $#$table | |
3882 or $table->[$index][0] eq $name; | |
3883 my $value = $huff ? huff($value) : $value; | |
3884 | |
3885 $buf = pack('B*', '0000' . ipack(4, $index) | |
3886 . ($huff ? '1' : '0') . ipack(7, length($value))); | |
3887 $buf .= $value; | |
3888 } | |
3889 | |
3890 # 6.2.2. Literal Header Field without Indexing -- New Name | |
3891 | |
3892 if ($mode == 4) { | |
3893 my $name = $huff ? huff($name) : $name; | |
3894 my $value = $huff ? huff($value) : $value; | |
3895 my $hbit = ($huff ? '1' : '0'); | |
3896 | |
3897 $buf = pack('B*', '00000000'); | |
3898 $buf .= pack('B*', $hbit . ipack(7, length($name))); | |
3899 $buf .= $name; | |
3900 $buf .= pack('B*', $hbit . ipack(7, length($value))); | |
3901 $buf .= $value; | |
3902 } | |
3903 | |
3904 # 6.2.3. Literal Header Field Never Indexed | |
3905 | |
3906 if ($mode == 5) { | |
3907 ++$index until $index > $#$table | |
3908 or $table->[$index][0] eq $name; | |
3909 my $value = $huff ? huff($value) : $value; | |
3910 | |
3911 $buf = pack('B*', '0001' . ipack(4, $index) | |
3912 . ($huff ? '1' : '0') . ipack(7, length($value))); | |
3913 $buf .= $value; | |
3914 } | |
3915 | |
3916 # 6.2.3. Literal Header Field Never Indexed -- New Name | |
3917 | |
3918 if ($mode == 6) { | |
3919 my $name = $huff ? huff($name) : $name; | |
3920 my $value = $huff ? huff($value) : $value; | |
3921 my $hbit = ($huff ? '1' : '0'); | |
3922 | |
3923 $buf = pack('B*', '00010000'); | |
3924 $buf .= pack('B*', $hbit . ipack(7, length($name))); | |
3925 $buf .= $name; | |
3926 $buf .= pack('B*', $hbit . ipack(7, length($value))); | |
3927 $buf .= $value; | |
3928 } | |
3929 | |
3930 return $buf; | |
3931 } | |
3932 | |
3933 sub hunpack { | |
3934 my ($ctx, $data, $length) = @_; | |
3935 my $table = $ctx->{dynamic_decode}; | |
3936 my %headers; | |
3937 my $skip = 0; | |
3938 my ($index, $name, $value); | |
3939 | |
3940 my $field = sub { | |
3941 my ($b) = @_; | |
3942 my ($len, $s, $huff) = iunpack(7, @_); | |
3943 | |
3944 my $field = substr($b, $s, $len); | |
3945 $field = $huff ? dehuff($field) : $field; | |
3946 $s += $len; | |
3947 return ($field, $s); | |
3948 }; | |
3949 | |
3950 my $add = sub { | |
3951 my ($h, $n, $v) = @_; | |
3952 return $h->{$n} = $v unless exists $h->{$n}; | |
3953 $h->{$n} = [ $h->{$n} ] unless ref $h->{$n}; | |
3954 push @{$h->{$n}}, $v; | |
3955 }; | |
3956 | |
3957 while ($skip < $length) { | |
3958 my $ib = unpack("\@$skip B8", $data); | |
3959 | |
3960 if (substr($ib, 0, 1) eq '1') { | |
3961 ($index, $skip) = iunpack(7, $data, $skip); | |
3962 $add->(\%headers, | |
3963 $table->[$index][0], $table->[$index][1]); | |
3964 next; | |
3965 } | |
3966 | |
3967 if (substr($ib, 0, 2) eq '01') { | |
3968 ($index, $skip) = iunpack(6, $data, $skip); | |
3969 $name = $table->[$index][0]; | |
3970 | |
3971 ($name, $skip) = $field->($data, $skip) unless $name; | |
3972 ($value, $skip) = $field->($data, $skip); | |
3973 | |
3974 splice @$table, | |
3975 $ctx->{static_table_size}, 0, [ $name, $value ]; | |
3976 $add->(\%headers, $name, $value); | |
3977 next; | |
3978 } | |
3979 | |
3980 if (substr($ib, 0, 4) eq '0000') { | |
3981 ($index, $skip) = iunpack(4, $data, $skip); | |
3982 $name = $table->[$index][0]; | |
3983 | |
3984 ($name, $skip) = $field->($data, $skip) unless $name; | |
3985 ($value, $skip) = $field->($data, $skip); | |
3986 | |
3987 $add->(\%headers, $name, $value); | |
3988 next; | |
3989 } | |
3990 last; | |
3991 } | |
3992 | |
3993 return \%headers; | |
3994 } | |
3995 | |
3996 sub huff_code { scalar { | |
3997 pack('C', 0) => '1111111111000', | |
3998 pack('C', 1) => '11111111111111111011000', | |
3999 pack('C', 2) => '1111111111111111111111100010', | |
4000 pack('C', 3) => '1111111111111111111111100011', | |
4001 pack('C', 4) => '1111111111111111111111100100', | |
4002 pack('C', 5) => '1111111111111111111111100101', | |
4003 pack('C', 6) => '1111111111111111111111100110', | |
4004 pack('C', 7) => '1111111111111111111111100111', | |
4005 pack('C', 8) => '1111111111111111111111101000', | |
4006 pack('C', 9) => '111111111111111111101010', | |
4007 pack('C', 10) => '111111111111111111111111111100', | |
4008 pack('C', 11) => '1111111111111111111111101001', | |
4009 pack('C', 12) => '1111111111111111111111101010', | |
4010 pack('C', 13) => '111111111111111111111111111101', | |
4011 pack('C', 14) => '1111111111111111111111101011', | |
4012 pack('C', 15) => '1111111111111111111111101100', | |
4013 pack('C', 16) => '1111111111111111111111101101', | |
4014 pack('C', 17) => '1111111111111111111111101110', | |
4015 pack('C', 18) => '1111111111111111111111101111', | |
4016 pack('C', 19) => '1111111111111111111111110000', | |
4017 pack('C', 20) => '1111111111111111111111110001', | |
4018 pack('C', 21) => '1111111111111111111111110010', | |
4019 pack('C', 22) => '111111111111111111111111111110', | |
4020 pack('C', 23) => '1111111111111111111111110011', | |
4021 pack('C', 24) => '1111111111111111111111110100', | |
4022 pack('C', 25) => '1111111111111111111111110101', | |
4023 pack('C', 26) => '1111111111111111111111110110', | |
4024 pack('C', 27) => '1111111111111111111111110111', | |
4025 pack('C', 28) => '1111111111111111111111111000', | |
4026 pack('C', 29) => '1111111111111111111111111001', | |
4027 pack('C', 30) => '1111111111111111111111111010', | |
4028 pack('C', 31) => '1111111111111111111111111011', | |
4029 pack('C', 32) => '010100', | |
4030 pack('C', 33) => '1111111000', | |
4031 pack('C', 34) => '1111111001', | |
4032 pack('C', 35) => '111111111010', | |
4033 pack('C', 36) => '1111111111001', | |
4034 pack('C', 37) => '010101', | |
4035 pack('C', 38) => '11111000', | |
4036 pack('C', 39) => '11111111010', | |
4037 pack('C', 40) => '1111111010', | |
4038 pack('C', 41) => '1111111011', | |
4039 pack('C', 42) => '11111001', | |
4040 pack('C', 43) => '11111111011', | |
4041 pack('C', 44) => '11111010', | |
4042 pack('C', 45) => '010110', | |
4043 pack('C', 46) => '010111', | |
4044 pack('C', 47) => '011000', | |
4045 pack('C', 48) => '00000', | |
4046 pack('C', 49) => '00001', | |
4047 pack('C', 50) => '00010', | |
4048 pack('C', 51) => '011001', | |
4049 pack('C', 52) => '011010', | |
4050 pack('C', 53) => '011011', | |
4051 pack('C', 54) => '011100', | |
4052 pack('C', 55) => '011101', | |
4053 pack('C', 56) => '011110', | |
4054 pack('C', 57) => '011111', | |
4055 pack('C', 58) => '1011100', | |
4056 pack('C', 59) => '11111011', | |
4057 pack('C', 60) => '111111111111100', | |
4058 pack('C', 61) => '100000', | |
4059 pack('C', 62) => '111111111011', | |
4060 pack('C', 63) => '1111111100', | |
4061 pack('C', 64) => '1111111111010', | |
4062 pack('C', 65) => '100001', | |
4063 pack('C', 66) => '1011101', | |
4064 pack('C', 67) => '1011110', | |
4065 pack('C', 68) => '1011111', | |
4066 pack('C', 69) => '1100000', | |
4067 pack('C', 70) => '1100001', | |
4068 pack('C', 71) => '1100010', | |
4069 pack('C', 72) => '1100011', | |
4070 pack('C', 73) => '1100100', | |
4071 pack('C', 74) => '1100101', | |
4072 pack('C', 75) => '1100110', | |
4073 pack('C', 76) => '1100111', | |
4074 pack('C', 77) => '1101000', | |
4075 pack('C', 78) => '1101001', | |
4076 pack('C', 79) => '1101010', | |
4077 pack('C', 80) => '1101011', | |
4078 pack('C', 81) => '1101100', | |
4079 pack('C', 82) => '1101101', | |
4080 pack('C', 83) => '1101110', | |
4081 pack('C', 84) => '1101111', | |
4082 pack('C', 85) => '1110000', | |
4083 pack('C', 86) => '1110001', | |
4084 pack('C', 87) => '1110010', | |
4085 pack('C', 88) => '11111100', | |
4086 pack('C', 89) => '1110011', | |
4087 pack('C', 90) => '11111101', | |
4088 pack('C', 91) => '1111111111011', | |
4089 pack('C', 92) => '1111111111111110000', | |
4090 pack('C', 93) => '1111111111100', | |
4091 pack('C', 94) => '11111111111100', | |
4092 pack('C', 95) => '100010', | |
4093 pack('C', 96) => '111111111111101', | |
4094 pack('C', 97) => '00011', | |
4095 pack('C', 98) => '100011', | |
4096 pack('C', 99) => '00100', | |
4097 pack('C', 100) => '100100', | |
4098 pack('C', 101) => '00101', | |
4099 pack('C', 102) => '100101', | |
4100 pack('C', 103) => '100110', | |
4101 pack('C', 104) => '100111', | |
4102 pack('C', 105) => '00110', | |
4103 pack('C', 106) => '1110100', | |
4104 pack('C', 107) => '1110101', | |
4105 pack('C', 108) => '101000', | |
4106 pack('C', 109) => '101001', | |
4107 pack('C', 110) => '101010', | |
4108 pack('C', 111) => '00111', | |
4109 pack('C', 112) => '101011', | |
4110 pack('C', 113) => '1110110', | |
4111 pack('C', 114) => '101100', | |
4112 pack('C', 115) => '01000', | |
4113 pack('C', 116) => '01001', | |
4114 pack('C', 117) => '101101', | |
4115 pack('C', 118) => '1110111', | |
4116 pack('C', 119) => '1111000', | |
4117 pack('C', 120) => '1111001', | |
4118 pack('C', 121) => '1111010', | |
4119 pack('C', 122) => '1111011', | |
4120 pack('C', 123) => '111111111111110', | |
4121 pack('C', 124) => '11111111100', | |
4122 pack('C', 125) => '11111111111101', | |
4123 pack('C', 126) => '1111111111101', | |
4124 pack('C', 127) => '1111111111111111111111111100', | |
4125 pack('C', 128) => '11111111111111100110', | |
4126 pack('C', 129) => '1111111111111111010010', | |
4127 pack('C', 130) => '11111111111111100111', | |
4128 pack('C', 131) => '11111111111111101000', | |
4129 pack('C', 132) => '1111111111111111010011', | |
4130 pack('C', 133) => '1111111111111111010100', | |
4131 pack('C', 134) => '1111111111111111010101', | |
4132 pack('C', 135) => '11111111111111111011001', | |
4133 pack('C', 136) => '1111111111111111010110', | |
4134 pack('C', 137) => '11111111111111111011010', | |
4135 pack('C', 138) => '11111111111111111011011', | |
4136 pack('C', 139) => '11111111111111111011100', | |
4137 pack('C', 140) => '11111111111111111011101', | |
4138 pack('C', 141) => '11111111111111111011110', | |
4139 pack('C', 142) => '111111111111111111101011', | |
4140 pack('C', 143) => '11111111111111111011111', | |
4141 pack('C', 144) => '111111111111111111101100', | |
4142 pack('C', 145) => '111111111111111111101101', | |
4143 pack('C', 146) => '1111111111111111010111', | |
4144 pack('C', 147) => '11111111111111111100000', | |
4145 pack('C', 148) => '111111111111111111101110', | |
4146 pack('C', 149) => '11111111111111111100001', | |
4147 pack('C', 150) => '11111111111111111100010', | |
4148 pack('C', 151) => '11111111111111111100011', | |
4149 pack('C', 152) => '11111111111111111100100', | |
4150 pack('C', 153) => '111111111111111011100', | |
4151 pack('C', 154) => '1111111111111111011000', | |
4152 pack('C', 155) => '11111111111111111100101', | |
4153 pack('C', 156) => '1111111111111111011001', | |
4154 pack('C', 157) => '11111111111111111100110', | |
4155 pack('C', 158) => '11111111111111111100111', | |
4156 pack('C', 159) => '111111111111111111101111', | |
4157 pack('C', 160) => '1111111111111111011010', | |
4158 pack('C', 161) => '111111111111111011101', | |
4159 pack('C', 162) => '11111111111111101001', | |
4160 pack('C', 163) => '1111111111111111011011', | |
4161 pack('C', 164) => '1111111111111111011100', | |
4162 pack('C', 165) => '11111111111111111101000', | |
4163 pack('C', 166) => '11111111111111111101001', | |
4164 pack('C', 167) => '111111111111111011110', | |
4165 pack('C', 168) => '11111111111111111101010', | |
4166 pack('C', 169) => '1111111111111111011101', | |
4167 pack('C', 170) => '1111111111111111011110', | |
4168 pack('C', 171) => '111111111111111111110000', | |
4169 pack('C', 172) => '111111111111111011111', | |
4170 pack('C', 173) => '1111111111111111011111', | |
4171 pack('C', 174) => '11111111111111111101011', | |
4172 pack('C', 175) => '11111111111111111101100', | |
4173 pack('C', 176) => '111111111111111100000', | |
4174 pack('C', 177) => '111111111111111100001', | |
4175 pack('C', 178) => '1111111111111111100000', | |
4176 pack('C', 179) => '111111111111111100010', | |
4177 pack('C', 180) => '11111111111111111101101', | |
4178 pack('C', 181) => '1111111111111111100001', | |
4179 pack('C', 182) => '11111111111111111101110', | |
4180 pack('C', 183) => '11111111111111111101111', | |
4181 pack('C', 184) => '11111111111111101010', | |
4182 pack('C', 185) => '1111111111111111100010', | |
4183 pack('C', 186) => '1111111111111111100011', | |
4184 pack('C', 187) => '1111111111111111100100', | |
4185 pack('C', 188) => '11111111111111111110000', | |
4186 pack('C', 189) => '1111111111111111100101', | |
4187 pack('C', 190) => '1111111111111111100110', | |
4188 pack('C', 191) => '11111111111111111110001', | |
4189 pack('C', 192) => '11111111111111111111100000', | |
4190 pack('C', 193) => '11111111111111111111100001', | |
4191 pack('C', 194) => '11111111111111101011', | |
4192 pack('C', 195) => '1111111111111110001', | |
4193 pack('C', 196) => '1111111111111111100111', | |
4194 pack('C', 197) => '11111111111111111110010', | |
4195 pack('C', 198) => '1111111111111111101000', | |
4196 pack('C', 199) => '1111111111111111111101100', | |
4197 pack('C', 200) => '11111111111111111111100010', | |
4198 pack('C', 201) => '11111111111111111111100011', | |
4199 pack('C', 202) => '11111111111111111111100100', | |
4200 pack('C', 203) => '111111111111111111111011110', | |
4201 pack('C', 204) => '111111111111111111111011111', | |
4202 pack('C', 205) => '11111111111111111111100101', | |
4203 pack('C', 206) => '111111111111111111110001', | |
4204 pack('C', 207) => '1111111111111111111101101', | |
4205 pack('C', 208) => '1111111111111110010', | |
4206 pack('C', 209) => '111111111111111100011', | |
4207 pack('C', 210) => '11111111111111111111100110', | |
4208 pack('C', 211) => '111111111111111111111100000', | |
4209 pack('C', 212) => '111111111111111111111100001', | |
4210 pack('C', 213) => '11111111111111111111100111', | |
4211 pack('C', 214) => '111111111111111111111100010', | |
4212 pack('C', 215) => '111111111111111111110010', | |
4213 pack('C', 216) => '111111111111111100100', | |
4214 pack('C', 217) => '111111111111111100101', | |
4215 pack('C', 218) => '11111111111111111111101000', | |
4216 pack('C', 219) => '11111111111111111111101001', | |
4217 pack('C', 220) => '1111111111111111111111111101', | |
4218 pack('C', 221) => '111111111111111111111100011', | |
4219 pack('C', 222) => '111111111111111111111100100', | |
4220 pack('C', 223) => '111111111111111111111100101', | |
4221 pack('C', 224) => '11111111111111101100', | |
4222 pack('C', 225) => '111111111111111111110011', | |
4223 pack('C', 226) => '11111111111111101101', | |
4224 pack('C', 227) => '111111111111111100110', | |
4225 pack('C', 228) => '1111111111111111101001', | |
4226 pack('C', 229) => '111111111111111100111', | |
4227 pack('C', 230) => '111111111111111101000', | |
4228 pack('C', 231) => '11111111111111111110011', | |
4229 pack('C', 232) => '1111111111111111101010', | |
4230 pack('C', 233) => '1111111111111111101011', | |
4231 pack('C', 234) => '1111111111111111111101110', | |
4232 pack('C', 235) => '1111111111111111111101111', | |
4233 pack('C', 236) => '111111111111111111110100', | |
4234 pack('C', 237) => '111111111111111111110101', | |
4235 pack('C', 238) => '11111111111111111111101010', | |
4236 pack('C', 239) => '11111111111111111110100', | |
4237 pack('C', 240) => '11111111111111111111101011', | |
4238 pack('C', 241) => '111111111111111111111100110', | |
4239 pack('C', 242) => '11111111111111111111101100', | |
4240 pack('C', 243) => '11111111111111111111101101', | |
4241 pack('C', 244) => '111111111111111111111100111', | |
4242 pack('C', 245) => '111111111111111111111101000', | |
4243 pack('C', 246) => '111111111111111111111101001', | |
4244 pack('C', 247) => '111111111111111111111101010', | |
4245 pack('C', 248) => '111111111111111111111101011', | |
4246 pack('C', 249) => '1111111111111111111111111110', | |
4247 pack('C', 250) => '111111111111111111111101100', | |
4248 pack('C', 251) => '111111111111111111111101101', | |
4249 pack('C', 252) => '111111111111111111111101110', | |
4250 pack('C', 253) => '111111111111111111111101111', | |
4251 pack('C', 254) => '111111111111111111111110000', | |
4252 pack('C', 255) => '11111111111111111111101110', | |
4253 '_eos' => '111111111111111111111111111111', | |
4254 }}; | |
4255 | |
4256 sub huff { | |
4257 my ($string) = @_; | |
4258 my $code = &huff_code; | |
4259 | |
4260 my $ret = join '', map { $code->{$_} } (split //, $string); | |
4261 my $len = length($ret) + (8 - length($ret) % 8); | |
4262 $ret .= $code->{_eos}; | |
4263 | |
4264 return pack("B$len", $ret); | |
4265 } | |
4266 | |
4267 sub dehuff { | |
4268 my ($string) = @_; | |
4269 my $code = &huff_code; | |
4270 my %decode = reverse %$code; | |
4271 | |
4272 my $ret = ''; my $c = ''; | |
4273 for (split //, unpack('B*', $string)) { | |
4274 $c .= $_; | |
4275 next unless exists $decode{$c}; | |
4276 last if $decode{$c} eq '_eos'; | |
4277 | |
4278 $ret .= $decode{$c}; | |
4279 $c = ''; | |
4280 } | |
4281 | |
4282 return $ret; | |
4283 } | |
4284 | |
4285 ############################################################################### | |
4286 | |
4287 sub read_body_file { | |
4288 my ($path) = @_; | |
4289 open FILE, $path or return "$!"; | |
4290 local $/; | |
4291 my $content = <FILE>; | |
4292 close FILE; | |
4293 return $content; | |
4294 } | |
4295 | 1103 |
4296 sub gunzip_like { | 1104 sub gunzip_like { |
4297 my ($in, $re, $name) = @_; | 1105 my ($in, $re, $name) = @_; |
4298 | 1106 |
4299 SKIP: { | 1107 SKIP: { |
4308 like($out, $re, $name); | 1116 like($out, $re, $name); |
4309 } | 1117 } |
4310 } | 1118 } |
4311 | 1119 |
4312 ############################################################################### | 1120 ############################################################################### |
4313 | |
4314 # for tests with multiple header fields | |
4315 | |
4316 sub http_daemon { | |
4317 my $server = IO::Socket::INET->new( | |
4318 Proto => 'tcp', | |
4319 LocalHost => '127.0.0.1', | |
4320 LocalPort => 8083, | |
4321 Listen => 5, | |
4322 Reuse => 1 | |
4323 ) | |
4324 or die "Can't create listening socket: $!\n"; | |
4325 | |
4326 local $SIG{PIPE} = 'IGNORE'; | |
4327 | |
4328 while (my $client = $server->accept()) { | |
4329 $client->autoflush(1); | |
4330 | |
4331 my $headers = ''; | |
4332 my $uri = ''; | |
4333 | |
4334 while (<$client>) { | |
4335 $headers .= $_; | |
4336 last if (/^\x0d?\x0a?$/); | |
4337 } | |
4338 | |
4339 next if $headers eq ''; | |
4340 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; | |
4341 | |
4342 if ($uri eq '/cookie') { | |
4343 | |
4344 my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig; | |
4345 $cookie2 = '' unless defined $cookie2; | |
4346 | |
4347 my ($cookie_a, $cookie_c) = ('', ''); | |
4348 $cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i; | |
4349 $cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i; | |
4350 | |
4351 print $client <<EOF; | |
4352 HTTP/1.1 200 OK | |
4353 Connection: close | |
4354 X-Sent-Cookie: $cookie | |
4355 X-Sent-Cookie2: $cookie2 | |
4356 X-Sent-Cookie-a: $cookie_a | |
4357 X-Sent-Cookie-c: $cookie_c | |
4358 | |
4359 EOF | |
4360 | |
4361 } elsif ($uri eq '/set-cookie') { | |
4362 | |
4363 print $client <<EOF; | |
4364 HTTP/1.1 200 OK | |
4365 Connection: close | |
4366 Set-Cookie: a=b | |
4367 Set-Cookie: c=d | |
4368 | |
4369 EOF | |
4370 | |
4371 } | |
4372 | |
4373 } continue { | |
4374 close $client; | |
4375 } | |
4376 } | |
4377 | |
4378 ############################################################################### |