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