http-client-branch: 8199135: Validate that method names are tokens http-client-branch
authordfuchs
Tue, 06 Mar 2018 18:55:10 +0000
branchhttp-client-branch
changeset 56254 4b2272dfe720
parent 56253 875dbf6234f2
child 56255 39e28481492d
http-client-branch: 8199135: Validate that method names are tokens
src/java.net.http/share/classes/java/net/http/HttpRequest.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java
test/jdk/java/net/httpclient/MethodsTest.java
--- a/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Tue Mar 06 18:43:27 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Tue Mar 06 18:55:10 2018 +0000
@@ -272,7 +272,10 @@
          * @param method the method to use
          * @param bodyPublisher the body publisher
          * @return this builder
-         * @throws IllegalArgumentException if the method is restricted
+         * @throws IllegalArgumentException if the method name is not
+         *         valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.1.1">
+         *         RFC 7230 section-3.1.1</a>, or the method is restricted by the
+         *         implementation.
          */
         public Builder method(String method, BodyPublisher bodyPublisher);
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java	Tue Mar 06 18:43:27 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java	Tue Mar 06 18:55:10 2018 +0000
@@ -195,6 +195,12 @@
             throw newIAE("illegal method <empty string>");
         if (method.equals("CONNECT"))
             throw newIAE("method CONNECT is not supported");
+        if (!Utils.isValidName(method))
+            throw newIAE("illegal method \""
+                    + method.replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    .replace("\t", "\\t")
+                    + "\"");
         return method0(method, requireNonNull(body));
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java	Tue Mar 06 18:43:27 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java	Tue Mar 06 18:55:10 2018 +0000
@@ -41,6 +41,7 @@
 import java.net.http.HttpHeaders;
 import java.net.http.HttpRequest;
 import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Utils;
 import jdk.internal.net.http.websocket.WebSocketRequest;
 
 import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
@@ -94,8 +95,14 @@
      */
     public HttpRequestImpl(HttpRequest request, ProxySelector ps) {
         String method = request.method();
+        if (method != null && !Utils.isValidName(method))
+            throw new IllegalArgumentException("illegal method \""
+                    + method.replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    .replace("\t", "\\t")
+                    + "\"");
         this.method = method == null ? "GET" : method;
-        this.userHeaders = request.headers();
+        this.userHeaders = ImmutableHeaders.of(request.headers());
         if (request instanceof HttpRequestImpl) {
             // all cases exception WebSocket should have a new system headers
             this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
@@ -145,6 +152,7 @@
     private HttpRequestImpl(URI uri,
                             String method,
                             HttpRequestImpl other) {
+        assert method == null || Utils.isValidName(method);
         this.method = method == null? "GET" : method;
         this.userHeaders = other.userHeaders;
         this.isWebSocket = other.isWebSocket;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/MethodsTest.java	Tue Mar 06 18:55:10 2018 +0000
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
+
+import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.URI;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Optional;
+
+/**
+ * @test
+ * @bug 8199135
+ * @modules java.net.http/jdk.internal.net.http.common
+ * @summary Basic test for method names
+ */
+public class MethodsTest {
+
+    static final URI TEST_URI = URI.create("http://www.foo.com/");
+    static final String FORBIDDEN = "()<>@,;:\\\"/[]?={} \t\r\n";
+
+    static void bad(String name) throws IOException, InterruptedException {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.method(name, HttpRequest.BodyPublishers.noBody());
+            throw new RuntimeException("Expected IAE for method:" + name);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return name;
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    return new HttpHeadersImpl();
+                }
+            };
+            HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected IAE for method:" + name);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+    }
+
+    static void good(String name) {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.method(name, HttpRequest.BodyPublishers.noBody());
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException("Unexpected IAE for header:" + name);
+        }
+    }
+    
+    public static void main(String[] args) throws Exception {
+        bad("bad:method");
+        bad("Foo\n");
+        good("X-Foo!");
+        good("Bar~");
+        good("x");
+        bad(" ");
+        bad("x y");
+        bad("x\t");
+        bad("Bar\r\n");
+        good("Hello#world");
+        good("Qwer#ert");
+        bad("m\u00e9thode");
+        for (char c =0; c < 256 ; c++) {
+            if (c < 32 || FORBIDDEN.indexOf(c) > -1 || c >= 127) {
+                bad("me" + c + "thod");
+                bad(c + "thod");
+                bad("me" + c);
+            } else {
+                good("me" + c + "thod");
+                good(c + "thod");
+                good("me" + c);
+            }
+        }
+    }
+}