8213189: Make restricted headers in HTTP Client configurable and remove Date by default
authormichaelm
Wed, 14 Nov 2018 14:23:21 +0000
changeset 52554 5f1ca46703f9
parent 52553 9ca9aa224c39
child 52555 3b2d22602c16
8213189: Make restricted headers in HTTP Client configurable and remove Date by default Reviewed-by: dfuchs
src/java.base/share/conf/net.properties
src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/ExchangeImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
test/jdk/java/net/httpclient/RequestBuilderTest.java
test/jdk/java/net/httpclient/RestrictedHeadersTest.java
test/jdk/java/net/httpclient/SpecialHeadersTest.java
test/jdk/java/net/httpclient/security/16.policy
test/jdk/java/net/httpclient/security/17.policy
test/jdk/java/net/httpclient/security/Driver.java
test/jdk/java/net/httpclient/security/Security.java
--- a/src/java.base/share/conf/net.properties	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.base/share/conf/net.properties	Wed Nov 14 14:23:21 2018 +0000
@@ -99,3 +99,21 @@
 #jdk.http.auth.proxying.disabledSchemes=
 jdk.http.auth.tunneling.disabledSchemes=Basic
 
+#
+# Allow restricted HTTP request headers
+#
+# By default, the following request headers are not allowed to be set by user code
+# in HttpRequests: "connection", "content-length", "expect", "host" and "upgrade".
+# The 'jdk.httpclient.allowRestrictedHeaders' property allows one or more of these
+# headers to be specified as a comma separated list to override the default restriction. 
+# The names are case-insensitive and white-space is ignored (removed before processing 
+# the list). Note, this capability is mostly intended for testing and isn't expected 
+# to be used in real deployments. Protocol errors or other undefined behavior is likely 
+# to occur when using them. The property is not set by default.
+# Note also, that there may be other headers that are restricted from being set
+# depending on the context. This includes the "Authorization" header when the
+# relevant HttpClient has an authenticator set. These restrictions cannot be
+# overridden by this property.
+#
+# jdk.httpclient.allowRestrictedHeaders=host
+#
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Wed Nov 14 14:23:21 2018 +0000
@@ -585,6 +585,19 @@
         } catch (SecurityException e) {
             return e;
         }
+        String hostHeader = userHeaders.firstValue("Host").orElse(null);
+        if (hostHeader != null && !hostHeader.equalsIgnoreCase(u.getHost())) {
+            // user has set a Host header different to request URI
+            // must check that for URLPermission also
+            URI u1 = replaceHostInURI(u, hostHeader);
+            URLPermission p1 = permissionForServer(u1, method, userHeaders.map());
+            try {
+                assert acc != null;
+                sm.checkPermission(p1, acc);
+            } catch (SecurityException e) {
+                return e;
+            }
+        }
         ProxySelector ps = client.proxySelector();
         if (ps != null) {
             if (!method.equals("CONNECT")) {
@@ -602,6 +615,15 @@
         return null;
     }
 
+    private static URI replaceHostInURI(URI u, String hostPort) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(u.getScheme())
+                .append("://")
+                .append(hostPort)
+                .append(u.getRawPath());
+        return URI.create(sb.toString());
+    }
+
     HttpClient.Version version() {
         return multi.version();
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ExchangeImpl.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ExchangeImpl.java	Wed Nov 14 14:23:21 2018 +0000
@@ -29,6 +29,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.Function;
+import java.net.http.HttpClient;
 import java.net.http.HttpResponse;
 import jdk.internal.net.http.common.Logger;
 import jdk.internal.net.http.common.MinimalFuture;
@@ -64,6 +65,9 @@
         return exchange;
     }
 
+    HttpClient client() {
+        return exchange.client();
+    }
 
     /**
      * Returns the {@link HttpConnection} instance to which this exchange is
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed Nov 14 14:23:21 2018 +0000
@@ -27,6 +27,7 @@
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.net.http.HttpClient;
 import java.net.http.HttpResponse.BodyHandler;
 import java.net.http.HttpResponse.BodySubscriber;
 import java.nio.ByteBuffer;
@@ -706,6 +707,10 @@
         }
     }
 
+    HttpClient client() {
+        return client;
+    }
+
     String dbgString() {
         return "Http1Exchange";
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java	Wed Nov 14 14:23:21 2018 +0000
@@ -27,6 +27,7 @@
 
 import java.io.IOException;
 import java.net.URI;
+import java.net.http.HttpClient;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,7 +60,7 @@
     private final Http1Exchange<?> http1Exchange;
     private final HttpConnection connection;
     private final HttpRequest.BodyPublisher requestPublisher;
-    private final HttpHeaders userHeaders;
+    private volatile HttpHeaders userHeaders;
     private final HttpHeadersBuilder systemHeadersBuilder;
     private volatile boolean streaming;
     private volatile long contentLength;
@@ -91,7 +92,7 @@
     }
 
 
-    private void collectHeaders0(StringBuilder sb) {
+    public void collectHeaders0(StringBuilder sb) {
         BiPredicate<String,String> filter =
                 connection.headerFilter(request);
 
@@ -99,6 +100,15 @@
         BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);
 
         HttpHeaders systemHeaders = systemHeadersBuilder.build();
+        HttpClient client = http1Exchange.client();
+
+        // Filter overridable headers from userHeaders
+        userHeaders = HttpHeaders.of(userHeaders.map(), Utils.CONTEXT_RESTRICTED(client));
+
+        final HttpHeaders uh = userHeaders;
+
+        // Filter any headers from systemHeaders that are set in userHeaders
+        systemHeaders = HttpHeaders.of(systemHeaders.map(), (k,v) -> uh.firstValue(k).isEmpty());
 
         // If we're sending this request through a tunnel,
         // then don't send any preemptive proxy-* headers that
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed Nov 14 14:23:21 2018 +0000
@@ -608,8 +608,20 @@
         if (contentLength > 0) {
             h.setHeader("content-length", Long.toString(contentLength));
         }
+        URI uri = request.uri();
+        if (uri != null) {
+            h.setHeader("host", Utils.hostString(request));
+        }
         HttpHeaders sysh = filterHeaders(h.build());
         HttpHeaders userh = filterHeaders(request.getUserHeaders());
+        // Filter context restricted from userHeaders
+        userh = HttpHeaders.of(userh.map(), Utils.CONTEXT_RESTRICTED(client()));
+
+        final HttpHeaders uh = userh;
+
+        // Filter any headers from systemHeaders that are set in userHeaders
+        sysh = HttpHeaders.of(sysh.map(), (k,v) -> uh.firstValue(k).isEmpty());
+
         OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(sysh, userh, this);
         if (contentLength == 0) {
             f.setFlag(HeadersFrame.END_STREAM);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java	Wed Nov 14 14:23:21 2018 +0000
@@ -45,6 +45,7 @@
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.net.URLPermission;
+import java.net.http.HttpClient;
 import java.net.http.HttpHeaders;
 import java.net.http.HttpTimeoutException;
 import java.nio.ByteBuffer;
@@ -75,6 +76,7 @@
 import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.joining;
+import jdk.internal.net.http.HttpRequestImpl;
 
 /**
  * Miscellaneous utilities
@@ -127,14 +129,23 @@
 
     public static final BiPredicate<String,String> ACCEPT_ALL = (x,y) -> true;
 
-    private static final Set<String> DISALLOWED_HEADERS_SET;
+    private static final Set<String> DISALLOWED_HEADERS_SET = getDisallowedHeaders();
+
+    private static Set<String> getDisallowedHeaders() {
+        Set<String> headers = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        headers.addAll(Set.of("connection", "content-length", "expect", "host", "upgrade"));
 
-    static {
-        // A case insensitive TreeSet of strings.
-        TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-        treeSet.addAll(Set.of("connection", "content-length",
-                "date", "expect", "from", "host", "upgrade", "via", "warning"));
-        DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
+        String v = getNetProperty("jdk.httpclient.allowRestrictedHeaders");
+        if (v != null) {
+            // any headers found are removed from set.
+            String[] tokens = v.trim().split(",");
+            for (String token : tokens) {
+                headers.remove(token);
+            }
+            return Collections.unmodifiableSet(headers);
+        } else {
+            return Collections.unmodifiableSet(headers);
+        }
     }
 
     public static final BiPredicate<String, String>
@@ -156,6 +167,19 @@
                 return true;
             };
 
+    // Headers that are not generally restricted, and can therefore be set by users,
+    // but can in some contexts be overridden by the implementation.
+    // Currently, only contains "Authorization" which will
+    // be overridden, when an Authenticator is set on the HttpClient.
+    // Needs to be BiPred<String,String> to fit with general form of predicates
+    // used by caller.
+
+    public static final BiPredicate<String, String> CONTEXT_RESTRICTED(HttpClient client) {
+        return (k, v) -> client.authenticator() == null ||
+                ! (k.equalsIgnoreCase("Authorization")
+                        && k.equalsIgnoreCase("Proxy-Authorization"));
+    }
+
     private static final Predicate<String> IS_PROXY_HEADER = (k) ->
             k != null && k.length() > 6 && "proxy-".equalsIgnoreCase(k.substring(0,6));
     private static final Predicate<String> NO_PROXY_HEADER =
@@ -323,8 +347,8 @@
                                                     Stream<String> headers) {
         String urlString = new StringBuilder()
                 .append(uri.getScheme()).append("://")
-                .append(uri.getAuthority())
-                .append(uri.getPath()).toString();
+                .append(uri.getRawAuthority())
+                .append(uri.getRawPath()).toString();
 
         StringBuilder actionStringBuilder = new StringBuilder(method);
         String collected = headers.collect(joining(","));
@@ -795,6 +819,33 @@
     }
 
     /**
+     * Return the host string from a HttpRequestImpl
+     *
+     * @param request
+     * @return
+     */
+    public static String hostString(HttpRequestImpl request) {
+        URI uri = request.uri();
+        int port = uri.getPort();
+        String host = uri.getHost();
+
+        boolean defaultPort;
+        if (port == -1) {
+            defaultPort = true;
+        } else if (uri.getScheme().equalsIgnoreCase("https")) {
+            defaultPort = port == 443;
+        } else {
+            defaultPort = port == 80;
+        }
+
+        if (defaultPort) {
+            return host;
+        } else {
+            return host + ":" + Integer.toString(port);
+        }
+    }
+
+    /**
      * Get a logger for debug HPACK traces.The logger should only be used
      * with levels whose severity is {@code <= DEBUG}.
      *
--- a/test/jdk/java/net/httpclient/RequestBuilderTest.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/test/jdk/java/net/httpclient/RequestBuilderTest.java	Wed Nov 14 14:23:21 2018 +0000
@@ -339,7 +339,7 @@
 
     // headers that are allowed now, but weren't before
     private static final Set<String> FORMERLY_RESTRICTED = Set.of("referer", "origin",
-            "OriGin", "Referer");
+            "OriGin", "Referer", "Date", "via", "WarnIng");
 
     @Test
     public void testFormerlyRestricted()  throws URISyntaxException {
@@ -354,14 +354,9 @@
     }
 
     private static final Set<String> RESTRICTED = Set.of("connection", "content-length",
-            "date", "expect", "from", "host",
-            "upgrade", "via", "warning",
-            "Connection", "Content-Length",
-            "DATE", "eXpect", "frOm", "hosT",
-            "upgradE", "vIa", "Warning",
-            "CONNection", "CONTENT-LENGTH",
-            "Date", "EXPECT", "From", "Host",
-            "Upgrade", "Via", "WARNING");
+            "expect", "host", "upgrade", "Connection", "Content-Length",
+            "eXpect", "hosT", "upgradE", "CONNection", "CONTENT-LENGTH",
+            "EXPECT", "Host", "Upgrade");
 
     interface WithHeader {
         HttpRequest.Builder withHeader(HttpRequest.Builder builder, String name, String value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/RestrictedHeadersTest.java	Wed Nov 14 14:23:21 2018 +0000
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8178699
+ * @modules java.net.http
+ * @run main/othervm RestrictedHeadersTest
+ * @run main/othervm -Djdk.httpclient.allowRestrictedHeaders=content-length,connection RestrictedHeadersTest content-length connection
+ * @run main/othervm -Djdk.httpclient.allowRestrictedHeaders=host,upgrade RestrictedHeadersTest host upgrade
+ * @run main/othervm -Djdk.httpclient.allowRestrictedHeaders=via RestrictedHeadersTest via
+ */
+
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.util.Set;
+
+public class RestrictedHeadersTest {
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            runDefaultTest();
+        } else {
+            runTest(Set.of(args));
+        }
+    }
+
+    // This list must be same as impl
+
+    static Set<String> defaultRestrictedHeaders =
+            Set.of("connection", "content-length", "expect", "host", "upgrade");
+
+    private static void runDefaultTest() {
+        System.out.println("DEFAULT TEST: no property set");
+        for (String header : defaultRestrictedHeaders) {
+            checkHeader(header, "foo", false);
+        }
+        // miscellaneous others that should succeed
+        checkHeader("foobar", "barfoo", true);
+        checkHeader("date", "today", true);
+    }
+
+    private static void checkHeader(String name, String value, boolean succeed) {
+        try {
+            HttpRequest request = HttpRequest.newBuilder(URI.create("https://foo.com/"))
+                    .header(name, value)
+                    .GET()
+                    .build();
+            if (!succeed) {
+                String s = name+"/"+value+" should have failed";
+                throw new RuntimeException(s);
+            }
+            System.out.printf("%s = %s succeeded as expected\n", name, value);
+        } catch (IllegalArgumentException iae) {
+            if (succeed) {
+                String s = name+"/"+value+" should have succeeded";
+                throw new RuntimeException(s);
+            }
+            System.out.printf("%s = %s failed as expected\n", name, value);
+        }
+    }
+
+    // args is the Set of allowed restricted headers
+    private static void runTest(Set<String> args) {
+        System.out.print("RUNTEST: allowed headers set in property: ");
+        for (String arg : args) System.out.printf("%s ", arg);
+        System.out.println("");
+
+        for (String header : args) {
+            checkHeader(header, "val", true);
+        }
+        for (String header : defaultRestrictedHeaders) {
+            if (!args.contains(header)) {
+                checkHeader(header, "foo", false);
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/SpecialHeadersTest.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/test/jdk/java/net/httpclient/SpecialHeadersTest.java	Wed Nov 14 14:23:21 2018 +0000
@@ -38,6 +38,9 @@
  * @run testng/othervm
  *       -Djdk.httpclient.HttpClient.log=requests,headers,errors
  *       SpecialHeadersTest
+ * @run testng/othervm -Djdk.httpclient.allowRestrictedHeaders=Host
+ *       -Djdk.httpclient.HttpClient.log=requests,headers,errors
+ *       SpecialHeadersTest
  */
 
 import com.sun.net.httpserver.HttpServer;
@@ -70,6 +73,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
 
 import static java.lang.System.err;
 import static java.lang.System.out;
@@ -104,21 +108,33 @@
             {"ORIGIN: upper"},
     };
 
+    // Needs net.property enabled for this part of test
+    static final String[][] headerNamesAndValues1 = new String[][]{
+            {"Host: <DEFAULT>"},
+            {"Host: camel-cased"},
+            {"host: all-lower-case"},
+            {"hoSt: mixed"}
+    };
+
     @DataProvider(name = "variants")
     public Object[][] variants() {
+        String prop = System.getProperty("jdk.httpclient.allowRestrictedHeaders");
+        boolean hostTest = prop != null && prop.equalsIgnoreCase("host");
+        final String[][] testInput = hostTest ? headerNamesAndValues1 : headerNamesAndValues;
+
         List<Object[]> list = new ArrayList<>();
 
         for (boolean sameClient : new boolean[] { false, true }) {
-            Arrays.asList(headerNamesAndValues).stream()
+            Arrays.asList(testInput).stream()
                     .map(e -> new Object[] {httpURI, e[0], sameClient})
                     .forEach(list::add);
-            Arrays.asList(headerNamesAndValues).stream()
+            Arrays.asList(testInput).stream()
                     .map(e -> new Object[] {httpsURI, e[0], sameClient})
                     .forEach(list::add);
-            Arrays.asList(headerNamesAndValues).stream()
+            Arrays.asList(testInput).stream()
                     .map(e -> new Object[] {http2URI, e[0], sameClient})
                     .forEach(list::add);
-            Arrays.asList(headerNamesAndValues).stream()
+            Arrays.asList(testInput).stream()
                     .map(e -> new Object[] {https2URI, e[0], sameClient})
                     .forEach(list::add);
         }
@@ -131,7 +147,8 @@
         return "Java-http-client/" + System.getProperty("java.version");
     }
 
-    static final Map<String, String> DEFAULTS = Map.of("USER-AGENT", userAgent());
+    static final Map<String, Function<URI,String>> DEFAULTS = Map.of(
+        "USER-AGENT", u -> userAgent(), "HOST", u -> u.getRawAuthority());
 
     @Test(dataProvider = "variants")
     void test(String uriString, String headerNameAndValue, boolean sameClient) throws Exception {
@@ -142,9 +159,9 @@
         String v = headerNameAndValue.substring(index+1).trim();
         String key = name.toUpperCase(Locale.ROOT);
         boolean useDefault = "<DEFAULT>".equals(v);
-        String value =  useDefault ? DEFAULTS.get(key) : v;
 
         URI uri = URI.create(uriString+"?name="+key);
+        String value =  useDefault ? DEFAULTS.get(key).apply(uri) : v;
 
         HttpClient client = null;
         for (int i=0; i< ITERATION_COUNT; i++) {
@@ -210,7 +227,7 @@
                 return Optional.empty();
             }
             @Override public HttpHeaders headers() {
-                Map<String, List<String>> map = Map.of("via", List.of("http://foo.com"));
+                Map<String, List<String>> map = Map.of("upgrade", List.of("http://foo.com"));
                 return HttpHeaders.of(map, (x, y) -> true);
             }
         };
@@ -231,9 +248,9 @@
         String v = headerNameAndValue.substring(index+1).trim();
         String key = name.toUpperCase(Locale.ROOT);
         boolean useDefault = "<DEFAULT>".equals(v);
-        String value =  useDefault ? DEFAULTS.get(key) : v;
 
         URI uri = URI.create(uriString+"?name="+key);
+        String value =  useDefault ? DEFAULTS.get(key).apply(uri) : v;
 
         HttpClient client = null;
         for (int i=0; i< ITERATION_COUNT; i++) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/security/16.policy	Wed Nov 14 14:23:21 2018 +0000
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2016, 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.
+//
+
+// Policy 16: Test tries to set Host header to localhost:123 but there is no permission
+
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "*", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET:Host";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/security/17.policy	Wed Nov 14 14:23:21 2018 +0000
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2016, 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.
+//
+
+// Policy 17. Grant permission to port 123 (no connect attempt is actually made)
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "*", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET:Host";
+    permission java.net.URLPermission "http://foohost:123/files/foo.txt", "GET:Host";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
+};
--- a/test/jdk/java/net/httpclient/security/Driver.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/test/jdk/java/net/httpclient/security/Driver.java	Wed Nov 14 14:23:21 2018 +0000
@@ -70,6 +70,8 @@
         runtest("10.policy", "10");
         runtest("11.policy", "11");
         runtest("12.policy", "12");
+        runtest("16.policy", "16", "-Djdk.httpclient.allowRestrictedHeaders=Host");
+        runtest("17.policy", "17", "-Djdk.httpclient.allowRestrictedHeaders=Host");
         System.out.println("DONE");
     }
 
@@ -114,7 +116,11 @@
     }
 
     public static void runtest(String policy, String testnum) throws Throwable {
+        runtest(policy, testnum, null);
+    }
 
+
+    public static void runtest(String policy, String testnum, String addProp) throws Throwable {
         String testJdk = System.getProperty("test.jdk", "?");
         String testSrc = System.getProperty("test.src", "?");
         String testClassPath = System.getProperty("test.class.path", "?");
@@ -136,6 +142,9 @@
             cmd.add("-Dport.number=" + Integer.toString(Utils.getFreePort()));
             cmd.add("-Dport.number1=" + Integer.toString(Utils.getFreePort()));
             cmd.add("-Djdk.httpclient.HttpClient.log=all,frames:all");
+            if (addProp != null) {
+                cmd.add(addProp);
+            }
             cmd.add("-cp");
             cmd.add(testClassPath);
             cmd.add("Security");
--- a/test/jdk/java/net/httpclient/security/Security.java	Wed Nov 14 17:16:44 2018 +0530
+++ b/test/jdk/java/net/httpclient/security/Security.java	Wed Nov 14 14:23:21 2018 +0000
@@ -377,6 +377,24 @@
                     else
                         throw new RuntimeException(t);
                 }
+            }),
+            // (16) allowed to set Host header but does not have permission
+            TestAndResult.of(true, () -> { //Policy 16
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
+                HttpRequest request = HttpRequest.newBuilder(u)
+                        .header("Host", "foohost:123")
+                        .GET().build();
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
+            }),
+            // (17) allowed to set Host header and does have permission
+            TestAndResult.of(false, () -> { //Policy 17
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
+                HttpRequest request = HttpRequest.newBuilder(u)
+                        .header("Host", "foohost:123")
+                        .GET().build();
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             })
         };
     }