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.net.URI;
  29 import java.time.Duration;
  30 import java.util.Locale;
  31 import java.util.Optional;
  32 import java.net.http.HttpClient;
  33 import java.net.http.HttpRequest;
  34 import java.net.http.HttpRequest.BodyPublisher;
  35 
  36 import jdk.internal.net.http.common.HttpHeadersBuilder;
  37 import jdk.internal.net.http.common.Utils;
  38 import static java.util.Objects.requireNonNull;
  39 import static jdk.internal.net.http.common.Utils.isValidName;
  40 import static jdk.internal.net.http.common.Utils.isValidValue;
  41 import static jdk.internal.net.http.common.Utils.newIAE;
  42 
  43 public class HttpRequestBuilderImpl implements HttpRequest.Builder {
  44 
  45     private HttpHeadersBuilder headersBuilder;
  46     private URI uri;
  47     private String method;
  48     private boolean expectContinue;
  49     private BodyPublisher bodyPublisher;
  50     private volatile Optional<HttpClient.Version> version;
  51     private Duration duration;
  52 
  53     public HttpRequestBuilderImpl(URI uri) {
  54         requireNonNull(uri, "uri must be non-null");
  55         checkURI(uri);
  56         this.uri = uri;
  57         this.headersBuilder = new HttpHeadersBuilder();
  58         this.method = "GET"; // default, as per spec
  59         this.version = Optional.empty();
  60     }
  61 
  62     public HttpRequestBuilderImpl() {
  63         this.headersBuilder = new HttpHeadersBuilder();
  64         this.method = "GET"; // default, as per spec
  65         this.version = Optional.empty();
  66     }
  67 
  68     @Override
  69     public HttpRequestBuilderImpl uri(URI uri) {
  70         requireNonNull(uri, "uri must be non-null");
  71         checkURI(uri);
  72         this.uri = uri;
  73         return this;
  74     }
  75 
  76     static void checkURI(URI uri) {
  77         String scheme = uri.getScheme();
  78         if (scheme == null)
  79             throw newIAE("URI with undefined scheme");
  80         scheme = scheme.toLowerCase(Locale.US);
  81         if (!(scheme.equals("https") || scheme.equals("http"))) {
  82             throw newIAE("invalid URI scheme %s", scheme);
  83         }
  84         if (uri.getHost() == null) {
  85             throw newIAE("unsupported URI %s", uri);
  86         }
  87     }
  88 
  89     @Override
  90     public HttpRequestBuilderImpl copy() {
  91         HttpRequestBuilderImpl b = new HttpRequestBuilderImpl();
  92         b.uri = this.uri;
  93         b.headersBuilder = this.headersBuilder.structuralCopy();
  94         b.method = this.method;
  95         b.expectContinue = this.expectContinue;
  96         b.bodyPublisher = bodyPublisher;
  97         b.uri = uri;
  98         b.duration = duration;
  99         b.version = version;
 100         return b;
 101     }
 102 
 103     private void checkNameAndValue(String name, String value) {
 104         requireNonNull(name, "name");
 105         requireNonNull(value, "value");
 106         if (!isValidName(name)) {
 107             throw newIAE("invalid header name: \"%s\"", name);
 108         }
 109         if (!Utils.ALLOWED_HEADERS.test(name, null)) {
 110             throw newIAE("restricted header name: \"%s\"", name);
 111         }
 112         if (!isValidValue(value)) {
 113             throw newIAE("invalid header value: \"%s\"", value);
 114         }
 115     }
 116 
 117     @Override
 118     public HttpRequestBuilderImpl setHeader(String name, String value) {
 119         checkNameAndValue(name, value);
 120         headersBuilder.setHeader(name, value);
 121         return this;
 122     }
 123 
 124     @Override
 125     public HttpRequestBuilderImpl header(String name, String value) {
 126         checkNameAndValue(name, value);
 127         headersBuilder.addHeader(name, value);
 128         return this;
 129     }
 130 
 131     @Override
 132     public HttpRequestBuilderImpl headers(String... params) {
 133         requireNonNull(params);
 134         if (params.length == 0 || params.length % 2 != 0) {
 135             throw newIAE("wrong number, %d, of parameters", params.length);
 136         }
 137         for (int i = 0; i < params.length; i += 2) {
 138             String name  = params[i];
 139             String value = params[i + 1];
 140             header(name, value);
 141         }
 142         return this;
 143     }
 144 
 145     @Override
 146     public HttpRequestBuilderImpl expectContinue(boolean enable) {
 147         expectContinue = enable;
 148         return this;
 149     }
 150 
 151     @Override
 152     public HttpRequestBuilderImpl version(HttpClient.Version version) {
 153         requireNonNull(version);
 154         this.version = Optional.of(version);
 155         return this;
 156     }
 157 
 158     HttpHeadersBuilder headersBuilder() {  return headersBuilder; }
 159 
 160     URI uri() { return uri; }
 161 
 162     String method() { return method; }
 163 
 164     boolean expectContinue() { return expectContinue; }
 165 
 166     BodyPublisher bodyPublisher() { return bodyPublisher; }
 167 
 168     Optional<HttpClient.Version> version() { return version; }
 169 
 170     @Override
 171     public HttpRequest.Builder GET() {
 172         return method0("GET", null);
 173     }
 174 
 175     @Override
 176     public HttpRequest.Builder POST(BodyPublisher body) {
 177         return method0("POST", requireNonNull(body));
 178     }
 179 
 180     @Override
 181     public HttpRequest.Builder DELETE() {
 182         return method0("DELETE", null);
 183     }
 184 
 185     @Override
 186     public HttpRequest.Builder PUT(BodyPublisher body) {
 187         return method0("PUT", requireNonNull(body));
 188     }
 189 
 190     @Override
 191     public HttpRequest.Builder method(String method, BodyPublisher body) {
 192         requireNonNull(method);
 193         if (method.equals(""))
 194             throw newIAE("illegal method <empty string>");
 195         if (method.equals("CONNECT"))
 196             throw newIAE("method CONNECT is not supported");
 197         if (!Utils.isValidName(method))
 198             throw newIAE("illegal method \""
 199                     + method.replace("\n","\\n")
 200                     .replace("\r", "\\r")
 201                     .replace("\t", "\\t")
 202                     + "\"");
 203         return method0(method, requireNonNull(body));
 204     }
 205 
 206     private HttpRequest.Builder method0(String method, BodyPublisher body) {
 207         assert method != null;
 208         assert !method.equals("");
 209         this.method = method;
 210         this.bodyPublisher = body;
 211         return this;
 212     }
 213 
 214     @Override
 215     public HttpRequest build() {
 216         if (uri == null)
 217             throw new IllegalStateException("uri is null");
 218         assert method != null;
 219         return new ImmutableHttpRequest(this);
 220     }
 221 
 222     public HttpRequestImpl buildForWebSocket() {
 223         if (uri == null)
 224             throw new IllegalStateException("uri is null");
 225         assert method != null;
 226         return new HttpRequestImpl(this);
 227     }
 228 
 229     @Override
 230     public HttpRequest.Builder timeout(Duration duration) {
 231         requireNonNull(duration);
 232         if (duration.isNegative() || Duration.ZERO.equals(duration))
 233             throw new IllegalArgumentException("Invalid duration: " + duration);
 234         this.duration = duration;
 235         return this;
 236     }
 237 
 238     Duration timeout() { return duration; }
 239 
 240 }