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