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.net.URISyntaxException;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Locale;
35  
36  import org.apache.hc.core5.http.Header;
37  import org.apache.hc.core5.http.HttpException;
38  import org.apache.hc.core5.http.HttpHeaders;
39  import org.apache.hc.core5.http.HttpRequest;
40  import org.apache.hc.core5.http.HttpVersion;
41  import org.apache.hc.core5.http.Method;
42  import org.apache.hc.core5.http.ProtocolException;
43  import org.apache.hc.core5.http.message.BasicHeader;
44  import org.apache.hc.core5.http.message.BasicHttpRequest;
45  import org.apache.hc.core5.http2.H2MessageConverter;
46  import org.apache.hc.core5.http2.H2PseudoRequestHeaders;
47  import org.apache.hc.core5.net.URIAuthority;
48  import org.apache.hc.core5.util.TextUtils;
49  
50  /**
51   * HTTP/2 request converter.
52   *
53   * @since 5.0
54   */
55  public final class DefaultH2RequestConverter implements H2MessageConverter<HttpRequest> {
56  
57      public final static DefaultH2RequestConverteruestConverter.html#DefaultH2RequestConverter">DefaultH2RequestConverter INSTANCE = new DefaultH2RequestConverter();
58  
59      @Override
60      public HttpRequest convert(final List<Header> headers) throws HttpException {
61          String method = null;
62          String scheme = null;
63          String authority = null;
64          String path = null;
65          final List<Header> messageHeaders = new ArrayList<>();
66  
67          for (int i = 0; i < headers.size(); i++) {
68              final Header header = headers.get(i);
69              final String name = header.getName();
70              final String value = header.getValue();
71  
72              for (int n = 0; n < name.length(); n++) {
73                  final char ch = name.charAt(n);
74                  if (Character.isAlphabetic(ch) && !Character.isLowerCase(ch)) {
75                      throw new ProtocolException("Header name '%s' is invalid (header name contains uppercase characters)", name);
76                  }
77              }
78  
79              if (name.startsWith(":")) {
80                  if (!messageHeaders.isEmpty()) {
81                      throw new ProtocolException("Invalid sequence of headers (pseudo-headers must precede message headers)");
82                  }
83  
84                  if (name.equals(H2PseudoRequestHeaders.METHOD)) {
85                      if (method != null) {
86                          throw new ProtocolException("Multiple '%s' request headers are illegal", name);
87                      }
88                      method = value;
89                  } else if (name.equals(H2PseudoRequestHeaders.SCHEME)) {
90                      if (scheme != null) {
91                          throw new ProtocolException("Multiple '%s' request headers are illegal", name);
92                      }
93                      scheme = value;
94                  } else if (name.equals(H2PseudoRequestHeaders.PATH)) {
95                      if (path != null) {
96                          throw new ProtocolException("Multiple '%s' request headers are illegal", name);
97                      }
98                      path = value;
99                  } else if (name.equals(H2PseudoRequestHeaders.AUTHORITY)) {
100                     authority = value;
101                 } else {
102                     throw new ProtocolException("Unsupported request header '%s'", name);
103                 }
104             } else {
105                 if (name.equalsIgnoreCase(HttpHeaders.CONNECTION)) {
106                     throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", header.getName(), header.getValue());
107                 }
108                 messageHeaders.add(header);
109             }
110         }
111         if (method == null) {
112             throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.METHOD);
113         }
114         if (Method.CONNECT.isSame(method)) {
115             if (authority == null) {
116                 throw new ProtocolException("Header '%s' is mandatory for CONNECT request", H2PseudoRequestHeaders.AUTHORITY);
117             }
118             if (scheme != null) {
119                 throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.SCHEME);
120             }
121             if (path != null) {
122                 throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.PATH);
123             }
124         } else {
125             if (scheme == null) {
126                 throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.SCHEME);
127             }
128             if (path == null) {
129                 throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.PATH);
130             }
131         }
132 
133         final HttpRequest httpRequest = new BasicHttpRequest(method, path);
134         httpRequest.setVersion(HttpVersion.HTTP_2);
135         httpRequest.setScheme(scheme);
136         try {
137             httpRequest.setAuthority(URIAuthority.create(authority));
138         } catch (final URISyntaxException ex) {
139             throw new ProtocolException(ex.getMessage(), ex);
140         }
141         httpRequest.setPath(path);
142         for (int i = 0; i < messageHeaders.size(); i++) {
143             httpRequest.addHeader(messageHeaders.get(i));
144         }
145         return httpRequest;
146     }
147 
148     @Override
149     public List<Header> convert(final HttpRequest message) throws HttpException {
150         if (TextUtils.isBlank(message.getMethod())) {
151             throw new ProtocolException("Request method is empty");
152         }
153         final boolean optionMethod = Method.CONNECT.name().equalsIgnoreCase(message.getMethod());
154         if (optionMethod) {
155             if (message.getAuthority() == null) {
156                 throw new ProtocolException("CONNECT request authority is not set");
157             }
158             if (message.getPath() != null) {
159                 throw new ProtocolException("CONNECT request path must be null");
160             }
161         } else {
162             if (TextUtils.isBlank(message.getScheme())) {
163                 throw new ProtocolException("Request scheme is not set");
164             }
165             if (TextUtils.isBlank(message.getPath())) {
166                 throw new ProtocolException("Request path is not set");
167             }
168         }
169         final List<Header> headers = new ArrayList<>();
170         headers.add(new BasicHeader(H2PseudoRequestHeaders.METHOD, message.getMethod(), false));
171         if (optionMethod) {
172             headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false));
173         }  else {
174             headers.add(new BasicHeader(H2PseudoRequestHeaders.SCHEME, message.getScheme(), false));
175             if (message.getAuthority() != null) {
176                 headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false));
177             }
178             headers.add(new BasicHeader(H2PseudoRequestHeaders.PATH, message.getPath(), false));
179         }
180 
181         for (final Iterator<Header> it = message.headerIterator(); it.hasNext(); ) {
182             final Header header = it.next();
183             final String name = header.getName();
184             final String value = header.getValue();
185             if (name.startsWith(":")) {
186                 throw new ProtocolException("Header name '%s' is invalid", name);
187             }
188             if (name.equalsIgnoreCase(HttpHeaders.CONNECTION)) {
189                 throw new ProtocolException("Header '%s: %s' is illegal for HTTP/2 messages", name, value);
190             }
191             headers.add(new BasicHeader(name.toLowerCase(Locale.ROOT), value));
192         }
193 
194         return headers;
195     }
196 
197 }