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 testConvertFromFieldsPseudoHeaderSequence() {
85          final List<Header> headers = Arrays.asList(
86                  new BasicHeader(":method", "GET"),
87                  new BasicHeader(":scheme", "http"),
88                  new BasicHeader("custom", "value"),
89                  new BasicHeader(":authority", "www.example.com"),
90                  new BasicHeader(":path", "/"));
91  
92          final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
93          Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
94                  "Invalid sequence of headers (pseudo-headers must precede message headers)");
95      }
96  
97      @Test
98      void testConvertFromFieldsMissingMethod() {
99          final List<Header> headers = Arrays.asList(
100                 new BasicHeader(":scheme", "http"),
101                 new BasicHeader(":authority", "www.example.com"),
102                 new BasicHeader(":path", "/"),
103                 new BasicHeader("custom", "value"));
104 
105         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
106         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
107                 "Mandatory request header ':method' not found");
108     }
109 
110     @Test
111     void testConvertFromFieldsMissingScheme() {
112         final List<Header> headers = Arrays.asList(
113                 new BasicHeader(":method", "GET"),
114                 new BasicHeader(":authority", "www.example.com"),
115                 new BasicHeader(":path", "/"),
116                 new BasicHeader("custom", "value"));
117 
118         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
119         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
120                 "Mandatory request header ':scheme' not found");
121     }
122 
123     @Test
124     void testConvertFromFieldsMissingPath() {
125         final List<Header> headers = Arrays.asList(
126                 new BasicHeader(":method", "GET"),
127                 new BasicHeader(":scheme", "http"),
128                 new BasicHeader(":authority", "www.example.com"),
129                 new BasicHeader("custom", "value"));
130 
131         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
132         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
133                 "Mandatory request header ':path' not found");
134     }
135 
136     @Test
137     void testConvertFromFieldsUnknownPseudoHeader() {
138         final List<Header> headers = Arrays.asList(
139                 new BasicHeader(":method", "GET"),
140                 new BasicHeader(":scheme", "http"),
141                 new BasicHeader(":authority", "www.example.com"),
142                 new BasicHeader(":path", "/"),
143                 new BasicHeader(":custom", "value"));
144 
145         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
146         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
147                 "Unsupported request header ':custom'");
148     }
149 
150     @Test
151     void testConvertFromFieldsMultipleMethod() {
152         final List<Header> headers = Arrays.asList(
153                 new BasicHeader(":method", "GET"),
154                 new BasicHeader(":method", "GET"),
155                 new BasicHeader(":scheme", "http"),
156                 new BasicHeader(":authority", "www.example.com"),
157                 new BasicHeader(":path", "/"),
158                 new BasicHeader("custom", "value"));
159 
160         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
161         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
162                 "Multiple ':method' request headers are illegal");
163     }
164 
165     @Test
166     void testConvertFromFieldsMultipleScheme() {
167         final List<Header> headers = Arrays.asList(
168                 new BasicHeader(":method", "GET"),
169                 new BasicHeader(":scheme", "http"),
170                 new BasicHeader(":scheme", "https"),
171                 new BasicHeader(":authority", "www.example.com"),
172                 new BasicHeader(":path", "/"),
173                 new BasicHeader("custom", "value"));
174 
175         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
176         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
177                 "Multiple ':scheme' request headers are illegal");
178     }
179 
180     @Test
181     void testConvertFromFieldsMultiplePath() {
182         final List<Header> headers = Arrays.asList(
183                 new BasicHeader(":method", "GET"),
184                 new BasicHeader(":scheme", "https"),
185                 new BasicHeader(":authority", "www.example.com"),
186                 new BasicHeader(":path", "/"),
187                 new BasicHeader(":path", "/"),
188                 new BasicHeader("custom", "value"));
189 
190         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
191         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
192                 "Multiple ':path' request headers are illegal");
193     }
194 
195     @Test
196     void testConvertFromFieldsConnect() throws Exception {
197 
198         final List<Header> headers = Arrays.asList(
199                 new BasicHeader(":method", "CONNECT"),
200                 new BasicHeader(":authority", "www.example.com"),
201                 new BasicHeader("custom", "value"));
202 
203         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
204         converter.convert(headers);
205     }
206 
207     @Test
208     void testConvertFromFieldsConnectMissingAuthority() {
209         final List<Header> headers = Arrays.asList(
210                 new BasicHeader(":method", "CONNECT"),
211                 new BasicHeader("custom", "value"));
212 
213         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
214         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
215                 "Header ':authority' is mandatory for CONNECT request");
216     }
217 
218     @Test
219     void testConvertFromFieldsConnectPresentScheme() {
220         final List<Header> headers = Arrays.asList(
221                 new BasicHeader(":method", "CONNECT"),
222                 new BasicHeader(":scheme", "http"),
223                 new BasicHeader(":authority", "www.example.com"),
224                 new BasicHeader("custom", "value"));
225 
226         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
227         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
228                 "Header ':scheme' must not be set for CONNECT request");
229     }
230 
231     @Test
232     void testConvertFromFieldsConnectPresentPath() {
233         final List<Header> headers = Arrays.asList(
234                 new BasicHeader(":method", "CONNECT"),
235                 new BasicHeader(":authority", "www.example.com"),
236                 new BasicHeader(":path", "/"),
237                 new BasicHeader("custom", "value"));
238 
239         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
240         Assertions.assertThrows(HttpException.class, () -> converter.convert(headers),
241                 "Header ':path' must not be set for CONNECT request");
242     }
243 
244     @Test
245     void testConvertFromMessageBasic() throws Exception {
246 
247         final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
248         request.addHeader("custom123", "Value");
249 
250         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
251         final List<Header> headers = converter.convert(request);
252 
253         Assertions.assertNotNull(headers);
254         Assertions.assertEquals(5, headers.size());
255         final Header header1 = headers.get(0);
256         Assertions.assertEquals(":method", header1.getName());
257         Assertions.assertEquals("GET", header1.getValue());
258         final Header header2 = headers.get(1);
259         Assertions.assertEquals(":scheme", header2.getName());
260         Assertions.assertEquals("http", header2.getValue());
261         final Header header3 = headers.get(2);
262         Assertions.assertEquals(":authority", header3.getName());
263         Assertions.assertEquals("host", header3.getValue());
264         final Header header4 = headers.get(3);
265         Assertions.assertEquals(":path", header4.getName());
266         Assertions.assertEquals("/", header4.getValue());
267         final Header header5 = headers.get(4);
268         Assertions.assertEquals("custom123", header5.getName());
269         Assertions.assertEquals("Value", header5.getValue());
270     }
271 
272     @Test
273     void testConvertFromMessageMissingScheme() {
274         final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
275         request.addHeader("Custom123", "Value");
276         request.setScheme(null);
277 
278         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
279         Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request scheme is not set");
280     }
281 
282     @Test
283     void testConvertFromMessageMissingPath() {
284         final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
285         request.addHeader("Custom123", "Value");
286         request.setPath(null);
287 
288         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
289         Assertions.assertThrows(HttpException.class, () -> converter.convert(request), "Request path is not set");
290     }
291 
292     @Test
293     void testConvertFromMessageConnect() throws Exception {
294 
295         final HttpRequest request = new BasicHttpRequest("CONNECT", new HttpHost("host:80"), null);
296         request.addHeader("custom123", "Value");
297 
298         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
299         final List<Header> headers = converter.convert(request);
300 
301         Assertions.assertNotNull(headers);
302         Assertions.assertEquals(3, headers.size());
303         final Header header1 = headers.get(0);
304         Assertions.assertEquals(":method", header1.getName());
305         Assertions.assertEquals("CONNECT", header1.getValue());
306         final Header header2 = headers.get(1);
307         Assertions.assertEquals(":authority", header2.getName());
308         Assertions.assertEquals("host:80", header2.getValue());
309         final Header header3 = headers.get(2);
310         Assertions.assertEquals("custom123", header3.getName());
311         Assertions.assertEquals("Value", header3.getValue());
312     }
313 
314     @Test
315     void testConvertFromMessageConnectMissingAuthority() {
316         final HttpRequest request = new BasicHttpRequest("CONNECT", null, null);
317         request.addHeader("Custom123", "Value");
318 
319         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
320         Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
321                 "CONNECT request authority is not set");
322     }
323 
324     @Test
325     void testConvertFromMessageConnectWithPath() {
326         final HttpRequest request = new BasicHttpRequest("CONNECT", "/");
327         request.setAuthority(new URIAuthority("host"));
328         request.addHeader("Custom123", "Value");
329 
330         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
331         Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
332                 "CONNECT request path must be null");
333     }
334 
335     @Test
336     void testConvertFromFieldsTETrailerHeader() throws Exception {
337 
338         final List<Header> headers = Arrays.asList(
339             new BasicHeader(":method", "GET"),
340             new BasicHeader(":scheme", "http"),
341             new BasicHeader(":authority", "www.example.com"),
342             new BasicHeader(":path", "/"),
343             new BasicHeader("te", "trailers"));
344 
345         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
346         final HttpRequest request = converter.convert(headers);
347         Assertions.assertNotNull(request);
348         Assertions.assertEquals("GET", request.getMethod());
349         Assertions.assertEquals("http", request.getScheme());
350         Assertions.assertEquals(new URIAuthority("www.example.com"), request.getAuthority());
351         Assertions.assertEquals("/", request.getPath());
352         final Header[] allHeaders = request.getHeaders();
353         Assertions.assertEquals(1, allHeaders.length);
354         Assertions.assertEquals("te", allHeaders[0].getName());
355         Assertions.assertEquals("trailers", allHeaders[0].getValue());
356     }
357 
358     @Test
359     void testConvertFromMessageInvalidHeader() {
360         final HttpRequest request = new BasicHttpRequest("GET", new HttpHost("host"), "/");
361         request.addHeader(":custom", "stuff");
362 
363         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
364         Assertions.assertThrows(HttpException.class, () -> converter.convert(request),
365                 "Header name ':custom' is invalid");
366     }
367 
368 
369     @Test
370     void testValidPath() throws Exception {
371         final List<Header> headers = Arrays.asList(
372                 new BasicHeader(":method", "GET"),
373                 new BasicHeader(":scheme", "http"),
374                 new BasicHeader(":authority", "www.example.com"),
375                 new BasicHeader(":path", "/"),
376                 new BasicHeader("te", "trailers")
377         );
378 
379         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
380         final HttpRequest request = converter.convert(headers);
381 
382         Assertions.assertNotNull(request);
383         Assertions.assertEquals("/", request.getPath());
384 
385     }
386 
387     @Test
388     void testInvalidPathEmpty() {
389         final List<Header> headers = Arrays.asList(
390                 new BasicHeader(":method", "GET"),
391                 new BasicHeader(":scheme", "http"),
392                 new BasicHeader(":authority", "www.example.com"),
393                 new BasicHeader(":path", ""),
394                 new BasicHeader("te", "trailers")
395         );
396 
397         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
398         Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
399     }
400 
401     @Test
402     void testInvalidPathNoSlash() {
403         final List<Header> headers = Arrays.asList(
404                 new BasicHeader(":method", "GET"),
405                 new BasicHeader(":scheme", "http"),
406                 new BasicHeader(":authority", "www.example.com"),
407                 new BasicHeader(":path", "noSlash"),
408                 new BasicHeader("te", "trailers")
409         );
410 
411         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
412         Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
413     }
414 
415     @Test
416     void testValidOptionsAsterisk() throws Exception {
417         final List<Header> headers = Arrays.asList(
418                 new BasicHeader(":method", "OPTIONS"),
419                 new BasicHeader(":scheme", "http"),
420                 new BasicHeader(":authority", "www.example.com"),
421                 new BasicHeader(":path", "*"),
422                 new BasicHeader("te", "trailers")
423         );
424 
425         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
426         final HttpRequest request = converter.convert(headers);
427         Assertions.assertNotNull(request);
428         Assertions.assertEquals("*", request.getPath());
429     }
430 
431     @Test
432     void testValidOptionsWithRootPath() throws HttpException {
433         final List<Header> headers = Arrays.asList(
434                 new BasicHeader(":method", "OPTIONS"),
435                 new BasicHeader(":scheme", "http"),
436                 new BasicHeader(":authority", "www.example.com"),
437                 new BasicHeader(":path", "/"),
438                 new BasicHeader("te", "trailers")
439         );
440 
441         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
442         final HttpRequest request = converter.convert(headers);
443         Assertions.assertNotNull(request);
444         Assertions.assertEquals("/", request.getPath());
445     }
446 
447     @Test
448     void testInvalidOptionsNeitherAsteriskNorRoot() {
449         final List<Header> headers = Arrays.asList(
450                 new BasicHeader(":method", "OPTIONS"),
451                 new BasicHeader(":scheme", "http"),
452                 new BasicHeader(":authority", "www.example.com"),
453                 new BasicHeader(":path", "invalid"),
454                 new BasicHeader("te", "trailers")
455         );
456 
457         final DefaultH2RequestConverter converter = new DefaultH2RequestConverter();
458         Assertions.assertThrows(ProtocolException.class, () -> converter.convert(headers));
459     }
460 
461 
462 }
463