comparison h2.t @ 646:843a74ff43bc

Tests: HTTP/2 tests.
author Sergey Kandaurov <pluknet@nginx.com>
date Mon, 17 Aug 2015 16:39:02 +0300
parents
children 4e36550410b3
comparison
equal deleted inserted replaced
645:ed103c38b115 646:843a74ff43bc
1 #!/usr/bin/perl
2
3 # (C) Sergey Kandaurov
4 # (C) Nginx, Inc.
5
6 # Tests for HTTP/2 protocol [RFC7540].
7
8 ###############################################################################
9
10 use warnings;
11 use strict;
12
13 use Test::More;
14
15 use IO::Select;
16 use Socket qw/ CRLF /;
17
18 BEGIN { use FindBin; chdir($FindBin::Bin); }
19
20 use lib 'lib';
21 use Test::Nginx;
22
23 ###############################################################################
24
25 select STDERR; $| = 1;
26 select STDOUT; $| = 1;
27
28 my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 proxy cache/)
29 ->has(qw/limit_conn rewrite realip shmem/)->plan(139);
30
31 $t->write_file_expand('nginx.conf', <<'EOF');
32
33 %%TEST_GLOBALS%%
34
35 daemon off;
36
37 events {
38 }
39
40 http {
41 %%TEST_GLOBALS_HTTP%%
42
43 proxy_cache_path %%TESTDIR%%/cache keys_zone=NAME:1m;
44 limit_conn_zone $binary_remote_addr zone=conn:1m;
45 output_buffers 2 16k;
46
47 server {
48 listen 127.0.0.1:8080 http2;
49 listen 127.0.0.1:8081;
50 listen 127.0.0.1:8082 proxy_protocol http2;
51 listen 127.0.0.1:8084 http2 ssl;
52 server_name localhost;
53
54 ssl_certificate_key localhost.key;
55 ssl_certificate localhost.crt;
56
57 location / {
58 add_header X-Header X-Foo;
59 add_header X-Sent-Foo $http_x_foo;
60 add_header X-Referer $http_referer;
61 return 200 'body';
62 }
63 location /t {
64 }
65 location /t3.html {
66 limit_conn conn 1;
67 }
68 location /gzip.html {
69 gzip on;
70 gzip_min_length 0;
71 alias %%TESTDIR%%/t2.html;
72 }
73 location /pp {
74 set_real_ip_from 127.0.0.1/32;
75 real_ip_header proxy_protocol;
76 alias %%TESTDIR%%/t2.html;
77 add_header X-PP $remote_addr;
78 }
79 location /redirect {
80 error_page 405 /;
81 return 405;
82 }
83 location /return301 {
84 return 301;
85 }
86 location /return301_absolute {
87 return 301 text;
88 }
89 location /return301_relative {
90 return 301 /;
91 }
92 location /proxy/ {
93 add_header X-UC-a $upstream_cookie_a;
94 add_header X-UC-c $upstream_cookie_c;
95 proxy_pass http://127.0.0.1:8083/;
96 proxy_cache NAME;
97 proxy_cache_valid 1m;
98 }
99 location /proxy2/ {
100 add_header X-Body "$request_body";
101 proxy_pass http://127.0.0.1:8081/;
102 proxy_cache NAME;
103 proxy_cache_valid 1m;
104 }
105 location /proxy_buffering_off {
106 proxy_pass http://127.0.0.1:8081/;
107 proxy_cache NAME;
108 proxy_cache_valid 1m;
109 proxy_buffering off;
110 }
111 location /set-cookie {
112 add_header Set-Cookie a=b;
113 add_header Set-Cookie c=d;
114 return 200;
115 }
116 location /cookie {
117 add_header X-Cookie $http_cookie;
118 add_header X-Cookie-a $cookie_a;
119 add_header X-Cookie-c $cookie_c;
120 return 200;
121 }
122 }
123
124 server {
125 listen 127.0.0.1:8085 http2;
126 server_name localhost;
127 return 200 first;
128 }
129
130 server {
131 listen 127.0.0.1:8085 http2;
132 server_name localhost2;
133 return 200 second;
134 }
135 }
136
137 EOF
138
139 $t->write_file('openssl.conf', <<EOF);
140 [ req ]
141 default_bits = 2048
142 encrypt_key = no
143 distinguished_name = req_distinguished_name
144 [ req_distinguished_name ]
145 EOF
146
147 my $d = $t->testdir();
148
149 foreach my $name ('localhost') {
150 system('openssl req -x509 -new '
151 . "-config '$d/openssl.conf' -subj '/CN=$name/' "
152 . "-out '$d/$name.crt' -keyout '$d/$name.key' "
153 . ">>$d/openssl.out 2>&1") == 0
154 or die "Can't create certificate for $name: $!\n";
155 }
156
157 $t->run_daemon(\&http_daemon);
158 $t->run()->waitforsocket('127.0.0.1:8083');
159
160 # file size is slightly beyond initial window size: 2**16 + 80 bytes
161
162 $t->write_file('t1.html',
163 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202)));
164 $t->write_file('tbig.html',
165 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202)));
166
167 $t->write_file('t2.html', 'SEE-THIS');
168 $t->write_file('t3.html', 'SEE-THIS');
169
170 my %cframe = (
171 0 => { name => 'DATA', value => \&data },
172 1 => { name => 'HEADERS', value => \&headers },
173 # 2 => { name => 'PRIORITY', value => \&priority },
174 3 => { name => 'RST_STREAM', value => \&rst_stream },
175 4 => { name => 'SETTINGS', value => \&settings },
176 # 5 => { name => 'PUSH_PROIMSE', value => \&push_promise },
177 6 => { name => 'PING', value => \&ping },
178 7 => { name => 'GOAWAY', value => \&goaway },
179 8 => { name => 'WINDOW_UPDATE', value => \&window_update },
180 # 9 => { name => 'CONTINUATION', value => \&continuation },
181 );
182
183 ###############################################################################
184
185 # SETTINGS
186
187 my $sess = new_session();
188 my $frames = h2_read($sess, all => [
189 { type => 'WINDOW_UPDATE' },
190 { type => 'SETTINGS'}
191 ]);
192
193 my ($frame) = grep { $_->{type} eq 'WINDOW_UPDATE' } @$frames;
194 ok($frame, 'WINDOW_UPDATE frame');
195 is($frame->{flags}, 0, 'WINDOW_UPDATE zero flags');
196 is($frame->{sid}, 0, 'WINDOW_UPDATE zero sid');
197 is($frame->{length}, 4, 'WINDOW_UPDATE fixed length');
198
199 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
200 ok($frame, 'SETTINGS frame');
201 is($frame->{flags}, 0, 'SETTINGS flags');
202
203 h2_settings($sess, 1);
204 h2_settings($sess, 0);
205
206 $frames = h2_read($sess, all => [{ type => 'SETTINGS' }]);
207
208 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
209 ok($frame, 'SETTINGS frame ack');
210 is($frame->{flags}, 1, 'SETTINGS flags ack');
211
212 # PING
213
214 h2_ping($sess, 'SEE-THIS');
215 $frames = h2_read($sess, all => [{ type => 'PING' }]);
216
217 ($frame) = grep { $_->{type} eq "PING" } @$frames;
218 ok($frame, 'PING frame');
219 is($frame->{value}, 'SEE-THIS', 'PING payload');
220 is($frame->{flags}, 1, 'PING flags ack');
221
222 # GET
223
224 my $sid = new_stream($sess);
225 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
226
227 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
228 ok($frame, 'HEADERS frame');
229 is($frame->{sid}, $sid, 'HEADERS stream');
230 is($frame->{headers}->{':status'}, 200, 'HEADERS status');
231 is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEADERS header');
232
233 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
234 ok($frame, 'DATA frame');
235 is($frame->{length}, length 'body', 'DATA length');
236 is($frame->{data}, 'body', 'DATA payload');
237
238 # GET in the new stream on same connection
239
240 $sid = new_stream($sess);
241 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
242
243 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
244 is($frame->{sid}, $sid, 'HEADERS stream 2');
245 is($frame->{headers}->{':status'}, 200, 'HEADERS status 2');
246 is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEADERS header 2');
247
248 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
249 ok($frame, 'DATA frame 2');
250 is($frame->{sid}, $sid, 'HEADERS stream 2');
251 is($frame->{length}, length 'body', 'DATA length 2');
252 is($frame->{data}, 'body', 'DATA payload 2');
253
254 # various HEADERS compression/encoding, see hpack() for mode details
255
256 # 6.1. Indexed Header Field Representation
257
258 $sid = new_stream($sess, { headers => [
259 { name => ':method', value => 'GET', mode => 0 },
260 { name => ':scheme', value => 'http', mode => 0 },
261 { name => ':path', value => '/', mode => 0 }]});
262 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
263
264 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
265 is($frame->{headers}->{':status'}, 200, 'indexed header field');
266
267 # 6.2.1. Literal Header Field with Incremental Indexing
268
269 $sid = new_stream($sess, { headers => [
270 { name => ':method', value => 'GET', mode => 1, huff => 0 },
271 { name => ':scheme', value => 'http', mode => 1, huff => 0 },
272 { name => ':path', value => '/', mode => 1, huff => 0 }]});
273 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
274
275 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
276 is($frame->{headers}->{':status'}, 200, 'literal with indexing');
277
278 $sid = new_stream($sess, { headers => [
279 { name => ':method', value => 'GET', mode => 1, huff => 1 },
280 { name => ':scheme', value => 'http', mode => 1, huff => 1 },
281 { name => ':path', value => '/', mode => 1, huff => 1 }]});
282 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
283
284 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
285 is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman');
286
287 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name
288
289 $sid = new_stream($sess, { headers => [
290 { name => ':method', value => 'GET', mode => 2, huff => 0 },
291 { name => ':scheme', value => 'http', mode => 2, huff => 0 },
292 { name => ':path', value => '/', mode => 2, huff => 0 }]});
293 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
294
295 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
296 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new');
297
298 $sid = new_stream($sess, { headers => [
299 { name => ':method', value => 'GET', mode => 2, huff => 1 },
300 { name => ':scheme', value => 'http', mode => 2, huff => 1 },
301 { name => ':path', value => '/', mode => 2, huff => 1 }]});
302 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
303
304 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
305 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman');
306
307 # 6.2.2. Literal Header Field without Indexing
308
309 $sid = new_stream($sess, { headers => [
310 { name => ':method', value => 'GET', mode => 3, huff => 0 },
311 { name => ':scheme', value => 'http', mode => 3, huff => 0 },
312 { name => ':path', value => '/', mode => 3, huff => 0 }]});
313 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
314
315 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
316 is($frame->{headers}->{':status'}, 200, 'literal without indexing');
317
318 $sid = new_stream($sess, { headers => [
319 { name => ':method', value => 'GET', mode => 3, huff => 1 },
320 { name => ':scheme', value => 'http', mode => 3, huff => 1 },
321 { name => ':path', value => '/', mode => 3, huff => 1 }]});
322 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
323
324 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
325 is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman');
326
327 # 6.2.2. Literal Header Field without Indexing -- New Name
328
329 $sid = new_stream($sess, { headers => [
330 { name => ':method', value => 'GET', mode => 4, huff => 0 },
331 { name => ':scheme', value => 'http', mode => 4, huff => 0 },
332 { name => ':path', value => '/', mode => 4, huff => 0 }]});
333 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
334
335 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
336 is($frame->{headers}->{':status'}, 200, 'literal without indexing - new');
337
338 $sid = new_stream($sess, { headers => [
339 { name => ':method', value => 'GET', mode => 4, huff => 1 },
340 { name => ':scheme', value => 'http', mode => 4, huff => 1 },
341 { name => ':path', value => '/', mode => 4, huff => 1 }]});
342 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
343
344 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
345 is($frame->{headers}->{':status'}, 200, 'literal without indexing - new huffman');
346
347 # 6.2.3. Literal Header Field Never Indexed
348
349 $sid = new_stream($sess, { headers => [
350 { name => ':method', value => 'GET', mode => 5, huff => 0 },
351 { name => ':scheme', value => 'http', mode => 5, huff => 0 },
352 { name => ':path', value => '/', mode => 5, huff => 0 }]});
353 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
354
355 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
356 is($frame->{headers}->{':status'}, 200, 'literal never indexed');
357
358 $sid = new_stream($sess, { headers => [
359 { name => ':method', value => 'GET', mode => 5, huff => 1 },
360 { name => ':scheme', value => 'http', mode => 5, huff => 1 },
361 { name => ':path', value => '/', mode => 5, huff => 1 }]});
362 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
363
364 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
365 is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman');
366
367 # 6.2.2. Literal Header Field Never Indexed -- New Name
368
369 $sid = new_stream($sess, { headers => [
370 { name => ':method', value => 'GET', mode => 6, huff => 0 },
371 { name => ':scheme', value => 'http', mode => 6, huff => 0 },
372 { name => ':path', value => '/', mode => 6, huff => 0 }]});
373 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
374
375 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
376 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new');
377
378 $sid = new_stream($sess, { headers => [
379 { name => ':method', value => 'GET', mode => 6, huff => 1 },
380 { name => ':scheme', value => 'http', mode => 6, huff => 1 },
381 { name => ':path', value => '/', mode => 6, huff => 1 }]});
382 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
383
384 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
385 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman');
386
387 # reuse literal with indexing
388
389 $sess = new_session();
390 $sid = new_stream($sess, { headers => [
391 { name => ':method', value => 'GET', mode => 0 },
392 { name => ':scheme', value => 'http', mode => 0 },
393 { name => ':path', value => '/', mode => 0 },
394 { name => 'referer', value => 'foo', mode => 1 }]});
395 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
396
397 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
398 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new');
399
400 $sid = new_stream($sess, { headers => [
401 { name => ':method', value => 'GET', mode => 0 },
402 { name => ':scheme', value => 'http', mode => 0 },
403 { name => ':path', value => '/', mode => 0 },
404 { name => 'referer', value => 'foo', mode => 0 }]});
405 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
406
407 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
408 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed');
409
410 $sid = new_stream($sess, { headers => [
411 { name => ':method', value => 'GET', mode => 0 },
412 { name => ':scheme', value => 'http', mode => 0 },
413 { name => ':path', value => '/', mode => 0 },
414 { name => 'x-foo', value => 'X-Bar', mode => 2 }]});
415 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
416
417 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
418 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new');
419
420 # reuse literal with indexing - reused name
421
422 $sid = new_stream($sess, { headers => [
423 { name => ':method', value => 'GET', mode => 0 },
424 { name => ':scheme', value => 'http', mode => 0 },
425 { name => ':path', value => '/', mode => 0 },
426 { name => 'x-foo', value => 'X-Bar', mode => 0 }]});
427 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
428
429 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
430 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed');
431
432 # reuse literal with indexing - reused value with moved index
433
434 $sid = new_stream($sess, { headers => [
435 { name => ':method', value => 'GET', mode => 0 },
436 { name => ':scheme', value => 'http', mode => 0 },
437 { name => ':path', value => '/', mode => 0 },
438 { name => 'referer', value => 'foo', mode => 0 }]});
439 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
440
441 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
442 is($frame->{headers}->{'x-referer'}, 'foo', 'moved index in dynamic table');
443
444 # 6.3. Dynamic Table Size Update
445
446 # remove one of two indexed headers from the dynamic table
447 # 'x-foo' has index 0, and 'referer' has index 1
448
449 $sid = new_stream($sess, { table_size => 61, headers => [
450 { name => ':method', value => 'GET', mode => 0 },
451 { name => ':scheme', value => 'http', mode => 0 },
452 { name => ':path', value => '/', mode => 0 },
453 { name => 'x-foo', value => 'X-Bar', mode => 0 }]});
454 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
455
456 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
457 isnt($frame, undef, 'updated table size - remaining index');
458
459 $sid = new_stream($sess, { headers => [
460 { name => ':method', value => 'GET', mode => 0 },
461 { name => ':scheme', value => 'http', mode => 0 },
462 { name => ':path', value => '/', mode => 0 },
463 { name => 'referer', value => 'foo', mode => 0 }]});
464 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
465
466 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
467 is($frame, undef, 'updated table size - invalid index');
468
469 # 5.4.1. Connection Error Handling
470 # An endpoint that encounters a connection error SHOULD first send a
471 # GOAWAY frame <..>
472
473 TODO: {
474 local $TODO = 'not yet';
475
476 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
477 isnt($frame, undef, 'updated table size - invalid index GOAWAY');
478
479 }
480
481 # HEAD
482
483 $sess = new_session();
484 $sid = new_stream($sess, { method => 'HEAD' });
485 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
486
487 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
488 is($frame->{sid}, $sid, 'HEAD - HEADERS');
489 is($frame->{headers}->{':status'}, 200, 'HEAD - HEADERS status');
490 is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEAD - HEADERS header');
491
492 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
493 is($frame, undef, 'HEAD - no body');
494
495 # GET with PROXY protocol
496
497 my $proxy = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF;
498 $sess = new_session(8082, proxy => $proxy);
499 $sid = new_stream($sess, { path => '/pp' });
500 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
501
502 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
503 ok($frame, 'PROXY HEADERS frame');
504 is($frame->{headers}->{'x-pp'}, '192.0.2.1', 'PROXY remote addr');
505
506 # range filter
507
508 $sess = new_session();
509 $sid = new_stream($sess, { headers => [
510 { name => ':method', value => 'GET', mode => 0 },
511 { name => ':scheme', value => 'http', mode => 0 },
512 { name => ':path', value => '/t1.html', mode => 1 },
513 { name => 'range', value => 'bytes=10-19', mode => 1 }]});
514 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
515
516 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
517 is($frame->{headers}->{':status'}, 206, 'range - HEADERS status');
518
519 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
520 is($frame->{length}, 10, 'range - DATA length');
521 is($frame->{data}, '002XXXX000', 'range - DATA payload');
522
523 # CONTINUATION
524
525 $sid = new_stream($sess, { continuation => 1, headers => [
526 { name => ':method', value => 'HEAD', mode => 1 },
527 { name => ':scheme', value => 'http', mode => 0 },
528 { name => ':path', value => '/', mode => 0 }]});
529 h2_continue($sess, $sid, { continuation => 1, headers => [
530 { name => 'x-foo', value => 'X-Bar', mode => 2 }]});
531 h2_continue($sess, $sid, { headers => [
532 { name => 'referer', value => 'foo', mode => 2 }]});
533 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
534
535 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
536 is($frame, undef, 'CONTINUATION - fragment 1');
537
538 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
539 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'CONTINUATION - fragment 2');
540 is($frame->{headers}->{'x-referer'}, 'foo', 'CONTINUATION - fragment 3');
541
542 # frame padding
543
544 $sess = new_session();
545 $sid = new_stream($sess, { padding => 42, headers => [
546 { name => ':method', value => 'GET', mode => 0 },
547 { name => ':scheme', value => 'http', mode => 0 },
548 { name => ':path', value => '/', mode => 0 }]});
549 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
550
551 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
552 is($frame->{headers}->{':status'}, 200, 'padding - HEADERS status');
553
554 $sid = new_stream($sess, { headers => [
555 { name => ':method', value => 'GET', mode => 0 },
556 { name => ':scheme', value => 'http', mode => 0 },
557 { name => ':path', value => '/', mode => 0 }]});
558 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
559
560 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
561 is($frame->{headers}->{':status'}, 200, 'padding - next stream');
562
563 # request header field with multiple values
564
565 # 8.1.2.5. Compressing the Cookie Header Field
566 # To allow for better compression efficiency, the Cookie header field
567 # MAY be split into separate header fields <..>.
568
569 $sess = new_session();
570 $sid = new_stream($sess, { headers => [
571 { name => ':method', value => 'GET', mode => 0 },
572 { name => ':scheme', value => 'http', mode => 0 },
573 { name => ':path', value => '/cookie', mode => 2 },
574 { name => 'cookie', value => 'a=b', mode => 2},
575 { name => 'cookie', value => 'c=d', mode => 2}]});
576 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
577
578 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
579 is($frame->{headers}->{'x-cookie-a'}, 'b',
580 'multiple request header fields - cookie');
581 is($frame->{headers}->{'x-cookie-c'}, 'd',
582 'multiple request header fields - cookie 2');
583 is($frame->{headers}->{'x-cookie'}, 'a=b; c=d',
584 'multiple request header fields - semi-colon');
585
586 # request header field with multiple values to HTTP backend
587
588 # 8.1.2.5. Compressing the Cookie Header Field
589 # these MUST be concatenated into a single octet string
590 # using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
591 # before being passed into a non-HTTP/2 context, such as an HTTP/1.1
592 # connection <..>
593
594 $sess = new_session();
595 $sid = new_stream($sess, { headers => [
596 { name => ':method', value => 'GET', mode => 0 },
597 { name => ':scheme', value => 'http', mode => 0 },
598 { name => ':path', value => '/proxy/cookie', mode => 2 },
599 { name => 'cookie', value => 'a=b', mode => 2 },
600 { name => 'cookie', value => 'c=d', mode => 2 }]});
601 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
602
603 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
604 is($frame->{headers}->{'x-cookie'}, 'a=b; c=d',
605 'multiple request header fields proxied - semi-colon');
606
607 # response header field with multiple values
608
609 $sess = new_session();
610 $sid = new_stream($sess, { path => '/set-cookie' });
611 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
612
613 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
614 is($frame->{headers}->{'set-cookie'}[0], 'a=b',
615 'multiple response header fields - cookie');
616 is($frame->{headers}->{'set-cookie'}[1], 'c=d',
617 'multiple response header fields - cookie 2');
618
619 # response header field with multiple values from HTTP backend
620
621 $sess = new_session();
622 $sid = new_stream($sess, { path => '/proxy/set-cookie' });
623 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
624
625 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
626 is($frame->{headers}->{'set-cookie'}[0], 'a=b',
627 'multiple response header proxied - cookie');
628 is($frame->{headers}->{'set-cookie'}[1], 'c=d',
629 'multiple response header proxied - cookie 2');
630 is($frame->{headers}->{'x-uc-a'}, 'b',
631 'multiple response header proxied - upstream cookie');
632 is($frame->{headers}->{'x-uc-c'}, 'd',
633 'multiple response header proxied - upstream cookie 2');
634
635 # internal redirect
636
637 $sess = new_session();
638 $sid = new_stream($sess, { path => '/redirect' });
639 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
640
641 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
642 is($frame->{headers}->{':status'}, 405, 'redirect - HEADERS');
643
644 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
645 ok($frame, 'redirect - DATA');
646 is($frame->{data}, 'body', 'redirect - DATA payload');
647
648 # return 301 with absolute URI
649
650 $sess = new_session();
651 $sid = new_stream($sess, { path => '/return301_absolute' });
652 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
653
654 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
655 is($frame->{headers}->{':status'}, 301, 'return 301 absolute - status');
656 is($frame->{headers}->{'location'}, 'text', 'return 301 absolute - location');
657
658 # return 301 with relative URI
659
660 $sess = new_session();
661 $sid = new_stream($sess, { path => '/return301_relative' });
662 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
663
664 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
665 is($frame->{headers}->{':status'}, 301, 'return 301 relative - status');
666 is($frame->{headers}->{'location'}, 'http://127.0.0.1:8080/',
667 'return 301 relative - location');
668
669 # return 301 with relative URI and ':authority' request header field
670
671 $sess = new_session();
672 $sid = new_stream($sess, { headers => [
673 { name => ':method', value => 'GET', mode => 0 },
674 { name => ':scheme', value => 'http', mode => 0 },
675 { name => ':path', value => '/return301_relative', mode => 2 },
676 { name => ':authority', value => 'localhost', mode => 2 }]});
677 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
678
679 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
680 is($frame->{headers}->{':status'}, 301,
681 'return 301 relative - authority - status');
682 is($frame->{headers}->{'location'}, 'http://localhost:8080/',
683 'return 301 relative - authority - location');
684
685 # return 301 with relative URI and 'host' request header field
686
687 $sess = new_session();
688 $sid = new_stream($sess, { headers => [
689 { name => ':method', value => 'GET', mode => 0 },
690 { name => ':scheme', value => 'http', mode => 0 },
691 { name => ':path', value => '/return301_relative', mode => 2 },
692 { name => 'host', value => 'localhost', mode => 2 }]});
693 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
694
695 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
696 is($frame->{headers}->{':status'}, 301,
697 'return 301 relative - host - status');
698 is($frame->{headers}->{'location'}, 'http://localhost:8080/',
699 'return 301 relative - host - location');
700
701 # virtual host
702
703 $sess = new_session(8085);
704 $sid = new_stream($sess, { headers => [
705 { name => ':method', value => 'GET', mode => 0 },
706 { name => ':scheme', value => 'http', mode => 0 },
707 { name => ':path', value => '/', mode => 0 },
708 { name => 'host', value => 'localhost', mode => 2 }]});
709 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
710
711 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
712 is($frame->{headers}->{':status'}, 200,
713 'virtual host - host - status');
714
715 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
716 is($frame->{data}, 'first', 'virtual host - host - DATA');
717
718 $sid = new_stream($sess, { headers => [
719 { name => ':method', value => 'GET', mode => 0 },
720 { name => ':scheme', value => 'http', mode => 0 },
721 { name => ':path', value => '/', mode => 0 },
722 { name => ':authority', value => 'localhost', mode => 2 }]});
723 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
724
725 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
726 is($frame->{headers}->{':status'}, 200,
727 'virtual host - authority - status');
728
729 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
730 is($frame->{data}, 'first', 'virtual host - authority - DATA');
731
732 # virtual host - second
733
734 $sid = new_stream($sess, { headers => [
735 { name => ':method', value => 'GET', mode => 0 },
736 { name => ':scheme', value => 'http', mode => 0 },
737 { name => ':path', value => '/', mode => 0 },
738 { name => 'host', value => 'localhost2', mode => 2 }]});
739 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
740
741 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
742 is($frame->{headers}->{':status'}, 200,
743 'virtual host 2 - host - status');
744
745 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
746 is($frame->{data}, 'second', 'virtual host 2 - host - DATA');
747
748 $sid = new_stream($sess, { headers => [
749 { name => ':method', value => 'GET', mode => 0 },
750 { name => ':scheme', value => 'http', mode => 0 },
751 { name => ':path', value => '/', mode => 0 },
752 { name => ':authority', value => 'localhost2', mode => 2 }]});
753 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
754
755 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
756 is($frame->{headers}->{':status'}, 200,
757 'virtual host 2 - authority - status');
758
759 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
760 is($frame->{data}, 'second', 'virtual host 2 - authority - DATA');
761
762 # gzip tests for internal nginx version
763
764 $sess = new_session();
765 $sid = new_stream($sess, { headers => [
766 { name => ':method', value => 'GET', mode => 0 },
767 { name => ':scheme', value => 'http', mode => 0 },
768 { name => ':path', value => '/gzip.html' },
769 { name => 'accept-encoding', value => 'gzip' }]});
770 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
771
772 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
773 is($frame->{headers}->{'content-encoding'}, 'gzip', 'gzip - encoding');
774
775 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
776 is(gunzip($frame->{data}), 'SEE-THIS', 'gzip - DATA');
777
778 # simple proxy cache test
779
780 $sess = new_session();
781 $sid = new_stream($sess, { path => '/proxy2/t2.html?2' });
782 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
783
784 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
785 is($frame->{headers}->{':status'}, '200', 'proxy cache');
786
787 my $etag = $frame->{headers}->{'etag'};
788
789 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
790 is($frame->{length}, length 'SEE-THIS', 'proxy cache - DATA');
791 is($frame->{data}, 'SEE-THIS', 'proxy cache - DATA payload');
792
793 $sid = new_stream($sess, { headers => [
794 { name => ':method', value => 'GET', mode => 0 },
795 { name => ':scheme', value => 'http', mode => 0 },
796 { name => ':path', value => '/proxy2/t2.html?2' },
797 { name => 'if-none-match', value => $etag }]});
798 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
799
800 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
801 is($frame->{headers}->{':status'}, 304, 'proxy cache conditional');
802
803 # HEADERS could be received with fin, followed by DATA
804
805 $sess = new_session();
806 $sid = new_stream($sess, { path => '/proxy2/t2.html', method => 'HEAD' });
807
808 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
809 push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
810 ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body');
811
812 # HEAD on empty cache with proxy_buffering off
813
814 $sess = new_session();
815 $sid = new_stream($sess,
816 { path => '/proxy_buffering_off/t2.html?1', method => 'HEAD' });
817
818 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
819 push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
820 ok(!grep ({ $_->{type} eq "DATA" } @$frames),
821 'proxy cache HEAD buffering off - no body');
822
823 # request body (uses proxied response)
824
825 $sess = new_session();
826 $sid = new_stream($sess, { path => '/proxy2/t2.html', body => 'TEST' });
827 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
828
829 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
830 is($frame->{headers}->{'x-body'}, 'TEST', 'request body');
831
832 # request body with padding (uses proxied response)
833
834 $sess = new_session();
835 $sid = new_stream($sess,
836 { path => '/proxy2/t2.html', body => 'TEST', body_padding => 42 });
837 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
838
839 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
840 is($frame->{headers}->{'x-body'}, 'TEST', 'request body with padding');
841
842 $sid = new_stream($sess);
843 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
844
845 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
846 is($frame->{headers}->{':status'}, '200', 'request body with padding - next');
847
848 # initial window size, client side
849
850 # 6.9.2. Initial Flow-Control Window Size
851 # When an HTTP/2 connection is first established, new streams are
852 # created with an initial flow-control window size of 65,535 octets.
853 # The connection flow-control window is also 65,535 octets.
854
855 $sess = new_session();
856 $sid = new_stream($sess, { path => '/t1.html' });
857 $frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
858
859 # with the default http2_chunk_size, data is divided into 8 data frames
860
861 my @data = grep { $_->{type} eq "DATA" } @$frames;
862 my $lengths = join ' ', map { $_->{length} } @data;
863 is($lengths, '8192 8192 8192 8192 8192 8192 8192 8191',
864 'iws - stream blocked on initial window size');
865
866 h2_ping($sess, 'SEE-THIS');
867 $frames = h2_read($sess, all => [{ type => 'PING' }]);
868
869 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
870 ok($frame, 'iws - PING not blocked');
871
872 h2_window($sess, 2**16, $sid);
873 $frames = h2_read($sess);
874 is(@$frames, 0, 'iws - updated stream window');
875
876 h2_window($sess, 2**16);
877 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
878
879 # with the default output_buffers, the remain data would be split
880 # on buffers boundary into separate data frames
881
882 @data = grep { $_->{type} eq "DATA" } @$frames;
883 my $sum = eval join '+', map { $_->{length} } @data;
884 is($sum, 81, 'iws - updated connection window');
885
886 # SETTINGS (initial window size, client side)
887
888 # 6.9.2. Initial Flow-Control Window Size
889 # Both endpoints can adjust the initial window size for new streams by
890 # including a value for SETTINGS_INITIAL_WINDOW_SIZE in the SETTINGS
891 # frame that forms part of the connection preface. The connection
892 # flow-control window can only be changed using WINDOW_UPDATE frames.
893
894 $sess = new_session();
895 h2_settings($sess, 0, 0x4 => 2**17);
896 h2_window($sess, 2**17);
897
898 $sid = new_stream($sess, { path => '/t1.html' });
899 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
900
901 @data = grep { $_->{type} eq "DATA" } @$frames;
902 $sum = eval join '+', map { $_->{length} } @data;
903 is($sum, 2**16 + 80, 'iws - increased');
904
905 # probe for negative available space in a flow control window
906
907 # 6.9.2. Initial Flow-Control Window Size
908 # A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available
909 # space in a flow-control window to become negative. A sender MUST
910 # track the negative flow-control window and MUST NOT send new flow-
911 # controlled frames until it receives WINDOW_UPDATE frames that cause
912 # the flow-control window to become positive.
913
914 $sess = new_session();
915 $sid = new_stream($sess, { path => '/t1.html' });
916 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
917
918 h2_window($sess, 1);
919 h2_settings($sess, 0, 0x4 => 42);
920 h2_window($sess, 1024, $sid);
921
922 $frames = h2_read($sess, all => [{ type => 'SETTINGS' }]);
923
924 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
925 ok($frame, 'negative window - SETTINGS frame ack');
926 is($frame->{flags}, 1, 'negative window - SETTINGS flags ack');
927
928 ($frame) = grep { $_->{type} ne 'SETTINGS' } @$frames;
929 is($frame, undef, 'negative window - no data');
930
931 # predefined window size, minus new iws settings, minus window update
932
933 h2_window($sess, 2**16 - 1 - 42 - 1024, $sid);
934
935 $frames = h2_read($sess);
936 is(@$frames, 0, 'zero window - no data');
937
938 h2_window($sess, 1, $sid);
939
940 $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
941 is(@$frames, 1, 'positive window');
942 is(@$frames[0]->{type}, 'DATA', 'positive window - data');
943 is(@$frames[0]->{length}, 1, 'positive window - data length');
944
945 # stream multiplexing + WINDOW_UPDATE
946
947 $sess = new_session();
948 $sid = new_stream($sess, { path => '/t1.html' });
949 $frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
950
951 @data = grep { $_->{type} eq "DATA" } @$frames;
952 $sum = eval join '+', map { $_->{length} } @data;
953 is($sum, 2**16 - 1, 'multiple - stream1 data');
954
955 my $sid2 = new_stream($sess, { path => '/t1.html' });
956 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
957
958 @data = grep { $_->{type} eq "DATA" } @$frames;
959 is(@data, 0, 'multiple - stream2 no data');
960
961 h2_window($sess, 2**17, $sid);
962 h2_window($sess, 2**17, $sid2);
963 h2_window($sess, 2**17);
964
965 $frames = h2_read($sess, all => [
966 { sid => $sid, fin => 1 },
967 { sid => $sid2, fin => 1 }
968 ]);
969
970 @data = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames;
971 $sum = eval join '+', map { $_->{length} } @data;
972 is($sum, 81, 'multiple - stream1 remain data');
973
974 @data = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames;
975 $sum = eval join '+', map { $_->{length} } @data;
976 is($sum, 2**16 + 80, 'multiple - stream2 full data');
977
978 # stream muliplexing + PRIORITY frames
979
980 $sess = new_session();
981 $sid = new_stream($sess, { path => '/t1.html' });
982 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
983
984 $sid2 = new_stream($sess, { path => '/t2.html' });
985 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
986
987 h2_priority($sess, 0, $sid);
988 h2_priority($sess, 255, $sid2);
989
990 h2_window($sess, 2**17, $sid);
991 h2_window($sess, 2**17, $sid2);
992 h2_window($sess, 2**17);
993
994 $frames = h2_read($sess, all => [
995 { sid => $sid, fin => 1 },
996 { sid => $sid2, fin => 1 }
997 ]);
998
999 @data = grep { $_->{type} eq "DATA" } @$frames;
1000 is(join(' ', map { $_->{sid} } @data), "$sid2 $sid", 'weight - PRIORITY 1');
1001
1002 # and vice versa
1003
1004 $sess = new_session();
1005 $sid = new_stream($sess, { path => '/t1.html' });
1006 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1007
1008 $sid2 = new_stream($sess, { path => '/t2.html' });
1009 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1010
1011 h2_priority($sess, 255, $sid);
1012 h2_priority($sess, 0, $sid2);
1013
1014 h2_window($sess, 2**17, $sid);
1015 h2_window($sess, 2**17, $sid2);
1016 h2_window($sess, 2**17);
1017
1018 $frames = h2_read($sess, all => [
1019 { sid => $sid, fin => 1 },
1020 { sid => $sid2, fin => 1 }
1021 ]);
1022
1023 @data = grep { $_->{type} eq "DATA" } @$frames;
1024 is(join(' ', map { $_->{sid} } @data), "$sid $sid2", 'weight - PRIORITY 2');
1025
1026 # stream muliplexing + HEADERS PRIORITY flag
1027
1028 $sess = new_session();
1029 $sid = new_stream($sess, { path => '/t1.html', prio => 0 });
1030 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1031
1032 $sid2 = new_stream($sess, { path => '/t2.html', prio => 255 });
1033 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1034
1035 h2_window($sess, 2**17, $sid);
1036 h2_window($sess, 2**17, $sid2);
1037 h2_window($sess, 2**17);
1038
1039 $frames = h2_read($sess, all => [
1040 { sid => $sid, fin => 1 },
1041 { sid => $sid2, fin => 1 }
1042 ]);
1043
1044 @data = grep { $_->{type} eq "DATA" } @$frames;
1045 my $sids = join ' ', map { $_->{sid} } @data;
1046 is($sids, "$sid2 $sid", 'weight - HEADERS PRIORITY 1');
1047
1048 # and vice versa
1049
1050 $sess = new_session();
1051 $sid = new_stream($sess, { path => '/t1.html', prio => 255 });
1052 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1053
1054 $sid2 = new_stream($sess, { path => '/t2.html', prio => 0 });
1055 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1056
1057 h2_window($sess, 2**17, $sid);
1058 h2_window($sess, 2**17, $sid2);
1059 h2_window($sess, 2**17);
1060
1061 $frames = h2_read($sess, all => [
1062 { sid => $sid, fin => 1 },
1063 { sid => $sid2, fin => 1 }
1064 ]);
1065
1066 @data = grep { $_->{type} eq "DATA" } @$frames;
1067 $sids = join ' ', map { $_->{sid} } @data;
1068 is($sids, "$sid $sid2", 'weight - HEADERS PRIORITY 2');
1069
1070 # 5.3.1. Stream Dependencies
1071
1072 # PRIORITY frame
1073
1074 $sess = new_session();
1075
1076 h2_priority($sess, 16, 3, 0);
1077 h2_priority($sess, 16, 1, 3);
1078
1079 $sid = new_stream($sess, { path => '/t1.html' });
1080 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1081
1082 $sid2 = new_stream($sess, { path => '/t2.html' });
1083 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1084
1085 h2_window($sess, 2**17, $sid);
1086 h2_window($sess, 2**17, $sid2);
1087 h2_window($sess, 2**17);
1088
1089 $frames = h2_read($sess, all => [
1090 { sid => $sid, fin => 1 },
1091 { sid => $sid2, fin => 1 },
1092 ]);
1093
1094 @data = grep { $_->{type} eq "DATA" } @$frames;
1095 $sids = join ' ', map { $_->{sid} } @data;
1096 is($sids, "$sid2 $sid", 'dependency - PRIORITY 1');
1097
1098 # and vice versa
1099
1100 $sess = new_session();
1101
1102 h2_priority($sess, 16, 1, 0);
1103 h2_priority($sess, 16, 3, 1);
1104
1105 $sid = new_stream($sess, { path => '/t1.html' });
1106 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1107
1108 $sid2 = new_stream($sess, { path => '/t2.html' });
1109 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1110
1111 h2_window($sess, 2**17, $sid);
1112 h2_window($sess, 2**17, $sid2);
1113 h2_window($sess, 2**17);
1114
1115 $frames = h2_read($sess, all => [
1116 { sid => $sid, fin => 1 },
1117 { sid => $sid2, fin => 1 },
1118 ]);
1119
1120 @data = grep { $_->{type} eq "DATA" } @$frames;
1121 $sids = join ' ', map { $_->{sid} } @data;
1122 is($sids, "$sid $sid2", 'dependency - PRIORITY 2');
1123
1124 # HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records
1125
1126 $sess = new_session();
1127
1128 h2_priority($sess, 16, 1, 0);
1129 h2_priority($sess, 16, 3, 0);
1130
1131 $sid = new_stream($sess, { path => '/t1.html', dep => 3 });
1132 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1133
1134 $sid2 = new_stream($sess, { path => '/t2.html' });
1135 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1136
1137 h2_window($sess, 2**17, $sid);
1138 h2_window($sess, 2**17, $sid2);
1139 h2_window($sess, 2**17);
1140
1141 $frames = h2_read($sess, all => [
1142 { sid => $sid, fin => 1 },
1143 { sid => $sid2, fin => 1 },
1144 ]);
1145
1146 @data = grep { $_->{type} eq "DATA" } @$frames;
1147 $sids = join ' ', map { $_->{sid} } @data;
1148 is($sids, "$sid2 $sid", 'dependency - HEADERS PRIORITY 1');
1149
1150 # and vice versa
1151
1152 $sess = new_session();
1153
1154 h2_priority($sess, 16, 1, 0);
1155 h2_priority($sess, 16, 3, 0);
1156
1157 $sid = new_stream($sess, { path => '/t1.html' });
1158 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1159
1160 $sid2 = new_stream($sess, { path => '/t2.html', dep => 1 });
1161 h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
1162
1163 h2_window($sess, 2**17, $sid);
1164 h2_window($sess, 2**17, $sid2);
1165 h2_window($sess, 2**17);
1166
1167 $frames = h2_read($sess, all => [
1168 { sid => $sid, fin => 1 },
1169 { sid => $sid2, fin => 1 },
1170 ]);
1171
1172 @data = grep { $_->{type} eq "DATA" } @$frames;
1173 $sids = join ' ', map { $_->{sid} } @data;
1174 is($sids, "$sid $sid2", 'dependency - HEADERS PRIORITY 2');
1175
1176 # limit_conn
1177
1178 $sess = new_session();
1179 h2_settings($sess, 0, 0x4 => 1);
1180
1181 $sid = new_stream($sess, { path => '/t3.html' });
1182 $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
1183
1184 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
1185 is($frame->{headers}->{':status'}, 200, 'limit_conn first stream');
1186
1187 $sid2 = new_stream($sess, { path => '/t3.html' });
1188 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
1189
1190 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
1191 is($frame->{headers}->{':status'}, 503, 'limit_conn rejected');
1192
1193 h2_settings($sess, 0, 0x4 => 2**16);
1194
1195 h2_read($sess, all => [
1196 { sid => $sid, fin => 1 },
1197 { sid => $sid2, fin => 1 }
1198 ]);
1199
1200 # limit_conn + client's RST_STREAM
1201
1202 $sess = new_session();
1203 h2_settings($sess, 0, 0x4 => 1);
1204
1205 $sid = new_stream($sess, { path => '/t3.html' });
1206 $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
1207 h2_rst($sess, $sid, 5);
1208
1209 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
1210 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 1');
1211
1212 $sid2 = new_stream($sess, { path => '/t3.html' });
1213 $frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
1214
1215 TODO: {
1216 local $TODO = 'not yet';
1217
1218 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
1219 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 2');
1220
1221 }
1222
1223
1224 # some invalid cases below
1225
1226 # GOAWAY on SYN_STREAM with even StreamID
1227
1228 TODO: {
1229 local $TODO = 'not yet';
1230
1231 $sess = new_session();
1232 new_stream($sess, { path => '/' }, 2);
1233 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
1234
1235 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
1236 ok($frame, 'even stream - GOAWAY frame');
1237 is($frame->{code}, 1, 'even stream - error code');
1238 is($frame->{sid}, 0, 'even stream - last used stream');
1239
1240 }
1241
1242 # GOAWAY on SYN_STREAM with backward StreamID
1243
1244 TODO: {
1245 local $TODO = 'not yet';
1246
1247 $sess = new_session();
1248 $sid = new_stream($sess, { path => '/' }, 3);
1249 h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
1250
1251 $sid2 = new_stream($sess, { path => '/' }, 1);
1252 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
1253
1254 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
1255 ok($frame, 'backward stream - GOAWAY frame');
1256 is($frame->{code}, 1, 'backward stream - error code');
1257 is($frame->{sid}, $sid, 'backward stream - last used stream');
1258
1259 }
1260
1261 # RST_STREAM on the second SYN_STREAM with same StreamID
1262
1263 TODO: {
1264 local $TODO = 'not yet';
1265
1266 $sess = new_session();
1267 $sid = new_stream($sess, { path => '/' });
1268 h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
1269
1270 $sid2 = new_stream($sess, { path => '/' }, $sid);
1271 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
1272
1273 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
1274 ok($frame, 'dup stream - RST_STREAM frame');
1275 is($frame->{code}, 1, 'dup stream - error code');
1276 is($frame->{sid}, $sid, 'dup stream - stream');
1277
1278 }
1279
1280 # missing mandatory request header ':scheme'
1281
1282 TODO: {
1283 local $TODO = 'not yet';
1284
1285 $sess = new_session();
1286 $sid = new_stream($sess, { headers => [
1287 { name => ':method', value => 'GET', mode => 0 },
1288 { name => ':path', value => '/', mode => 0 }]});
1289 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
1290
1291 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
1292 is($frame->{headers}->{':status'}, 400, 'incomplete headers');
1293
1294 }
1295
1296 # GOAWAY - force closing a connection by server
1297
1298 $sid = new_stream($sess, { path => 't1.html' });
1299 h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
1300
1301 $t->stop();
1302
1303 TODO: {
1304 local $TODO = 'not yet';
1305
1306 $frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
1307
1308 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
1309 ok($frame, 'GOAWAY on connection close');
1310
1311 }
1312
1313 ###############################################################################
1314
1315 sub h2_ping {
1316 my ($sess, $payload) = @_;
1317
1318 raw_write($sess->{socket}, pack("x2C2x5a8", 8, 0x6, $payload));
1319 }
1320
1321 sub h2_rst {
1322 my ($sess, $stream, $error) = @_;
1323
1324 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error));
1325 }
1326
1327 sub h2_priority {
1328 my ($sess, $w, $stream, $dep) = @_;
1329
1330 $stream = 0 unless defined $stream;
1331 $dep = 0 unless defined $dep;
1332 raw_write($sess->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w));
1333 }
1334
1335 sub h2_window {
1336 my ($sess, $win, $stream) = @_;
1337
1338 $stream = 0 unless defined $stream;
1339 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win));
1340 }
1341
1342 sub h2_settings {
1343 my ($sess, $ack, %extra) = @_;
1344
1345 my $len = 6 * keys %extra;
1346 my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0;
1347 $buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra;
1348 raw_write($sess->{socket}, $buf);
1349 }
1350
1351 sub h2_continue {
1352 my ($ctx, $stream, $uri) = @_;
1353
1354 $uri->{h2_continue} = 1;
1355 return new_stream($ctx, $uri, $stream);
1356 }
1357
1358 sub new_stream {
1359 my ($ctx, $uri, $stream) = @_;
1360 my ($input, $buf);
1361 my ($d, $status);
1362
1363 my $host = $uri->{host} || '127.0.0.1:8080';
1364 my $method = $uri->{method} || 'GET';
1365 my $scheme = $uri->{scheme} || 'http';
1366 my $path = $uri->{path} || '/';
1367 my $headers = $uri->{headers};
1368 my $body = $uri->{body};
1369 my $prio = $uri->{prio};
1370 my $dep = $uri->{dep};
1371
1372 my $pad = defined $uri->{padding} ? $uri->{padding} : 0;
1373 my $padlen = defined $uri->{padding} ? 1 : 0;
1374 my $bpad = defined $uri->{body_padding} ? $uri->{body_padding} : 0;
1375 my $bpadlen = defined $uri->{body_padding} ? 1 : 0;
1376
1377 my $type = defined $uri->{h2_continue} ? 0x9 : 0x1;
1378 my $flags = defined $uri->{continuation} ? 0x0 : 0x4;
1379 $flags |= 0x1 unless defined $body;
1380 $flags |= 0x8 if $padlen;
1381 $flags |= 0x20 if defined $dep || defined $prio;
1382
1383 if ($stream) {
1384 $ctx->{last_stream} = $stream;
1385 } else {
1386 $ctx->{last_stream} += 2;
1387 }
1388
1389 $buf = pack("xxx"); # Length stub
1390 $buf .= pack("CC", $type, $flags); # END_HEADERS
1391 $buf .= pack("N", $ctx->{last_stream}); # Stream-ID
1392
1393 $dep = 0 if defined $prio and not defined $dep;
1394 $prio = 16 if defined $dep and not defined $prio;
1395
1396 unless ($headers) {
1397 $input = hpack($ctx, ":method", $method);
1398 $input .= hpack($ctx, ":scheme", $scheme);
1399 $input .= hpack($ctx, ":path", $path);
1400 $input .= hpack($ctx, ":authority", $host);
1401 $input .= hpack($ctx, "content-length", length($body)) if $body;
1402
1403 } else {
1404 $input = join '', map {
1405 hpack($ctx, $_->{name}, $_->{value},
1406 mode => $_->{mode}, huff => $_->{huff})
1407 } @$headers if $headers;
1408 }
1409
1410 # 5.1. Integer Representation
1411
1412 sub intpack {
1413 my $d = shift;
1414 return pack('B8', '001' . sprintf("%5b", $d)) if $d < 31;
1415
1416 my $o = '00111111';
1417 $d -= 31;
1418 while ($d >= 128) {
1419 $o .= sprintf("%8b", $d % 128 + 128);
1420 $d /= 128;
1421 }
1422 $o .= sprintf("%08b", $d);
1423 return pack('B*', $o);
1424 }
1425
1426 $input = intpack($uri->{table_size}) . $input
1427 if defined $uri->{table_size};
1428
1429 # set length, attach headers, padding, priority
1430
1431 my $hlen = length($input) + $pad + $padlen;
1432 $hlen += 5 if $flags & 0x20;
1433 $buf |= pack_length($hlen);
1434
1435 $buf .= pack 'C', $pad if $padlen; # Pad Length?
1436 $buf .= pack 'NC', $dep, $prio if $flags & 0x20;
1437 $buf .= $input;
1438 $buf .= (pack 'C', 0) x $pad if $padlen; # Padding
1439
1440 if (defined $body) {
1441 $buf .= pack_length(length($body) + $bpad + $bpadlen);
1442 my $flags = $bpadlen ? 0x8 : 0x0;
1443 $buf .= pack 'CC', 0x0, 0x1 | $flags; # DATA, END_STREAM
1444 $buf .= pack 'N', $ctx->{last_stream};
1445 $buf .= pack 'C', $bpad if $bpadlen; # DATA Pad Length?
1446 $buf .= $body;
1447 $buf .= (pack 'C', 0) x $bpad if $bpadlen; # DATA Padding
1448 }
1449
1450 raw_write($ctx->{socket}, $buf);
1451 return $ctx->{last_stream};
1452 }
1453
1454 sub h2_read {
1455 my ($sess, %extra) = @_;
1456 my (@got);
1457 my $s = $sess->{socket};
1458 my $buf = '';
1459
1460 while (1) {
1461 $buf = raw_read($s, $buf, 9);
1462 last unless length $buf;
1463
1464 my $length = unpack_length($buf);
1465 my $type = unpack('x3C', $buf);
1466 my $flags = unpack('x4C', $buf);
1467
1468 $buf = raw_read($s, $buf, $length + 9);
1469 last unless length $buf;
1470
1471 my $frame = $cframe{$type}{value}($sess, $buf, $length);
1472 $frame->{length} = $length;
1473 $frame->{type} = $cframe{$type}{name};
1474 $frame->{flags} = $flags;
1475 push @got, $frame;
1476
1477 $buf = substr($buf, $length + 9);
1478
1479 last unless test_fin($got[-1], $extra{all});
1480 };
1481 return \@got;
1482 }
1483
1484 sub test_fin {
1485 my ($frame, $all) = @_;
1486 my @test = @{$all};
1487
1488 # wait for the specified DATA length
1489
1490 for (@test) {
1491 if ($_->{length} && $frame->{type} eq 'DATA') {
1492 # check also for StreamID if needed
1493
1494 if (!$_->{sid} || $_->{sid} == $frame->{sid}) {
1495 $_->{length} -= $frame->{length};
1496 }
1497 }
1498 }
1499 @test = grep { !(defined $_->{length} && $_->{length} == 0) } @test;
1500
1501 # wait for the fin flag
1502
1503 @test = grep { !(defined $_->{fin}
1504 && $_->{sid} == $frame->{sid} && $_->{fin} & $frame->{flags})
1505 } @test if defined $frame->{flags};
1506
1507 # wait for the specified frame
1508
1509 @test = grep { !($_->{type} && $_->{type} eq $frame->{type}) } @test;
1510
1511 @{$all} = @test;
1512 }
1513
1514 sub headers {
1515 my ($ctx, $buf, $len) = @_;
1516 my %payload;
1517 my $skip = 9;
1518
1519 my $stream = unpack "x5 B32", $buf;
1520 substr($stream, 0, 1) = 0;
1521 $stream = unpack("N", pack("B32", $stream));
1522 $payload{sid} = $stream;
1523
1524 my $headers = substr($buf, $skip);
1525 $payload{headers} = hunpack($ctx, $headers, $len);
1526
1527
1528 return \%payload;
1529 }
1530
1531 sub data {
1532 my ($ctx, $buf, $len) = @_;
1533 my %frame;
1534
1535 my $stream = unpack "x5 B32", $buf;
1536 substr($stream, 0, 1) = 0;
1537 $stream = unpack("N", pack("B32", $stream));
1538 $frame{sid} = $stream;
1539
1540 $frame{data} = substr($buf, 9, $len);
1541 return \%frame;
1542 }
1543
1544 sub settings {
1545 my ($ctx, $buf, $len) = @_;
1546 my %payload;
1547 my $skip = 9;
1548
1549 my $stream = unpack "x5 B32", $buf;
1550 substr($stream, 0, 1) = 0;
1551 $stream = unpack("N", pack("B32", $stream));
1552 $payload{sid} = $stream;
1553
1554 fail("stream identifier field is anything other than 0x0")
1555 if $stream;
1556
1557 for (1 .. $len / 6) {
1558 my $id = hex unpack "\@$skip n", $buf; $skip += 2;
1559 $payload{$id} = unpack "\@$skip N", $buf; $skip += 4;
1560 }
1561 return \%payload;
1562 }
1563
1564 sub ping {
1565 my ($ctx, $buf, $len) = @_;
1566 my %payload;
1567 my $skip = 9;
1568
1569 $payload{value} = unpack "\@$skip a8", $buf;
1570 return \%payload;
1571 }
1572
1573 sub rst_stream {
1574 my ($ctx, $buf, $len) = @_;
1575 my %payload;
1576 my $skip = 9;
1577
1578 my $stream = unpack "x5 B32", $buf;
1579 substr($stream, 0, 1) = 0;
1580 $stream = unpack("N", pack("B32", $stream));
1581 $payload{sid} = $stream;
1582
1583 $payload{code} = unpack "\@$skip N", $buf;
1584 return \%payload;
1585 }
1586
1587 sub goaway {
1588 my ($ctx, $buf, $len) = @_;
1589 my %payload;
1590 my $skip = 9;
1591
1592 my $stream = unpack "x5 B32", $buf;
1593 substr($stream, 0, 1) = 0;
1594 $stream = unpack("N", pack("B32", $stream));
1595 $payload{sid} = $stream;
1596
1597 fail("stream identifier field is anything other than 0x0")
1598 if $stream;
1599
1600 $stream = unpack "\@$skip B32", $buf;
1601 substr($stream, 0, 1) = 0;
1602 $stream = unpack("N", pack("B32", $stream));
1603 $payload{last_sid} = $stream;
1604 $skip += 4;
1605
1606 $len = $len - $skip;
1607 $payload{debug} = unpack "\@$skip H$len", $buf;
1608 return \%payload;
1609 }
1610
1611 sub window_update {
1612 my ($ctx, $buf, $len) = @_;
1613 my %payload;
1614 my $skip = 5;
1615
1616 my $stream = unpack "\@$skip B32", $buf; $skip += 4;
1617 substr($stream, 0, 1) = 0;
1618 $stream = unpack("N", pack("B32", $stream));
1619 $payload{sid} = $stream;
1620
1621 my $value = unpack "\@$skip B32", $buf;
1622 substr($value, 0, 1) = 0;
1623 $payload{wdelta} = unpack("N", pack("B32", $value));
1624 return \%payload;
1625 }
1626
1627 sub pack_length {
1628 pack 'c3', unpack 'xc3', pack 'N', $_[0];
1629 }
1630
1631 sub unpack_length {
1632 unpack 'N', pack 'xc3', unpack 'c3', $_[0];
1633 }
1634
1635 sub raw_read {
1636 my ($s, $buf, $len) = @_;
1637 my $got = '';
1638
1639 while (length($buf) < $len && IO::Select->new($s)->can_read(1)) {
1640 $s->sysread($got, $len - length($buf)) or last;
1641 log_in($got);
1642 $buf .= $got;
1643 }
1644 return $buf;
1645 }
1646
1647 sub raw_write {
1648 my ($s, $message) = @_;
1649
1650 local $SIG{PIPE} = 'IGNORE';
1651
1652 while (IO::Select->new($s)->can_write(0.4)) {
1653 log_out($message);
1654 my $n = $s->syswrite($message);
1655 last unless $n;
1656 $message = substr($message, $n);
1657 last unless length $message;
1658 }
1659 }
1660
1661 sub new_session {
1662 my ($port, %extra) = @_;
1663 my ($s);
1664
1665 $s = new_socket($port);
1666
1667 if ($extra{proxy}) {
1668 raw_write($s, $extra{proxy});
1669 }
1670
1671 # preface
1672
1673 raw_write($s, 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF);
1674
1675 return { socket => $s, last_stream => -1,
1676 dynamic_encode => [ static_table() ],
1677 dynamic_decode => [ static_table() ],
1678 static_table_size => scalar @{[static_table()]} };
1679 }
1680
1681 sub new_socket {
1682 my ($port) = @_;
1683 my $s;
1684
1685 $port = 8080 unless defined $port;
1686
1687 eval {
1688 local $SIG{ALRM} = sub { die "timeout\n" };
1689 local $SIG{PIPE} = sub { die "sigpipe\n" };
1690 alarm(2);
1691 $s = IO::Socket::INET->new(
1692 Proto => 'tcp',
1693 PeerAddr => "127.0.0.1:$port",
1694 );
1695 alarm(0);
1696 };
1697 alarm(0);
1698
1699 if ($@) {
1700 log_in("died: $@");
1701 return undef;
1702 }
1703
1704 return $s;
1705 }
1706
1707 sub static_table {
1708 [ '', '' ], # unused
1709 [ ':authority', '' ],
1710 [ ':method', 'GET' ],
1711 [ ':method', 'POST' ],
1712 [ ':path', '/' ],
1713 [ ':path', '/index.html' ],
1714 [ ':scheme', 'http' ],
1715 [ ':scheme', 'https' ],
1716 [ ':status', '200' ],
1717 [ ':status', '204' ],
1718 [ ':status', '206' ],
1719 [ ':status', '304' ],
1720 [ ':status', '400' ],
1721 [ ':status', '404' ],
1722 [ ':status', '500' ],
1723 [ 'accept-charset', '' ],
1724 [ 'accept-encoding', 'gzip, deflate' ],
1725 [ 'accept-language', '' ],
1726 [ 'accept-ranges', '' ],
1727 [ 'accept', '' ],
1728 [ 'access-control-allow-origin',
1729 '' ],
1730 [ 'age', '' ],
1731 [ 'allow', '' ],
1732 [ 'authorization', '' ],
1733 [ 'cache-control', '' ],
1734 [ 'content-disposition',
1735 '' ],
1736 [ 'content-encoding', '' ],
1737 [ 'content-language', '' ],
1738 [ 'content-length', '' ],
1739 [ 'content-location', '' ],
1740 [ 'content-range', '' ],
1741 [ 'content-type', '' ],
1742 [ 'cookie', '' ],
1743 [ 'date', '' ],
1744 [ 'etag', '' ],
1745 [ 'expect', '' ],
1746 [ 'expires', '' ],
1747 [ 'from', '' ],
1748 [ 'host', '' ],
1749 [ 'if-match', '' ],
1750 [ 'if-modified-since', '' ],
1751 [ 'if-none-match', '' ],
1752 [ 'if-range', '' ],
1753 [ 'if-unmodified-since',
1754 '' ],
1755 [ 'last-modified', '' ],
1756 [ 'link', '' ],
1757 [ 'location', '' ],
1758 [ 'max-forwards', '' ],
1759 [ 'proxy-authenticate', '' ],
1760 [ 'proxy-authorization',
1761 '' ],
1762 [ 'range', '' ],
1763 [ 'referer', '' ],
1764 [ 'refresh', '' ],
1765 [ 'retry-after', '' ],
1766 [ 'server', '' ],
1767 [ 'set-cookie', '' ],
1768 [ 'strict-transport-security',
1769 '' ],
1770 [ 'transfer-encoding', '' ],
1771 [ 'user-agent', '' ],
1772 [ 'vary', '' ],
1773 [ 'via', '' ],
1774 [ 'www-authenticate', '' ],
1775 }
1776
1777 sub hpack {
1778 my ($ctx, $name, $value, %extra) = @_;
1779 my @table = @{$ctx->{dynamic_encode}};
1780 my $mode = defined $extra{mode} ? $extra{mode} : 1;
1781 my $huff = $extra{huff};
1782
1783 my ($index, $buf) = 0;
1784
1785 # 6.1. Indexed Header Field Representation
1786
1787 if ($mode == 0) {
1788 ++$index until $index > $#table
1789 or $table[$index][0] eq $name
1790 and $table[$index][1] eq $value;
1791 $buf = pack('B*', '1' . sprintf("%7b", $index));
1792 }
1793
1794 # 6.2.1. Literal Header Field with Incremental Indexing
1795
1796 if ($mode == 1) {
1797 splice @{$ctx->{dynamic_encode}}, $ctx->{static_table_size}, 0,
1798 [ $name, $value ];
1799
1800 ++$index until $index > $#table or $table[$index][0] eq $name;
1801 my $value = $huff ? huff($value) : $value;
1802
1803 $buf = pack('B*', '01' . sprintf("%6b", $index)
1804 . ($huff ? '1' : '0') . sprintf("%7b", length($value)));
1805 $buf .= $value;
1806 }
1807
1808 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name
1809
1810 if ($mode == 2) {
1811 splice @{$ctx->{dynamic_encode}}, $ctx->{static_table_size}, 0,
1812 [ $name, $value ];
1813
1814 my $name = $huff ? huff($name) : $name;
1815 my $value = $huff ? huff($value) : $value;
1816 my $hbit = ($huff ? '1' : '0');
1817
1818 $buf = pack('B*', '01000000');
1819 $buf .= pack('B*', $hbit . sprintf("%7b", length($name)));
1820 $buf .= $name;
1821 $buf .= pack('B*', $hbit . sprintf("%7b", length($value)));
1822 $buf .= $value;
1823 }
1824
1825 # 6.2.2. Literal Header Field without Indexing
1826
1827 if ($mode == 3) {
1828 ++$index until $index > $#table or $table[$index][0] eq $name;
1829 my $value = $huff ? huff($value) : $value;
1830
1831 $buf = pack('B*', '0000' . sprintf("%4b", $index)
1832 . ($huff ? '1' : '0') . sprintf("%7b", length($value)));
1833 $buf .= $value;
1834 }
1835
1836 # 6.2.2. Literal Header Field without Indexing -- New Name
1837
1838 if ($mode == 4) {
1839 my $name = $huff ? huff($name) : $name;
1840 my $value = $huff ? huff($value) : $value;
1841 my $hbit = ($huff ? '1' : '0');
1842
1843 $buf = pack('B*', '00000000');
1844 $buf .= pack('B*', $hbit . sprintf("%7b", length($name)));
1845 $buf .= $name;
1846 $buf .= pack('B*', $hbit . sprintf("%7b", length($value)));
1847 $buf .= $value;
1848 }
1849
1850 # 6.2.3. Literal Header Field Never Indexed
1851
1852 if ($mode == 5) {
1853 ++$index until $index > $#table or $table[$index][0] eq $name;
1854 my $value = $huff ? huff($value) : $value;
1855
1856 $buf = pack('B*', '0001' . sprintf("%4b", $index)
1857 . ($huff ? '1' : '0') . sprintf("%7b", length($value)));
1858 $buf .= $value;
1859 }
1860
1861 # 6.2.3. Literal Header Field Never Indexed -- New Name
1862
1863 if ($mode == 6) {
1864 my $name = $huff ? huff($name) : $name;
1865 my $value = $huff ? huff($value) : $value;
1866 my $hbit = ($huff ? '1' : '0');
1867
1868 $buf = pack('B*', '00010000');
1869 $buf .= pack('B*', $hbit . sprintf("%7b", length($name)));
1870 $buf .= $name;
1871 $buf .= pack('B*', $hbit . sprintf("%7b", length($value)));
1872 $buf .= $value;
1873 }
1874
1875 return $buf;
1876 }
1877
1878 sub hunpack {
1879 my ($ctx, $data, $length) = @_;
1880 my @table = @{$ctx->{dynamic_decode}};
1881 my %headers;
1882 my $skip = 0;
1883 my ($name, $value);
1884
1885 sub index {
1886 my ($b, $i) = @_;
1887 unpack("C", pack("B8", '0' x $i . substr($b, $i, 8 - $i)));
1888 }
1889
1890 sub field {
1891 my ($b, $s) = @_;
1892 my $len = unpack("\@$s B8", $b);
1893 my $huff = substr($len, 0, 1) ? 1 : 0;
1894 $len = unpack("C", pack("B8", '0' . substr($len, 1, 8)));
1895 $s++;
1896
1897 my $field = substr($b, $s, $len);
1898 $field = $huff ? dehuff($field) : $field;
1899 $s += $len;
1900 return ($field, $s);
1901 }
1902
1903 sub add {
1904 my ($h, $n, $v) = @_;
1905 return $h->{$n} = $v unless exists $h->{$n};
1906 $h->{$n} = [ $h->{$n} ];
1907 push $h->{$n}, $v;
1908 }
1909
1910 while ($skip < $length) {
1911 my $ib = unpack("\@$skip B8", $data);
1912
1913 if (substr($ib, 0, 1) eq '1') {
1914 my $index = &index($ib, 1);
1915 add(\%headers, $table[$index][0], $table[$index][1]);
1916 $skip += 1;
1917 next;
1918 }
1919
1920 if (substr($ib, 0, 2) eq '01') {
1921 $name = $table[&index($ib, 2)][0];
1922 $skip++;
1923
1924 ($name, $skip) = field($data, $skip) unless $name;
1925 ($value, $skip) = field($data, $skip);
1926
1927 splice @{$ctx->{dynamic_decode}},
1928 $ctx->{static_table_size}, 0, [ $name, $value ];
1929 add(\%headers, $name, $value);
1930 next;
1931 }
1932
1933 if (substr($ib, 0, 4) eq '0000') {
1934 $name = $table[&index($ib, 4)][0];
1935 $skip++;
1936
1937 ($name, $skip) = field($data, $skip) unless $name;
1938 ($value, $skip) = field($data, $skip);
1939
1940 add(\%headers, $name, $value);
1941 next;
1942 }
1943 }
1944
1945 return \%headers;
1946 }
1947
1948 sub huff_code { scalar {
1949 pack('C', 0) => '1111111111000',
1950 pack('C', 1) => '11111111111111111011000',
1951 pack('C', 2) => '1111111111111111111111100010',
1952 pack('C', 3) => '1111111111111111111111100011',
1953 pack('C', 4) => '1111111111111111111111100100',
1954 pack('C', 5) => '1111111111111111111111100101',
1955 pack('C', 6) => '1111111111111111111111100110',
1956 pack('C', 7) => '1111111111111111111111100111',
1957 pack('C', 8) => '1111111111111111111111101000',
1958 pack('C', 9) => '111111111111111111101010',
1959 pack('C', 10) => '111111111111111111111111111100',
1960 pack('C', 11) => '1111111111111111111111101001',
1961 pack('C', 12) => '1111111111111111111111101010',
1962 pack('C', 13) => '111111111111111111111111111101',
1963 pack('C', 14) => '1111111111111111111111101011',
1964 pack('C', 15) => '1111111111111111111111101100',
1965 pack('C', 16) => '1111111111111111111111101101',
1966 pack('C', 17) => '1111111111111111111111101110',
1967 pack('C', 18) => '1111111111111111111111101111',
1968 pack('C', 19) => '1111111111111111111111110000',
1969 pack('C', 20) => '1111111111111111111111110001',
1970 pack('C', 21) => '1111111111111111111111110010',
1971 pack('C', 22) => '111111111111111111111111111110',
1972 pack('C', 23) => '1111111111111111111111110011',
1973 pack('C', 24) => '1111111111111111111111110100',
1974 pack('C', 25) => '1111111111111111111111110101',
1975 pack('C', 26) => '1111111111111111111111110110',
1976 pack('C', 27) => '1111111111111111111111110111',
1977 pack('C', 28) => '1111111111111111111111111000',
1978 pack('C', 29) => '1111111111111111111111111001',
1979 pack('C', 30) => '1111111111111111111111111010',
1980 pack('C', 31) => '1111111111111111111111111011',
1981 pack('C', 32) => '010100',
1982 pack('C', 33) => '1111111000',
1983 pack('C', 34) => '1111111001',
1984 pack('C', 35) => '111111111010',
1985 pack('C', 36) => '1111111111001',
1986 pack('C', 37) => '010101',
1987 pack('C', 38) => '11111000',
1988 pack('C', 39) => '11111111010',
1989 pack('C', 40) => '1111111010',
1990 pack('C', 41) => '1111111011',
1991 pack('C', 42) => '11111001',
1992 pack('C', 43) => '11111111011',
1993 pack('C', 44) => '11111010',
1994 pack('C', 45) => '010110',
1995 pack('C', 46) => '010111',
1996 pack('C', 47) => '011000',
1997 pack('C', 48) => '00000',
1998 pack('C', 49) => '00001',
1999 pack('C', 50) => '00010',
2000 pack('C', 51) => '011001',
2001 pack('C', 52) => '011010',
2002 pack('C', 53) => '011011',
2003 pack('C', 54) => '011100',
2004 pack('C', 55) => '011101',
2005 pack('C', 56) => '011110',
2006 pack('C', 57) => '011111',
2007 pack('C', 58) => '1011100',
2008 pack('C', 59) => '11111011',
2009 pack('C', 60) => '111111111111100',
2010 pack('C', 61) => '100000',
2011 pack('C', 62) => '111111111011',
2012 pack('C', 63) => '1111111100',
2013 pack('C', 64) => '1111111111010',
2014 pack('C', 65) => '100001',
2015 pack('C', 66) => '1011101',
2016 pack('C', 67) => '1011110',
2017 pack('C', 68) => '1011111',
2018 pack('C', 69) => '1100000',
2019 pack('C', 70) => '1100001',
2020 pack('C', 71) => '1100010',
2021 pack('C', 72) => '1100011',
2022 pack('C', 73) => '1100100',
2023 pack('C', 74) => '1100101',
2024 pack('C', 75) => '1100110',
2025 pack('C', 76) => '1100111',
2026 pack('C', 77) => '1101000',
2027 pack('C', 78) => '1101001',
2028 pack('C', 79) => '1101010',
2029 pack('C', 80) => '1101011',
2030 pack('C', 81) => '1101100',
2031 pack('C', 82) => '1101101',
2032 pack('C', 83) => '1101110',
2033 pack('C', 84) => '1101111',
2034 pack('C', 85) => '1110000',
2035 pack('C', 86) => '1110001',
2036 pack('C', 87) => '1110010',
2037 pack('C', 88) => '11111100',
2038 pack('C', 89) => '1110011',
2039 pack('C', 90) => '11111101',
2040 pack('C', 91) => '1111111111011',
2041 pack('C', 92) => '1111111111111110000',
2042 pack('C', 93) => '1111111111100',
2043 pack('C', 94) => '11111111111100',
2044 pack('C', 95) => '100010',
2045 pack('C', 96) => '111111111111101',
2046 pack('C', 97) => '00011',
2047 pack('C', 98) => '100011',
2048 pack('C', 99) => '00100',
2049 pack('C', 100) => '100100',
2050 pack('C', 101) => '00101',
2051 pack('C', 102) => '100101',
2052 pack('C', 103) => '100110',
2053 pack('C', 104) => '100111',
2054 pack('C', 105) => '00110',
2055 pack('C', 106) => '1110100',
2056 pack('C', 107) => '1110101',
2057 pack('C', 108) => '101000',
2058 pack('C', 109) => '101001',
2059 pack('C', 110) => '101010',
2060 pack('C', 111) => '00111',
2061 pack('C', 112) => '101011',
2062 pack('C', 113) => '1110110',
2063 pack('C', 114) => '101100',
2064 pack('C', 115) => '01000',
2065 pack('C', 116) => '01001',
2066 pack('C', 117) => '101101',
2067 pack('C', 118) => '1110111',
2068 pack('C', 119) => '1111000',
2069 pack('C', 120) => '1111001',
2070 pack('C', 121) => '1111010',
2071 pack('C', 122) => '1111011',
2072 pack('C', 123) => '111111111111110',
2073 pack('C', 124) => '11111111100',
2074 pack('C', 125) => '11111111111101',
2075 pack('C', 126) => '1111111111101',
2076 pack('C', 127) => '1111111111111111111111111100',
2077 pack('C', 128) => '11111111111111100110',
2078 pack('C', 129) => '1111111111111111010010',
2079 pack('C', 130) => '11111111111111100111',
2080 pack('C', 131) => '11111111111111101000',
2081 pack('C', 132) => '1111111111111111010011',
2082 pack('C', 133) => '1111111111111111010100',
2083 pack('C', 134) => '1111111111111111010101',
2084 pack('C', 135) => '11111111111111111011001',
2085 pack('C', 136) => '1111111111111111010110',
2086 pack('C', 137) => '11111111111111111011010',
2087 pack('C', 138) => '11111111111111111011011',
2088 pack('C', 139) => '11111111111111111011100',
2089 pack('C', 140) => '11111111111111111011101',
2090 pack('C', 141) => '11111111111111111011110',
2091 pack('C', 142) => '111111111111111111101011',
2092 pack('C', 143) => '11111111111111111011111',
2093 pack('C', 144) => '111111111111111111101100',
2094 pack('C', 145) => '111111111111111111101101',
2095 pack('C', 146) => '1111111111111111010111',
2096 pack('C', 147) => '11111111111111111100000',
2097 pack('C', 148) => '111111111111111111101110',
2098 pack('C', 149) => '11111111111111111100001',
2099 pack('C', 150) => '11111111111111111100010',
2100 pack('C', 151) => '11111111111111111100011',
2101 pack('C', 152) => '11111111111111111100100',
2102 pack('C', 153) => '111111111111111011100',
2103 pack('C', 154) => '1111111111111111011000',
2104 pack('C', 155) => '11111111111111111100101',
2105 pack('C', 156) => '1111111111111111011001',
2106 pack('C', 157) => '11111111111111111100110',
2107 pack('C', 158) => '11111111111111111100111',
2108 pack('C', 159) => '111111111111111111101111',
2109 pack('C', 160) => '1111111111111111011010',
2110 pack('C', 161) => '111111111111111011101',
2111 pack('C', 162) => '11111111111111101001',
2112 pack('C', 163) => '1111111111111111011011',
2113 pack('C', 164) => '1111111111111111011100',
2114 pack('C', 165) => '11111111111111111101000',
2115 pack('C', 166) => '11111111111111111101001',
2116 pack('C', 167) => '111111111111111011110',
2117 pack('C', 168) => '11111111111111111101010',
2118 pack('C', 169) => '1111111111111111011101',
2119 pack('C', 170) => '1111111111111111011110',
2120 pack('C', 171) => '111111111111111111110000',
2121 pack('C', 172) => '111111111111111011111',
2122 pack('C', 173) => '1111111111111111011111',
2123 pack('C', 174) => '11111111111111111101011',
2124 pack('C', 175) => '11111111111111111101100',
2125 pack('C', 176) => '111111111111111100000',
2126 pack('C', 177) => '111111111111111100001',
2127 pack('C', 178) => '1111111111111111100000',
2128 pack('C', 179) => '111111111111111100010',
2129 pack('C', 180) => '11111111111111111101101',
2130 pack('C', 181) => '1111111111111111100001',
2131 pack('C', 182) => '11111111111111111101110',
2132 pack('C', 183) => '11111111111111111101111',
2133 pack('C', 184) => '11111111111111101010',
2134 pack('C', 185) => '1111111111111111100010',
2135 pack('C', 186) => '1111111111111111100011',
2136 pack('C', 187) => '1111111111111111100100',
2137 pack('C', 188) => '11111111111111111110000',
2138 pack('C', 189) => '1111111111111111100101',
2139 pack('C', 190) => '1111111111111111100110',
2140 pack('C', 191) => '11111111111111111110001',
2141 pack('C', 192) => '11111111111111111111100000',
2142 pack('C', 193) => '11111111111111111111100001',
2143 pack('C', 194) => '11111111111111101011',
2144 pack('C', 195) => '1111111111111110001',
2145 pack('C', 196) => '1111111111111111100111',
2146 pack('C', 197) => '11111111111111111110010',
2147 pack('C', 198) => '1111111111111111101000',
2148 pack('C', 199) => '1111111111111111111101100',
2149 pack('C', 200) => '11111111111111111111100010',
2150 pack('C', 201) => '11111111111111111111100011',
2151 pack('C', 202) => '11111111111111111111100100',
2152 pack('C', 203) => '111111111111111111111011110',
2153 pack('C', 204) => '111111111111111111111011111',
2154 pack('C', 205) => '11111111111111111111100101',
2155 pack('C', 206) => '111111111111111111110001',
2156 pack('C', 207) => '1111111111111111111101101',
2157 pack('C', 208) => '1111111111111110010',
2158 pack('C', 209) => '111111111111111100011',
2159 pack('C', 210) => '11111111111111111111100110',
2160 pack('C', 211) => '111111111111111111111100000',
2161 pack('C', 212) => '111111111111111111111100001',
2162 pack('C', 213) => '11111111111111111111100111',
2163 pack('C', 214) => '111111111111111111111100010',
2164 pack('C', 215) => '111111111111111111110010',
2165 pack('C', 216) => '111111111111111100100',
2166 pack('C', 217) => '111111111111111100101',
2167 pack('C', 218) => '11111111111111111111101000',
2168 pack('C', 219) => '11111111111111111111101001',
2169 pack('C', 220) => '1111111111111111111111111101',
2170 pack('C', 221) => '111111111111111111111100011',
2171 pack('C', 222) => '111111111111111111111100100',
2172 pack('C', 223) => '111111111111111111111100101',
2173 pack('C', 224) => '11111111111111101100',
2174 pack('C', 225) => '111111111111111111110011',
2175 pack('C', 226) => '11111111111111101101',
2176 pack('C', 227) => '111111111111111100110',
2177 pack('C', 228) => '1111111111111111101001',
2178 pack('C', 229) => '111111111111111100111',
2179 pack('C', 230) => '111111111111111101000',
2180 pack('C', 231) => '11111111111111111110011',
2181 pack('C', 232) => '1111111111111111101010',
2182 pack('C', 233) => '1111111111111111101011',
2183 pack('C', 234) => '1111111111111111111101110',
2184 pack('C', 235) => '1111111111111111111101111',
2185 pack('C', 236) => '111111111111111111110100',
2186 pack('C', 237) => '111111111111111111110101',
2187 pack('C', 238) => '11111111111111111111101010',
2188 pack('C', 239) => '11111111111111111110100',
2189 pack('C', 240) => '11111111111111111111101011',
2190 pack('C', 241) => '111111111111111111111100110',
2191 pack('C', 242) => '11111111111111111111101100',
2192 pack('C', 243) => '11111111111111111111101101',
2193 pack('C', 244) => '111111111111111111111100111',
2194 pack('C', 245) => '111111111111111111111101000',
2195 pack('C', 246) => '111111111111111111111101001',
2196 pack('C', 247) => '111111111111111111111101010',
2197 pack('C', 248) => '111111111111111111111101011',
2198 pack('C', 249) => '1111111111111111111111111110',
2199 pack('C', 250) => '111111111111111111111101100',
2200 pack('C', 251) => '111111111111111111111101101',
2201 pack('C', 252) => '111111111111111111111101110',
2202 pack('C', 253) => '111111111111111111111101111',
2203 pack('C', 254) => '111111111111111111111110000',
2204 pack('C', 255) => '11111111111111111111101110',
2205 '_eos' => '111111111111111111111111111111',
2206 }};
2207
2208 sub huff {
2209 my ($string) = @_;
2210 my $code = &huff_code;
2211
2212 my $ret = join '', map { $code->{$_} } (split //, $string);
2213 my $len = length($ret) + (8 - length($ret) % 8);
2214 $ret .= $code->{_eos};
2215
2216 return pack("B$len", $ret);
2217 }
2218
2219 sub dehuff {
2220 my ($string) = @_;
2221 my $code = &huff_code;
2222 my %decode = reverse %$code;
2223
2224 my $ret = ''; my $c = '';
2225 for (split //, unpack('B*', $string)) {
2226 $c .= $_;
2227 next unless exists $decode{$c};
2228 last if $decode{$c} eq '_eos';
2229
2230 $ret .= $decode{$c};
2231 $c = '';
2232 }
2233
2234 return $ret;
2235 }
2236
2237 ###############################################################################
2238
2239 sub gunzip {
2240 my ($in) = @_;
2241
2242 SKIP: {
2243 eval { require IO::Uncompress::Gunzip; };
2244 Test::More::skip(
2245 "IO::Uncompress::Gunzip not installed", 1) if $@;
2246
2247 my $out;
2248
2249 IO::Uncompress::Gunzip::gunzip(\$in => \$out);
2250
2251 return $out;
2252 }
2253 }
2254
2255 ###############################################################################
2256
2257 # for tests with multiple header fields
2258
2259 sub http_daemon {
2260 my $server = IO::Socket::INET->new(
2261 Proto => 'tcp',
2262 LocalHost => '127.0.0.1',
2263 LocalPort => 8083,
2264 Listen => 5,
2265 Reuse => 1
2266 )
2267 or die "Can't create listening socket: $!\n";
2268
2269 local $SIG{PIPE} = 'IGNORE';
2270
2271 while (my $client = $server->accept()) {
2272 $client->autoflush(1);
2273
2274 my $headers = '';
2275 my $uri = '';
2276 my $cookie = '';
2277
2278 while (<$client>) {
2279 $headers .= $_;
2280 last if (/^\x0d?\x0a?$/);
2281 }
2282
2283 next if $headers eq '';
2284 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
2285 $cookie = $1 if $headers =~ /Cookie: (.+)/i;
2286
2287 if ($uri eq '/cookie') {
2288
2289 print $client <<EOF;
2290 HTTP/1.1 200 OK
2291 Connection: close
2292 X-Cookie: $cookie
2293
2294 EOF
2295
2296 } elsif ($uri eq '/set-cookie') {
2297
2298 print $client <<EOF;
2299 HTTP/1.1 200 OK
2300 Connection: close
2301 Set-Cookie: a=b
2302 Set-Cookie: c=d
2303
2304 EOF
2305
2306 }
2307
2308 } continue {
2309 close $client;
2310 }
2311 }
2312
2313 ###############################################################################