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 ###############################################################################