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