< prev index next >
1 /*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 */
24 package java.net.http;
25
26 import java.io.IOException;
27 import java.io.UncheckedIOException;
28 import java.net.InetSocketAddress;
29 import java.net.SocketPermission;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.net.URLPermission;
33 import java.security.AccessControlContext;
34 import java.security.AccessController;
35 import java.security.PrivilegedAction;
36 import java.security.PrivilegedActionException;
37 import java.security.PrivilegedExceptionAction;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.concurrent.CompletableFuture;
41
42 /**
43 * One request/response exchange (handles 100/101 intermediate response also).
44 * depth field used to track number of times a new request is being sent
45 * for a given API request. If limit exceeded exception is thrown.
46 *
47 * Security check is performed here:
48 * - uses AccessControlContext captured at API level
49 * - checks for appropriate URLPermission for request
50 * - if permission allowed, grants equivalent SocketPermission to call
51 * - in case of direct HTTP proxy, checks additionally for access to proxy
52 * (CONNECT proxying uses its own Exchange, so check done there)
53 *
54 */
55 class Exchange {
56
57 final HttpRequestImpl request;
58 final HttpClientImpl client;
59 ExchangeImpl exchImpl;
60 HttpResponseImpl response;
61 final List<SocketPermission> permissions = new LinkedList<>();
62 AccessControlContext acc;
63 boolean upgrading; // to HTTP/2
64
65 Exchange(HttpRequestImpl request) {
66 this.request = request;
67 this.upgrading = false;
68 this.client = request.client();
69 }
70
71 /* If different AccessControlContext to be used */
72 Exchange(HttpRequestImpl request, AccessControlContext acc) {
73 this.request = request;
74 this.acc = acc;
75 this.upgrading = false;
76 this.client = request.client();
77 }
78
79 public HttpRequestImpl request() {
80 return request;
81 }
82
83 public HttpResponseImpl response() throws IOException, InterruptedException {
84 response = responseImpl(null);
85 return response;
86 }
87
88 public void cancel() {
89 if (exchImpl != null)
90 exchImpl.cancel();
91 }
92
93 public void h2Upgrade() {
94 upgrading = true;
95 request.setH2Upgrade();
96 }
97
98 static final SocketPermission[] SOCKET_ARRAY = new SocketPermission[0];
99
100 HttpResponseImpl responseImpl(HttpConnection connection)
101 throws IOException, InterruptedException
102 {
103 if (acc == null) {
104 acc = request.getAccessControlContext();
105 }
106 SecurityException e = securityCheck(acc);
107 if (e != null)
108 throw e;
109
110 if (permissions.size() > 0) {
111 try {
112 return AccessController.doPrivileged(
113 (PrivilegedExceptionAction<HttpResponseImpl>)() ->
114 responseImpl0(connection),
115 null,
116 permissions.toArray(SOCKET_ARRAY));
117 } catch (Throwable ee) {
118 if (ee instanceof PrivilegedActionException) {
119 ee = ee.getCause();
120 }
121 if (ee instanceof IOException)
122 throw (IOException)ee;
123 else
124 throw new RuntimeException(ee); // TODO: fix
125 }
126 } else {
127 return responseImpl0(connection);
128 }
129 }
130
131 HttpResponseImpl responseImpl0(HttpConnection connection)
132 throws IOException, InterruptedException
133 {
134 exchImpl = ExchangeImpl.get(this, connection);
135 if (request.expectContinue()) {
136 request.addSystemHeader("Expect", "100-Continue");
137 exchImpl.sendHeadersOnly();
138 HttpResponseImpl resp = exchImpl.getResponse();
139 Utils.logResponse(resp);
140 if (resp.statusCode() != 100) {
141 return resp;
142 }
143 exchImpl.sendBody();
144 return exchImpl.getResponse();
145 } else {
146 exchImpl.sendRequest();
147 HttpResponseImpl resp = exchImpl.getResponse();
148 Utils.logResponse(resp);
149 return checkForUpgrade(resp, exchImpl);
150 }
151 }
152
153 // Completed HttpResponse will be null if response succeeded
154 // will be a non null responseAsync if expect continue returns an error
155
156 public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
157 return responseAsyncImpl(null);
158 }
159
160 CompletableFuture<HttpResponseImpl> responseAsyncImpl(HttpConnection connection) {
161 if (acc == null) {
162 acc = request.getAccessControlContext();
163 }
164 SecurityException e = securityCheck(acc);
165 if (e != null) {
166 return CompletableFuture.failedFuture(e);
167 }
168 if (permissions.size() > 0) {
169 return AccessController.doPrivileged(
170 (PrivilegedAction<CompletableFuture<HttpResponseImpl>>)() ->
171 responseAsyncImpl0(connection),
172 null,
173 permissions.toArray(SOCKET_ARRAY));
174 } else {
175 return responseAsyncImpl0(connection);
176 }
177 }
178
179 CompletableFuture<HttpResponseImpl> responseAsyncImpl0(HttpConnection connection) {
180 try {
181 exchImpl = ExchangeImpl.get(this, connection);
182 } catch (IOException | InterruptedException e) {
183 return CompletableFuture.failedFuture(e);
184 }
185 if (request.expectContinue()) {
186 request.addSystemHeader("Expect", "100-Continue");
187 return exchImpl.sendHeadersAsync()
188 .thenCompose(exchImpl::getResponseAsync)
189 .thenCompose((HttpResponseImpl r1) -> {
190 int rcode = r1.statusCode();
191 CompletableFuture<HttpResponseImpl> cf =
192 checkForUpgradeAsync(r1, exchImpl);
193 if (cf != null)
194 return cf;
195 if (rcode == 100) {
196 return exchImpl.sendBodyAsync()
197 .thenCompose(exchImpl::getResponseAsync)
198 .thenApply((r) -> {
199 Utils.logResponse(r);
200 return r;
201 });
202 } else {
203 Exchange.this.response = r1;
204 Utils.logResponse(r1);
205 return CompletableFuture.completedFuture(r1);
206 }
207 });
208 } else {
209 return exchImpl
210 .sendRequestAsync()
211 .thenCompose(exchImpl::getResponseAsync)
212 .thenCompose((HttpResponseImpl r1) -> {
213 int rcode = r1.statusCode();
214 CompletableFuture<HttpResponseImpl> cf =
215 checkForUpgradeAsync(r1, exchImpl);
216 if (cf != null) {
217 return cf;
218 } else {
219 Exchange.this.response = r1;
220 Utils.logResponse(r1);
221 return CompletableFuture.completedFuture(r1);
222 }
223 })
224 .thenApply((HttpResponseImpl response) -> {
225 this.response = response;
226 Utils.logResponse(response);
227 return response;
228 });
229 }
230 }
231
232 // if this response was received in reply to an upgrade
233 // then create the Http2Connection from the HttpConnection
234 // initialize it and wait for the real response on a newly created Stream
235
236 private CompletableFuture<HttpResponseImpl>
237 checkForUpgradeAsync(HttpResponseImpl resp,
238 ExchangeImpl ex) {
239 int rcode = resp.statusCode();
240 if (upgrading && (rcode == 101)) {
241 Http1Exchange e = (Http1Exchange)ex;
242 // check for 101 switching protocols
243 return e.responseBodyAsync(HttpResponse.ignoreBody())
244 .thenCompose((Void v) ->
245 Http2Connection.createAsync(e.connection(),
246 client.client2(),
247 this)
248 .thenCompose((Http2Connection c) -> {
249 c.putConnection();
250 Stream s = c.getStream(1);
251 exchImpl = s;
252 return s.getResponseAsync(null);
253 })
254 );
255 }
256 return CompletableFuture.completedFuture(resp);
257 }
258
259 private HttpResponseImpl checkForUpgrade(HttpResponseImpl resp,
260 ExchangeImpl ex)
261 throws IOException, InterruptedException
262 {
263 int rcode = resp.statusCode();
264 if (upgrading && (rcode == 101)) {
265 Http1Exchange e = (Http1Exchange) ex;
266 // must get connection from Http1Exchange
267 e.responseBody(HttpResponse.ignoreBody(), false);
268 Http2Connection h2con = new Http2Connection(e.connection(),
269 client.client2(),
270 this);
271 h2con.putConnection();
272 Stream s = h2con.getStream(1);
273 exchImpl = s;
274 return s.getResponse();
275 }
276 return resp;
277 }
278
279
280 <T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
281 try {
282 return exchImpl.responseBody(processor);
283 } catch (IOException e) {
284 throw new UncheckedIOException(e);
285 }
286 }
287
288
289
290 <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
291 return exchImpl.responseBodyAsync(processor);
292 }
293
294 private URI getURIForSecurityCheck() {
295 URI u;
296 String method = request.method();
297 InetSocketAddress authority = request.authority();
298 URI uri = request.uri();
299
300 // CONNECT should be restricted at API level
301 if (method.equalsIgnoreCase("CONNECT")) {
302 try {
303 u = new URI("socket",
304 null,
305 authority.getHostString(),
306 authority.getPort(),
307 null,
308 null,
309 null);
310 } catch (URISyntaxException e) {
311 throw new InternalError(e); // shouldn't happen
312 }
313 } else {
314 u = uri;
315 }
316 return u;
317 }
318
319 /**
320 * Do the security check and return any exception.
321 * Return null if no check needed or passes.
322 *
323 * Also adds any generated permissions to the "permissions" list.
324 */
325 private SecurityException securityCheck(AccessControlContext acc) {
326 SecurityManager sm = System.getSecurityManager();
327 if (sm == null) {
328 return null;
329 }
330
331 String method = request.method();
332 HttpHeaders userHeaders = request.getUserHeaders();
333 URI u = getURIForSecurityCheck();
334 URLPermission p = Utils.getPermission(u, method, userHeaders.map());
335
336 try {
337 assert acc != null;
338 sm.checkPermission(p, acc);
339 permissions.add(getSocketPermissionFor(u));
340 } catch (SecurityException e) {
341 return e;
342 }
343 InetSocketAddress proxy = request.proxy();
344 if (proxy != null) {
345 // may need additional check
346 if (!method.equals("CONNECT")) {
347 // a direct http proxy. Need to check access to proxy
348 try {
349 u = new URI("socket", null, proxy.getHostString(),
350 proxy.getPort(), null, null, null);
351 } catch (URISyntaxException e) {
352 throw new InternalError(e); // shouldn't happen
353 }
354 p = new URLPermission(u.toString(), "CONNECT");
355 try {
356 sm.checkPermission(p, acc);
357 } catch (SecurityException e) {
358 permissions.clear();
359 return e;
360 }
361 String sockperm = proxy.getHostString() +
362 ":" + Integer.toString(proxy.getPort());
363
364 permissions.add(new SocketPermission(sockperm, "connect,resolve"));
365 }
366 }
367 return null;
368 }
369
370 private static SocketPermission getSocketPermissionFor(URI url) {
371 if (System.getSecurityManager() == null)
372 return null;
373
374 StringBuilder sb = new StringBuilder();
375 String host = url.getHost();
376 sb.append(host);
377 int port = url.getPort();
378 if (port == -1) {
379 String scheme = url.getScheme();
380 if ("http".equals(scheme)) {
381 sb.append(":80");
382 } else { // scheme must be https
383 sb.append(":443");
384 }
385 } else {
386 sb.append(':')
387 .append(Integer.toString(port));
388 }
389 String target = sb.toString();
390 return new SocketPermission(target, "connect");
391 }
392
393 AccessControlContext getAccessControlContext() {
394 return acc;
395 }
396 }
< prev index next >