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