8213189: Make restricted headers in HTTP Client configurable and remove Date by default

Reviewed-by: dfuchs
This commit is contained in:
Michael McMahon 2018-11-14 14:23:21 +00:00
parent eafb5eb05e
commit 028f2e14b3
14 changed files with 376 additions and 28 deletions

View file

@ -585,6 +585,19 @@ final class Exchange<T> {
} 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 @@ final class Exchange<T> {
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();
}

View file

@ -29,6 +29,7 @@ import java.io.IOException;
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 @@ abstract class ExchangeImpl<T> {
return exchange;
}
HttpClient client() {
return exchange.client();
}
/**
* Returns the {@link HttpConnection} instance to which this exchange is

View file

@ -27,6 +27,7 @@ package jdk.internal.net.http;
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 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
}
}
HttpClient client() {
return client;
}
String dbgString() {
return "Http1Exchange";
}

View file

@ -27,6 +27,7 @@ package jdk.internal.net.http;
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 @@ class Http1Request {
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 @@ class Http1Request {
}
private void collectHeaders0(StringBuilder sb) {
public void collectHeaders0(StringBuilder sb) {
BiPredicate<String,String> filter =
connection.headerFilter(request);
@ -99,6 +100,15 @@ class Http1Request {
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

View file

@ -608,8 +608,20 @@ class Stream<T> extends ExchangeImpl<T> {
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);

View file

@ -45,6 +45,7 @@ import java.net.ConnectException;
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 java.util.stream.Stream;
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 final class Utils {
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();
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);
private static Set<String> getDisallowedHeaders() {
Set<String> headers = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
headers.addAll(Set.of("connection", "content-length", "expect", "host", "upgrade"));
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 @@ public final class Utils {
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 @@ public final class Utils {
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(","));
@ -794,6 +818,33 @@ public final class Utils {
return getDebugLogger(dbgTag, errLevel);
}
/**
* 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}.