blob: af4d4f31fb186706a24f88f2618f075240a22e1b [file] [log] [blame]
Andrey Zelenkov82aff462016-03-23 17:23:08 +03001#!/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
11use warnings;
12use strict;
13
14use Test::More;
15
Sergey Kandaurov62a669e2016-10-16 13:01:36 +030016use Config;
17
Andrey Zelenkov82aff462016-03-23 17:23:08 +030018BEGIN { use FindBin; chdir($FindBin::Bin); }
19
20use lib 'lib';
21use Test::Nginx;
Sergey Kandaurov67599f42016-06-17 11:36:33 +030022use Test::Nginx::HTTP2;
Andrey Zelenkov82aff462016-03-23 17:23:08 +030023
24###############################################################################
25
26select STDERR; $| = 1;
27select STDOUT; $| = 1;
28
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +030029my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(93)
Andrey Zelenkov82aff462016-03-23 17:23:08 +030030 ->write_file_expand('nginx.conf', <<'EOF');
31
32%%TEST_GLOBALS%%
33
34daemon off;
35
36events {
37}
38
39http {
40 %%TEST_GLOBALS_HTTP%%
41
42 server {
Andrey Zelenkove59bf362016-07-12 17:39:03 +030043 listen 127.0.0.1:8080 http2;
44 listen 127.0.0.1:8081;
45 listen 127.0.0.1:8082 http2 sndbuf=128;
Andrey Zelenkov82aff462016-03-23 17:23:08 +030046 server_name localhost;
47
48 http2_max_field_size 128k;
49 http2_max_header_size 128k;
50
51 location / {
52 add_header X-Sent-Foo $http_x_foo;
53 add_header X-Referer $http_referer;
54 return 200;
55 }
56 location /frame_size {
57 add_header X-LongHeader $arg_h;
58 add_header X-LongHeader $arg_h;
59 add_header X-LongHeader $arg_h;
60 alias %%TESTDIR%%/t2.html;
61 }
62 location /continuation {
63 add_header X-LongHeader $arg_h;
64 add_header X-LongHeader $arg_h;
65 add_header X-LongHeader $arg_h;
66 return 200 body;
67
68 location /continuation/204 {
69 return 204;
70 }
71 }
72 location /proxy/ {
73 add_header X-UC-a $upstream_cookie_a;
74 add_header X-UC-c $upstream_cookie_c;
Andrey Zelenkove59bf362016-07-12 17:39:03 +030075 proxy_pass http://127.0.0.1:8083/;
Andrey Zelenkov82aff462016-03-23 17:23:08 +030076 proxy_set_header X-Cookie-a $cookie_a;
77 proxy_set_header X-Cookie-c $cookie_c;
78 }
79 location /proxy2/ {
Andrey Zelenkove59bf362016-07-12 17:39:03 +030080 proxy_pass http://127.0.0.1:8081/;
Andrey Zelenkov82aff462016-03-23 17:23:08 +030081 }
82 location /set-cookie {
83 add_header Set-Cookie a=b;
84 add_header Set-Cookie c=d;
85 return 200;
86 }
87 location /cookie {
88 add_header X-Cookie $http_cookie;
89 add_header X-Cookie-a $cookie_a;
90 add_header X-Cookie-c $cookie_c;
91 return 200;
92 }
93 }
94
95 server {
Andrey Zelenkove59bf362016-07-12 17:39:03 +030096 listen 127.0.0.1:8084 http2;
Andrey Zelenkov82aff462016-03-23 17:23:08 +030097 server_name localhost;
98
99 http2_max_field_size 22;
100 }
101
102 server {
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300103 listen 127.0.0.1:8085 http2;
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300104 server_name localhost;
105
106 http2_max_header_size 64;
107 }
108}
109
110EOF
111
112$t->run_daemon(\&http_daemon);
Sergey Kandaurov3591a682017-06-16 15:18:40 +0300113$t->run()->waitforsocket('127.0.0.1:' . port(8083));
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300114
115# file size is slightly beyond initial window size: 2**16 + 80 bytes
116
117$t->write_file('t1.html',
118 join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202)));
119
120$t->write_file('t2.html', 'SEE-THIS');
121
122###############################################################################
123
124# 6.1. Indexed Header Field Representation
125
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300126my $s = Test::Nginx::HTTP2->new();
127my $sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300128 { name => ':method', value => 'GET', mode => 0 },
129 { name => ':scheme', value => 'http', mode => 0 },
130 { name => ':path', value => '/', mode => 0 },
131 { name => ':authority', value => 'localhost', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300132my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300133
134my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
135is($frame->{headers}->{':status'}, 200, 'indexed header field');
136
137# 6.2.1. Literal Header Field with Incremental Indexing
138
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300139$s = Test::Nginx::HTTP2->new();
140$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300141 { name => ':method', value => 'GET', mode => 1, huff => 0 },
142 { name => ':scheme', value => 'http', mode => 1, huff => 0 },
143 { name => ':path', value => '/', mode => 1, huff => 0 },
144 { name => ':authority', value => 'localhost', mode => 1, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300145$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300146
147($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
148is($frame->{headers}->{':status'}, 200, 'literal with indexing');
149
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300150$s = Test::Nginx::HTTP2->new();
151$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300152 { name => ':method', value => 'GET', mode => 1, huff => 1 },
153 { name => ':scheme', value => 'http', mode => 1, huff => 1 },
154 { name => ':path', value => '/', mode => 1, huff => 1 },
155 { name => ':authority', value => 'localhost', mode => 1, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300156$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300157
158($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
159is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman');
160
161# 6.2.1. Literal Header Field with Incremental Indexing -- New Name
162
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300163$s = Test::Nginx::HTTP2->new();
164$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300165 { name => ':method', value => 'GET', mode => 2, huff => 0 },
166 { name => ':scheme', value => 'http', mode => 2, huff => 0 },
167 { name => ':path', value => '/', mode => 2, huff => 0 },
168 { name => ':authority', value => 'localhost', mode => 2, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300169$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300170
171($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
172is($frame->{headers}->{':status'}, 200, 'literal with indexing - new');
173
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300174$s = Test::Nginx::HTTP2->new();
175$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300176 { name => ':method', value => 'GET', mode => 2, huff => 1 },
177 { name => ':scheme', value => 'http', mode => 2, huff => 1 },
178 { name => ':path', value => '/', mode => 2, huff => 1 },
179 { name => ':authority', value => 'localhost', mode => 2, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300180$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300181
182($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
183is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman');
184
185# 6.2.2. Literal Header Field without Indexing
186
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300187$s = Test::Nginx::HTTP2->new();
188$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300189 { name => ':method', value => 'GET', mode => 3, huff => 0 },
190 { name => ':scheme', value => 'http', mode => 3, huff => 0 },
191 { name => ':path', value => '/', mode => 3, huff => 0 },
192 { name => ':authority', value => 'localhost', mode => 3, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300193$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300194
195($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
196is($frame->{headers}->{':status'}, 200, 'literal without indexing');
197
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300198$s = Test::Nginx::HTTP2->new();
199$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300200 { name => ':method', value => 'GET', mode => 3, huff => 1 },
201 { name => ':scheme', value => 'http', mode => 3, huff => 1 },
202 { name => ':path', value => '/', mode => 3, huff => 1 },
203 { name => ':authority', value => 'localhost', mode => 3, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300204$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300205
206($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
207is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman');
208
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300209$s = Test::Nginx::HTTP2->new();
210$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300211 { name => ':method', value => 'GET', mode => 3, huff => 0 },
212 { name => ':scheme', value => 'http', mode => 3, huff => 0 },
213 { name => ':path', value => '/', mode => 3, huff => 0 },
214 { name => ':authority', value => 'localhost', mode => 3, huff => 0 },
215 { name => 'referer', value => 'foo', mode => 3, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300216$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300217
218($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
219is($frame->{headers}->{':status'}, 200,
220 'literal without indexing - multibyte index');
221is($frame->{headers}->{'x-referer'}, 'foo',
222 'literal without indexing - multibyte index value');
223
224# 6.2.2. Literal Header Field without Indexing -- New Name
225
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300226$s = Test::Nginx::HTTP2->new();
227$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300228 { name => ':method', value => 'GET', mode => 4, huff => 0 },
229 { name => ':scheme', value => 'http', mode => 4, huff => 0 },
230 { name => ':path', value => '/', mode => 4, huff => 0 },
231 { name => ':authority', value => 'localhost', mode => 4, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300232$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300233
234($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
235is($frame->{headers}->{':status'}, 200, 'literal without indexing - new');
236
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300237$s = Test::Nginx::HTTP2->new();
238$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300239 { name => ':method', value => 'GET', mode => 4, huff => 1 },
240 { name => ':scheme', value => 'http', mode => 4, huff => 1 },
241 { name => ':path', value => '/', mode => 4, huff => 1 },
242 { name => ':authority', value => 'localhost', mode => 4, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300243$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300244
245($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
246is($frame->{headers}->{':status'}, 200,
247 'literal without indexing - new huffman');
248
249# 6.2.3. Literal Header Field Never Indexed
250
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300251$s = Test::Nginx::HTTP2->new();
252$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300253 { name => ':method', value => 'GET', mode => 5, huff => 0 },
254 { name => ':scheme', value => 'http', mode => 5, huff => 0 },
255 { name => ':path', value => '/', mode => 5, huff => 0 },
256 { name => ':authority', value => 'localhost', mode => 5, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300257$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300258
259($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
260is($frame->{headers}->{':status'}, 200, 'literal never indexed');
261
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300262$s = Test::Nginx::HTTP2->new();
263$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300264 { name => ':method', value => 'GET', mode => 5, huff => 1 },
265 { name => ':scheme', value => 'http', mode => 5, huff => 1 },
266 { name => ':path', value => '/', mode => 5, huff => 1 },
267 { name => ':authority', value => 'localhost', mode => 5, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300268$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300269
270($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
271is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman');
272
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300273$s = Test::Nginx::HTTP2->new();
274$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300275 { name => ':method', value => 'GET', mode => 5, huff => 0 },
276 { name => ':scheme', value => 'http', mode => 5, huff => 0 },
277 { name => ':path', value => '/', mode => 5, huff => 0 },
278 { name => ':authority', value => 'localhost', mode => 5, huff => 0 },
279 { name => 'referer', value => 'foo', mode => 5, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300280$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300281
282($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
283is($frame->{headers}->{':status'}, 200,
284 'literal never indexed - multibyte index');
285is($frame->{headers}->{'x-referer'}, 'foo',
286 'literal never indexed - multibyte index value');
287
288# 6.2.3. Literal Header Field Never Indexed -- New Name
289
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300290$s = Test::Nginx::HTTP2->new();
291$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300292 { name => ':method', value => 'GET', mode => 6, huff => 0 },
293 { name => ':scheme', value => 'http', mode => 6, huff => 0 },
294 { name => ':path', value => '/', mode => 6, huff => 0 },
295 { name => ':authority', value => 'localhost', mode => 6, huff => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300296$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300297
298($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
299is($frame->{headers}->{':status'}, 200, 'literal never indexed - new');
300
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300301$s = Test::Nginx::HTTP2->new();
302$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300303 { name => ':method', value => 'GET', mode => 6, huff => 1 },
304 { name => ':scheme', value => 'http', mode => 6, huff => 1 },
305 { name => ':path', value => '/', mode => 6, huff => 1 },
306 { name => ':authority', value => 'localhost', mode => 6, huff => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300307$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300308
309($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
310is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman');
311
312# reuse literal with multibyte indexing
313
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300314$s = Test::Nginx::HTTP2->new();
315$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300316 { name => ':method', value => 'GET', mode => 0 },
317 { name => ':scheme', value => 'http', mode => 0 },
318 { name => ':path', value => '/', mode => 0 },
319 { name => ':authority', value => 'localhost', mode => 1 },
320 { name => 'referer', value => 'foo', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300321$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300322
323($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
324is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new');
325
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300326$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300327 { name => ':method', value => 'GET', mode => 0 },
328 { name => ':scheme', value => 'http', mode => 0 },
329 { name => ':path', value => '/', mode => 0 },
330 { name => ':authority', value => 'localhost', mode => 0 },
331 { name => 'referer', value => 'foo', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300332$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300333
334($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
335is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed');
336
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300337$s = Test::Nginx::HTTP2->new();
338$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300339 { name => ':method', value => 'GET', mode => 0 },
340 { name => ':scheme', value => 'http', mode => 0 },
341 { name => ':path', value => '/', mode => 0 },
342 { name => ':authority', value => 'localhost', mode => 1 },
343 { name => 'x-foo', value => 'X-Bar', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300344$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300345
346($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
347is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new');
348
349# reuse literal with multibyte indexing - reused name
350
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300351$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300352 { name => ':method', value => 'GET', mode => 0 },
353 { name => ':scheme', value => 'http', mode => 0 },
354 { name => ':path', value => '/', mode => 0 },
355 { name => ':authority', value => 'localhost', mode => 0 },
356 { name => 'x-foo', value => 'X-Bar', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300357$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300358
359($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
360is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed');
361
362# reuse literal with multibyte indexing - reused name only
363
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300364$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300365 { name => ':method', value => 'GET', mode => 0 },
366 { name => ':scheme', value => 'http', mode => 0 },
367 { name => ':path', value => '/', mode => 0 },
368 { name => ':authority', value => 'localhost', mode => 0 },
369 { name => 'x-foo', value => 'X-Baz', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300370$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300371
372($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
373is($frame->{headers}->{'x-sent-foo'}, 'X-Baz',
374 'name with indexing - indexed name');
375
376# response header field with characters not suitable for huffman encoding
377
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300378$s = Test::Nginx::HTTP2->new();
379$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300380 { name => ':method', value => 'GET', mode => 0 },
381 { name => ':scheme', value => 'http', mode => 0 },
382 { name => ':path', value => '/', mode => 0 },
383 { name => ':authority', value => 'localhost', mode => 1 },
384 { name => 'x-foo', value => '{{{{{', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300385$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300386
387($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
388is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars');
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300389like($s->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding');
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300390
391# response header field with huffman encoding
392# NB: implementation detail, not obligated
393
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300394$s = Test::Nginx::HTTP2->new();
395$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300396 { name => ':method', value => 'GET', mode => 0 },
397 { name => ':scheme', value => 'http', mode => 0 },
398 { name => ':path', value => '/', mode => 0 },
399 { name => ':authority', value => 'localhost', mode => 1 },
400 { name => 'x-foo', value => 'aaaaa', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300401$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300402
403($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
404is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars');
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300405unlike($s->{headers}, qr/aaaaa/, 'well known chars - huffman encoding');
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300406
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300407# response header field with huffman encoding - complete table mod \0, CR, LF
408# first saturate with short-encoded characters (NB: implementation detail)
409
410my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255);
411
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300412$s = Test::Nginx::HTTP2->new();
413$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300414 { name => ':method', value => 'GET', mode => 0 },
415 { name => ':scheme', value => 'http', mode => 0 },
416 { name => ':path', value => '/', mode => 0 },
417 { name => ':authority', value => 'localhost', mode => 1 },
418 { name => 'x-foo', value => $field, mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300419$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300420
421($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
422is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars');
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300423unlike($s->{headers}, qr/abcde/, 'all chars - huffman encoding');
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300424
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300425# 6.3. Dynamic Table Size Update
426
427# remove some indexed headers from the dynamic table
428# by maintaining dynamic table space only for index 0
429# 'x-foo' has index 0, and 'referer' has index 1
430
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300431$s = Test::Nginx::HTTP2->new();
432$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300433 { name => ':method', value => 'GET', mode => 0 },
434 { name => ':scheme', value => 'http', mode => 0 },
435 { name => ':path', value => '/', mode => 0 },
436 { name => ':authority', value => 'localhost', mode => 1 },
437 { name => 'referer', value => 'foo', mode => 1 },
438 { name => 'x-foo', value => 'X-Bar', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300439$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300440
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300441$sid = $s->new_stream({ table_size => 61, headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300442 { name => ':method', value => 'GET', mode => 0 },
443 { name => ':scheme', value => 'http', mode => 0 },
444 { name => ':path', value => '/', mode => 0 },
445 { name => 'x-foo', value => 'X-Bar', mode => 0 },
446 { name => ':authority', value => 'localhost', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300447$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300448
449($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
450isnt($frame, undef, 'updated table size - remaining index');
451
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300452$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300453 { name => ':method', value => 'GET', mode => 0 },
454 { name => ':scheme', value => 'http', mode => 0 },
455 { name => ':path', value => '/', mode => 0 },
456 { name => ':authority', value => 'localhost', mode => 1 },
457 { name => 'referer', value => 'foo', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300458$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300459
460($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
461is($frame, undef, 'invalid index');
462
463# 5.4.1. Connection Error Handling
464# An endpoint that encounters a connection error SHOULD first send a
465# GOAWAY frame <..>
466
467($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
468ok($frame, 'invalid index - GOAWAY');
469
470# RFC 7541, 2.3.3. Index Address Space
471# Indices strictly greater than the sum of the lengths of both tables
472# MUST be treated as a decoding error.
473
474# 4.3. Header Compression and Decompression
475# A decoding error in a header block MUST be treated
476# as a connection error of type COMPRESSION_ERROR.
477
478is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream');
479is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR');
480
481# HPACK zero index
482
483# RFC 7541, 6.1 Indexed Header Field Representation
484# The index value of 0 is not used. It MUST be treated as a decoding
485# error if found in an indexed header field representation.
486
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300487$s = Test::Nginx::HTTP2->new();
488$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300489 { name => ':method', value => 'GET', mode => 0 },
490 { name => ':scheme', value => 'http', mode => 0 },
491 { name => ':path', value => '/', mode => 0 },
492 { name => ':authority', value => 'localhost', mode => 1 },
493 { name => '', value => '', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300494$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300495
496ok($frame, 'zero index - GOAWAY');
497is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR');
498
499# invalid table size update
500
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300501$s = Test::Nginx::HTTP2->new();
502$sid = $s->new_stream({ table_size => 4097, headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300503 { name => ':method', value => 'GET', mode => 0 },
504 { name => ':scheme', value => 'http', mode => 0 },
505 { name => ':path', value => '/', mode => 0 },
506 { name => 'x-foo', value => 'X-Bar', mode => 0 },
507 { name => ':authority', value => 'localhost', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300508$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300509
510($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
511ok($frame, 'invalid table size - GOAWAY');
512is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream');
513is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR');
514
515# request header field with multiple values
516
517# 8.1.2.5. Compressing the Cookie Header Field
518# To allow for better compression efficiency, the Cookie header field
519# MAY be split into separate header fields <..>.
520
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300521$s = Test::Nginx::HTTP2->new();
522$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300523 { name => ':method', value => 'GET', mode => 0 },
524 { name => ':scheme', value => 'http', mode => 0 },
525 { name => ':path', value => '/cookie', mode => 2 },
526 { name => ':authority', value => 'localhost', mode => 1 },
527 { name => 'cookie', value => 'a=b', mode => 2},
528 { name => 'cookie', value => 'c=d', mode => 2}]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300529$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300530
531($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
532is($frame->{headers}->{'x-cookie-a'}, 'b',
533 'multiple request header fields - cookie');
534is($frame->{headers}->{'x-cookie-c'}, 'd',
535 'multiple request header fields - cookie 2');
536is($frame->{headers}->{'x-cookie'}, 'a=b; c=d',
537 'multiple request header fields - semi-colon');
538
539# request header field with multiple values to HTTP backend
540
541# 8.1.2.5. Compressing the Cookie Header Field
542# these MUST be concatenated into a single octet string
543# using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
544# before being passed into a non-HTTP/2 context, such as an HTTP/1.1
545# connection <..>
546
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300547$s = Test::Nginx::HTTP2->new();
548$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300549 { name => ':method', value => 'GET', mode => 0 },
550 { name => ':scheme', value => 'http', mode => 0 },
551 { name => ':path', value => '/proxy/cookie', mode => 2 },
552 { name => ':authority', value => 'localhost', mode => 1 },
553 { name => 'cookie', value => 'a=b', mode => 2 },
554 { name => 'cookie', value => 'c=d', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300555$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300556
557($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
558is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d',
559 'multiple request header fields proxied - semi-colon');
560is($frame->{headers}->{'x-sent-cookie2'}, '',
561 'multiple request header fields proxied - dublicate cookie');
562is($frame->{headers}->{'x-sent-cookie-a'}, 'b',
563 'multiple request header fields proxied - cookie 1');
564is($frame->{headers}->{'x-sent-cookie-c'}, 'd',
565 'multiple request header fields proxied - cookie 2');
566
567# response header field with multiple values
568
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300569$s = Test::Nginx::HTTP2->new();
570$sid = $s->new_stream({ path => '/set-cookie' });
571$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300572
573($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
574is($frame->{headers}->{'set-cookie'}[0], 'a=b',
575 'multiple response header fields - cookie');
576is($frame->{headers}->{'set-cookie'}[1], 'c=d',
577 'multiple response header fields - cookie 2');
578
579# response header field with multiple values from HTTP backend
580
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300581$s = Test::Nginx::HTTP2->new();
582$sid = $s->new_stream({ path => '/proxy/set-cookie' });
583$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300584
585($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
586is($frame->{headers}->{'set-cookie'}[0], 'a=b',
587 'multiple response header proxied - cookie');
588is($frame->{headers}->{'set-cookie'}[1], 'c=d',
589 'multiple response header proxied - cookie 2');
590is($frame->{headers}->{'x-uc-a'}, 'b',
591 'multiple response header proxied - upstream cookie');
592is($frame->{headers}->{'x-uc-c'}, 'd',
593 'multiple response header proxied - upstream cookie 2');
594
595# CONTINUATION in response
596# put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2)
597# to break header block into separate frames, one such field per frame
598
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300599$s = Test::Nginx::HTTP2->new();
600$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**13 });
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300601
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300602$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300603my @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300604my $data = $data[-1];
605is(@{$data->{headers}{'x-longheader'}}, 3,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300606 'response CONTINUATION - headers');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300607is($data->{headers}{'x-longheader'}[0], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300608 'response CONTINUATION - header 1');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300609is($data->{headers}{'x-longheader'}[1], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300610 'response CONTINUATION - header 2');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300611is($data->{headers}{'x-longheader'}[2], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300612 'response CONTINUATION - header 3');
613@data = sort { $a <=> $b } map { $_->{length} } @data;
614cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size');
615
616# same but without response DATA frames
617
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300618$s = Test::Nginx::HTTP2->new();
619$sid = $s->new_stream({ path => '/continuation/204?h=' . 'x' x 2**13 });
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300620
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300621$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300622@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300623$data = $data[-1];
624is(@{$data->{headers}{'x-longheader'}}, 3,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300625 'no body CONTINUATION - headers');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300626is($data->{headers}{'x-longheader'}[0], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300627 'no body CONTINUATION - header 1');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300628is($data->{headers}{'x-longheader'}[1], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300629 'no body CONTINUATION - header 2');
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300630is($data->{headers}{'x-longheader'}[2], 'x' x 2**13,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300631 'no body CONTINUATION - header 3');
632@data = sort { $a <=> $b } map { $_->{length} } @data;
633cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size');
634
635# response header block is always split by SETTINGS_MAX_FRAME_SIZE
636
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300637$s = Test::Nginx::HTTP2->new();
638$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 });
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300639
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300640$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300641@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
642@data = sort { $a <=> $b } map { $_->{length} } @data;
643cmp_ok($data[-1], '<=', 2**14, 'response header frames limited');
644
645# response header frame sent in parts
646
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300647$s = Test::Nginx::HTTP2->new(port(8082));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300648$s->h2_settings(0, 0x5 => 2**17);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300649
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300650$sid = $s->new_stream({ path => '/frame_size?h=' . 'x' x 2**15 });
651$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300652
653($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
654ok($frame, 'response header - parts');
655
656SKIP: {
657skip 'response header failed', 1 unless $frame;
Sergey Kandaurov62a669e2016-10-16 13:01:36 +0300658skip 'broken sendfile', 1 if $^O eq 'freebsd' and
659 $Config{osvers} =~ '11.0-release' and
660 $t->read_file('nginx.conf') =~ /sendfile on/;
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300661
662is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304,
663 'response header - headers');
664
665}
666
667# response header block split and sent in parts
668
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300669$s = Test::Nginx::HTTP2->new(port(8082));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300670$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 });
671$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300672
673@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300674ok(@data, 'response header split');
675
676SKIP: {
677skip 'response header split failed', 2 unless @data;
678
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300679my ($lengths) = sort { $b <=> $a } map { $_->{length} } @data;
680cmp_ok($lengths, '<=', 16384, 'response header split - max size');
681
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300682is(length join('', @{$data[-1]->{headers}->{'x-longheader'}}), 98304,
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300683 'response header split - headers');
684
Sergey Kandaurov1e210ec2017-05-03 13:36:40 +0300685}
686
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300687# max_field_size - header field name
688
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300689$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300690$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300691 { name => ':method', value => 'GET', mode => 0 },
692 { name => ':scheme', value => 'http', mode => 0 },
693 { name => ':path', value => '/t2.html', mode => 1 },
694 { name => ':authority', value => 'localhost', mode => 1 },
695 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300696$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300697
698($frame) = grep { $_->{type} eq 'DATA' } @$frames;
699ok($frame, 'field name size less');
700
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300701$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300702 { name => ':method', value => 'GET', mode => 0 },
703 { name => ':scheme', value => 'http', mode => 0 },
704 { name => ':path', value => '/t2.html', mode => 1 },
705 { name => ':authority', value => 'localhost', mode => 1 },
706 { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300707$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300708
709($frame) = grep { $_->{type} eq 'DATA' } @$frames;
710ok($frame, 'field name size second');
711
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300712$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300713$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300714 { name => ':method', value => 'GET', mode => 0 },
715 { name => ':scheme', value => 'http', mode => 0 },
716 { name => ':path', value => '/t2.html', mode => 1 },
717 { name => ':authority', value => 'localhost', mode => 1 },
718 { name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300719$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300720
721($frame) = grep { $_->{type} eq 'DATA' } @$frames;
722ok($frame, 'field name size equal');
723
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300724$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300725$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300726 { name => ':method', value => 'GET', mode => 0 },
727 { name => ':scheme', value => 'http', mode => 0 },
728 { name => ':path', value => '/t2.html', mode => 1 },
729 { name => ':authority', value => 'localhost', mode => 1 },
730 { name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300731$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300732
733($frame) = grep { $_->{type} eq 'DATA' } @$frames;
734is($frame, undef, 'field name size greater');
735
736# max_field_size - header field value
737
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300738$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300739$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300740 { name => ':method', value => 'GET', mode => 0 },
741 { name => ':scheme', value => 'http', mode => 0 },
742 { name => ':path', value => '/t2.html', mode => 1 },
743 { name => ':authority', value => 'localhost', mode => 1 },
744 { name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300745$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300746
747($frame) = grep { $_->{type} eq 'DATA' } @$frames;
748ok($frame, 'field value size less');
749
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300750$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300751$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300752 { name => ':method', value => 'GET', mode => 0 },
753 { name => ':scheme', value => 'http', mode => 0 },
754 { name => ':path', value => '/t2.html', mode => 1 },
755 { name => ':authority', value => 'localhost', mode => 1 },
756 { name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300757$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300758
759($frame) = grep { $_->{type} eq 'DATA' } @$frames;
760ok($frame, 'field value size equal');
761
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300762$s = Test::Nginx::HTTP2->new(port(8084));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300763$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300764 { name => ':method', value => 'GET', mode => 0 },
765 { name => ':scheme', value => 'http', mode => 0 },
766 { name => ':path', value => '/t2.html', mode => 1 },
767 { name => ':authority', value => 'localhost', mode => 1 },
768 { name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300769$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300770
771($frame) = grep { $_->{type} eq 'DATA' } @$frames;
772is($frame, undef, 'field value size greater');
773
774# max_header_size
775
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300776$s = Test::Nginx::HTTP2->new(port(8085));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300777$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300778 { name => ':method', value => 'GET', mode => 0 },
779 { name => ':scheme', value => 'http', mode => 0 },
780 { name => ':path', value => '/t2.html', mode => 1 },
781 { name => ':authority', value => 'localhost', mode => 1 },
782 { name => 'longname9', value => 'x', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300783$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300784
785($frame) = grep { $_->{type} eq 'DATA' } @$frames;
786ok($frame, 'header size less');
787
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300788$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300789 { name => ':method', value => 'GET', mode => 0 },
790 { name => ':scheme', value => 'http', mode => 0 },
791 { name => ':path', value => '/t2.html', mode => 1 },
792 { name => ':authority', value => 'localhost', mode => 1 },
793 { name => 'longname9', value => 'x', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300794$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300795
796($frame) = grep { $_->{type} eq 'DATA' } @$frames;
797ok($frame, 'header size second');
798
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300799$s = Test::Nginx::HTTP2->new(port(8085));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300800$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300801 { name => ':method', value => 'GET', mode => 0 },
802 { name => ':scheme', value => 'http', mode => 0 },
803 { name => ':path', value => '/t2.html', mode => 1 },
804 { name => ':authority', value => 'localhost', mode => 1 },
805 { name => 'longname9', value => 'xx', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300806$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300807
808($frame) = grep { $_->{type} eq 'DATA' } @$frames;
809ok($frame, 'header size equal');
810
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300811$s = Test::Nginx::HTTP2->new(port(8085));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300812$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300813 { name => ':method', value => 'GET', mode => 0 },
814 { name => ':scheme', value => 'http', mode => 0 },
815 { name => ':path', value => '/t2.html', mode => 1 },
816 { name => ':authority', value => 'localhost', mode => 1 },
817 { name => 'longname9', value => 'xxx', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300818$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300819
820($frame) = grep { $_->{type} eq 'DATA' } @$frames;
821is($frame, undef, 'header size greater');
822
823# header size is based on (decompressed) header list
824# two extra 1-byte indices would otherwise fit in max_header_size
825
Andrey Zelenkove59bf362016-07-12 17:39:03 +0300826$s = Test::Nginx::HTTP2->new(port(8085));
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300827$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300828 { name => ':method', value => 'GET', mode => 0 },
829 { name => ':scheme', value => 'http', mode => 0 },
830 { name => ':path', value => '/t2.html', mode => 1 },
831 { name => ':authority', value => 'localhost', mode => 1 },
832 { name => 'longname9', value => 'x', mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300833$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300834
835($frame) = grep { $_->{type} eq 'DATA' } @$frames;
836ok($frame, 'header size new index');
837
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300838$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300839 { name => ':method', value => 'GET', mode => 0 },
840 { name => ':scheme', value => 'http', mode => 0 },
841 { name => ':path', value => '/t2.html', mode => 1 },
842 { name => ':authority', value => 'localhost', mode => 1 },
843 { name => 'longname9', value => 'x', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300844$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300845
846($frame) = grep { $_->{type} eq 'DATA' } @$frames;
847ok($frame, 'header size indexed');
848
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300849$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300850 { name => ':method', value => 'GET', mode => 0 },
851 { name => ':scheme', value => 'http', mode => 0 },
852 { name => ':path', value => '/t2.html', mode => 1 },
853 { name => ':authority', value => 'localhost', mode => 1 },
854 { name => 'longname9', value => 'x', mode => 0 },
855 { name => 'longname9', value => 'x', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300856$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300857
858($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames;
859is($frame->{code}, 0xb, 'header size indexed greater');
860
861# HPACK table boundary
862
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300863$s = Test::Nginx::HTTP2->new();
864$s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300865 { name => ':method', value => 'GET', mode => 0 },
866 { name => ':scheme', value => 'http', mode => 0 },
867 { name => ':path', value => '/', mode => 0 },
868 { name => ':authority', value => '', mode => 0 },
869 { name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]);
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300870$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300871 { name => ':method', value => 'GET', mode => 0 },
872 { name => ':scheme', value => 'http', mode => 0 },
873 { name => ':path', value => '/', mode => 0 },
874 { name => ':authority', value => '', mode => 0 },
875 { name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]);
876
877($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
878ok($frame, 'HPACK table boundary');
879
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300880$s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300881 { name => ':method', value => 'GET', mode => 0 },
882 { name => ':scheme', value => 'http', mode => 0 },
883 { name => ':path', value => '/', mode => 0 },
884 { name => ':authority', value => '', mode => 0 },
885 { name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]);
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300886$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300887 { name => ':method', value => 'GET', mode => 0 },
888 { name => ':scheme', value => 'http', mode => 0 },
889 { name => ':path', value => '/', mode => 0 },
890 { name => ':authority', value => '', mode => 0 },
891 { name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]);
892
893($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
894ok($frame, 'HPACK table boundary - header field name');
895
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300896$s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300897 { name => ':method', value => 'GET', mode => 0 },
898 { name => ':scheme', value => 'http', mode => 0 },
899 { name => ':path', value => '/', mode => 0 },
900 { name => ':authority', value => '', mode => 0 },
901 { name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]);
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300902$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300903 { name => ':method', value => 'GET', mode => 0 },
904 { name => ':scheme', value => 'http', mode => 0 },
905 { name => ':path', value => '/', mode => 0 },
906 { name => ':authority', value => '', mode => 0 },
907 { name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]);
908
909($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
910ok($frame, 'HPACK table boundary - header field value');
911
912# ensure that request header field value with newline doesn't get split
913#
914# 10.3. Intermediary Encapsulation Attacks
915# Any request or response that contains a character not permitted
916# in a header field value MUST be treated as malformed.
917
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300918$s = Test::Nginx::HTTP2->new();
919$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300920 { name => ':method', value => 'GET', mode => 0 },
921 { name => ':scheme', value => 'http', mode => 0 },
922 { name => ':path', value => '/proxy2/', mode => 1 },
923 { name => ':authority', value => 'localhost', mode => 1 },
924 { name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300925$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300926
927# 10.3. Intermediary Encapsulation Attacks
928# An intermediary therefore cannot translate an HTTP/2 request or response
929# containing an invalid field name into an HTTP/1.1 message.
930
931($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
932isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header');
933
934# 8.1.2.6. Malformed Requests and Responses
935# Malformed requests or responses that are detected MUST be treated
936# as a stream error (Section 5.4.2) of type PROTOCOL_ERROR.
937
938($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
939is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid');
940is($frame->{length}, 4, 'newline in request header - RST_STREAM length');
941is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags');
942is($frame->{code}, 1, 'newline in request header - RST_STREAM code');
943
944# invalid header name as seen with underscore should not lead to ignoring rest
945
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300946$s = Test::Nginx::HTTP2->new();
947$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300948 { name => ':method', value => 'GET', mode => 0 },
949 { name => ':scheme', value => 'http', mode => 0 },
950 { name => ':path', value => '/', mode => 0 },
951 { name => ':authority', value => 'localhost', mode => 1 },
952 { name => 'x_foo', value => "x-bar", mode => 2 },
953 { name => 'referer', value => "see-this", mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300954$frames = $s->read(all => [{ type => 'HEADERS' }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300955
956($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
957is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name');
958
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300959# missing mandatory request header ':scheme'
960
961TODO: {
Sergey Kandaurovd3af7fb2017-06-14 12:38:45 +0300962local $TODO = 'not yet' unless $t->has_version('1.13.2');
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300963
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300964$s = Test::Nginx::HTTP2->new();
965$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300966 { name => ':method', value => 'GET', mode => 0 },
967 { name => ':path', value => '/', mode => 0 },
968 { name => ':authority', value => 'localhost', mode => 1 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300969$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300970
971($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
972is($frame->{headers}->{':status'}, 400, 'incomplete headers');
973
974}
975
976# empty request header ':authority'
977
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300978$s = Test::Nginx::HTTP2->new();
979$sid = $s->new_stream({ headers => [
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300980 { name => ':method', value => 'GET', mode => 0 },
981 { name => ':scheme', value => 'http', mode => 0 },
982 { name => ':path', value => '/', mode => 0 },
983 { name => ':authority', value => '', mode => 0 }]});
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300984$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300985
986($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
987is($frame->{headers}->{':status'}, 400, 'empty authority');
988
989# client sent invalid :path header
990
Sergey Kandaurov67599f42016-06-17 11:36:33 +0300991$sid = $s->new_stream({ path => 't1.html' });
992$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300993
994($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
995is($frame->{code}, 1, 'invalid path');
996
997###############################################################################
998
Andrey Zelenkov82aff462016-03-23 17:23:08 +0300999sub http_daemon {
1000 my $server = IO::Socket::INET->new(
1001 Proto => 'tcp',
1002 LocalHost => '127.0.0.1',
Andrey Zelenkove59bf362016-07-12 17:39:03 +03001003 LocalPort => port(8083),
Andrey Zelenkov82aff462016-03-23 17:23:08 +03001004 Listen => 5,
1005 Reuse => 1
1006 )
1007 or die "Can't create listening socket: $!\n";
1008
1009 local $SIG{PIPE} = 'IGNORE';
1010
1011 while (my $client = $server->accept()) {
1012 $client->autoflush(1);
1013
1014 my $headers = '';
1015 my $uri = '';
1016
1017 while (<$client>) {
1018 $headers .= $_;
1019 last if (/^\x0d?\x0a?$/);
1020 }
1021
1022 next if $headers eq '';
1023 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
1024
1025 if ($uri eq '/cookie') {
1026
1027 my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig;
1028 $cookie2 = '' unless defined $cookie2;
1029
1030 my ($cookie_a, $cookie_c) = ('', '');
1031 $cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i;
1032 $cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i;
1033
1034 print $client <<EOF;
1035HTTP/1.1 200 OK
1036Connection: close
1037X-Sent-Cookie: $cookie
1038X-Sent-Cookie2: $cookie2
1039X-Sent-Cookie-a: $cookie_a
1040X-Sent-Cookie-c: $cookie_c
1041
1042EOF
1043
1044 } elsif ($uri eq '/set-cookie') {
1045
1046 print $client <<EOF;
1047HTTP/1.1 200 OK
1048Connection: close
1049Set-Cookie: a=b
1050Set-Cookie: c=d
1051
1052EOF
1053
1054 }
1055
1056 } continue {
1057 close $client;
1058 }
1059}
1060
1061###############################################################################