1 /* 2 * Copyright (c) 2015, 2020, 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 jdk.internal.net.http; 27 28 import java.io.IOException; 29 import java.lang.System.Logger.Level; 30 import java.net.InetSocketAddress; 31 import java.net.ProxySelector; 32 import java.net.URI; 33 import java.net.URISyntaxException; 34 import java.net.URLPermission; 35 import java.security.AccessControlContext; 36 import java.time.Duration; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Optional; 40 import java.util.concurrent.CompletableFuture; 41 import java.util.concurrent.Executor; 42 import java.util.function.Function; 43 import java.net.http.HttpClient; 44 import java.net.http.HttpHeaders; 45 import java.net.http.HttpResponse; 46 import java.net.http.HttpTimeoutException; 47 48 import jdk.internal.net.http.common.Logger; 49 import jdk.internal.net.http.common.MinimalFuture; 50 import jdk.internal.net.http.common.Utils; 51 import jdk.internal.net.http.common.Log; 52 53 import static jdk.internal.net.http.common.Utils.permissionForProxy; 54 55 /** 56 * One request/response exchange (handles 100/101 intermediate response also). 57 * depth field used to track number of times a new request is being sent 58 * for a given API request. If limit exceeded exception is thrown. 59 * 60 * Security check is performed here: 61 * - uses AccessControlContext captured at API level 62 * - checks for appropriate URLPermission for request 63 * - if permission allowed, grants equivalent SocketPermission to call 64 * - in case of direct HTTP proxy, checks additionally for access to proxy 65 * (CONNECT proxying uses its own Exchange, so check done there) 66 * 67 */ 68 final class Exchange<T> { 69 70 final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG); 71 72 final HttpRequestImpl request; 73 final HttpClientImpl client; 74 volatile ExchangeImpl<T> exchImpl; 75 volatile CompletableFuture<? extends ExchangeImpl<T>> exchangeCF; 76 volatile CompletableFuture<Void> bodyIgnored; 77 78 // used to record possible cancellation raised before the exchImpl 79 // has been established. 80 private volatile IOException failed; 81 final AccessControlContext acc; 82 final MultiExchange<T> multi; 83 final Executor parentExecutor; 84 volatile boolean upgrading; // to HTTP/2 85 volatile boolean upgraded; // to HTTP/2 86 final PushGroup<T> pushGroup; 87 final String dbgTag; 88 89 // Keeps track of the underlying connection when establishing an HTTP/2 90 // exchange so that it can be aborted/timed out mid setup. 91 final ConnectionAborter connectionAborter = new ConnectionAborter(); 92 Exchange(HttpRequestImpl request, MultiExchange<T> multi)93 Exchange(HttpRequestImpl request, MultiExchange<T> multi) { 94 this.request = request; 95 this.upgrading = false; 96 this.client = multi.client(); 97 this.multi = multi; 98 this.acc = multi.acc; 99 this.parentExecutor = multi.executor; 100 this.pushGroup = multi.pushGroup; 101 this.dbgTag = "Exchange"; 102 } 103 104 /* If different AccessControlContext to be used */ Exchange(HttpRequestImpl request, MultiExchange<T> multi, AccessControlContext acc)105 Exchange(HttpRequestImpl request, 106 MultiExchange<T> multi, 107 AccessControlContext acc) 108 { 109 this.request = request; 110 this.acc = acc; 111 this.upgrading = false; 112 this.client = multi.client(); 113 this.multi = multi; 114 this.parentExecutor = multi.executor; 115 this.pushGroup = multi.pushGroup; 116 this.dbgTag = "Exchange"; 117 } 118 getPushGroup()119 PushGroup<T> getPushGroup() { 120 return pushGroup; 121 } 122 executor()123 Executor executor() { 124 return parentExecutor; 125 } 126 request()127 public HttpRequestImpl request() { 128 return request; 129 } 130 remainingConnectTimeout()131 public Optional<Duration> remainingConnectTimeout() { 132 return multi.remainingConnectTimeout(); 133 } 134 client()135 HttpClientImpl client() { 136 return client; 137 } 138 139 // Keeps track of the underlying connection when establishing an HTTP/2 140 // exchange so that it can be aborted/timed out mid setup. 141 static final class ConnectionAborter { 142 private volatile HttpConnection connection; 143 private volatile boolean closeRequested; 144 connection(HttpConnection connection)145 void connection(HttpConnection connection) { 146 this.connection = connection; 147 if (closeRequested) closeConnection(); 148 } 149 closeConnection()150 void closeConnection() { 151 closeRequested = true; 152 HttpConnection connection = this.connection; 153 this.connection = null; 154 if (connection != null) { 155 try { 156 connection.close(); 157 } catch (Throwable t) { 158 // ignore 159 } 160 } 161 } 162 disable()163 void disable() { 164 connection = null; 165 closeRequested = false; 166 } 167 } 168 169 // Called for 204 response - when no body is permitted 170 // This is actually only needed for HTTP/1.1 in order 171 // to return the connection to the pool (or close it) nullBody(HttpResponse<T> resp, Throwable t)172 void nullBody(HttpResponse<T> resp, Throwable t) { 173 exchImpl.nullBody(resp, t); 174 } 175 readBodyAsync(HttpResponse.BodyHandler<T> handler)176 public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) { 177 // If we received a 407 while establishing the exchange 178 // there will be no body to read: bodyIgnored will be true, 179 // and exchImpl will be null (if we were trying to establish 180 // an HTTP/2 tunnel through an HTTP/1.1 proxy) 181 if (bodyIgnored != null) return MinimalFuture.completedFuture(null); 182 183 // The connection will not be returned to the pool in the case of WebSocket 184 return exchImpl.readBodyAsync(handler, !request.isWebSocket(), parentExecutor) 185 .whenComplete((r,t) -> exchImpl.completed()); 186 } 187 188 /** 189 * Called after a redirect or similar kind of retry where a body might 190 * be sent but we don't want it. Should send a RESET in h2. For http/1.1 191 * we can consume small quantity of data, or close the connection in 192 * other cases. 193 */ ignoreBody()194 public CompletableFuture<Void> ignoreBody() { 195 if (bodyIgnored != null) return bodyIgnored; 196 return exchImpl.ignoreBody(); 197 } 198 199 /** 200 * Called when a new exchange is created to replace this exchange. 201 * At this point it is guaranteed that readBody/readBodyAsync will 202 * not be called. 203 */ released()204 public void released() { 205 ExchangeImpl<?> impl = exchImpl; 206 if (impl != null) impl.released(); 207 // Don't set exchImpl to null here. We need to keep 208 // it alive until it's replaced by a Stream in wrapForUpgrade. 209 // Setting it to null here might get it GC'ed too early, because 210 // the Http1Response is now only weakly referenced by the Selector. 211 } 212 cancel()213 public void cancel() { 214 // cancel can be called concurrently before or at the same time 215 // that the exchange impl is being established. 216 // In that case we won't be able to propagate the cancellation 217 // right away 218 if (exchImpl != null) { 219 exchImpl.cancel(); 220 } else { 221 // no impl - can't cancel impl yet. 222 // call cancel(IOException) instead which takes care 223 // of race conditions between impl/cancel. 224 cancel(new IOException("Request cancelled")); 225 } 226 } 227 cancel(IOException cause)228 public void cancel(IOException cause) { 229 if (debug.on()) debug.log("cancel exchImpl: %s, with \"%s\"", exchImpl, cause); 230 // If the impl is non null, propagate the exception right away. 231 // Otherwise record it so that it can be propagated once the 232 // exchange impl has been established. 233 ExchangeImpl<?> impl = exchImpl; 234 if (impl != null) { 235 // propagate the exception to the impl 236 if (debug.on()) debug.log("Cancelling exchImpl: %s", exchImpl); 237 impl.cancel(cause); 238 } else { 239 // no impl yet. record the exception 240 failed = cause; 241 242 // abort/close the connection if setting up the exchange. This can 243 // be important when setting up HTTP/2 244 connectionAborter.closeConnection(); 245 246 // now call checkCancelled to recheck the impl. 247 // if the failed state is set and the impl is not null, reset 248 // the failed state and propagate the exception to the impl. 249 checkCancelled(); 250 } 251 } 252 253 // This method will raise an exception if one was reported and if 254 // it is possible to do so. If the exception can be raised, then 255 // the failed state will be reset. Otherwise, the failed state 256 // will persist until the exception can be raised and the failed state 257 // can be cleared. 258 // Takes care of possible race conditions. checkCancelled()259 private void checkCancelled() { 260 ExchangeImpl<?> impl = null; 261 IOException cause = null; 262 CompletableFuture<? extends ExchangeImpl<T>> cf = null; 263 if (failed != null) { 264 synchronized(this) { 265 cause = failed; 266 impl = exchImpl; 267 cf = exchangeCF; 268 } 269 } 270 if (cause == null) return; 271 if (impl != null) { 272 // The exception is raised by propagating it to the impl. 273 if (debug.on()) debug.log("Cancelling exchImpl: %s", impl); 274 impl.cancel(cause); 275 failed = null; 276 } else { 277 Log.logTrace("Exchange: request [{0}/timeout={1}ms] no impl is set." 278 + "\n\tCan''t cancel yet with {2}", 279 request.uri(), 280 request.timeout().isPresent() ? 281 // calling duration.toMillis() can throw an exception. 282 // this is just debugging, we don't care if it overflows. 283 (request.timeout().get().getSeconds() * 1000 284 + request.timeout().get().getNano() / 1000000) : -1, 285 cause); 286 if (cf != null) cf.completeExceptionally(cause); 287 } 288 } 289 checkCancelled(CompletableFuture<T> cf, HttpConnection connection)290 <T> CompletableFuture<T> checkCancelled(CompletableFuture<T> cf, HttpConnection connection) { 291 return cf.handle((r,t) -> { 292 if (t == null) { 293 if (multi.requestCancelled()) { 294 // if upgraded, we don't close the connection. 295 // cancelling will be handled by the HTTP/2 exchange 296 // in its own time. 297 if (!upgraded) { 298 t = getCancelCause(); 299 if (t == null) t = new IOException("Request cancelled"); 300 if (debug.on()) debug.log("exchange cancelled during connect: " + t); 301 try { 302 connection.close(); 303 } catch (Throwable x) { 304 if (debug.on()) debug.log("Failed to close connection", x); 305 } 306 return MinimalFuture.<T>failedFuture(t); 307 } 308 } 309 } 310 return cf; 311 }).thenCompose(Function.identity()); 312 } 313 314 public void h2Upgrade() { 315 upgrading = true; 316 request.setH2Upgrade(client.client2()); 317 } 318 319 synchronized IOException getCancelCause() { 320 return failed; 321 } 322 323 // get/set the exchange impl, solving race condition issues with 324 // potential concurrent calls to cancel() or cancel(IOException) 325 private CompletableFuture<? extends ExchangeImpl<T>> 326 establishExchange(HttpConnection connection) { 327 if (debug.on()) { 328 debug.log("establishing exchange for %s,%n\t proxy=%s", 329 request, request.proxy()); 330 } 331 // check if we have been cancelled first. 332 Throwable t = getCancelCause(); 333 checkCancelled(); 334 if (t != null) { 335 if (debug.on()) { 336 debug.log("exchange was cancelled: returned failed cf (%s)", String.valueOf(t)); 337 } 338 return exchangeCF = MinimalFuture.failedFuture(t); 339 } 340 341 CompletableFuture<? extends ExchangeImpl<T>> cf, res; 342 cf = ExchangeImpl.get(this, connection); 343 // We should probably use a VarHandle to get/set exchangeCF 344 // instead - as we need CAS semantics. 345 synchronized (this) { exchangeCF = cf; }; 346 res = cf.whenComplete((r,x) -> { 347 synchronized(Exchange.this) { 348 if (exchangeCF == cf) exchangeCF = null; 349 } 350 }); 351 checkCancelled(); 352 return res.thenCompose((eimpl) -> { 353 // recheck for cancelled, in case of race conditions 354 exchImpl = eimpl; 355 IOException tt = getCancelCause(); 356 checkCancelled(); 357 if (tt != null) { 358 return MinimalFuture.failedFuture(tt); 359 } else { 360 // Now we're good to go. Because exchImpl is no longer 361 // null cancel() will be able to propagate directly to 362 // the impl after this point ( if needed ). 363 return MinimalFuture.completedFuture(eimpl); 364 } }); 365 } 366 367 // Completed HttpResponse will be null if response succeeded 368 // will be a non null responseAsync if expect continue returns an error 369 370 public CompletableFuture<Response> responseAsync() { 371 return responseAsyncImpl(null); 372 } 373 374 CompletableFuture<Response> responseAsyncImpl(HttpConnection connection) { 375 SecurityException e = checkPermissions(); 376 if (e != null) { 377 return MinimalFuture.failedFuture(e); 378 } else { 379 return responseAsyncImpl0(connection); 380 } 381 } 382 383 // check whether the headersSentCF was completed exceptionally with 384 // ProxyAuthorizationRequired. If so the Response embedded in the 385 // exception is returned. Otherwise we proceed. 386 private CompletableFuture<Response> checkFor407(ExchangeImpl<T> ex, Throwable t, 387 Function<ExchangeImpl<T>,CompletableFuture<Response>> andThen) { 388 t = Utils.getCompletionCause(t); 389 if (t instanceof ProxyAuthenticationRequired) { 390 if (debug.on()) debug.log("checkFor407: ProxyAuthenticationRequired: building synthetic response"); 391 bodyIgnored = MinimalFuture.completedFuture(null); 392 Response proxyResponse = ((ProxyAuthenticationRequired)t).proxyResponse; 393 HttpConnection c = ex == null ? null : ex.connection(); 394 Response syntheticResponse = new Response(request, this, 395 proxyResponse.headers, c, proxyResponse.statusCode, 396 proxyResponse.version, true); 397 return MinimalFuture.completedFuture(syntheticResponse); 398 } else if (t != null) { 399 if (debug.on()) debug.log("checkFor407: no response - %s", (Object)t); 400 return MinimalFuture.failedFuture(t); 401 } else { 402 if (debug.on()) debug.log("checkFor407: all clear"); 403 return andThen.apply(ex); 404 } 405 } 406 407 // After sending the request headers, if no ProxyAuthorizationRequired 408 // was raised and the expectContinue flag is on, we need to wait 409 // for the 100-Continue response 410 private CompletableFuture<Response> expectContinue(ExchangeImpl<T> ex) { 411 assert request.expectContinue(); 412 return ex.getResponseAsync(parentExecutor) 413 .thenCompose((Response r1) -> { 414 Log.logResponse(r1::toString); 415 int rcode = r1.statusCode(); 416 if (rcode == 100) { 417 Log.logTrace("Received 100-Continue: sending body"); 418 if (debug.on()) debug.log("Received 100-Continue for %s", r1); 419 CompletableFuture<Response> cf = 420 exchImpl.sendBodyAsync() 421 .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor)); 422 cf = wrapForUpgrade(cf); 423 cf = wrapForLog(cf); 424 return cf; 425 } else { 426 Log.logTrace("Expectation failed: Received {0}", 427 rcode); 428 if (debug.on()) debug.log("Expect-Continue failed (%d) for: %s", rcode, r1); 429 if (upgrading && rcode == 101) { 430 IOException failed = new IOException( 431 "Unable to handle 101 while waiting for 100"); 432 return MinimalFuture.failedFuture(failed); 433 } 434 return exchImpl.readBodyAsync(this::ignoreBody, false, parentExecutor) 435 .thenApply(v -> r1); 436 } 437 }); 438 } 439 440 // After sending the request headers, if no ProxyAuthorizationRequired 441 // was raised and the expectContinue flag is off, we can immediately 442 // send the request body and proceed. 443 private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) { 444 assert !request.expectContinue(); 445 if (debug.on()) debug.log("sendRequestBody"); 446 CompletableFuture<Response> cf = ex.sendBodyAsync() 447 .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor)); 448 cf = wrapForUpgrade(cf); 449 cf = wrapForLog(cf); 450 return cf; 451 } 452 453 CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) { 454 Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check; 455 bodyIgnored = null; 456 if (request.expectContinue()) { 457 request.addSystemHeader("Expect", "100-Continue"); 458 Log.logTrace("Sending Expect: 100-Continue"); 459 // wait for 100-Continue before sending body 460 after407Check = this::expectContinue; 461 } else { 462 // send request body and proceed. 463 after407Check = this::sendRequestBody; 464 } 465 // The ProxyAuthorizationRequired can be triggered either by 466 // establishExchange (case of HTTP/2 SSL tunneling through HTTP/1.1 proxy 467 // or by sendHeaderAsync (case of HTTP/1.1 SSL tunneling through HTTP/1.1 proxy 468 // Therefore we handle it with a call to this checkFor407(...) after these 469 // two places. 470 Function<ExchangeImpl<T>, CompletableFuture<Response>> afterExch407Check = 471 (ex) -> ex.sendHeadersAsync() 472 .handle((r,t) -> this.checkFor407(r, t, after407Check)) 473 .thenCompose(Function.identity()); 474 return establishExchange(connection) 475 .handle((r,t) -> this.checkFor407(r,t, afterExch407Check)) 476 .thenCompose(Function.identity()); 477 } 478 479 private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) { 480 if (upgrading) { 481 return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl)); 482 } 483 return cf; 484 } 485 486 private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) { 487 if (Log.requests()) { 488 return cf.thenApply(response -> { 489 Log.logResponse(response::toString); 490 return response; 491 }); 492 } 493 return cf; 494 } 495 496 HttpResponse.BodySubscriber<T> ignoreBody(HttpResponse.ResponseInfo hdrs) { 497 return HttpResponse.BodySubscribers.replacing(null); 498 } 499 500 // if this response was received in reply to an upgrade 501 // then create the Http2Connection from the HttpConnection 502 // initialize it and wait for the real response on a newly created Stream 503 504 private CompletableFuture<Response> 505 checkForUpgradeAsync(Response resp, 506 ExchangeImpl<T> ex) { 507 508 int rcode = resp.statusCode(); 509 if (upgrading && (rcode == 101)) { 510 Http1Exchange<T> e = (Http1Exchange<T>)ex; 511 // check for 101 switching protocols 512 // 101 responses are not supposed to contain a body. 513 // => should we fail if there is one? 514 if (debug.on()) debug.log("Upgrading async %s", e.connection()); 515 return e.readBodyAsync(this::ignoreBody, false, parentExecutor) 516 .thenCompose((T v) -> {// v is null 517 debug.log("Ignored body"); 518 // we pass e::getBuffer to allow the ByteBuffers to accumulate 519 // while we build the Http2Connection 520 ex.upgraded(); 521 upgraded = true; 522 return Http2Connection.createAsync(e.connection(), 523 client.client2(), 524 this, e::drainLeftOverBytes) 525 .thenCompose((Http2Connection c) -> { 526 boolean cached = c.offerConnection(); 527 if (cached) connectionAborter.disable(); 528 Stream<T> s = c.getStream(1); 529 530 if (s == null) { 531 // s can be null if an exception occurred 532 // asynchronously while sending the preface. 533 Throwable t = c.getRecordedCause(); 534 IOException ioe; 535 if (t != null) { 536 if (!cached) 537 c.close(); 538 ioe = new IOException("Can't get stream 1: " + t, t); 539 } else { 540 ioe = new IOException("Can't get stream 1"); 541 } 542 return MinimalFuture.failedFuture(ioe); 543 } 544 exchImpl.released(); 545 Throwable t; 546 // There's a race condition window where an external 547 // thread (SelectorManager) might complete the 548 // exchange in timeout at the same time where we're 549 // trying to switch the exchange impl. 550 // 'failed' will be reset to null after 551 // exchImpl.cancel() has completed, so either we 552 // will observe failed != null here, or we will 553 // observe e.getCancelCause() != null, or the 554 // timeout exception will be routed to 's'. 555 // Either way, we need to relay it to s. 556 synchronized (this) { 557 exchImpl = s; 558 t = failed; 559 } 560 // Check whether the HTTP/1.1 was cancelled. 561 if (t == null) t = e.getCancelCause(); 562 // if HTTP/1.1 exchange was timed out, or the request 563 // was cancelled don't try to go further. 564 if (t instanceof HttpTimeoutException || multi.requestCancelled()) { 565 if (t == null) t = new IOException("Request cancelled"); 566 s.cancelImpl(t); 567 return MinimalFuture.failedFuture(t); 568 } 569 if (debug.on()) 570 debug.log("Getting response async %s", s); 571 return s.getResponseAsync(null); 572 });} 573 ); 574 } 575 return MinimalFuture.completedFuture(resp); 576 } 577 578 private URI getURIForSecurityCheck() { 579 URI u; 580 String method = request.method(); 581 InetSocketAddress authority = request.authority(); 582 URI uri = request.uri(); 583 584 // CONNECT should be restricted at API level 585 if (method.equalsIgnoreCase("CONNECT")) { 586 try { 587 u = new URI("socket", 588 null, 589 authority.getHostString(), 590 authority.getPort(), 591 null, 592 null, 593 null); 594 } catch (URISyntaxException e) { 595 throw new InternalError(e); // shouldn't happen 596 } 597 } else { 598 u = uri; 599 } 600 return u; 601 } 602 603 /** 604 * Returns the security permission required for the given details. 605 * If method is CONNECT, then uri must be of form "scheme://host:port" 606 */ 607 private static URLPermission permissionForServer(URI uri, 608 String method, 609 Map<String, List<String>> headers) { 610 if (method.equals("CONNECT")) { 611 return new URLPermission(uri.toString(), "CONNECT"); 612 } else { 613 return Utils.permissionForServer(uri, method, headers.keySet().stream()); 614 } 615 } 616 617 /** 618 * Performs the necessary security permission checks required to retrieve 619 * the response. Returns a security exception representing the denied 620 * permission, or null if all checks pass or there is no security manager. 621 */ 622 private SecurityException checkPermissions() { 623 String method = request.method(); 624 SecurityManager sm = System.getSecurityManager(); 625 if (sm == null || method.equals("CONNECT")) { 626 // tunneling will have a null acc, which is fine. The proxy 627 // permission check will have already been preformed. 628 return null; 629 } 630 631 HttpHeaders userHeaders = request.getUserHeaders(); 632 URI u = getURIForSecurityCheck(); 633 URLPermission p = permissionForServer(u, method, userHeaders.map()); 634 635 try { 636 assert acc != null; 637 sm.checkPermission(p, acc); 638 } catch (SecurityException e) { 639 return e; 640 } 641 String hostHeader = userHeaders.firstValue("Host").orElse(null); 642 if (hostHeader != null && !hostHeader.equalsIgnoreCase(u.getHost())) { 643 // user has set a Host header different to request URI 644 // must check that for URLPermission also 645 URI u1 = replaceHostInURI(u, hostHeader); 646 URLPermission p1 = permissionForServer(u1, method, userHeaders.map()); 647 try { 648 assert acc != null; 649 sm.checkPermission(p1, acc); 650 } catch (SecurityException e) { 651 return e; 652 } 653 } 654 ProxySelector ps = client.proxySelector(); 655 if (ps != null) { 656 if (!method.equals("CONNECT")) { 657 // a non-tunneling HTTP proxy. Need to check access 658 URLPermission proxyPerm = permissionForProxy(request.proxy()); 659 if (proxyPerm != null) { 660 try { 661 sm.checkPermission(proxyPerm, acc); 662 } catch (SecurityException e) { 663 return e; 664 } 665 } 666 } 667 } 668 return null; 669 } 670 671 private static URI replaceHostInURI(URI u, String hostPort) { 672 StringBuilder sb = new StringBuilder(); 673 sb.append(u.getScheme()) 674 .append("://") 675 .append(hostPort) 676 .append(u.getRawPath()); 677 return URI.create(sb.toString()); 678 } 679 680 HttpClient.Version version() { 681 return multi.version(); 682 } 683 684 String dbgString() { 685 return dbgTag; 686 } 687 } 688