Mercurial > hg > nginx-tests
comparison h2_headers.t @ 876:a6abbfed42c0
Tests: split HTTP/2 tests, HTTP2 package introduced.
author | Andrey Zelenkov <zelenkov@nginx.com> |
---|---|
date | Wed, 23 Mar 2016 17:23:08 +0300 |
parents | |
children | 3b90649691cc |
comparison
equal
deleted
inserted
replaced
875:c380b4b7e2e4 | 876:a6abbfed42c0 |
---|---|
1 #!/usr/bin/perl | |
2 | |
3 # (C) Sergey Kandaurov | |
4 # (C) Nginx, Inc. | |
5 | |
6 # Tests for HTTP/2 protocol with headers. | |
7 # various HEADERS compression/encoding, see hpack() for mode details. | |
8 | |
9 ############################################################################### | |
10 | |
11 use warnings; | |
12 use strict; | |
13 | |
14 use Test::More; | |
15 | |
16 BEGIN { use FindBin; chdir($FindBin::Bin); } | |
17 | |
18 use lib 'lib'; | |
19 use Test::Nginx; | |
20 use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /; | |
21 | |
22 ############################################################################### | |
23 | |
24 select STDERR; $| = 1; | |
25 select STDOUT; $| = 1; | |
26 | |
27 my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(92) | |
28 ->write_file_expand('nginx.conf', <<'EOF'); | |
29 | |
30 %%TEST_GLOBALS%% | |
31 | |
32 daemon off; | |
33 | |
34 events { | |
35 } | |
36 | |
37 http { | |
38 %%TEST_GLOBALS_HTTP%% | |
39 | |
40 server { | |
41 listen 127.0.0.1:8080 http2; | |
42 listen 127.0.0.1:8081; | |
43 listen 127.0.0.1:8082 http2 sndbuf=128; | |
44 server_name localhost; | |
45 | |
46 http2_max_field_size 128k; | |
47 http2_max_header_size 128k; | |
48 | |
49 location / { | |
50 add_header X-Sent-Foo $http_x_foo; | |
51 add_header X-Referer $http_referer; | |
52 return 200; | |
53 } | |
54 location /frame_size { | |
55 add_header X-LongHeader $arg_h; | |
56 add_header X-LongHeader $arg_h; | |
57 add_header X-LongHeader $arg_h; | |
58 alias %%TESTDIR%%/t2.html; | |
59 } | |
60 location /continuation { | |
61 add_header X-LongHeader $arg_h; | |
62 add_header X-LongHeader $arg_h; | |
63 add_header X-LongHeader $arg_h; | |
64 return 200 body; | |
65 | |
66 location /continuation/204 { | |
67 return 204; | |
68 } | |
69 } | |
70 location /proxy/ { | |
71 add_header X-UC-a $upstream_cookie_a; | |
72 add_header X-UC-c $upstream_cookie_c; | |
73 proxy_pass http://127.0.0.1:8083/; | |
74 proxy_set_header X-Cookie-a $cookie_a; | |
75 proxy_set_header X-Cookie-c $cookie_c; | |
76 } | |
77 location /proxy2/ { | |
78 proxy_pass http://127.0.0.1:8081/; | |
79 } | |
80 location /set-cookie { | |
81 add_header Set-Cookie a=b; | |
82 add_header Set-Cookie c=d; | |
83 return 200; | |
84 } | |
85 location /cookie { | |
86 add_header X-Cookie $http_cookie; | |
87 add_header X-Cookie-a $cookie_a; | |
88 add_header X-Cookie-c $cookie_c; | |
89 return 200; | |
90 } | |
91 } | |
92 | |
93 server { | |
94 listen 127.0.0.1:8084 http2; | |
95 server_name localhost; | |
96 | |
97 http2_max_field_size 22; | |
98 } | |
99 | |
100 server { | |
101 listen 127.0.0.1:8085 http2; | |
102 server_name localhost; | |
103 | |
104 http2_max_header_size 64; | |
105 } | |
106 } | |
107 | |
108 EOF | |
109 | |
110 $t->run_daemon(\&http_daemon); | |
111 | |
112 open OLDERR, ">&", \*STDERR; close STDERR; | |
113 $t->run(); | |
114 open STDERR, ">&", \*OLDERR; | |
115 | |
116 $t->waitforsocket('127.0.0.1:8083'); | |
117 | |
118 # file size is slightly beyond initial window size: 2**16 + 80 bytes | |
119 | |
120 $t->write_file('t1.html', | |
121 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202))); | |
122 | |
123 $t->write_file('t2.html', 'SEE-THIS'); | |
124 | |
125 ############################################################################### | |
126 | |
127 # 6.1. Indexed Header Field Representation | |
128 | |
129 my $sess = new_session(); | |
130 my $sid = new_stream($sess, { headers => [ | |
131 { name => ':method', value => 'GET', mode => 0 }, | |
132 { name => ':scheme', value => 'http', mode => 0 }, | |
133 { name => ':path', value => '/', mode => 0 }, | |
134 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
135 my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
136 | |
137 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
138 is($frame->{headers}->{':status'}, 200, 'indexed header field'); | |
139 | |
140 # 6.2.1. Literal Header Field with Incremental Indexing | |
141 | |
142 $sess = new_session(); | |
143 $sid = new_stream($sess, { headers => [ | |
144 { name => ':method', value => 'GET', mode => 1, huff => 0 }, | |
145 { name => ':scheme', value => 'http', mode => 1, huff => 0 }, | |
146 { name => ':path', value => '/', mode => 1, huff => 0 }, | |
147 { name => ':authority', value => 'localhost', mode => 1, huff => 0 }]}); | |
148 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
149 | |
150 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
151 is($frame->{headers}->{':status'}, 200, 'literal with indexing'); | |
152 | |
153 $sess = new_session(); | |
154 $sid = new_stream($sess, { headers => [ | |
155 { name => ':method', value => 'GET', mode => 1, huff => 1 }, | |
156 { name => ':scheme', value => 'http', mode => 1, huff => 1 }, | |
157 { name => ':path', value => '/', mode => 1, huff => 1 }, | |
158 { name => ':authority', value => 'localhost', mode => 1, huff => 1 }]}); | |
159 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
160 | |
161 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
162 is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman'); | |
163 | |
164 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name | |
165 | |
166 $sess = new_session(); | |
167 $sid = new_stream($sess, { headers => [ | |
168 { name => ':method', value => 'GET', mode => 2, huff => 0 }, | |
169 { name => ':scheme', value => 'http', mode => 2, huff => 0 }, | |
170 { name => ':path', value => '/', mode => 2, huff => 0 }, | |
171 { name => ':authority', value => 'localhost', mode => 2, huff => 0 }]}); | |
172 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
173 | |
174 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
175 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new'); | |
176 | |
177 $sess = new_session(); | |
178 $sid = new_stream($sess, { headers => [ | |
179 { name => ':method', value => 'GET', mode => 2, huff => 1 }, | |
180 { name => ':scheme', value => 'http', mode => 2, huff => 1 }, | |
181 { name => ':path', value => '/', mode => 2, huff => 1 }, | |
182 { name => ':authority', value => 'localhost', mode => 2, huff => 1 }]}); | |
183 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
184 | |
185 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
186 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman'); | |
187 | |
188 # 6.2.2. Literal Header Field without Indexing | |
189 | |
190 $sess = new_session(); | |
191 $sid = new_stream($sess, { headers => [ | |
192 { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
193 { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
194 { name => ':path', value => '/', mode => 3, huff => 0 }, | |
195 { name => ':authority', value => 'localhost', mode => 3, huff => 0 }]}); | |
196 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
197 | |
198 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
199 is($frame->{headers}->{':status'}, 200, 'literal without indexing'); | |
200 | |
201 $sess = new_session(); | |
202 $sid = new_stream($sess, { headers => [ | |
203 { name => ':method', value => 'GET', mode => 3, huff => 1 }, | |
204 { name => ':scheme', value => 'http', mode => 3, huff => 1 }, | |
205 { name => ':path', value => '/', mode => 3, huff => 1 }, | |
206 { name => ':authority', value => 'localhost', mode => 3, huff => 1 }]}); | |
207 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
208 | |
209 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
210 is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman'); | |
211 | |
212 $sess = new_session(); | |
213 $sid = new_stream($sess, { headers => [ | |
214 { name => ':method', value => 'GET', mode => 3, huff => 0 }, | |
215 { name => ':scheme', value => 'http', mode => 3, huff => 0 }, | |
216 { name => ':path', value => '/', mode => 3, huff => 0 }, | |
217 { name => ':authority', value => 'localhost', mode => 3, huff => 0 }, | |
218 { name => 'referer', value => 'foo', mode => 3, huff => 0 }]}); | |
219 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
220 | |
221 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
222 is($frame->{headers}->{':status'}, 200, | |
223 'literal without indexing - multibyte index'); | |
224 is($frame->{headers}->{'x-referer'}, 'foo', | |
225 'literal without indexing - multibyte index value'); | |
226 | |
227 # 6.2.2. Literal Header Field without Indexing -- New Name | |
228 | |
229 $sess = new_session(); | |
230 $sid = new_stream($sess, { headers => [ | |
231 { name => ':method', value => 'GET', mode => 4, huff => 0 }, | |
232 { name => ':scheme', value => 'http', mode => 4, huff => 0 }, | |
233 { name => ':path', value => '/', mode => 4, huff => 0 }, | |
234 { name => ':authority', value => 'localhost', mode => 4, huff => 0 }]}); | |
235 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
236 | |
237 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
238 is($frame->{headers}->{':status'}, 200, 'literal without indexing - new'); | |
239 | |
240 $sess = new_session(); | |
241 $sid = new_stream($sess, { headers => [ | |
242 { name => ':method', value => 'GET', mode => 4, huff => 1 }, | |
243 { name => ':scheme', value => 'http', mode => 4, huff => 1 }, | |
244 { name => ':path', value => '/', mode => 4, huff => 1 }, | |
245 { name => ':authority', value => 'localhost', mode => 4, huff => 1 }]}); | |
246 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
247 | |
248 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
249 is($frame->{headers}->{':status'}, 200, | |
250 'literal without indexing - new huffman'); | |
251 | |
252 # 6.2.3. Literal Header Field Never Indexed | |
253 | |
254 $sess = new_session(); | |
255 $sid = new_stream($sess, { headers => [ | |
256 { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
257 { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
258 { name => ':path', value => '/', mode => 5, huff => 0 }, | |
259 { name => ':authority', value => 'localhost', mode => 5, huff => 0 }]}); | |
260 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
261 | |
262 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
263 is($frame->{headers}->{':status'}, 200, 'literal never indexed'); | |
264 | |
265 $sess = new_session(); | |
266 $sid = new_stream($sess, { headers => [ | |
267 { name => ':method', value => 'GET', mode => 5, huff => 1 }, | |
268 { name => ':scheme', value => 'http', mode => 5, huff => 1 }, | |
269 { name => ':path', value => '/', mode => 5, huff => 1 }, | |
270 { name => ':authority', value => 'localhost', mode => 5, huff => 1 }]}); | |
271 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
272 | |
273 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
274 is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman'); | |
275 | |
276 $sess = new_session(); | |
277 $sid = new_stream($sess, { headers => [ | |
278 { name => ':method', value => 'GET', mode => 5, huff => 0 }, | |
279 { name => ':scheme', value => 'http', mode => 5, huff => 0 }, | |
280 { name => ':path', value => '/', mode => 5, huff => 0 }, | |
281 { name => ':authority', value => 'localhost', mode => 5, huff => 0 }, | |
282 { name => 'referer', value => 'foo', mode => 5, huff => 0 }]}); | |
283 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
284 | |
285 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
286 is($frame->{headers}->{':status'}, 200, | |
287 'literal never indexed - multibyte index'); | |
288 is($frame->{headers}->{'x-referer'}, 'foo', | |
289 'literal never indexed - multibyte index value'); | |
290 | |
291 # 6.2.3. Literal Header Field Never Indexed -- New Name | |
292 | |
293 $sess = new_session(); | |
294 $sid = new_stream($sess, { headers => [ | |
295 { name => ':method', value => 'GET', mode => 6, huff => 0 }, | |
296 { name => ':scheme', value => 'http', mode => 6, huff => 0 }, | |
297 { name => ':path', value => '/', mode => 6, huff => 0 }, | |
298 { name => ':authority', value => 'localhost', mode => 6, huff => 0 }]}); | |
299 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
300 | |
301 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
302 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new'); | |
303 | |
304 $sess = new_session(); | |
305 $sid = new_stream($sess, { headers => [ | |
306 { name => ':method', value => 'GET', mode => 6, huff => 1 }, | |
307 { name => ':scheme', value => 'http', mode => 6, huff => 1 }, | |
308 { name => ':path', value => '/', mode => 6, huff => 1 }, | |
309 { name => ':authority', value => 'localhost', mode => 6, huff => 1 }]}); | |
310 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
311 | |
312 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
313 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman'); | |
314 | |
315 # reuse literal with multibyte indexing | |
316 | |
317 $sess = new_session(); | |
318 $sid = new_stream($sess, { headers => [ | |
319 { name => ':method', value => 'GET', mode => 0 }, | |
320 { name => ':scheme', value => 'http', mode => 0 }, | |
321 { name => ':path', value => '/', mode => 0 }, | |
322 { name => ':authority', value => 'localhost', mode => 1 }, | |
323 { name => 'referer', value => 'foo', mode => 1 }]}); | |
324 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
325 | |
326 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
327 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new'); | |
328 | |
329 $sid = new_stream($sess, { headers => [ | |
330 { name => ':method', value => 'GET', mode => 0 }, | |
331 { name => ':scheme', value => 'http', mode => 0 }, | |
332 { name => ':path', value => '/', mode => 0 }, | |
333 { name => ':authority', value => 'localhost', mode => 0 }, | |
334 { name => 'referer', value => 'foo', mode => 0 }]}); | |
335 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
336 | |
337 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
338 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed'); | |
339 | |
340 $sess = new_session(); | |
341 $sid = new_stream($sess, { headers => [ | |
342 { name => ':method', value => 'GET', mode => 0 }, | |
343 { name => ':scheme', value => 'http', mode => 0 }, | |
344 { name => ':path', value => '/', mode => 0 }, | |
345 { name => ':authority', value => 'localhost', mode => 1 }, | |
346 { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
347 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
348 | |
349 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
350 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new'); | |
351 | |
352 # reuse literal with multibyte indexing - reused name | |
353 | |
354 $sid = new_stream($sess, { headers => [ | |
355 { name => ':method', value => 'GET', mode => 0 }, | |
356 { name => ':scheme', value => 'http', mode => 0 }, | |
357 { name => ':path', value => '/', mode => 0 }, | |
358 { name => ':authority', value => 'localhost', mode => 0 }, | |
359 { name => 'x-foo', value => 'X-Bar', mode => 0 }]}); | |
360 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
361 | |
362 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
363 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed'); | |
364 | |
365 # reuse literal with multibyte indexing - reused name only | |
366 | |
367 $sid = new_stream($sess, { headers => [ | |
368 { name => ':method', value => 'GET', mode => 0 }, | |
369 { name => ':scheme', value => 'http', mode => 0 }, | |
370 { name => ':path', value => '/', mode => 0 }, | |
371 { name => ':authority', value => 'localhost', mode => 0 }, | |
372 { name => 'x-foo', value => 'X-Baz', mode => 1 }]}); | |
373 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
374 | |
375 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
376 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', | |
377 'name with indexing - indexed name'); | |
378 | |
379 # response header field with characters not suitable for huffman encoding | |
380 | |
381 $sess = new_session(); | |
382 $sid = new_stream($sess, { headers => [ | |
383 { name => ':method', value => 'GET', mode => 0 }, | |
384 { name => ':scheme', value => 'http', mode => 0 }, | |
385 { name => ':path', value => '/', mode => 0 }, | |
386 { name => ':authority', value => 'localhost', mode => 1 }, | |
387 { name => 'x-foo', value => '{{{{{', mode => 2 }]}); | |
388 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
389 | |
390 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
391 is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars'); | |
392 like($sess->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding'); | |
393 | |
394 # response header field with huffman encoding | |
395 # NB: implementation detail, not obligated | |
396 | |
397 $sess = new_session(); | |
398 $sid = new_stream($sess, { headers => [ | |
399 { name => ':method', value => 'GET', mode => 0 }, | |
400 { name => ':scheme', value => 'http', mode => 0 }, | |
401 { name => ':path', value => '/', mode => 0 }, | |
402 { name => ':authority', value => 'localhost', mode => 1 }, | |
403 { name => 'x-foo', value => 'aaaaa', mode => 2 }]}); | |
404 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
405 | |
406 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
407 is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars'); | |
408 | |
409 TODO: { | |
410 local $TODO = 'not yet' unless $t->has_version('1.9.12'); | |
411 | |
412 unlike($sess->{headers}, qr/aaaaa/, 'well known chars - huffman encoding'); | |
413 | |
414 } | |
415 | |
416 # response header field with huffman encoding - complete table mod \0, CR, LF | |
417 # first saturate with short-encoded characters (NB: implementation detail) | |
418 | |
419 my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255); | |
420 | |
421 $sess = new_session(); | |
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 => ':authority', value => 'localhost', mode => 1 }, | |
427 { name => 'x-foo', value => $field, mode => 2 }]}); | |
428 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
429 | |
430 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
431 is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars'); | |
432 | |
433 TODO: { | |
434 local $TODO = 'not yet' unless $t->has_version('1.9.12'); | |
435 | |
436 unlike($sess->{headers}, qr/abcde/, 'all chars - huffman encoding'); | |
437 | |
438 } | |
439 | |
440 # 6.3. Dynamic Table Size Update | |
441 | |
442 # remove some indexed headers from the dynamic table | |
443 # by maintaining dynamic table space only for index 0 | |
444 # 'x-foo' has index 0, and 'referer' has index 1 | |
445 | |
446 $sess = new_session(); | |
447 $sid = new_stream($sess, { headers => [ | |
448 { name => ':method', value => 'GET', mode => 0 }, | |
449 { name => ':scheme', value => 'http', mode => 0 }, | |
450 { name => ':path', value => '/', mode => 0 }, | |
451 { name => ':authority', value => 'localhost', mode => 1 }, | |
452 { name => 'referer', value => 'foo', mode => 1 }, | |
453 { name => 'x-foo', value => 'X-Bar', mode => 2 }]}); | |
454 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
455 | |
456 $sid = new_stream($sess, { table_size => 61, headers => [ | |
457 { name => ':method', value => 'GET', mode => 0 }, | |
458 { name => ':scheme', value => 'http', mode => 0 }, | |
459 { name => ':path', value => '/', mode => 0 }, | |
460 { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
461 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
462 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
463 | |
464 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
465 isnt($frame, undef, 'updated table size - remaining index'); | |
466 | |
467 $sid = new_stream($sess, { headers => [ | |
468 { name => ':method', value => 'GET', mode => 0 }, | |
469 { name => ':scheme', value => 'http', mode => 0 }, | |
470 { name => ':path', value => '/', mode => 0 }, | |
471 { name => ':authority', value => 'localhost', mode => 1 }, | |
472 { name => 'referer', value => 'foo', mode => 0 }]}); | |
473 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
474 | |
475 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
476 is($frame, undef, 'invalid index'); | |
477 | |
478 # 5.4.1. Connection Error Handling | |
479 # An endpoint that encounters a connection error SHOULD first send a | |
480 # GOAWAY frame <..> | |
481 | |
482 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
483 ok($frame, 'invalid index - GOAWAY'); | |
484 | |
485 # RFC 7541, 2.3.3. Index Address Space | |
486 # Indices strictly greater than the sum of the lengths of both tables | |
487 # MUST be treated as a decoding error. | |
488 | |
489 # 4.3. Header Compression and Decompression | |
490 # A decoding error in a header block MUST be treated | |
491 # as a connection error of type COMPRESSION_ERROR. | |
492 | |
493 is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream'); | |
494 is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR'); | |
495 | |
496 # HPACK zero index | |
497 | |
498 # RFC 7541, 6.1 Indexed Header Field Representation | |
499 # The index value of 0 is not used. It MUST be treated as a decoding | |
500 # error if found in an indexed header field representation. | |
501 | |
502 $sess = new_session(); | |
503 $sid = new_stream($sess, { headers => [ | |
504 { name => ':method', value => 'GET', mode => 0 }, | |
505 { name => ':scheme', value => 'http', mode => 0 }, | |
506 { name => ':path', value => '/', mode => 0 }, | |
507 { name => ':authority', value => 'localhost', mode => 1 }, | |
508 { name => '', value => '', mode => 0 }]}); | |
509 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
510 | |
511 ok($frame, 'zero index - GOAWAY'); | |
512 is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR'); | |
513 | |
514 # invalid table size update | |
515 | |
516 $sess = new_session(); | |
517 $sid = new_stream($sess, { table_size => 4097, headers => [ | |
518 { name => ':method', value => 'GET', mode => 0 }, | |
519 { name => ':scheme', value => 'http', mode => 0 }, | |
520 { name => ':path', value => '/', mode => 0 }, | |
521 { name => 'x-foo', value => 'X-Bar', mode => 0 }, | |
522 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
523 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
524 | |
525 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; | |
526 ok($frame, 'invalid table size - GOAWAY'); | |
527 is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream'); | |
528 is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR'); | |
529 | |
530 # request header field with multiple values | |
531 | |
532 # 8.1.2.5. Compressing the Cookie Header Field | |
533 # To allow for better compression efficiency, the Cookie header field | |
534 # MAY be split into separate header fields <..>. | |
535 | |
536 $sess = new_session(); | |
537 $sid = new_stream($sess, { headers => [ | |
538 { name => ':method', value => 'GET', mode => 0 }, | |
539 { name => ':scheme', value => 'http', mode => 0 }, | |
540 { name => ':path', value => '/cookie', mode => 2 }, | |
541 { name => ':authority', value => 'localhost', mode => 1 }, | |
542 { name => 'cookie', value => 'a=b', mode => 2}, | |
543 { name => 'cookie', value => 'c=d', mode => 2}]}); | |
544 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
545 | |
546 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
547 is($frame->{headers}->{'x-cookie-a'}, 'b', | |
548 'multiple request header fields - cookie'); | |
549 is($frame->{headers}->{'x-cookie-c'}, 'd', | |
550 'multiple request header fields - cookie 2'); | |
551 is($frame->{headers}->{'x-cookie'}, 'a=b; c=d', | |
552 'multiple request header fields - semi-colon'); | |
553 | |
554 # request header field with multiple values to HTTP backend | |
555 | |
556 # 8.1.2.5. Compressing the Cookie Header Field | |
557 # these MUST be concatenated into a single octet string | |
558 # using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") | |
559 # before being passed into a non-HTTP/2 context, such as an HTTP/1.1 | |
560 # connection <..> | |
561 | |
562 $sess = new_session(); | |
563 $sid = new_stream($sess, { headers => [ | |
564 { name => ':method', value => 'GET', mode => 0 }, | |
565 { name => ':scheme', value => 'http', mode => 0 }, | |
566 { name => ':path', value => '/proxy/cookie', mode => 2 }, | |
567 { name => ':authority', value => 'localhost', mode => 1 }, | |
568 { name => 'cookie', value => 'a=b', mode => 2 }, | |
569 { name => 'cookie', value => 'c=d', mode => 2 }]}); | |
570 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
571 | |
572 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
573 is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d', | |
574 'multiple request header fields proxied - semi-colon'); | |
575 is($frame->{headers}->{'x-sent-cookie2'}, '', | |
576 'multiple request header fields proxied - dublicate cookie'); | |
577 is($frame->{headers}->{'x-sent-cookie-a'}, 'b', | |
578 'multiple request header fields proxied - cookie 1'); | |
579 is($frame->{headers}->{'x-sent-cookie-c'}, 'd', | |
580 'multiple request header fields proxied - cookie 2'); | |
581 | |
582 # response header field with multiple values | |
583 | |
584 $sess = new_session(); | |
585 $sid = new_stream($sess, { path => '/set-cookie' }); | |
586 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
587 | |
588 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
589 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
590 'multiple response header fields - cookie'); | |
591 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
592 'multiple response header fields - cookie 2'); | |
593 | |
594 # response header field with multiple values from HTTP backend | |
595 | |
596 $sess = new_session(); | |
597 $sid = new_stream($sess, { path => '/proxy/set-cookie' }); | |
598 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
599 | |
600 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
601 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
602 'multiple response header proxied - cookie'); | |
603 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
604 'multiple response header proxied - cookie 2'); | |
605 is($frame->{headers}->{'x-uc-a'}, 'b', | |
606 'multiple response header proxied - upstream cookie'); | |
607 is($frame->{headers}->{'x-uc-c'}, 'd', | |
608 'multiple response header proxied - upstream cookie 2'); | |
609 | |
610 # CONTINUATION in response | |
611 # put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2) | |
612 # to break header block into separate frames, one such field per frame | |
613 | |
614 $sess = new_session(); | |
615 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**13 }); | |
616 | |
617 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
618 my @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
619 is(@{$data[-1]->{headers}{'x-longheader'}}, 3, | |
620 'response CONTINUATION - headers'); | |
621 is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
622 'response CONTINUATION - header 1'); | |
623 is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
624 'response CONTINUATION - header 2'); | |
625 is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
626 'response CONTINUATION - header 3'); | |
627 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
628 cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size'); | |
629 | |
630 # same but without response DATA frames | |
631 | |
632 $sess = new_session(); | |
633 $sid = new_stream($sess, { path => '/continuation/204?h=' . 'x' x 2**13 }); | |
634 | |
635 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
636 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
637 is(@{$data[-1]->{headers}{'x-longheader'}}, 3, | |
638 'no body CONTINUATION - headers'); | |
639 is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13, | |
640 'no body CONTINUATION - header 1'); | |
641 is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13, | |
642 'no body CONTINUATION - header 2'); | |
643 is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13, | |
644 'no body CONTINUATION - header 3'); | |
645 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
646 cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size'); | |
647 | |
648 # response header block is always split by SETTINGS_MAX_FRAME_SIZE | |
649 | |
650 $sess = new_session(); | |
651 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 }); | |
652 | |
653 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
654 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
655 @data = sort { $a <=> $b } map { $_->{length} } @data; | |
656 cmp_ok($data[-1], '<=', 2**14, 'response header frames limited'); | |
657 | |
658 # response header frame sent in parts | |
659 | |
660 TODO: { | |
661 local $TODO = 'not yet' unless $t->has_version('1.9.7'); | |
662 | |
663 $sess = new_session(8082); | |
664 h2_settings($sess, 0, 0x5 => 2**17); | |
665 | |
666 $sid = new_stream($sess, { path => '/frame_size?h=' . 'x' x 2**15 }); | |
667 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
668 | |
669 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
670 ok($frame, 'response header - parts'); | |
671 | |
672 SKIP: { | |
673 skip 'response header failed', 1 unless $frame; | |
674 | |
675 is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304, | |
676 'response header - headers'); | |
677 | |
678 } | |
679 | |
680 # response header block split and sent in parts | |
681 | |
682 $sess = new_session(8082); | |
683 $sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 }); | |
684 $frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]); | |
685 | |
686 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames; | |
687 my ($lengths) = sort { $b <=> $a } map { $_->{length} } @data; | |
688 cmp_ok($lengths, '<=', 16384, 'response header split - max size'); | |
689 | |
690 is(length join('', @{@$frames[-1]->{headers}->{'x-longheader'}}), 98304, | |
691 'response header split - headers'); | |
692 | |
693 } | |
694 | |
695 # max_field_size - header field name | |
696 | |
697 $sess = new_session(8084); | |
698 $sid = new_stream($sess, { headers => [ | |
699 { name => ':method', value => 'GET', mode => 0 }, | |
700 { name => ':scheme', value => 'http', mode => 0 }, | |
701 { name => ':path', value => '/t2.html', mode => 1 }, | |
702 { name => ':authority', value => 'localhost', mode => 1 }, | |
703 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
704 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
705 | |
706 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
707 ok($frame, 'field name size less'); | |
708 | |
709 $sid = new_stream($sess, { headers => [ | |
710 { name => ':method', value => 'GET', mode => 0 }, | |
711 { name => ':scheme', value => 'http', mode => 0 }, | |
712 { name => ':path', value => '/t2.html', mode => 1 }, | |
713 { name => ':authority', value => 'localhost', mode => 1 }, | |
714 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); | |
715 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
716 | |
717 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
718 ok($frame, 'field name size second'); | |
719 | |
720 $sess = new_session(8084); | |
721 $sid = new_stream($sess, { headers => [ | |
722 { name => ':method', value => 'GET', mode => 0 }, | |
723 { name => ':scheme', value => 'http', mode => 0 }, | |
724 { name => ':path', value => '/t2.html', mode => 1 }, | |
725 { name => ':authority', value => 'localhost', mode => 1 }, | |
726 { name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]}); | |
727 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
728 | |
729 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
730 ok($frame, 'field name size equal'); | |
731 | |
732 $sess = new_session(8084); | |
733 $sid = new_stream($sess, { headers => [ | |
734 { name => ':method', value => 'GET', mode => 0 }, | |
735 { name => ':scheme', value => 'http', mode => 0 }, | |
736 { name => ':path', value => '/t2.html', mode => 1 }, | |
737 { name => ':authority', value => 'localhost', mode => 1 }, | |
738 { name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]}); | |
739 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
740 | |
741 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
742 is($frame, undef, 'field name size greater'); | |
743 | |
744 # max_field_size - header field value | |
745 | |
746 $sess = new_session(8084); | |
747 $sid = new_stream($sess, { headers => [ | |
748 { name => ':method', value => 'GET', mode => 0 }, | |
749 { name => ':scheme', value => 'http', mode => 0 }, | |
750 { name => ':path', value => '/t2.html', mode => 1 }, | |
751 { name => ':authority', value => 'localhost', mode => 1 }, | |
752 { name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]}); | |
753 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
754 | |
755 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
756 ok($frame, 'field value size less'); | |
757 | |
758 $sess = new_session(8084); | |
759 $sid = new_stream($sess, { headers => [ | |
760 { name => ':method', value => 'GET', mode => 0 }, | |
761 { name => ':scheme', value => 'http', mode => 0 }, | |
762 { name => ':path', value => '/t2.html', mode => 1 }, | |
763 { name => ':authority', value => 'localhost', mode => 1 }, | |
764 { name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]}); | |
765 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
766 | |
767 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
768 ok($frame, 'field value size equal'); | |
769 | |
770 $sess = new_session(8084); | |
771 $sid = new_stream($sess, { headers => [ | |
772 { name => ':method', value => 'GET', mode => 0 }, | |
773 { name => ':scheme', value => 'http', mode => 0 }, | |
774 { name => ':path', value => '/t2.html', mode => 1 }, | |
775 { name => ':authority', value => 'localhost', mode => 1 }, | |
776 { name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]}); | |
777 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
778 | |
779 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
780 is($frame, undef, 'field value size greater'); | |
781 | |
782 # max_header_size | |
783 | |
784 $sess = new_session(8085); | |
785 $sid = new_stream($sess, { headers => [ | |
786 { name => ':method', value => 'GET', mode => 0 }, | |
787 { name => ':scheme', value => 'http', mode => 0 }, | |
788 { name => ':path', value => '/t2.html', mode => 1 }, | |
789 { name => ':authority', value => 'localhost', mode => 1 }, | |
790 { name => 'longname9', value => 'x', mode => 2 }]}); | |
791 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
792 | |
793 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
794 ok($frame, 'header size less'); | |
795 | |
796 $sid = new_stream($sess, { headers => [ | |
797 { name => ':method', value => 'GET', mode => 0 }, | |
798 { name => ':scheme', value => 'http', mode => 0 }, | |
799 { name => ':path', value => '/t2.html', mode => 1 }, | |
800 { name => ':authority', value => 'localhost', mode => 1 }, | |
801 { name => 'longname9', value => 'x', mode => 2 }]}); | |
802 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
803 | |
804 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
805 ok($frame, 'header size second'); | |
806 | |
807 $sess = new_session(8085); | |
808 $sid = new_stream($sess, { headers => [ | |
809 { name => ':method', value => 'GET', mode => 0 }, | |
810 { name => ':scheme', value => 'http', mode => 0 }, | |
811 { name => ':path', value => '/t2.html', mode => 1 }, | |
812 { name => ':authority', value => 'localhost', mode => 1 }, | |
813 { name => 'longname9', value => 'xx', mode => 2 }]}); | |
814 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
815 | |
816 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
817 ok($frame, 'header size equal'); | |
818 | |
819 $sess = new_session(8085); | |
820 $sid = new_stream($sess, { headers => [ | |
821 { name => ':method', value => 'GET', mode => 0 }, | |
822 { name => ':scheme', value => 'http', mode => 0 }, | |
823 { name => ':path', value => '/t2.html', mode => 1 }, | |
824 { name => ':authority', value => 'localhost', mode => 1 }, | |
825 { name => 'longname9', value => 'xxx', mode => 2 }]}); | |
826 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
827 | |
828 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
829 is($frame, undef, 'header size greater'); | |
830 | |
831 # header size is based on (decompressed) header list | |
832 # two extra 1-byte indices would otherwise fit in max_header_size | |
833 | |
834 $sess = new_session(8085); | |
835 $sid = new_stream($sess, { headers => [ | |
836 { name => ':method', value => 'GET', mode => 0 }, | |
837 { name => ':scheme', value => 'http', mode => 0 }, | |
838 { name => ':path', value => '/t2.html', mode => 1 }, | |
839 { name => ':authority', value => 'localhost', mode => 1 }, | |
840 { name => 'longname9', value => 'x', mode => 2 }]}); | |
841 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
842 | |
843 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
844 ok($frame, 'header size new index'); | |
845 | |
846 $sid = new_stream($sess, { headers => [ | |
847 { name => ':method', value => 'GET', mode => 0 }, | |
848 { name => ':scheme', value => 'http', mode => 0 }, | |
849 { name => ':path', value => '/t2.html', mode => 1 }, | |
850 { name => ':authority', value => 'localhost', mode => 1 }, | |
851 { name => 'longname9', value => 'x', mode => 0 }]}); | |
852 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
853 | |
854 ($frame) = grep { $_->{type} eq 'DATA' } @$frames; | |
855 ok($frame, 'header size indexed'); | |
856 | |
857 $sid = new_stream($sess, { headers => [ | |
858 { name => ':method', value => 'GET', mode => 0 }, | |
859 { name => ':scheme', value => 'http', mode => 0 }, | |
860 { name => ':path', value => '/t2.html', mode => 1 }, | |
861 { name => ':authority', value => 'localhost', mode => 1 }, | |
862 { name => 'longname9', value => 'x', mode => 0 }, | |
863 { name => 'longname9', value => 'x', mode => 0 }]}); | |
864 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
865 | |
866 ($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; | |
867 is($frame->{code}, 0xb, 'header size indexed greater'); | |
868 | |
869 # HPACK table boundary | |
870 | |
871 $sess = new_session(); | |
872 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
873 { name => ':method', value => 'GET', mode => 0 }, | |
874 { name => ':scheme', value => 'http', mode => 0 }, | |
875 { name => ':path', value => '/', mode => 0 }, | |
876 { name => ':authority', value => '', mode => 0 }, | |
877 { name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]); | |
878 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
879 { name => ':method', value => 'GET', mode => 0 }, | |
880 { name => ':scheme', value => 'http', mode => 0 }, | |
881 { name => ':path', value => '/', mode => 0 }, | |
882 { name => ':authority', value => '', mode => 0 }, | |
883 { name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]); | |
884 | |
885 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
886 ok($frame, 'HPACK table boundary'); | |
887 | |
888 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
889 { name => ':method', value => 'GET', mode => 0 }, | |
890 { name => ':scheme', value => 'http', mode => 0 }, | |
891 { name => ':path', value => '/', mode => 0 }, | |
892 { name => ':authority', value => '', mode => 0 }, | |
893 { name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]); | |
894 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
895 { name => ':method', value => 'GET', mode => 0 }, | |
896 { name => ':scheme', value => 'http', mode => 0 }, | |
897 { name => ':path', value => '/', mode => 0 }, | |
898 { name => ':authority', value => '', mode => 0 }, | |
899 { name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]); | |
900 | |
901 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
902 ok($frame, 'HPACK table boundary - header field name'); | |
903 | |
904 h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
905 { name => ':method', value => 'GET', mode => 0 }, | |
906 { name => ':scheme', value => 'http', mode => 0 }, | |
907 { name => ':path', value => '/', mode => 0 }, | |
908 { name => ':authority', value => '', mode => 0 }, | |
909 { name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]); | |
910 $frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [ | |
911 { name => ':method', value => 'GET', mode => 0 }, | |
912 { name => ':scheme', value => 'http', mode => 0 }, | |
913 { name => ':path', value => '/', mode => 0 }, | |
914 { name => ':authority', value => '', mode => 0 }, | |
915 { name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]); | |
916 | |
917 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
918 ok($frame, 'HPACK table boundary - header field value'); | |
919 | |
920 # ensure that request header field value with newline doesn't get split | |
921 # | |
922 # 10.3. Intermediary Encapsulation Attacks | |
923 # Any request or response that contains a character not permitted | |
924 # in a header field value MUST be treated as malformed. | |
925 | |
926 $sess = new_session(); | |
927 $sid = new_stream($sess, { headers => [ | |
928 { name => ':method', value => 'GET', mode => 0 }, | |
929 { name => ':scheme', value => 'http', mode => 0 }, | |
930 { name => ':path', value => '/proxy2/', mode => 1 }, | |
931 { name => ':authority', value => 'localhost', mode => 1 }, | |
932 { name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]}); | |
933 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
934 | |
935 # 10.3. Intermediary Encapsulation Attacks | |
936 # An intermediary therefore cannot translate an HTTP/2 request or response | |
937 # containing an invalid field name into an HTTP/1.1 message. | |
938 | |
939 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
940 isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header'); | |
941 | |
942 # 8.1.2.6. Malformed Requests and Responses | |
943 # Malformed requests or responses that are detected MUST be treated | |
944 # as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. | |
945 | |
946 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
947 is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid'); | |
948 is($frame->{length}, 4, 'newline in request header - RST_STREAM length'); | |
949 is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags'); | |
950 is($frame->{code}, 1, 'newline in request header - RST_STREAM code'); | |
951 | |
952 # invalid header name as seen with underscore should not lead to ignoring rest | |
953 | |
954 TODO: { | |
955 local $TODO = 'not yet' unless $t->has_version('1.9.7'); | |
956 | |
957 $sess = new_session(); | |
958 $sid = new_stream($sess, { headers => [ | |
959 { name => ':method', value => 'GET', mode => 0 }, | |
960 { name => ':scheme', value => 'http', mode => 0 }, | |
961 { name => ':path', value => '/', mode => 0 }, | |
962 { name => ':authority', value => 'localhost', mode => 1 }, | |
963 { name => 'x_foo', value => "x-bar", mode => 2 }, | |
964 { name => 'referer', value => "see-this", mode => 1 }]}); | |
965 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
966 | |
967 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
968 is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name'); | |
969 | |
970 } | |
971 | |
972 # missing mandatory request header ':scheme' | |
973 | |
974 TODO: { | |
975 local $TODO = 'not yet'; | |
976 | |
977 $sess = new_session(); | |
978 $sid = new_stream($sess, { headers => [ | |
979 { name => ':method', value => 'GET', mode => 0 }, | |
980 { name => ':path', value => '/', mode => 0 }, | |
981 { name => ':authority', value => 'localhost', mode => 1 }]}); | |
982 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
983 | |
984 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
985 is($frame->{headers}->{':status'}, 400, 'incomplete headers'); | |
986 | |
987 } | |
988 | |
989 # empty request header ':authority' | |
990 | |
991 $sess = new_session(); | |
992 $sid = new_stream($sess, { headers => [ | |
993 { name => ':method', value => 'GET', mode => 0 }, | |
994 { name => ':scheme', value => 'http', mode => 0 }, | |
995 { name => ':path', value => '/', mode => 0 }, | |
996 { name => ':authority', value => '', mode => 0 }]}); | |
997 $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]); | |
998 | |
999 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
1000 is($frame->{headers}->{':status'}, 400, 'empty authority'); | |
1001 | |
1002 # client sent invalid :path header | |
1003 | |
1004 $sid = new_stream($sess, { path => 't1.html' }); | |
1005 $frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]); | |
1006 | |
1007 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; | |
1008 is($frame->{code}, 1, 'invalid path'); | |
1009 | |
1010 ############################################################################### | |
1011 | |
1012 sub read_body_file { | |
1013 my ($path) = @_; | |
1014 open FILE, $path or return "$!"; | |
1015 local $/; | |
1016 my $content = <FILE>; | |
1017 close FILE; | |
1018 return $content; | |
1019 } | |
1020 | |
1021 ############################################################################### | |
1022 | |
1023 sub http_daemon { | |
1024 my $server = IO::Socket::INET->new( | |
1025 Proto => 'tcp', | |
1026 LocalHost => '127.0.0.1', | |
1027 LocalPort => 8083, | |
1028 Listen => 5, | |
1029 Reuse => 1 | |
1030 ) | |
1031 or die "Can't create listening socket: $!\n"; | |
1032 | |
1033 local $SIG{PIPE} = 'IGNORE'; | |
1034 | |
1035 while (my $client = $server->accept()) { | |
1036 $client->autoflush(1); | |
1037 | |
1038 my $headers = ''; | |
1039 my $uri = ''; | |
1040 | |
1041 while (<$client>) { | |
1042 $headers .= $_; | |
1043 last if (/^\x0d?\x0a?$/); | |
1044 } | |
1045 | |
1046 next if $headers eq ''; | |
1047 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; | |
1048 | |
1049 if ($uri eq '/cookie') { | |
1050 | |
1051 my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig; | |
1052 $cookie2 = '' unless defined $cookie2; | |
1053 | |
1054 my ($cookie_a, $cookie_c) = ('', ''); | |
1055 $cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i; | |
1056 $cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i; | |
1057 | |
1058 print $client <<EOF; | |
1059 HTTP/1.1 200 OK | |
1060 Connection: close | |
1061 X-Sent-Cookie: $cookie | |
1062 X-Sent-Cookie2: $cookie2 | |
1063 X-Sent-Cookie-a: $cookie_a | |
1064 X-Sent-Cookie-c: $cookie_c | |
1065 | |
1066 EOF | |
1067 | |
1068 } elsif ($uri eq '/set-cookie') { | |
1069 | |
1070 print $client <<EOF; | |
1071 HTTP/1.1 200 OK | |
1072 Connection: close | |
1073 Set-Cookie: a=b | |
1074 Set-Cookie: c=d | |
1075 | |
1076 EOF | |
1077 | |
1078 } | |
1079 | |
1080 } continue { | |
1081 close $client; | |
1082 } | |
1083 } | |
1084 | |
1085 ############################################################################### |