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.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
51
52
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 }