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 }