1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http2.impl;
29
30 import java.util.Arrays;
31 import java.util.List;
32
33 import org.apache.hc.core5.http.Header;
34 import org.apache.hc.core5.http.HttpException;
35 import org.apache.hc.core5.http.HttpHost;
36 import org.apache.hc.core5.http.HttpRequest;
37 import org.apache.hc.core5.http.ProtocolException;
38 import org.apache.hc.core5.http.message.BasicHeader;
39 import org.apache.hc.core5.http.message.BasicHttpRequest;
40 import org.apache.hc.core5.net.URIAuthority;
41 import org.junit.jupiter.api.Assertions;
42 import org.junit.jupiter.api.Test;
43
44 class TestDefaultH2RequestConverter {
45
46 @Test
47 void testConvertFromFieldsBasic() throws Exception {
48
49 final List<Header> headers = Arrays.asList(
50 new BasicHeader(":method", "GET"),
51 new BasicHeader(":scheme", "http"),
52 new BasicHeader(":authority", "www.example.com"),
53 new BasicHeader(":path", "/"),
54 new BasicHeader("custom123", "value"));
55
56 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
57 final HttpRequest request = converter.convert(headers);
58 Assertions.assertNotNull(request);
59 Assertions.assertEquals("GET", request.getMethod());
60 Assertions.assertEquals("http", request.getScheme());
61 Assertions.assertEquals(new URIAuthority("www.example.com"), request.getAuthority());
62 Assertions.assertEquals("/", request.getPath());
63 final Header[] allHeaders = request.getHeaders();
64 Assertions.assertEquals(1, allHeaders.length);
65 Assertions.assertEquals("custom123", allHeaders[0].getName());
66 Assertions.assertEquals("value", allHeaders[0].getValue());
67 }
68
69 @Test
70 void testConvertFromFieldsUpperCaseHeaderName() {
71 final List<Header> headers = Arrays.asList(
72 new BasicHeader(":method", "GET"),
73 new BasicHeader(":scheme", "http"),
74 new BasicHeader(":authority", "www.example.com"),
75 new BasicHeader(":Path", "/"),
76 new BasicHeader("custom", "value"));
77
78 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
79 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
80 "Header name ':Path' is invalid (header name contains uppercase characters)");
81 }
82
83 @Test
84 void testConvertFromFieldsConnectionHeader() {
85 final List<Header> headers = Arrays.asList(
86 new BasicHeader(":method", "GET"),
87 new BasicHeader(":scheme", "http"),
88 new BasicHeader(":authority", "www.example.com"),
89 new BasicHeader(":path", "/"),
90 new BasicHeader("connection", "keep-alive"));
91
92 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
93 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
94 "Header 'connection: keep-alive' is illegal for HTTP/2 messages");
95 }
96
97 @Test
98 void testConvertFromFieldsPseudoHeaderSequence() {
99 final List<Header> headers = Arrays.asList(
100 new BasicHeader(":method", "GET"),
101 new BasicHeader(":scheme", "http"),
102 new BasicHeader("custom", "value"),
103 new BasicHeader(":authority", "www.example.com"),
104 new BasicHeader(":path", "/"));
105
106 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
107 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
108 "Invalid sequence of headers (pseudo-headers must precede message headers)");
109 }
110
111 @Test
112 void testConvertFromFieldsMissingMethod() {
113 final List<Header> headers = Arrays.asList(
114 new BasicHeader(":scheme", "http"),
115 new BasicHeader(":authority", "www.example.com"),
116 new BasicHeader(":path", "/"),
117 new BasicHeader("custom", "value"));
118
119 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
120 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
121 "Mandatory request header ':method' not found");
122 }
123
124 @Test
125 void testConvertFromFieldsMissingScheme() {
126 final List<Header> headers = Arrays.asList(
127 new BasicHeader(":method", "GET"),
128 new BasicHeader(":authority", "www.example.com"),
129 new BasicHeader(":path", "/"),
130 new BasicHeader("custom", "value"));
131
132 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
133 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
134 "Mandatory request header ':scheme' not found");
135 }
136
137 @Test
138 void testConvertFromFieldsMissingPath() {
139 final List<Header> headers = Arrays.asList(
140 new BasicHeader(":method", "GET"),
141 new BasicHeader(":scheme", "http"),
142 new BasicHeader(":authority", "www.example.com"),
143 new BasicHeader("custom", "value"));
144
145 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
146 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
147 "Mandatory request header ':path' not found");
148 }
149
150 @Test
151 void testConvertFromFieldsUnknownPseudoHeader() {
152 final List<Header> headers = Arrays.asList(
153 new BasicHeader(":method", "GET"),
154 new BasicHeader(":scheme", "http"),
155 new BasicHeader(":authority", "www.example.com"),
156 new BasicHeader(":path", "/"),
157 new BasicHeader(":custom", "value"));
158
159 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
160 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
161 "Unsupported request header ':custom'");
162 }
163
164 @Test
165 void testConvertFromFieldsMultipleMethod() {
166 final List<Header> headers = Arrays.asList(
167 new BasicHeader(":method", "GET"),
168 new BasicHeader(":method", "GET"),
169 new BasicHeader(":scheme", "http"),
170 new BasicHeader(":authority", "www.example.com"),
171 new BasicHeader(":path", "/"),
172 new BasicHeader("custom", "value"));
173
174 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
175 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
176 "Multiple ':method' request headers are illegal");
177 }
178
179 @Test
180 void testConvertFromFieldsMultipleScheme() {
181 final List<Header> headers = Arrays.asList(
182 new BasicHeader(":method", "GET"),
183 new BasicHeader(":scheme", "http"),
184 new BasicHeader(":scheme", "https"),
185 new BasicHeader(":authority", "www.example.com"),
186 new BasicHeader(":path", "/"),
187 new BasicHeader("custom", "value"));
188
189 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
190 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
191 "Multiple ':scheme' request headers are illegal");
192 }
193
194 @Test
195 void testConvertFromFieldsMultiplePath() {
196 final List<Header> headers = Arrays.asList(
197 new BasicHeader(":method", "GET"),
198 new BasicHeader(":scheme", "https"),
199 new BasicHeader(":authority", "www.example.com"),
200 new BasicHeader(":path", "/"),
201 new BasicHeader(":path", "/"),
202 new BasicHeader("custom", "value"));
203
204 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
205 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
206 "Multiple ':path' request headers are illegal");
207 }
208
209 @Test
210 void testConvertFromFieldsConnect() throws Exception {
211
212 final List<Header> headers = Arrays.asList(
213 new BasicHeader(":method", "CONNECT"),
214 new BasicHeader(":authority", "www.example.com"),
215 new BasicHeader("custom", "value"));
216
217 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
218 converter.convert(headers);
219 }
220
221 @Test
222 void testConvertFromFieldsConnectMissingAuthority() {
223 final List<Header> headers = Arrays.asList(
224 new BasicHeader(":method", "CONNECT"),
225 new BasicHeader("custom", "value"));
226
227 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
228 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
229 "Header ':authority' is mandatory for CONNECT request");
230 }
231
232 @Test
233 void testConvertFromFieldsConnectPresentScheme() {
234 final List<Header> headers = Arrays.asList(
235 new BasicHeader(":method", "CONNECT"),
236 new BasicHeader(":scheme", "http"),
237 new BasicHeader(":authority", "www.example.com"),
238 new BasicHeader("custom", "value"));
239
240 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
241 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
242 "Header ':scheme' must not be set for CONNECT request");
243 }
244
245 @Test
246 void testConvertFromFieldsConnectPresentPath() {
247 final List<Header> headers = Arrays.asList(
248 new BasicHeader(":method", "CONNECT"),
249 new BasicHeader(":authority", "www.example.com"),
250 new BasicHeader(":path", "/"),
251 new BasicHeader("custom", "value"));
252
253 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
254 Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
255 "Header ':path' must not be set for CONNECT request");
256 }
257
258 @Test
259 void testConvertFromMessageBasic() throws Exception {
260
261 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
262 request.addHeader("custom123", "Value");
263
264 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
265 final List<Header> headers = converter.convert(request);
266
267 Assertions.assertNotNull(headers);
268 Assertions.assertEquals(5, headers.size());
269 final Header header1 = headers.get(0);
270 Assertions.assertEquals(":method", header1.getName());
271 Assertions.assertEquals("GET", header1.getValue());
272 final Header header2 = headers.get(1);
273 Assertions.assertEquals(":scheme", header2.getName());
274 Assertions.assertEquals("http", header2.getValue());
275 final Header header3 = headers.get(2);
276 Assertions.assertEquals(":authority", header3.getName());
277 Assertions.assertEquals("host", header3.getValue());
278 final Header header4 = headers.get(3);
279 Assertions.assertEquals(":path", header4.getName());
280 Assertions.assertEquals("/", header4.getValue());
281 final Header header5 = headers.get(4);
282 Assertions.assertEquals("custom123", header5.getName());
283 Assertions.assertEquals("Value", header5.getValue());
284 }
285
286 @Test
287 void testConvertFromMessageMissingScheme() {
288 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
289 request.addHeader("Custom123", "Value");
290 request.setScheme(null);
291
292 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
293 Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request scheme is not set");
294 }
295
296 @Test
297 void testConvertFromMessageMissingPath() {
298 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
299 request.addHeader("Custom123", "Value");
300 request.setPath(null);
301
302 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
303 Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request path is not set");
304 }
305
306 @Test
307 void testConvertFromMessageConnect() throws Exception {
308
309 final HttpRequest request = new BasicHttpRequest("CONNECT", new HttpHost("host:80"), null);
310 request.addHeader("custom123", "Value");
311
312 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
313 final List<Header> headers = converter.convert(request);
314
315 Assertions.assertNotNull(headers);
316 Assertions.assertEquals(3, headers.size());
317 final Header header1 = headers.get(0);
318 Assertions.assertEquals(":method", header1.getName());
319 Assertions.assertEquals("CONNECT", header1.getValue());
320 final Header header2 = headers.get(1);
321 Assertions.assertEquals(":authority", header2.getName());
322 Assertions.assertEquals("host:80", header2.getValue());
323 final Header header3 = headers.get(2);
324 Assertions.assertEquals("custom123", header3.getName());
325 Assertions.assertEquals("Value", header3.getValue());
326 }
327
328 @Test
329 void testConvertFromMessageConnectMissingAuthority() {
330 final HttpRequest request = new BasicHttpRequest("CONNECT", null, null);
331 request.addHeader("Custom123", "Value");
332
333 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
334 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
335 "CONNECT request authority is not set");
336 }
337
338 @Test
339 void testConvertFromMessageConnectWithPath() {
340 final HttpRequest request = new BasicHttpRequest("CONNECT", "/");
341 request.setAuthority(new URIAuthority("host"));
342 request.addHeader("Custom123", "Value");
343
344 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
345 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
346 "CONNECT request path must be null");
347 }
348
349 @Test
350 void testConvertFromMessageConnectionHeader() {
351 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
352 request.addHeader("Connection", "Keep-Alive");
353
354 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
355 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
356 "Header 'Connection: Keep-Alive' is illegal for HTTP/2 messages");
357 }
358
359 @Test
360 void testConvertFromFieldsKeepAliveHeader() {
361 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
362 request.addHeader("Keep-Alive", "timeout=5, max=1000");
363
364 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
365 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
366 "Header 'Keep-Alive: timeout=5, max=1000' is illegal for HTTP/2 messages");
367 }
368
369 @Test
370 void testConvertFromFieldsProxyConnectionHeader() {
371 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
372 request.addHeader("Proxy-Connection", "keep-alive");
373
374 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
375 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
376 "Header 'Proxy-Connection: Keep-Alive' is illegal for HTTP/2 messages");
377 }
378
379 @Test
380 void testConvertFromFieldsTransferEncodingHeader() {
381 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
382 request.addHeader("Transfer-Encoding", "gzip");
383
384 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
385 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
386 "Header 'Transfer-Encoding: gzip' is illegal for HTTP/2 messages");
387 }
388
389 @Test
390 void testConvertFromFieldsHostHeader() {
391 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
392 request.addHeader("Host", "host");
393
394 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
395 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
396 "Header 'Host: host' is illegal for HTTP/2 messages");
397 }
398
399 @Test
400 void testConvertFromFieldsUpgradeHeader() {
401 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
402 request.addHeader("Upgrade", "example/1, foo/2");
403
404 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
405 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
406 "Header 'Upgrade: example/1, foo/2' is illegal for HTTP/2 messages");
407 }
408
409 @Test
410 void testConvertFromFieldsTEHeader() {
411 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
412 request.addHeader("TE", "gzip");
413
414 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
415 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
416 "Header 'TE: gzip' is illegal for HTTP/2 messages");
417 }
418
419 @Test
420 void testConvertFromFieldsTETrailerHeader() throws Exception {
421
422 final List<Header> headers = Arrays.asList(
423 new BasicHeader(":method", "GET"),
424 new BasicHeader(":scheme", "http"),
425 new BasicHeader(":authority", "www.example.com"),
426 new BasicHeader(":path", "/"),
427 new BasicHeader("te", "trailers"));
428
429 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
430 final HttpRequest request = converter.convert(headers);
431 Assertions.assertNotNull(request);
432 Assertions.assertEquals("GET", request.getMethod());
433 Assertions.assertEquals("http", request.getScheme());
434 Assertions.assertEquals(new URIAuthority("www.example.com"), request.getAuthority());
435 Assertions.assertEquals("/", request.getPath());
436 final Header[] allHeaders = request.getHeaders();
437 Assertions.assertEquals(1, allHeaders.length);
438 Assertions.assertEquals("te", allHeaders[0].getName());
439 Assertions.assertEquals("trailers", allHeaders[0].getValue());
440 }
441
442 @Test
443 void testConvertFromMessageInvalidHeader() {
444 final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
445 request.addHeader(":custom", "stuff");
446
447 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
448 Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
449 "Header name ':custom' is invalid");
450 }
451
452
453 @Test
454 void testValidPath() throws Exception {
455 final List<Header> headers = Arrays.asList(
456 new BasicHeader(":method", "GET"),
457 new BasicHeader(":scheme", "http"),
458 new BasicHeader(":authority", "www.example.com"),
459 new BasicHeader(":path", "/"),
460 new BasicHeader("te", "trailers")
461 );
462
463 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
464 final HttpRequest request = converter.convert(headers);
465
466 Assertions.assertNotNull(request);
467 Assertions.assertEquals("/", request.getPath());
468
469 }
470
471 @Test
472 void testInvalidPathEmpty() {
473 final List<Header> headers = Arrays.asList(
474 new BasicHeader(":method", "GET"),
475 new BasicHeader(":scheme", "http"),
476 new BasicHeader(":authority", "www.example.com"),
477 new BasicHeader(":path", ""),
478 new BasicHeader("te", "trailers")
479 );
480
481 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
482 Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
483 }
484
485 @Test
486 void testInvalidPathNoSlash() {
487 final List<Header> headers = Arrays.asList(
488 new BasicHeader(":method", "GET"),
489 new BasicHeader(":scheme", "http"),
490 new BasicHeader(":authority", "www.example.com"),
491 new BasicHeader(":path", "noSlash"),
492 new BasicHeader("te", "trailers")
493 );
494
495 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
496 Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
497 }
498
499 @Test
500 void testValidOptionsAsterisk() throws Exception {
501 final List<Header> headers = Arrays.asList(
502 new BasicHeader(":method", "OPTIONS"),
503 new BasicHeader(":scheme", "http"),
504 new BasicHeader(":authority", "www.example.com"),
505 new BasicHeader(":path", "*"),
506 new BasicHeader("te", "trailers")
507 );
508
509 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
510 final HttpRequest request = converter.convert(headers);
511 Assertions.assertNotNull(request);
512 Assertions.assertEquals("*", request.getPath());
513 }
514
515 @Test
516 void testValidOptionsWithRootPath() throws HttpException {
517 final List<Header> headers = Arrays.asList(
518 new BasicHeader(":method", "OPTIONS"),
519 new BasicHeader(":scheme", "http"),
520 new BasicHeader(":authority", "www.example.com"),
521 new BasicHeader(":path", "/"),
522 new BasicHeader("te", "trailers")
523 );
524
525 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
526 final HttpRequest request = converter.convert(headers);
527 Assertions.assertNotNull(request);
528 Assertions.assertEquals("/", request.getPath());
529 }
530
531 @Test
532 void testInvalidOptionsNeitherAsteriskNorRoot() {
533 final List<Header> headers = Arrays.asList(
534 new BasicHeader(":method", "OPTIONS"),
535 new BasicHeader(":scheme", "http"),
536 new BasicHeader(":authority", "www.example.com"),
537 new BasicHeader(":path", "invalid"),
538 new BasicHeader("te", "trailers")
539 );
540
541 final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
542 Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
543 }
544
545
546 }
547