Mercurial > hg > nginx-tests
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 ############################################################################### |