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