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 }