Mercurial > hg > nginx-tests
comparison h3_headers.t @ 1876:74cb9454a13e
Tests: HTTP/3 header tests.
author | Sergey Kandaurov <pluknet@nginx.com> |
---|---|
date | Tue, 30 Aug 2022 14:29:39 +0400 |
parents | |
children | ff50c265a5ac |
comparison
equal
deleted
inserted
replaced
1875:f50c2a65ccc0 | 1876:74cb9454a13e |
---|---|
1 #!/usr/bin/perl | |
2 | |
3 # (C) Sergey Kandaurov | |
4 # (C) Nginx, Inc. | |
5 | |
6 # Tests for HTTP/3 headers. | |
7 | |
8 ############################################################################### | |
9 | |
10 use warnings; | |
11 use strict; | |
12 | |
13 use Test::More; | |
14 | |
15 BEGIN { use FindBin; chdir($FindBin::Bin); } | |
16 | |
17 use lib 'lib'; | |
18 use Test::Nginx; | |
19 use Test::Nginx::HTTP3; | |
20 | |
21 ############################################################################### | |
22 | |
23 select STDERR; $| = 1; | |
24 select STDOUT; $| = 1; | |
25 | |
26 eval { require Crypt::Misc; die if $Crypt::Misc::VERSION < 0.067; }; | |
27 plan(skip_all => 'CryptX version >= 0.067 required') if $@; | |
28 | |
29 my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite/) | |
30 ->has_daemon('openssl')->plan(66) | |
31 ->write_file_expand('nginx.conf', <<'EOF'); | |
32 | |
33 %%TEST_GLOBALS%% | |
34 | |
35 daemon off; | |
36 | |
37 events { | |
38 } | |
39 | |
40 http { | |
41 %%TEST_GLOBALS_HTTP%% | |
42 | |
43 ssl_certificate_key localhost.key; | |
44 ssl_certificate localhost.crt; | |
45 | |
46 server { | |
47 listen 127.0.0.1:%%PORT_8980_UDP%% quic; | |
48 listen 127.0.0.1:8081; | |
49 server_name localhost; | |
50 | |
51 location / { | |
52 add_header X-Sent-Foo $http_x_foo; | |
53 add_header X-Referer $http_referer; | |
54 add_header X-Path $uri; | |
55 return 200; | |
56 } | |
57 | |
58 location /proxy/ { | |
59 add_header X-UC-a $upstream_cookie_a; | |
60 add_header X-UC-c $upstream_cookie_c; | |
61 proxy_pass http://127.0.0.1:8083/; | |
62 proxy_set_header X-Cookie-a $cookie_a; | |
63 proxy_set_header X-Cookie-c $cookie_c; | |
64 } | |
65 | |
66 location /proxy2/ { | |
67 proxy_pass http://127.0.0.1:8081/; | |
68 } | |
69 | |
70 location /set-cookie { | |
71 add_header Set-Cookie a=b; | |
72 add_header Set-Cookie c=d; | |
73 return 200; | |
74 } | |
75 | |
76 location /cookie { | |
77 add_header X-Cookie $http_cookie; | |
78 add_header X-Cookie-a $cookie_a; | |
79 add_header X-Cookie-c $cookie_c; | |
80 return 200; | |
81 } | |
82 } | |
83 | |
84 server { | |
85 listen 127.0.0.1:%%PORT_8984_UDP%% quic; | |
86 server_name localhost; | |
87 | |
88 large_client_header_buffers 4 512; | |
89 } | |
90 | |
91 server { | |
92 listen 127.0.0.1:%%PORT_8985_UDP%% quic; | |
93 server_name localhost; | |
94 | |
95 large_client_header_buffers 1 512; | |
96 } | |
97 | |
98 server { | |
99 listen 127.0.0.1:%%PORT_8986_UDP%% quic; | |
100 server_name localhost; | |
101 | |
102 underscores_in_headers on; | |
103 add_header X-Sent-Foo $http_x_foo always; | |
104 } | |
105 | |
106 server { | |
107 listen 127.0.0.1:%%PORT_8987_UDP%% quic; | |
108 server_name localhost; | |
109 | |
110 ignore_invalid_headers off; | |
111 add_header X-Sent-Foo $http_x_foo always; | |
112 } | |
113 } | |
114 | |
115 EOF | |
116 | |
117 $t->write_file('openssl.conf', <<EOF); | |
118 [ req ] | |
119 default_bits = 2048 | |
120 encrypt_key = no | |
121 distinguished_name = req_distinguished_name | |
122 [ req_distinguished_name ] | |
123 EOF | |
124 | |
125 my $d = $t->testdir(); | |
126 | |
127 foreach my $name ('localhost') { | |
128 system('openssl req -x509 -new ' | |
129 . "-config $d/openssl.conf -subj /CN=$name/ " | |
130 . "-out $d/$name.crt -keyout $d/$name.key " | |
131 . ">>$d/openssl.out 2>&1") == 0 | |
132 or die "Can't create certificate for $name: $!\n"; | |
133 } | |
134 | |
135 $t->run_daemon(\&http_daemon); | |
136 $t->run()->waitforsocket('127.0.0.1:' . port(8083)); | |
137 | |
138 $t->write_file('t2.html', 'SEE-THIS'); | |
139 | |
140 ############################################################################### | |
141 | |
142 my ($s, $sid, $frames, $frame); | |
143 | |
144 # 4.5.2. Indexed Field Line | |
145 | |
146 $s = Test::Nginx::HTTP3->new(); | |
147 $sid = $s->new_stream({ headers => [ | |
148 { name => ':method', value => 'GET', mode => 0 }, | |
149 { name => ':scheme', value => 'http', mode => 0 }, | |
150 { name => ':path', value => '/', mode => 0 }, | |
151 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
152 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
153 | |
154 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
155 is($frame->{headers}->{'x-path'}, '/', 'indexed'); | |
156 | |
157 $s->insert_literal(':path', '/foo'); | |
158 $sid = $s->new_stream({ headers => [ | |
159 { name => ':method', value => 'GET', mode => 0 }, | |
160 { name => ':scheme', value => 'http', mode => 0 }, | |
161 { name => ':path', value => '/foo', mode => 0, dyn => 1 }, | |
162 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
163 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
164 | |
165 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
166 is($frame->{headers}->{'x-path'}, '/foo', 'indexed dynamic'); | |
167 | |
168 $s->insert_literal(':path', '/bar', huff => 1); | |
169 $sid = $s->new_stream({ headers => [ | |
170 { name => ':method', value => 'GET', mode => 0 }, | |
171 { name => ':scheme', value => 'http', mode => 0 }, | |
172 { name => ':path', value => '/bar', mode => 0, dyn => 1 }, | |
173 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
174 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
175 | |
176 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
177 is($frame->{headers}->{'x-path'}, '/bar', 'indexed dynamic huffman'); | |
178 | |
179 $sid = $s->new_stream({ headers => [ | |
180 { name => ':method', value => 'GET', mode => 0 }, | |
181 { name => ':scheme', value => 'http', mode => 0 }, | |
182 { name => ':path', value => '/foo', mode => 0, dyn => 1 }, | |
183 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
184 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
185 | |
186 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
187 is($frame->{headers}->{'x-path'}, '/foo', 'indexed dynamic previous'); | |
188 | |
189 $s->insert_reference(':path', '/qux'); | |
190 $sid = $s->new_stream({ headers => [ | |
191 { name => ':method', value => 'GET', mode => 0 }, | |
192 { name => ':scheme', value => 'http', mode => 0 }, | |
193 { name => ':path', value => '/qux', mode => 0, dyn => 1 }, | |
194 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
195 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
196 | |
197 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
198 is($frame->{headers}->{'x-path'}, '/qux', 'indexed reference'); | |
199 | |
200 $s->insert_reference(':path', '/corge', dyn => 1); | |
201 $sid = $s->new_stream({ headers => [ | |
202 { name => ':method', value => 'GET', mode => 0 }, | |
203 { name => ':scheme', value => 'http', mode => 0 }, | |
204 { name => ':path', value => '/corge', mode => 0, dyn => 1 }, | |
205 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
206 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
207 | |
208 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
209 is($frame->{headers}->{'x-path'}, '/corge', 'indexed reference dynamic'); | |
210 | |
211 $s->insert_reference(':path', '/grault', huff => 1); | |
212 $sid = $s->new_stream({ headers => [ | |
213 { name => ':method', value => 'GET', mode => 0 }, | |
214 { name => ':scheme', value => 'http', mode => 0 }, | |
215 { name => ':path', value => '/grault', mode => 0, dyn => 1 }, | |
216 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
217 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
218 | |
219 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
220 is($frame->{headers}->{'x-path'}, '/grault', 'indexed reference huffman'); | |
221 | |
222 # 4.5.3. Indexed Field Line with Post-Base Index | |
223 | |
224 $s = Test::Nginx::HTTP3->new(); | |
225 $s->insert_literal(':path', '/foo'); | |
226 $sid = $s->new_stream({ base => -1, headers => [ | |
227 { name => ':method', value => 'GET', mode => 0 }, | |
228 { name => ':scheme', value => 'http', mode => 0 }, | |
229 { name => ':path', value => '/foo', mode => 1 }, | |
230 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
231 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
232 | |
233 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
234 is($frame->{headers}->{'x-path'}, '/foo', 'post-base index'); | |
235 | |
236 # 4.5.4. Literal Field Line with Name Reference | |
237 | |
238 $s = Test::Nginx::HTTP3->new(); | |
239 $sid = $s->new_stream({ headers => [ | |
240 { name => ':method', value => 'GET', mode => 2 }, | |
241 { name => ':scheme', value => 'http', mode => 2 }, | |
242 { name => ':path', value => '/', mode => 2 }, | |
243 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
244 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
245 | |
246 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
247 is($frame->{headers}->{':status'}, 200, 'reference'); | |
248 | |
249 $s = Test::Nginx::HTTP3->new(); | |
250 $sid = $s->new_stream({ headers => [ | |
251 { name => ':method', value => 'GET', mode => 2, huff => 1 }, | |
252 { name => ':scheme', value => 'http', mode => 2, huff => 1 }, | |
253 { name => ':path', value => '/', mode => 2, huff => 1 }, | |
254 { name => ':authority', value => 'localhost', mode => 2, huff => 1 }]}); | |
255 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
256 | |
257 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
258 is($frame->{headers}->{':status'}, 200, 'reference huffman'); | |
259 | |
260 $s = Test::Nginx::HTTP3->new(); | |
261 $sid = $s->new_stream({ headers => [ | |
262 { name => ':method', value => 'GET', mode => 2, ni => 1 }, | |
263 { name => ':scheme', value => 'http', mode => 2, ni => 1 }, | |
264 { name => ':path', value => '/', mode => 2, ni => 1 }, | |
265 { name => ':authority', value => 'localhost', mode => 2, ni => 1 }]}); | |
266 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
267 | |
268 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
269 is($frame->{headers}->{':status'}, 200, 'reference never indexed'); | |
270 | |
271 $s->insert_literal('x-foo', 'X-Bar'); | |
272 $sid = $s->new_stream({ headers => [ | |
273 { name => ':method', value => 'GET', mode => 2 }, | |
274 { name => ':scheme', value => 'http', mode => 2 }, | |
275 { name => ':path', value => '/', mode => 2 }, | |
276 { name => ':authority', value => 'localhost', mode => 2 }, | |
277 { name => 'x-foo', value => 'X-Baz', mode => 2, dyn => 1 }]}); | |
278 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
279 | |
280 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
281 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'reference dynamic'); | |
282 | |
283 # 4.5.5. Literal Field Line with Post-Base Name Reference | |
284 | |
285 $s = Test::Nginx::HTTP3->new(); | |
286 $s->insert_literal('x-foo', 'X-Bar'); | |
287 $sid = $s->new_stream({ base => -1, headers => [ | |
288 { name => ':method', value => 'GET', mode => 0 }, | |
289 { name => ':scheme', value => 'http', mode => 0 }, | |
290 { name => ':path', value => '/', mode => 0 }, | |
291 { name => ':authority', value => 'localhost', mode => 4 }, | |
292 { name => 'x-foo', value => 'X-Baz', mode => 3 }]}); | |
293 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
294 | |
295 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
296 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'base-base ref'); | |
297 | |
298 $sid = $s->new_stream({ base => -1, headers => [ | |
299 { name => ':method', value => 'GET', mode => 0 }, | |
300 { name => ':scheme', value => 'http', mode => 0 }, | |
301 { name => ':path', value => '/', mode => 0 }, | |
302 { name => ':authority', value => 'localhost', mode => 4 }, | |
303 { name => 'x-foo', value => 'X-Baz', mode => 3, huff => 1 }]}); | |
304 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
305 | |
306 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
307 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'post-base ref huffman'); | |
308 | |
309 $sid = $s->new_stream({ base => -1, headers => [ | |
310 { name => ':method', value => 'GET', mode => 0 }, | |
311 { name => ':scheme', value => 'http', mode => 0 }, | |
312 { name => ':path', value => '/', mode => 0 }, | |
313 { name => ':authority', value => 'localhost', mode => 4 }, | |
314 { name => 'x-foo', value => 'X-Baz', mode => 3, ni => 1 }]}); | |
315 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
316 | |
317 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
318 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'post-base ref never indexed'); | |
319 | |
320 # 4.5.6. Literal Field Line with Literal Name | |
321 | |
322 $s = Test::Nginx::HTTP3->new(); | |
323 $sid = $s->new_stream({ headers => [ | |
324 { name => ':method', value => 'GET', mode => 4 }, | |
325 { name => ':scheme', value => 'http', mode => 4 }, | |
326 { name => ':path', value => '/', mode => 4 }, | |
327 { name => ':authority', value => 'localhost', mode => 4 }]}); | |
328 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
329 | |
330 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
331 is($frame->{headers}->{':status'}, 200, 'literal'); | |
332 | |
333 $s = Test::Nginx::HTTP3->new(); | |
334 $sid = $s->new_stream({ headers => [ | |
335 { name => ':method', value => 'GET', mode => 4, huff => 1 }, | |
336 { name => ':scheme', value => 'http', mode => 4, huff => 1 }, | |
337 { name => ':path', value => '/', mode => 4, huff => 1 }, | |
338 { name => ':authority', value => 'localhost', mode => 4, huff => 1 }]}); | |
339 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
340 | |
341 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
342 is($frame->{headers}->{':status'}, 200, 'literal huffman'); | |
343 | |
344 $s = Test::Nginx::HTTP3->new(); | |
345 $sid = $s->new_stream({ headers => [ | |
346 { name => ':method', value => 'GET', mode => 4, ni => 1 }, | |
347 { name => ':scheme', value => 'http', mode => 4, ni => 1 }, | |
348 { name => ':path', value => '/', mode => 4, ni => 1 }, | |
349 { name => ':authority', value => 'localhost', mode => 4, ni => 1 }]}); | |
350 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
351 | |
352 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
353 is($frame->{headers}->{':status'}, 200, 'literal never indexed'); | |
354 | |
355 # response header field with characters not suitable for huffman encoding | |
356 | |
357 $s = Test::Nginx::HTTP3->new(); | |
358 $sid = $s->new_stream({ headers => [ | |
359 { name => ':method', value => 'GET', mode => 0 }, | |
360 { name => ':scheme', value => 'http', mode => 0 }, | |
361 { name => ':path', value => '/', mode => 0 }, | |
362 { name => ':authority', value => 'localhost', mode => 2 }, | |
363 { name => 'x-foo', value => '{{{{{', mode => 4 }]}); | |
364 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
365 | |
366 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
367 is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars'); | |
368 like($s->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding'); | |
369 | |
370 # response header field with huffman encoding | |
371 # NB: implementation detail, not obligated | |
372 | |
373 $s = Test::Nginx::HTTP3->new(); | |
374 $sid = $s->new_stream({ headers => [ | |
375 { name => ':method', value => 'GET', mode => 0 }, | |
376 { name => ':scheme', value => 'http', mode => 0 }, | |
377 { name => ':path', value => '/', mode => 0 }, | |
378 { name => ':authority', value => 'localhost', mode => 2 }, | |
379 { name => 'x-foo', value => 'aaaaa', mode => 4 }]}); | |
380 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
381 | |
382 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
383 is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars'); | |
384 unlike($s->{headers}, qr/aaaaa/, 'well known chars - huffman encoding'); | |
385 | |
386 # response header field with huffman encoding - complete table mod \0, CR, LF | |
387 # first saturate with short-encoded characters (NB: implementation detail) | |
388 | |
389 my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255); | |
390 | |
391 $s = Test::Nginx::HTTP3->new(); | |
392 $sid = $s->new_stream({ headers => [ | |
393 { name => ':method', value => 'GET', mode => 0 }, | |
394 { name => ':scheme', value => 'http', mode => 0 }, | |
395 { name => ':path', value => '/', mode => 0 }, | |
396 { name => ':authority', value => 'localhost', mode => 2 }, | |
397 { name => 'x-foo', value => $field, mode => 4 }]}); | |
398 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
399 | |
400 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
401 is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars'); | |
402 unlike($s->{headers}, qr/abcde/, 'all chars - huffman encoding'); | |
403 | |
404 # 3.2.2. Dynamic Table Capacity and Eviction | |
405 | |
406 # remove some indexed headers from the dynamic table | |
407 # by maintaining dynamic table space only for index 0 | |
408 | |
409 $s = Test::Nginx::HTTP3->new(undef, capacity => 64); | |
410 $s->insert_literal('x-foo', 'X-Bar'); | |
411 $sid = $s->new_stream({ headers => [ | |
412 { name => ':method', value => 'GET', mode => 0 }, | |
413 { name => ':scheme', value => 'http', mode => 0 }, | |
414 { name => ':path', value => '/', mode => 0 }, | |
415 { name => ':authority', value => 'localhost', mode => 2 }, | |
416 { name => 'x-foo', value => 'X-Bar', mode => 0, dyn => 1 }]}); | |
417 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
418 | |
419 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
420 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'capacity insert'); | |
421 | |
422 $s->insert_literal('x-foo', 'X-Baz'); | |
423 $sid = $s->new_stream({ headers => [ | |
424 { name => ':method', value => 'GET', mode => 0 }, | |
425 { name => ':scheme', value => 'http', mode => 0 }, | |
426 { name => ':path', value => '/', mode => 0 }, | |
427 { name => ':authority', value => 'localhost', mode => 2 }, | |
428 { name => 'x-foo', value => 'X-Baz', mode => 0, dyn => 1 }]}); | |
429 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
430 | |
431 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
432 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'capacity replace'); | |
433 | |
434 $sid = $s->new_stream({ headers => [ | |
435 { name => ':method', value => 'GET', mode => 0 }, | |
436 { name => ':scheme', value => 'http', mode => 0 }, | |
437 { name => ':path', value => '/', mode => 0 }, | |
438 { name => ':authority', value => 'localhost', mode => 2 }, | |
439 { name => 'x-foo', value => 'X-Bar', mode => 0, dyn => 1 }]}); | |
440 $frames = $s->read(all => [{ type => 'DECODER_C' }]); | |
441 | |
442 ($frame) = grep { $_->{type} eq "DECODER_C" } @$frames; | |
443 is($frame->{'val'}, $sid, 'capacity eviction'); | |
444 | |
445 # insert with referenced entry eviction | |
446 | |
447 $s = Test::Nginx::HTTP3->new(undef, capacity => 64); | |
448 $s->insert_literal('x-foo', 'X-Bar'); | |
449 $s->insert_reference('x-foo', 'X-Baz', dyn => 1); | |
450 $sid = $s->new_stream({ headers => [ | |
451 { name => ':method', value => 'GET', mode => 0 }, | |
452 { name => ':scheme', value => 'http', mode => 0 }, | |
453 { name => ':path', value => '/', mode => 0 }, | |
454 { name => ':authority', value => 'localhost', mode => 2 }, | |
455 { name => 'x-foo', value => 'X-Baz', mode => 0, dyn => 1 }]}); | |
456 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
457 | |
458 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
459 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz', 'insert ref eviction'); | |
460 | |
461 $s = Test::Nginx::HTTP3->new(undef, capacity => 64); | |
462 $s->insert_literal('x-foo', 'X-Bar'); | |
463 $s->duplicate('x-foo'); | |
464 $sid = $s->new_stream({ headers => [ | |
465 { name => ':method', value => 'GET', mode => 0 }, | |
466 { name => ':scheme', value => 'http', mode => 0 }, | |
467 { name => ':path', value => '/', mode => 0 }, | |
468 { name => ':authority', value => 'localhost', mode => 2 }, | |
469 { name => 'x-foo', value => 'X-Bar', mode => 0, dyn => 1 }]}); | |
470 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
471 | |
472 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
473 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'duplicate eviction'); | |
474 | |
475 # invalid capacity | |
476 | |
477 $s = Test::Nginx::HTTP3->new(undef, capacity => 4097); | |
478 $frames = $s->read(all => [{ type => 'CONNECTION_CLOSE' }]); | |
479 | |
480 ($frame) = grep { $_->{type} eq "CONNECTION_CLOSE" } @$frames; | |
481 is($frame->{'phrase'}, 'stream error', 'capacity invalid'); | |
482 | |
483 # request header field with multiple values | |
484 | |
485 # 4.2.1. Field Compression | |
486 # To allow for better compression efficiency, the Cookie header field | |
487 # MAY be split into separate field lines <..>. | |
488 | |
489 $s = Test::Nginx::HTTP3->new(); | |
490 $sid = $s->new_stream({ headers => [ | |
491 { name => ':method', value => 'GET', mode => 0 }, | |
492 { name => ':scheme', value => 'http', mode => 0 }, | |
493 { name => ':path', value => '/cookie', mode => 2 }, | |
494 { name => ':authority', value => 'localhost', mode => 2 }, | |
495 { name => 'cookie', value => 'a=b', mode => 2 }, | |
496 { name => 'cookie', value => 'c=d', mode => 2 }]}); | |
497 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
498 | |
499 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
500 is($frame->{headers}->{'x-cookie-a'}, 'b', | |
501 'multiple request header fields - cookie'); | |
502 is($frame->{headers}->{'x-cookie-c'}, 'd', | |
503 'multiple request header fields - cookie 2'); | |
504 is($frame->{headers}->{'x-cookie'}, 'a=b; c=d', | |
505 'multiple request header fields - semi-colon'); | |
506 | |
507 # request header field with multiple values to HTTP backend | |
508 | |
509 # 4.2.1. Field Compression | |
510 # these MUST be concatenated into a single byte string | |
511 # using the two-byte delimiter of "; " (ASCII 0x3b, 0x20) | |
512 # before being passed into a context other than HTTP/2 or | |
513 # HTTP/3, such as an HTTP/1.1 connection <..> | |
514 | |
515 $s = Test::Nginx::HTTP3->new(); | |
516 $sid = $s->new_stream({ headers => [ | |
517 { name => ':method', value => 'GET', mode => 0 }, | |
518 { name => ':scheme', value => 'http', mode => 0 }, | |
519 { name => ':path', value => '/proxy/cookie', mode => 2 }, | |
520 { name => ':authority', value => 'localhost', mode => 2 }, | |
521 { name => 'cookie', value => 'a=b', mode => 2 }, | |
522 { name => 'cookie', value => 'c=d', mode => 2 }]}); | |
523 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
524 | |
525 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
526 is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d', | |
527 'multiple request header fields proxied - semi-colon'); | |
528 is($frame->{headers}->{'x-sent-cookie2'}, '', | |
529 'multiple request header fields proxied - dublicate cookie'); | |
530 is($frame->{headers}->{'x-sent-cookie-a'}, 'b', | |
531 'multiple request header fields proxied - cookie 1'); | |
532 is($frame->{headers}->{'x-sent-cookie-c'}, 'd', | |
533 'multiple request header fields proxied - cookie 2'); | |
534 | |
535 # response header field with multiple values | |
536 | |
537 $s = Test::Nginx::HTTP3->new(); | |
538 $sid = $s->new_stream({ path => '/set-cookie' }); | |
539 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
540 | |
541 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
542 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
543 'multiple response header fields - cookie'); | |
544 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
545 'multiple response header fields - cookie 2'); | |
546 | |
547 # response header field with multiple values from HTTP backend | |
548 | |
549 $s = Test::Nginx::HTTP3->new(); | |
550 $sid = $s->new_stream({ path => '/proxy/set-cookie' }); | |
551 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
552 | |
553 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
554 is($frame->{headers}->{'set-cookie'}[0], 'a=b', | |
555 'multiple response header proxied - cookie'); | |
556 is($frame->{headers}->{'set-cookie'}[1], 'c=d', | |
557 'multiple response header proxied - cookie 2'); | |
558 is($frame->{headers}->{'x-uc-a'}, 'b', | |
559 'multiple response header proxied - upstream cookie'); | |
560 is($frame->{headers}->{'x-uc-c'}, 'd', | |
561 'multiple response header proxied - upstream cookie 2'); | |
562 | |
563 # max_field_size - header field name | |
564 | |
565 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
566 $s->insert_literal('x' x 511, 'value'); | |
567 $sid = $s->new_stream({ headers => [ | |
568 { name => ':method', value => 'GET', mode => 0 }, | |
569 { name => ':scheme', value => 'http', mode => 0 }, | |
570 { name => ':path', value => '/t2.html', mode => 2 }, | |
571 { name => ':authority', value => 'localhost', mode => 2 }, | |
572 { name => 'x' x 511, value => 'value', mode => 0, dyn => 1 }]}); | |
573 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
574 | |
575 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
576 ok($frame, 'field name size less'); | |
577 | |
578 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
579 $s->insert_literal('x' x 512, 'value'); | |
580 $sid = $s->new_stream({ headers => [ | |
581 { name => ':method', value => 'GET', mode => 0 }, | |
582 { name => ':scheme', value => 'http', mode => 0 }, | |
583 { name => ':path', value => '/t2.html', mode => 2 }, | |
584 { name => ':authority', value => 'localhost', mode => 2 }, | |
585 { name => 'x' x 512, value => 'value', mode => 0, dyn => 1 }]}); | |
586 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
587 | |
588 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
589 ok($frame, 'field name size equal'); | |
590 | |
591 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
592 $s->insert_literal('x' x 513, 'value'); | |
593 $sid = $s->new_stream({ headers => [ | |
594 { name => ':method', value => 'GET', mode => 0 }, | |
595 { name => ':scheme', value => 'http', mode => 0 }, | |
596 { name => ':path', value => '/t2.html', mode => 2 }, | |
597 { name => ':authority', value => 'localhost', mode => 2 }, | |
598 { name => 'x' x 513, value => 'value', mode => 0, dyn => 1 }]}); | |
599 $frames = $s->read(all => [{ type => 'CONNECTION_CLOSE' }]); | |
600 | |
601 ($frame) = grep { $_->{type} eq "CONNECTION_CLOSE" } @$frames; | |
602 is($frame->{'phrase'}, 'stream error', 'field name size greater'); | |
603 | |
604 # max_field_size - header field value | |
605 | |
606 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
607 $s->insert_literal('name', 'x' x 511); | |
608 $sid = $s->new_stream({ headers => [ | |
609 { name => ':method', value => 'GET', mode => 0 }, | |
610 { name => ':scheme', value => 'http', mode => 0 }, | |
611 { name => ':path', value => '/t2.html', mode => 2 }, | |
612 { name => ':authority', value => 'localhost', mode => 2 }, | |
613 { name => 'name', value => 'x' x 511, mode => 0, dyn => 1 }]}); | |
614 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
615 | |
616 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
617 ok($frame, 'field value size less'); | |
618 | |
619 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
620 $s->insert_literal('name', 'x' x 512); | |
621 $sid = $s->new_stream({ headers => [ | |
622 { name => ':method', value => 'GET', mode => 0 }, | |
623 { name => ':scheme', value => 'http', mode => 0 }, | |
624 { name => ':path', value => '/t2.html', mode => 2 }, | |
625 { name => ':authority', value => 'localhost', mode => 2 }, | |
626 { name => 'name', value => 'x' x 512, mode => 0, dyn => 1 }]}); | |
627 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
628 | |
629 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
630 ok($frame, 'field value size equal'); | |
631 | |
632 $s = Test::Nginx::HTTP3->new(8984, capacity => 2048); | |
633 $s->insert_literal('name', 'x' x 513); | |
634 $frames = $s->read(all => [{ type => 'CONNECTION_CLOSE' }]); | |
635 | |
636 ($frame) = grep { $_->{type} eq "CONNECTION_CLOSE" } @$frames; | |
637 is($frame->{'phrase'}, 'stream error', 'field value size greater'); | |
638 | |
639 # max_header_size | |
640 | |
641 $s = Test::Nginx::HTTP3->new(8985, capacity => 2048); | |
642 $s->insert_literal('longname', 'x' x 450); | |
643 $sid = $s->new_stream({ headers => [ | |
644 { name => ':method', value => 'GET', mode => 0 }, | |
645 { name => ':scheme', value => 'http', mode => 0 }, | |
646 { name => ':path', value => '/t2.html', mode => 2 }, | |
647 { name => ':authority', value => 'localhost', mode => 2 }, | |
648 { name => 'longname', value => 'x' x 450, mode => 0, dyn => 1 }]}); | |
649 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
650 | |
651 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
652 ok($frame, 'header size less'); | |
653 | |
654 $s = Test::Nginx::HTTP3->new(8985, capacity => 2048); | |
655 $s->insert_literal('longname', 'x' x 451); | |
656 $sid = $s->new_stream({ headers => [ | |
657 { name => ':method', value => 'GET', mode => 0 }, | |
658 { name => ':scheme', value => 'http', mode => 0 }, | |
659 { name => ':path', value => '/t2.html', mode => 2 }, | |
660 { name => ':authority', value => 'localhost', mode => 2 }, | |
661 { name => 'longname', value => 'x' x 451, mode => 0, dyn => 1 }]}); | |
662 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
663 | |
664 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
665 ok($frame, 'header size equal'); | |
666 | |
667 $s = Test::Nginx::HTTP3->new(8985, capacity => 2048); | |
668 $s->insert_literal('longname', 'x' x 452); | |
669 $sid = $s->new_stream({ headers => [ | |
670 { name => ':method', value => 'GET', mode => 0 }, | |
671 { name => ':scheme', value => 'http', mode => 0 }, | |
672 { name => ':path', value => '/t2.html', mode => 2 }, | |
673 { name => ':authority', value => 'localhost', mode => 2 }, | |
674 { name => 'longname', value => 'x' x 452, mode => 0, dyn => 1 }]}); | |
675 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
676 | |
677 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
678 is($frame->{headers}->{':status'}, 400, 'header size greater'); | |
679 | |
680 # header size is based on (decompressed) header list | |
681 # two extra 1-byte indices would otherwise fit in max_header_size | |
682 | |
683 $s = Test::Nginx::HTTP3->new(8985, capacity => 2048); | |
684 $s->insert_literal('longname', 'x' x 400); | |
685 $sid = $s->new_stream({ headers => [ | |
686 { name => ':method', value => 'GET', mode => 0 }, | |
687 { name => ':scheme', value => 'http', mode => 0 }, | |
688 { name => ':path', value => '/t2.html', mode => 2 }, | |
689 { name => ':authority', value => 'localhost', mode => 2 }, | |
690 { name => 'longname', value => 'x' x 400, mode => 0, dyn => 1 }]}); | |
691 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
692 | |
693 ($frame) = grep { $_->{type} eq "DATA" } @$frames; | |
694 ok($frame, 'header size indexed'); | |
695 | |
696 $sid = $s->new_stream({ headers => [ | |
697 { name => ':method', value => 'GET', mode => 0 }, | |
698 { name => ':scheme', value => 'http', mode => 0 }, | |
699 { name => ':path', value => '/t2.html', mode => 2 }, | |
700 { name => ':authority', value => 'localhost', mode => 2 }, | |
701 { name => 'longname', value => 'x' x 400, mode => 0, dyn => 1 }, | |
702 { name => 'longname', value => 'x' x 400, mode => 0, dyn => 1 }]}); | |
703 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
704 | |
705 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
706 is($frame->{headers}->{':status'}, 400, 'header size indexed greater'); | |
707 | |
708 # ensure that request header field value with newline doesn't get split | |
709 # | |
710 # 10.3. Intermediary-Encapsulation Attacks | |
711 # Requests or responses containing invalid field names MUST be treated | |
712 # as malformed. | |
713 | |
714 $s = Test::Nginx::HTTP3->new(); | |
715 $sid = $s->new_stream({ headers => [ | |
716 { name => ':method', value => 'GET', mode => 0 }, | |
717 { name => ':scheme', value => 'http', mode => 0 }, | |
718 { name => ':path', value => '/proxy2/', mode => 2 }, | |
719 { name => ':authority', value => 'localhost', mode => 2 }, | |
720 { name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 4 }]}); | |
721 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
722 | |
723 # 10.3. Intermediary Encapsulation Attacks | |
724 # Therefore, an intermediary cannot translate an HTTP/3 request or response | |
725 # containing an invalid field name into an HTTP/1.1 message. | |
726 | |
727 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
728 isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header'); | |
729 | |
730 is($frame->{headers}->{':status'}, 400, 'newline in request header - bad request'); | |
731 | |
732 # invalid header name as seen with underscore should not lead to ignoring rest | |
733 | |
734 $s = Test::Nginx::HTTP3->new(); | |
735 $sid = $s->new_stream({ headers => [ | |
736 { name => ':method', value => 'GET', mode => 0 }, | |
737 { name => ':scheme', value => 'http', mode => 0 }, | |
738 { name => ':path', value => '/', mode => 0 }, | |
739 { name => ':authority', value => 'localhost', mode => 2 }, | |
740 { name => 'x_foo', value => "x-bar", mode => 4 }, | |
741 { name => 'referer', value => "see-this", mode => 2 }]}); | |
742 $frames = $s->read(all => [{ type => 'HEADERS' }]); | |
743 | |
744 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
745 is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name'); | |
746 | |
747 # other invalid header name characters as seen with ':' | |
748 | |
749 $s = Test::Nginx::HTTP3->new(); | |
750 $sid = $s->new_stream({ headers => [ | |
751 { name => ':method', value => 'GET', mode => 0 }, | |
752 { name => ':scheme', value => 'http', mode => 0 }, | |
753 { name => ':path', value => '/', mode => 0 }, | |
754 { name => ':authority', value => 'localhost', mode => 2 }, | |
755 { name => 'x:foo', value => "x-bar", mode => 4 }, | |
756 { name => 'referer', value => "see-this", mode => 2 }]}); | |
757 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
758 | |
759 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
760 is($frame->{headers}->{':status'}, 400, 'colon in header name'); | |
761 | |
762 $s = Test::Nginx::HTTP3->new(); | |
763 $sid = $s->new_stream({ headers => [ | |
764 { name => ':method', value => 'GET', mode => 0 }, | |
765 { name => ':scheme', value => 'http', mode => 0 }, | |
766 { name => ':path', value => '/', mode => 0 }, | |
767 { name => ':authority', value => 'localhost', mode => 2 }, | |
768 { name => 'x foo', value => "bar", mode => 4 }]}); | |
769 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
770 | |
771 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
772 is($frame->{headers}->{':status'}, 400, 'space in header name'); | |
773 | |
774 $s = Test::Nginx::HTTP3->new(); | |
775 $sid = $s->new_stream({ headers => [ | |
776 { name => ':method', value => 'GET', mode => 0 }, | |
777 { name => ':scheme', value => 'http', mode => 0 }, | |
778 { name => ':path', value => '/', mode => 0 }, | |
779 { name => ':authority', value => 'localhost', mode => 2 }, | |
780 { name => "foo\x02", value => "bar", mode => 4 }]}); | |
781 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
782 | |
783 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
784 is($frame->{headers}->{':status'}, 400, 'control in header name'); | |
785 | |
786 # header name with underscore - underscores_in_headers on | |
787 | |
788 $s = Test::Nginx::HTTP3->new(8986); | |
789 $sid = $s->new_stream({ headers => [ | |
790 { name => ':method', value => 'GET', mode => 0 }, | |
791 { name => ':scheme', value => 'http', mode => 0 }, | |
792 { name => ':path', value => '/', mode => 0 }, | |
793 { name => ':authority', value => 'localhost', mode => 2 }, | |
794 { name => 'x_foo', value => "x-bar", mode => 4 }, | |
795 { name => 'referer', value => "see-this", mode => 2 }]}); | |
796 $frames = $s->read(all => [{ type => 'HEADERS' }]); | |
797 | |
798 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
799 is($frame->{headers}->{'x-sent-foo'}, 'x-bar', | |
800 'underscore in header name - underscores_in_headers'); | |
801 | |
802 # header name with underscore - ignore_invalid_headers off | |
803 | |
804 $s = Test::Nginx::HTTP3->new(8987); | |
805 $sid = $s->new_stream({ headers => [ | |
806 { name => ':method', value => 'GET', mode => 0 }, | |
807 { name => ':scheme', value => 'http', mode => 0 }, | |
808 { name => ':path', value => '/', mode => 0 }, | |
809 { name => ':authority', value => 'localhost', mode => 2 }, | |
810 { name => 'x_foo', value => "x-bar", mode => 4 }, | |
811 { name => 'referer', value => "see-this", mode => 2 }]}); | |
812 $frames = $s->read(all => [{ type => 'HEADERS' }]); | |
813 | |
814 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
815 is($frame->{headers}->{'x-sent-foo'}, 'x-bar', | |
816 'underscore in header name - ignore_invalid_headers'); | |
817 | |
818 # missing mandatory request header ':scheme' | |
819 | |
820 $s = Test::Nginx::HTTP3->new(); | |
821 $sid = $s->new_stream({ headers => [ | |
822 { name => ':method', value => 'GET', mode => 0 }, | |
823 { name => ':path', value => '/', mode => 0 }, | |
824 { name => ':authority', value => 'localhost', mode => 2 }]}); | |
825 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
826 | |
827 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
828 is($frame->{headers}->{':status'}, 400, 'incomplete headers'); | |
829 | |
830 # empty request header ':authority' | |
831 | |
832 $s = Test::Nginx::HTTP3->new(); | |
833 $sid = $s->new_stream({ headers => [ | |
834 { name => ':method', value => 'GET', mode => 0 }, | |
835 { name => ':scheme', value => 'http', mode => 0 }, | |
836 { name => ':path', value => '/', mode => 0 }, | |
837 { name => ':authority', value => '', mode => 0 }]}); | |
838 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
839 | |
840 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
841 is($frame->{headers}->{':status'}, 400, 'empty authority'); | |
842 | |
843 # client sent invalid :path header | |
844 | |
845 $sid = $s->new_stream({ path => 't2.html' }); | |
846 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
847 | |
848 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
849 is($frame->{headers}->{':status'}, 400, 'invalid path'); | |
850 | |
851 $sid = $s->new_stream({ path => "/t2.html\x02" }); | |
852 $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
853 | |
854 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
855 is($frame->{headers}->{':status'}, 400, 'invalid path control'); | |
856 | |
857 ############################################################################### | |
858 | |
859 sub http_daemon { | |
860 my $server = IO::Socket::INET->new( | |
861 Proto => 'tcp', | |
862 LocalHost => '127.0.0.1', | |
863 LocalPort => port(8083), | |
864 Listen => 5, | |
865 Reuse => 1 | |
866 ) | |
867 or die "Can't create listening socket: $!\n"; | |
868 | |
869 local $SIG{PIPE} = 'IGNORE'; | |
870 | |
871 while (my $client = $server->accept()) { | |
872 $client->autoflush(1); | |
873 | |
874 my $headers = ''; | |
875 my $uri = ''; | |
876 | |
877 while (<$client>) { | |
878 $headers .= $_; | |
879 last if (/^\x0d?\x0a?$/); | |
880 } | |
881 | |
882 next if $headers eq ''; | |
883 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; | |
884 | |
885 if ($uri eq '/cookie') { | |
886 | |
887 my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig; | |
888 $cookie2 = '' unless defined $cookie2; | |
889 | |
890 my ($cookie_a, $cookie_c) = ('', ''); | |
891 $cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i; | |
892 $cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i; | |
893 | |
894 print $client <<EOF; | |
895 HTTP/1.1 200 OK | |
896 Connection: close | |
897 X-Sent-Cookie: $cookie | |
898 X-Sent-Cookie2: $cookie2 | |
899 X-Sent-Cookie-a: $cookie_a | |
900 X-Sent-Cookie-c: $cookie_c | |
901 | |
902 EOF | |
903 | |
904 } elsif ($uri eq '/set-cookie') { | |
905 | |
906 print $client <<EOF; | |
907 HTTP/1.1 200 OK | |
908 Connection: close | |
909 Set-Cookie: a=b | |
910 Set-Cookie: c=d | |
911 | |
912 EOF | |
913 | |
914 } | |
915 } | |
916 } | |
917 | |
918 ############################################################################### |