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 private 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 }