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