View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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