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.HttpRequest;
38 import org.apache.hc.core5.http.HttpVersion;
39 import org.apache.hc.core5.http.Method;
40 import org.apache.hc.core5.http.ProtocolException;
41 import org.apache.hc.core5.http.URIScheme;
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 messageHeaders.add(header);
110 }
111 }
112 if (method == null) {
113 throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.METHOD);
114 }
115 if (Method.CONNECT.isSame(method)) {
116 if (authority == null) {
117 throw new ProtocolException("Header '%s' is mandatory for CONNECT request", H2PseudoRequestHeaders.AUTHORITY);
118 }
119 if (scheme != null) {
120 throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.SCHEME);
121 }
122 if (path != null) {
123 throw new ProtocolException("Header '%s' must not be set for CONNECT request", H2PseudoRequestHeaders.PATH);
124 }
125 } else {
126 if (scheme == null) {
127 throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.SCHEME);
128 }
129 if (path == null) {
130 throw new ProtocolException("Mandatory request header '%s' not found", H2PseudoRequestHeaders.PATH);
131 }
132 validatePathPseudoHeader(method, scheme, path);
133 }
134
135 final HttpRequest httpRequest = new BasicHttpRequest(method, path);
136 httpRequest.setVersion(HttpVersion.HTTP_2);
137 httpRequest.setScheme(scheme);
138 try {
139 httpRequest.setAuthority(URIAuthority.create(authority));
140 } catch (final URISyntaxException ex) {
141 throw new ProtocolException(ex.getMessage(), ex);
142 }
143 httpRequest.setPath(path);
144 for (int i = 0; i < messageHeaders.size(); i++) {
145 httpRequest.addHeader(messageHeaders.get(i));
146 }
147 return httpRequest;
148 }
149
150 @Override
151 public List<Header> convert(final HttpRequest message) throws HttpException {
152 if (TextUtils.isBlank(message.getMethod())) {
153 throw new ProtocolException("Request method is empty");
154 }
155 final boolean optionMethod = Method.CONNECT.name().equalsIgnoreCase(message.getMethod());
156 if (optionMethod) {
157 if (message.getAuthority() == null) {
158 throw new ProtocolException("CONNECT request authority is not set");
159 }
160 if (message.getPath() != null) {
161 throw new ProtocolException("CONNECT request path must be null");
162 }
163 } else {
164 if (TextUtils.isBlank(message.getScheme())) {
165 throw new ProtocolException("Request scheme is not set");
166 }
167 if (TextUtils.isBlank(message.getPath())) {
168 throw new ProtocolException("Request path is not set");
169 }
170 }
171 final List<Header> headers = new ArrayList<>();
172 headers.add(new BasicHeader(H2PseudoRequestHeaders.METHOD, message.getMethod(), false));
173 if (optionMethod) {
174 headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false));
175 } else {
176 headers.add(new BasicHeader(H2PseudoRequestHeaders.SCHEME, message.getScheme(), false));
177 if (message.getAuthority() != null) {
178 headers.add(new BasicHeader(H2PseudoRequestHeaders.AUTHORITY, message.getAuthority(), false));
179 }
180 headers.add(new BasicHeader(H2PseudoRequestHeaders.PATH, message.getPath(), false));
181 }
182
183 for (final Iterator<Header> it = message.headerIterator(); it.hasNext(); ) {
184 final Header header = it.next();
185 final String name = header.getName();
186 final String value = header.getValue();
187 if (name.startsWith(":")) {
188 throw new ProtocolException("Header name '%s' is invalid", name);
189 }
190 headers.add(new BasicHeader(TextUtils.toLowerCase(name), value));
191 }
192
193 return headers;
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private void validatePathPseudoHeader(final String method, final String scheme, final String path) throws ProtocolException {
214 if (URIScheme.HTTP.name().equalsIgnoreCase(scheme) || URIScheme.HTTPS.name().equalsIgnoreCase(scheme)) {
215 if (TextUtils.isBlank(path)) {
216 throw new ProtocolException("':path' pseudo-header field must not be empty for 'http' or 'https' URIs");
217 } else {
218 final boolean isRoot = path.startsWith("/");
219 if (Method.OPTIONS.isSame(method)) {
220 if (!"*".equals(path) && !isRoot) {
221 throw new ProtocolException("OPTIONS request for an 'http' or 'https' URI must have a ':path' pseudo-header field with a value of '*' or '/'");
222 }
223 } else {
224 if (!isRoot) {
225 throw new ProtocolException("':path' pseudo-header field for 'http' or 'https' URIs must start with '/'");
226 }
227 }
228 }
229 }
230 }
231 }