1 /*
   2  * Copyright (c) 2015, 2018, 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.net.InetSocketAddress;
  30 import java.net.Proxy;
  31 import java.net.ProxySelector;
  32 import java.net.URI;
  33 import java.security.AccessControlContext;
  34 import java.security.AccessController;
  35 import java.security.PrivilegedAction;
  36 import java.time.Duration;
  37 import java.util.List;
  38 import java.util.Locale;
  39 import java.util.Objects;
  40 import java.util.Optional;
  41 import java.net.http.HttpClient;
  42 import java.net.http.HttpHeaders;
  43 import java.net.http.HttpRequest;
  44 import jdk.internal.net.http.common.HttpHeadersBuilder;
  45 import jdk.internal.net.http.common.Utils;
  46 import jdk.internal.net.http.websocket.WebSocketRequest;
  47 
  48 import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
  49 
  50 public class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
  51 
  52     private final HttpHeaders userHeaders;
  53     private final HttpHeadersBuilder systemHeadersBuilder;
  54     private final URI uri;
  55     private volatile Proxy proxy; // ensure safe publishing
  56     private final InetSocketAddress authority; // only used when URI not specified
  57     private final String method;
  58     final BodyPublisher requestPublisher;
  59     final boolean secure;
  60     final boolean expectContinue;
  61     private volatile boolean isWebSocket;
  62     private volatile AccessControlContext acc;
  63     private final Duration timeout;  // may be null
  64     private final Optional<HttpClient.Version> version;
  65 
  66     private static String userAgent() {
  67         PrivilegedAction<String> pa = () -> System.getProperty("java.version");
  68         String version = AccessController.doPrivileged(pa);
  69         return "Java-http-client/" + version;
  70     }
  71 
  72     /** The value of the User-Agent header for all requests sent by the client. */
  73     public static final String USER_AGENT = userAgent();
  74 
  75     /**
  76      * Creates an HttpRequestImpl from the given builder.
  77      */
  78     public HttpRequestImpl(HttpRequestBuilderImpl builder) {
  79         String method = builder.method();
  80         this.method = method == null ? "GET" : method;
  81         this.userHeaders = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
  82         this.systemHeadersBuilder = new HttpHeadersBuilder();
  83         this.uri = builder.uri();
  84         assert uri != null;
  85         this.proxy = null;
  86         this.expectContinue = builder.expectContinue();
  87         this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
  88         this.requestPublisher = builder.bodyPublisher();  // may be null
  89         this.timeout = builder.timeout();
  90         this.version = builder.version();
  91         this.authority = null;
  92     }
  93 
  94     /**
  95      * Creates an HttpRequestImpl from the given request.
  96      */
  97     public HttpRequestImpl(HttpRequest request, ProxySelector ps) {
  98         String method = request.method();
  99         if (method != null && !Utils.isValidName(method))
 100             throw new IllegalArgumentException("illegal method \""
 101                     + method.replace("\n","\\n")
 102                     .replace("\r", "\\r")
 103                     .replace("\t", "\\t")
 104                     + "\"");
 105         URI requestURI = Objects.requireNonNull(request.uri(),
 106                 "uri must be non null");
 107         Duration timeout = request.timeout().orElse(null);
 108         this.method = method == null ? "GET" : method;
 109         this.userHeaders = HttpHeaders.of(request.headers().map(), Utils.VALIDATE_USER_HEADER);
 110         if (request instanceof HttpRequestImpl) {
 111             // all cases exception WebSocket should have a new system headers
 112             this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
 113             if (isWebSocket) {
 114                 this.systemHeadersBuilder = ((HttpRequestImpl)request).systemHeadersBuilder;
 115             } else {
 116                 this.systemHeadersBuilder = new HttpHeadersBuilder();
 117             }
 118         } else {
 119             HttpRequestBuilderImpl.checkURI(requestURI);
 120             checkTimeout(timeout);
 121             this.systemHeadersBuilder = new HttpHeadersBuilder();
 122         }
 123         if (!userHeaders.firstValue("User-Agent").isPresent()) {
 124             this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
 125         }
 126         this.uri = requestURI;
 127         if (isWebSocket) {
 128             // WebSocket determines and sets the proxy itself
 129             this.proxy = ((HttpRequestImpl) request).proxy;
 130         } else {
 131             if (ps != null)
 132                 this.proxy = retrieveProxy(ps, uri);
 133             else
 134                 this.proxy = null;
 135         }
 136         this.expectContinue = request.expectContinue();
 137         this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
 138         this.requestPublisher = request.bodyPublisher().orElse(null);
 139         this.timeout = timeout;
 140         this.version = request.version();
 141         this.authority = null;
 142     }
 143 
 144     private static void checkTimeout(Duration duration) {
 145         if (duration != null) {
 146             if (duration.isNegative() || Duration.ZERO.equals(duration))
 147                 throw new IllegalArgumentException("Invalid duration: " + duration);
 148         }
 149     }
 150 
 151     /** Returns a new instance suitable for redirection. */
 152     public static HttpRequestImpl newInstanceForRedirection(URI uri,
 153                                                             String method,
 154                                                             HttpRequestImpl other) {
 155         return new HttpRequestImpl(uri, method, other);
 156     }
 157 
 158     /** Returns a new instance suitable for authentication. */
 159     public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) {
 160         return new HttpRequestImpl(other.uri(), other.method(), other);
 161     }
 162 
 163     /**
 164      * Creates a HttpRequestImpl using fields of an existing request impl.
 165      * The newly created HttpRequestImpl does not copy the system headers.
 166      */
 167     private HttpRequestImpl(URI uri,
 168                             String method,
 169                             HttpRequestImpl other) {
 170         assert method == null || Utils.isValidName(method);
 171         this.method = method == null? "GET" : method;
 172         this.userHeaders = other.userHeaders;
 173         this.isWebSocket = other.isWebSocket;
 174         this.systemHeadersBuilder = new HttpHeadersBuilder();
 175         if (!userHeaders.firstValue("User-Agent").isPresent()) {
 176             this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
 177         }
 178         this.uri = uri;
 179         this.proxy = other.proxy;
 180         this.expectContinue = other.expectContinue;
 181         this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
 182         this.requestPublisher = other.requestPublisher;  // may be null
 183         this.acc = other.acc;
 184         this.timeout = other.timeout;
 185         this.version = other.version();
 186         this.authority = null;
 187     }
 188 
 189     /* used for creating CONNECT requests  */
 190     HttpRequestImpl(String method, InetSocketAddress authority, HttpHeaders headers) {
 191         // TODO: isWebSocket flag is not specified, but the assumption is that
 192         // such a request will never be made on a connection that will be returned
 193         // to the connection pool (we might need to revisit this constructor later)
 194         assert "CONNECT".equalsIgnoreCase(method);
 195         this.method = method;
 196         this.systemHeadersBuilder = new HttpHeadersBuilder();
 197         this.userHeaders = headers;
 198         this.uri = URI.create("socket://" + authority.getHostString() + ":"
 199                               + Integer.toString(authority.getPort()) + "/");
 200         this.proxy = null;
 201         this.requestPublisher = null;
 202         this.authority = authority;
 203         this.secure = false;
 204         this.expectContinue = false;
 205         this.timeout = null;
 206         // The CONNECT request sent for tunneling is only used in two cases:
 207         //   1. websocket, which only supports HTTP/1.1
 208         //   2. SSL tunneling through a HTTP/1.1 proxy
 209         // In either case we do not want to upgrade the connection to the proxy.
 210         // What we want to possibly upgrade is the tunneled connection to the
 211         // target server (so not the CONNECT request itself)
 212         this.version = Optional.of(HttpClient.Version.HTTP_1_1);
 213     }
 214 
 215     final boolean isConnect() {
 216         return "CONNECT".equalsIgnoreCase(method);
 217     }
 218 
 219     /**
 220      * Creates a HttpRequestImpl from the given set of Headers and the associated
 221      * "parent" request. Fields not taken from the headers are taken from the
 222      * parent.
 223      */
 224     static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
 225                                              HttpHeaders headers)
 226         throws IOException
 227     {
 228         return new HttpRequestImpl(parent, headers);
 229     }
 230 
 231     // only used for push requests
 232     private HttpRequestImpl(HttpRequestImpl parent, HttpHeaders headers)
 233         throws IOException
 234     {
 235         this.method = headers.firstValue(":method")
 236                 .orElseThrow(() -> new IOException("No method in Push Promise"));
 237         String path = headers.firstValue(":path")
 238                 .orElseThrow(() -> new IOException("No path in Push Promise"));
 239         String scheme = headers.firstValue(":scheme")
 240                 .orElseThrow(() -> new IOException("No scheme in Push Promise"));
 241         String authority = headers.firstValue(":authority")
 242                 .orElseThrow(() -> new IOException("No authority in Push Promise"));
 243         StringBuilder sb = new StringBuilder();
 244         sb.append(scheme).append("://").append(authority).append(path);
 245         this.uri = URI.create(sb.toString());
 246         this.proxy = null;
 247         this.userHeaders = HttpHeaders.of(headers.map(), ALLOWED_HEADERS);
 248         this.systemHeadersBuilder = parent.systemHeadersBuilder;
 249         this.expectContinue = parent.expectContinue;
 250         this.secure = parent.secure;
 251         this.requestPublisher = parent.requestPublisher;
 252         this.acc = parent.acc;
 253         this.timeout = parent.timeout;
 254         this.version = parent.version;
 255         this.authority = null;
 256     }
 257 
 258     @Override
 259     public String toString() {
 260         return (uri == null ? "" : uri.toString()) + " " + method;
 261     }
 262 
 263     @Override
 264     public HttpHeaders headers() {
 265         return userHeaders;
 266     }
 267 
 268     InetSocketAddress authority() { return authority; }
 269 
 270     void setH2Upgrade(Http2ClientImpl h2client) {
 271         systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings");
 272         systemHeadersBuilder.setHeader("Upgrade", "h2c");
 273         systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString());
 274     }
 275 
 276     @Override
 277     public boolean expectContinue() { return expectContinue; }
 278 
 279     /** Retrieves the proxy, from the given ProxySelector, if there is one. */
 280     private static Proxy retrieveProxy(ProxySelector ps, URI uri) {
 281         Proxy proxy = null;
 282         List<Proxy> pl = ps.select(uri);
 283         if (!pl.isEmpty()) {
 284             Proxy p = pl.get(0);
 285             if (p.type() == Proxy.Type.HTTP)
 286                 proxy = p;
 287         }
 288         return proxy;
 289     }
 290 
 291     InetSocketAddress proxy() {
 292         if (proxy == null || proxy.type() != Proxy.Type.HTTP
 293                 || method.equalsIgnoreCase("CONNECT")) {
 294             return null;
 295         }
 296         return (InetSocketAddress)proxy.address();
 297     }
 298 
 299     boolean secure() { return secure; }
 300 
 301     @Override
 302     public void setProxy(Proxy proxy) {
 303         assert isWebSocket;
 304         this.proxy = proxy;
 305     }
 306 
 307     @Override
 308     public void isWebSocket(boolean is) {
 309         isWebSocket = is;
 310     }
 311 
 312     boolean isWebSocket() {
 313         return isWebSocket;
 314     }
 315 
 316     @Override
 317     public Optional<BodyPublisher> bodyPublisher() {
 318         return requestPublisher == null ? Optional.empty()
 319                                         : Optional.of(requestPublisher);
 320     }
 321 
 322     /**
 323      * Returns the request method for this request. If not set explicitly,
 324      * the default method for any request is "GET".
 325      */
 326     @Override
 327     public String method() { return method; }
 328 
 329     @Override
 330     public URI uri() { return uri; }
 331 
 332     @Override
 333     public Optional<Duration> timeout() {
 334         return timeout == null ? Optional.empty() : Optional.of(timeout);
 335     }
 336 
 337     HttpHeaders getUserHeaders() { return userHeaders; }
 338 
 339     HttpHeadersBuilder getSystemHeadersBuilder() { return systemHeadersBuilder; }
 340 
 341     @Override
 342     public Optional<HttpClient.Version> version() { return version; }
 343 
 344     void addSystemHeader(String name, String value) {
 345         systemHeadersBuilder.addHeader(name, value);
 346     }
 347 
 348     @Override
 349     public void setSystemHeader(String name, String value) {
 350         systemHeadersBuilder.setHeader(name, value);
 351     }
 352 
 353     InetSocketAddress getAddress() {
 354         URI uri = uri();
 355         if (uri == null) {
 356             return authority();
 357         }
 358         int p = uri.getPort();
 359         if (p == -1) {
 360             if (uri.getScheme().equalsIgnoreCase("https")) {
 361                 p = 443;
 362             } else {
 363                 p = 80;
 364             }
 365         }
 366         final String host = uri.getHost();
 367         final int port = p;
 368         if (proxy() == null) {
 369             PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);
 370             return AccessController.doPrivileged(pa);
 371         } else {
 372             return InetSocketAddress.createUnresolved(host, port);
 373         }
 374     }
 375 }