--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java Sun Nov 05 21:19:55 2017 +0000
@@ -83,7 +83,7 @@
}
private URI getProxyURI(HttpRequestImpl r) {
- InetSocketAddress proxy = r.proxy(exchange.client());
+ InetSocketAddress proxy = r.proxy();
if (proxy == null) {
return null;
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java Sun Nov 05 21:19:55 2017 +0000
@@ -28,22 +28,17 @@
import java.io.IOException;
import java.lang.System.Logger.Level;
import java.net.InetSocketAddress;
+import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketPermission;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLPermission;
import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import jdk.incubator.http.internal.common.MinimalFuture;
import jdk.incubator.http.internal.common.Utils;
@@ -425,9 +420,9 @@
* Returns the security permission required for the given details.
* If method is CONNECT, then uri must be of form "scheme://host:port"
*/
- private static URLPermission getPermissionFor(URI uri,
- String method,
- Map<String, List<String>> headers) {
+ private static URLPermission permissionForServer(URI uri,
+ String method,
+ Map<String, List<String>> headers) {
StringBuilder sb = new StringBuilder();
String urlstring, actionstring;
@@ -461,20 +456,39 @@
}
/**
+ * Returns the security permissions required to connect to the proxy, or
+ * null if none is required or applicable.
+ */
+ static URLPermission permissionForProxy(HttpRequestImpl request) {
+ InetSocketAddress proxyAddress = request.proxy();
+ if (proxyAddress == null)
+ return null;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("socket://")
+ .append(proxyAddress.getHostString()).append(":")
+ .append(proxyAddress.getPort());
+ String urlstring = sb.toString();
+ return new URLPermission(urlstring.toString(), "CONNECT");
+ }
+
+ /**
* Performs the necessary security permission checks required to retrieve
* the response. Returns a security exception representing the denied
* permission, or null if all checks pass or there is no security manager.
*/
private SecurityException checkPermissions() {
+ String method = request.method();
SecurityManager sm = System.getSecurityManager();
- if (sm == null) {
+ if (sm == null || method.equals("CONNECT")) {
+ // tunneling will have a null acc, which is fine. The proxy
+ // permission check will have already been preformed.
return null;
}
- String method = request.method();
HttpHeaders userHeaders = request.getUserHeaders();
URI u = getURIForSecurityCheck();
- URLPermission p = getPermissionFor(u, method, userHeaders.map());
+ URLPermission p = permissionForServer(u, method, userHeaders.map());
try {
assert acc != null;
@@ -484,22 +498,15 @@
}
ProxySelector ps = client.proxy().orElse(null);
if (ps != null) {
- InetSocketAddress proxy = (InetSocketAddress)
- ps.select(u).get(0).address(); // TODO: check this
- // may need additional check
if (!method.equals("CONNECT")) {
- // a direct http proxy. Need to check access to proxy
- try {
- u = new URI("socket", null, proxy.getHostString(),
- proxy.getPort(), null, null, null);
- } catch (URISyntaxException e) {
- throw new InternalError(e); // shouldn't happen
- }
- p = new URLPermission(u.toString(), "CONNECT");
- try {
- sm.checkPermission(p, acc);
- } catch (SecurityException e) {
- return e;
+ // a non-tunneling HTTP proxy. Need to check access
+ URLPermission proxyPerm = permissionForProxy(request);
+ if (proxyPerm != null) {
+ try {
+ sm.checkPermission(proxyPerm, acc);
+ } catch (SecurityException e) {
+ return e;
+ }
}
}
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java Sun Nov 05 21:19:55 2017 +0000
@@ -145,7 +145,7 @@
URI uri = request.uri();
String method = request.method();
- if ((request.proxy(client) == null && !method.equals("CONNECT"))
+ if ((request.proxy() == null && !method.equals("CONNECT"))
|| request.isWebSocket()) {
return getPathAndQuery(uri);
}
@@ -158,6 +158,11 @@
return getPathAndQuery(uri);
}
}
+ if (request.method().equals("CONNECT")) {
+ // use authority for connect itself
+ return authorityString(request.authority());
+ }
+
return uri == null? authorityString(request.authority()) : uri.toString();
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -98,7 +98,7 @@
*/
CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) {
URI uri = req.uri();
- InetSocketAddress proxy = req.proxy(client);
+ InetSocketAddress proxy = req.proxy();
String key = Http2Connection.keyFor(uri, proxy);
synchronized (opening) {
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java Sun Nov 05 21:19:55 2017 +0000
@@ -320,7 +320,7 @@
this(connection,
h2client,
1,
- keyFor(request.uri(), request.proxy(h2client.client())));
+ keyFor(request.uri(), request.proxy()));
Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java Sun Nov 05 21:19:55 2017 +0000
@@ -372,7 +372,8 @@
* @param responseBodyHandler the response body handler
* @return the response body
* @throws java.io.IOException if an I/O error occurs when sending or receiving
- * @throws java.lang.InterruptedException if the operation is interrupted
+ * @throws InterruptedException if the operation is interrupted
+ * @throws IllegalArgumentException if the request method is not supported
* @throws SecurityException If a security manager has been installed
* and it denies {@link java.net.URLPermission access} to the
* URL in the given request, or proxy if one is configured.
@@ -459,6 +460,7 @@
* @return a builder of {@code WebSocket} instances
* @throws UnsupportedOperationException
* if this {@code HttpClient} does not provide WebSocket support
+ * @throws IllegalArgumentException if the given URI is not a valid WebSocket URI
*/
public WebSocket.Builder newWebSocketBuilder(URI uri,
WebSocket.Listener listener)
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -33,6 +33,7 @@
import java.net.Authenticator;
import java.net.CookieManager;
import java.net.NetPermission;
+import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.nio.channels.CancelledKeyException;
@@ -407,23 +408,32 @@
@Override
public <T> CompletableFuture<HttpResponse<T>>
- sendAsync(HttpRequest req, BodyHandler<T> responseHandler)
+ sendAsync(HttpRequest userRequest, BodyHandler<T> responseHandler)
{
AccessControlContext acc = null;
if (System.getSecurityManager() != null)
acc = AccessController.getContext();
+ // Clone the, possibly untrusted, HttpRequest
+ HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector, acc);
+ if (requestImpl.method().equals("CONNECT"))
+ throw new IllegalArgumentException("Unsupported method CONNECT");
+
long start = DEBUGELAPSED ? System.nanoTime() : 0;
reference();
try {
- debug.log(Level.DEBUG, "ClientImpl (async) send %s", req);
+ debug.log(Level.DEBUG, "ClientImpl (async) send %s", userRequest);
- MultiExchange<Void,T> mex = new MultiExchange<>(req, this, responseHandler, acc);
+ MultiExchange<Void,T> mex = new MultiExchange<>(userRequest,
+ requestImpl,
+ this,
+ responseHandler,
+ acc);
CompletableFuture<HttpResponse<T>> res =
mex.responseAsync().whenComplete((b,t) -> unreference());
if (DEBUGELAPSED) {
res = res.whenComplete(
- (b,t) -> debugCompleted("ClientImpl (async)", start, req));
+ (b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
}
// makes sure that any dependent actions happen in the executor
if (acc != null) {
@@ -434,29 +444,38 @@
return res;
} catch(Throwable t) {
unreference();
- debugCompleted("ClientImpl (async)", start, req);
+ debugCompleted("ClientImpl (async)", start, userRequest);
throw t;
}
}
@Override
public <U, T> CompletableFuture<U>
- sendAsync(HttpRequest req, MultiSubscriber<U, T> responseHandler) {
+ sendAsync(HttpRequest userRequest, MultiSubscriber<U, T> responseHandler) {
AccessControlContext acc = null;
if (System.getSecurityManager() != null)
acc = AccessController.getContext();
+ // Clone the, possibly untrusted, HttpRequest
+ HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector, acc);
+ if (requestImpl.method().equals("CONNECT"))
+ throw new IllegalArgumentException("Unsupported method CONNECT");
+
long start = DEBUGELAPSED ? System.nanoTime() : 0;
reference();
try {
- debug.log(Level.DEBUG, "ClientImpl (async) send multi %s", req);
+ debug.log(Level.DEBUG, "ClientImpl (async) send multi %s", userRequest);
- MultiExchange<U,T> mex = new MultiExchange<>(req, this, responseHandler, acc);
+ MultiExchange<U,T> mex = new MultiExchange<>(userRequest,
+ requestImpl,
+ this,
+ responseHandler,
+ acc);
CompletableFuture<U> res = mex.multiResponseAsync()
.whenComplete((b,t) -> unreference());
if (DEBUGELAPSED) {
res = res.whenComplete(
- (b,t) -> debugCompleted("ClientImpl (async)", start, req));
+ (b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
}
// makes sure that any dependent actions happen in the executor
if (acc != null) {
@@ -467,7 +486,7 @@
return res;
} catch(Throwable t) {
unreference();
- debugCompleted("ClientImpl (async)", start, req);
+ debugCompleted("ClientImpl (async)", start, userRequest);
throw t;
}
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java Sun Nov 05 21:19:55 2017 +0000
@@ -153,7 +153,7 @@
HttpRequestImpl request,
Version version) {
HttpConnection c = null;
- InetSocketAddress proxy = request.proxy(client);
+ InetSocketAddress proxy = request.proxy();
if (proxy != null && proxy.isUnresolved()) {
// The default proxy selector may select a proxy whose address is
// unresolved. We must resolve the address before connecting to it.
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java Sun Nov 05 21:19:55 2017 +0000
@@ -199,10 +199,10 @@
* default executor will execute asynchronous and dependent tasks in a context
* that is granted no permissions. Custom {@linkplain HttpRequest.BodyPublisher
* request body publishers}, {@linkplain HttpResponse.BodyHandler response body
- * handlers}, and {@linkplain HttpResponse.BodySubscriber response body
- * subscribers}, if executing operations that require privileges, should do so
- * within an appropriate {@linkplain AccessController#doPrivileged(PrivilegedAction)
- * privileged context}.
+ * handlers}, {@linkplain HttpResponse.BodySubscriber response body subscribers},
+ * and {@linkplain WebSocket.Listener WebSocket Listeners}, if executing
+ * operations that require privileges, should do so within an appropriate
+ * {@linkplain AccessController#doPrivileged(PrivilegedAction) privileged context}.
*
* <p> <b>Examples</b>
* <pre>{@code
@@ -437,8 +437,8 @@
* @apiNote The {@linkplain #noBody() noBody} request body publisher can
* be used where no request body is required or appropriate.
*
+ * @param method the method to use
* @param bodyPublisher the body publisher
- * @param method the method to use
* @return a {@code HttpRequest}
* @throws IllegalArgumentException if the method is unrecognised
*/
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -187,6 +187,8 @@
requireNonNull(method);
if (method.equals(""))
throw newIAE("illegal method <empty string>");
+ if (method.equals("CONNECT"))
+ throw newIAE("method CONNECT is not supported");
return method0(method, requireNonNull(body));
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -30,12 +30,14 @@
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.Duration;
+import java.util.List;
import java.util.Locale;
import java.util.Optional;
@@ -46,6 +48,7 @@
private final HttpHeaders userHeaders;
private final HttpHeadersImpl systemHeaders;
private final URI uri;
+ private Proxy proxy;
private InetSocketAddress authority; // only used when URI not specified
private final String method;
final BodyPublisher requestPublisher;
@@ -66,6 +69,7 @@
this.systemHeaders = new HttpHeadersImpl();
this.uri = builder.uri();
assert uri != null;
+ this.proxy = null;
this.expectContinue = builder.expectContinue();
this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
this.requestPublisher = builder.bodyPublisher(); // may be null
@@ -76,7 +80,7 @@
/**
* Creates an HttpRequestImpl from the given request.
*/
- public HttpRequestImpl(HttpRequest request, AccessControlContext acc) {
+ public HttpRequestImpl(HttpRequest request, ProxySelector ps, AccessControlContext acc) {
String method = request.method();
this.method = method == null ? "GET" : method;
this.userHeaders = request.headers();
@@ -87,6 +91,15 @@
this.systemHeaders = new HttpHeadersImpl();
}
this.uri = request.uri();
+ if (isWebSocket) {
+ // WebSocket determines and sets the proxy itself
+ this.proxy = ((HttpRequestImpl) request).proxy;
+ } else {
+ if (ps != null)
+ this.proxy = retrieveProxy(ps, uri);
+ else
+ this.proxy = null;
+ }
this.expectContinue = request.expectContinue();
this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
this.requestPublisher = request.bodyPublisher().orElse(null);
@@ -107,6 +120,7 @@
this.isWebSocket = other.isWebSocket;
this.systemHeaders = other.systemHeaders;
this.uri = uri;
+ this.proxy = other.proxy;
this.expectContinue = other.expectContinue;
this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
this.requestPublisher = other.requestPublisher; // may be null
@@ -125,6 +139,7 @@
this.userHeaders = ImmutableHeaders.empty();
this.uri = URI.create("socket://" + authority.getHostString() + ":"
+ Integer.toString(authority.getPort()) + "/");
+ this.proxy = null;
this.requestPublisher = null;
this.authority = authority;
this.secure = false;
@@ -166,7 +181,7 @@
StringBuilder sb = new StringBuilder();
sb.append(scheme).append("://").append(authority).append(path);
this.uri = URI.create(sb.toString());
-
+ this.proxy = null;
this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
this.systemHeaders = parent.systemHeaders;
this.expectContinue = parent.expectContinue;
@@ -198,17 +213,35 @@
@Override
public boolean expectContinue() { return expectContinue; }
- InetSocketAddress proxy(HttpClientImpl client) {
- ProxySelector ps = client.proxy().orElse(null);
- if (ps == null || method.equalsIgnoreCase("CONNECT")) {
+ /** Retrieves the proxy, from the given ProxySelector, if there is one. */
+ private static Proxy retrieveProxy(ProxySelector ps, URI uri) {
+ Proxy proxy = null;
+ List<Proxy> pl = ps.select(uri);
+ if (pl.size() > 0) {
+ Proxy p = pl.get(0);
+ if (p.type().equals(Proxy.Type.HTTP))
+ proxy = p;
+ }
+ return proxy;
+ }
+
+ InetSocketAddress proxy() {
+ if (proxy == null || !proxy.type().equals(Proxy.Type.HTTP)
+ || method.equalsIgnoreCase("CONNECT")) {
return null;
}
- return (InetSocketAddress)ps.select(uri).get(0).address();
+ return (InetSocketAddress)proxy.address();
}
boolean secure() { return secure; }
@Override
+ public void setProxy(Proxy proxy) {
+ assert isWebSocket;
+ this.proxy = proxy;
+ }
+
+ @Override
public void isWebSocket(boolean is) {
isWebSocket = is;
}
@@ -269,7 +302,7 @@
}
final String host = uri.getHost();
final int port = p;
- if (proxy(client) == null) {
+ if (proxy() == null) {
PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);
return AccessController.doPrivileged(pa);
} else {
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java Sun Nov 05 21:19:55 2017 +0000
@@ -803,7 +803,7 @@
* of {@link #completion(CompletableFuture, CompletableFuture)} can be used to determine
* when the final PUSH_PROMISE is received.
*
- * @param request the push promise
+ * @param pushPromise the push promise
*
* @return an optional body handler
*/
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java Sun Nov 05 21:19:55 2017 +0000
@@ -98,13 +98,14 @@
/**
* MultiExchange with one final response.
*/
- MultiExchange(HttpRequest req,
+ MultiExchange(HttpRequest userRequest,
+ HttpRequestImpl requestImpl,
HttpClientImpl client,
HttpResponse.BodyHandler<T> responseHandler,
AccessControlContext acc) {
this.previous = null;
- this.userRequest = req;
- this.request = new HttpRequestImpl(req, acc);
+ this.userRequest = userRequest;
+ this.request = requestImpl;
this.currentreq = request;
this.client = client;
this.filters = client.filterChain();
@@ -124,13 +125,14 @@
/**
* MultiExchange with multiple responses (HTTP/2 server pushes).
*/
- MultiExchange(HttpRequest req,
+ MultiExchange(HttpRequest userRequest,
+ HttpRequestImpl requestImpl,
HttpClientImpl client,
HttpResponse.MultiSubscriber<U, T> multiResponseSubscriber,
AccessControlContext acc) {
this.previous = null;
- this.userRequest = req;
- this.request = new HttpRequestImpl(req, acc);
+ this.userRequest = userRequest;
+ this.request = requestImpl;
this.currentreq = request;
this.client = client;
this.filters = client.filterChain();
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java Sun Nov 05 21:19:55 2017 +0000
@@ -64,7 +64,7 @@
HttpClientImpl client = client();
assert client != null;
HttpRequestImpl req = new HttpRequestImpl("CONNECT", address);
- MultiExchange<Void,Void> mulEx = new MultiExchange<>(req, client, discard(null), null);
+ MultiExchange<Void,Void> mulEx = new MultiExchange<>(null, req, client, discard(null), null);
Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
return connectExchange
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java Sun Nov 05 21:19:55 2017 +0000
@@ -192,8 +192,12 @@
* <li> {@link InterruptedException} -
* if the operation was interrupted
* <li> {@link SecurityException} -
- * if a security manager is set, and the caller does not
- * have a {@link java.net.URLPermission} for the WebSocket URI
+ * If a security manager has been installed and it denies
+ * {@link java.net.URLPermission access} to the WebSocket URI,
+ * that was used to create this builder.
+ * <a href="HttpRequest.html#securitychecks">Security checks</a>
+ * contains more information relating to the security context
+ * in which the the listener is invoked.
* <li> {@link IllegalArgumentException} -
* if any of the additional {@link #header(String, String)
* headers} are illegal;
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static jdk.incubator.http.internal.common.Pair.pair;
@@ -52,10 +53,28 @@
public BuilderImpl(HttpClient client, URI uri, Listener listener) {
this.client = requireNonNull(client, "client");
- this.uri = requireNonNull(uri, "uri");
+ this.uri = checkURI(requireNonNull(uri, "uri"));
this.listener = requireNonNull(listener, "listener");
}
+ private static IllegalArgumentException newIAE(String message, Object... args) {
+ return new IllegalArgumentException(format(message, args));
+ }
+
+ private static URI checkURI(URI uri) {
+ String scheme = uri.getScheme();
+ if (scheme == null)
+ throw newIAE("URI with undefined scheme");
+ scheme = scheme.toLowerCase();
+ if (!(scheme.equals("ws") || scheme.equals("wss")))
+ throw newIAE("invalid URI scheme %s", scheme);
+ if (uri.getHost() == null)
+ throw newIAE("URI must contain a host: %s", uri);
+ if (uri.getFragment() != null)
+ throw newIAE("URI must not contain a fragment: %s", uri);
+ return uri;
+ }
+
@Override
public Builder header(String name, String value) {
requireNonNull(name, "name");
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OpeningHandshake.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OpeningHandshake.java Sun Nov 05 21:19:55 2017 +0000
@@ -28,6 +28,7 @@
import jdk.incubator.http.internal.common.MinimalFuture;
import java.io.IOException;
+import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import jdk.incubator.http.HttpClient;
@@ -40,8 +41,10 @@
import jdk.incubator.http.internal.common.Pair;
import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Base64;
@@ -99,7 +102,7 @@
private final Collection<String> subprotocols;
private final String nonce;
- OpeningHandshake(BuilderImpl b) {
+ OpeningHandshake(BuilderImpl b, Proxy proxy) {
this.client = b.getClient();
URI httpURI = createRequestURI(b.getUri());
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(httpURI);
@@ -130,6 +133,7 @@
r.isWebSocket(true);
r.setSystemHeader(HEADER_UPGRADE, "websocket");
r.setSystemHeader(HEADER_CONNECTION, "Upgrade");
+ r.setProxy(proxy);
}
private static Collection<String> createRequestSubprotocols(
@@ -153,9 +157,7 @@
*
* https://tools.ietf.org/html/rfc6455#section-3
*/
- private static URI createRequestURI(URI uri) {
- // TODO: check permission for WebSocket URI and translate it into
- // http/https permission
+ static URI createRequestURI(URI uri) {
String s = uri.getScheme(); // The scheme might be null (i.e. undefined)
if (!("ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s))
|| uri.getFragment() != null)
@@ -178,8 +180,10 @@
}
CompletableFuture<Result> send() {
- return client.sendAsync(this.request, BodyHandler.<Void>discard(null))
- .thenCompose(this::resultFrom);
+ PrivilegedAction<CompletableFuture<Result>> pa = () ->
+ client.sendAsync(this.request, BodyHandler.<Void>discard(null))
+ .thenCompose(this::resultFrom);
+ return AccessController.doPrivileged(pa);
}
/*
@@ -247,7 +251,8 @@
String expected = Base64.getEncoder().encodeToString(this.sha1.digest());
String actual = requireSingle(headers, HEADER_ACCEPT);
if (!actual.trim().equals(expected)) {
- throw checkFailed("Bad " + HEADER_ACCEPT);
+ throw checkFailed("Bad " + HEADER_ACCEPT + ", expected:["
+ + expected + "] ,got:[" + actual.trim() + "]");
}
String subprotocol = checkAndReturnSubprotocol(headers);
RawChannel channel = ((RawChannel.Provider) response).rawChannel();
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java Sun Nov 05 21:19:55 2017 +0000
@@ -26,9 +26,17 @@
package jdk.incubator.http.internal.websocket;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.ProxySelector;
import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLPermission;
import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -36,11 +44,14 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
+
+import jdk.incubator.http.HttpClient;
import jdk.incubator.http.WebSocket;
import jdk.incubator.http.internal.common.Log;
import jdk.incubator.http.internal.common.Pair;
import jdk.incubator.http.internal.common.SequentialScheduler;
import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
+import jdk.incubator.http.internal.common.Utils;
import jdk.incubator.http.internal.websocket.OpeningHandshake.Result;
import jdk.incubator.http.internal.websocket.OutgoingMessage.Binary;
import jdk.incubator.http.internal.websocket.OutgoingMessage.Close;
@@ -51,8 +62,8 @@
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.CompletableFuture.failedFuture;
+import static java.util.stream.Collectors.joining;
import static jdk.incubator.http.internal.common.Pair.pair;
-import jdk.incubator.http.internal.common.Utils;
import static jdk.incubator.http.internal.websocket.StatusCodes.CLOSED_ABNORMALLY;
import static jdk.incubator.http.internal.websocket.StatusCodes.NO_STATUS_CODE;
import static jdk.incubator.http.internal.websocket.StatusCodes.isLegalToSendFromClient;
@@ -105,7 +116,87 @@
private final CompletableFuture<?> closeReceived = new CompletableFuture<>();
private final CompletableFuture<?> closeSent = new CompletableFuture<>();
+ /** Returns the security permission required for the given details. */
+ static URLPermission permissionForServer(URI uri,
+ Collection<Pair<String, String>> headers) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(uri.getScheme()).append("://")
+ .append(uri.getAuthority())
+ .append(uri.getPath());
+ String urlstring = sb.toString();
+
+ String actionstring = headers.stream()
+ .map(p -> p.first)
+ .distinct()
+ .collect(joining(","));
+ if (actionstring != null && !actionstring.equals(""))
+ actionstring = ":" + actionstring; // Note: no method in the action string
+
+ return new URLPermission(urlstring, actionstring);
+ }
+
+ /**
+ * Returns the security permissions required to connect to the proxy, or
+ * null if none is required or applicable.
+ */
+ static URLPermission permissionForProxy(Proxy proxy) {
+ InetSocketAddress proxyAddress = (InetSocketAddress)proxy.address();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("socket://")
+ .append(proxyAddress.getHostString()).append(":")
+ .append(proxyAddress.getPort());
+ String urlstring = sb.toString();
+ return new URLPermission(urlstring.toString(), "CONNECT");
+ }
+
+ /**
+ * Returns the proxy for the given URI when sent through the given client,
+ * or null if none is required or applicable.
+ */
+ static Proxy proxyFor(HttpClient client, URI uri) {
+ Optional<ProxySelector> optional = client.proxy();
+ if (!optional.isPresent())
+ return null;
+
+ uri = OpeningHandshake.createRequestURI(uri); // based on the HTTP scheme
+ List<Proxy> pl = optional.get().select(uri);
+ if (pl.size() < 1)
+ return null;
+
+ Proxy proxy = pl.get(0);
+ if (!proxy.type().equals(Proxy.Type.HTTP))
+ return null;
+
+ return proxy;
+ }
+
+ /**
+ * Performs the necessary security permissions checks to connect ( possibly
+ * through a proxy ) to the builders WebSocket URI.
+ *
+ * @throws SecurityException if the security manager denies access
+ */
+ static void checkPermissions(BuilderImpl b, Proxy proxy) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(permissionForServer(b.getUri(), b.getHeaders()));
+ if (proxy != null) {
+ URLPermission perm = permissionForProxy(proxy);
+ if (perm != null)
+ sm.checkPermission(perm);
+ }
+ }
+ }
+
static CompletableFuture<WebSocket> newInstanceAsync(BuilderImpl b) {
+ Proxy proxy = proxyFor(b.getClient(), b.getUri());
+ try {
+ checkPermissions(b, proxy);
+ } catch (Throwable throwable) {
+ return failedFuture(throwable);
+ }
+
Function<Result, WebSocket> newWebSocket = r -> {
WebSocketImpl ws = new WebSocketImpl(b.getUri(),
r.subprotocol,
@@ -119,7 +210,7 @@
};
OpeningHandshake h;
try {
- h = new OpeningHandshake(b);
+ h = new OpeningHandshake(b, proxy);
} catch (IllegalArgumentException e) {
return failedFuture(e);
}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketRequest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketRequest.java Sun Nov 05 21:19:55 2017 +0000
@@ -25,6 +25,8 @@
package jdk.incubator.http.internal.websocket;
+import java.net.Proxy;
+
/*
* https://tools.ietf.org/html/rfc6455#section-4.1
*/
@@ -41,4 +43,9 @@
* WebSocket specification.
*/
void setSystemHeader(String name, String value);
+
+ /*
+ * Sets the proxy for this request.
+ */
+ void setProxy(Proxy proxy);
}
--- a/test/jdk/java/net/httpclient/ProxyAuthTest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/ProxyAuthTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2017, 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
@@ -29,7 +29,6 @@
* @modules java.base/sun.net.www
* jdk.incubator.httpclient
* @summary Verify that Proxy-Authenticate header is correctly handled
- *
* @run main/othervm ProxyAuthTest
*/
@@ -42,14 +41,17 @@
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
+import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.URI;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;
import java.util.Base64;
+import java.util.List;
import sun.net.www.MessageHeader;
import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
@@ -68,8 +70,9 @@
InetSocketAddress paddr = new InetSocketAddress("localhost", port);
URI uri = new URI("http://www.google.ie/");
+ CountingProxySelector ps = CountingProxySelector.of(paddr);
HttpClient client = HttpClient.newBuilder()
- .proxy(ProxySelector.of(paddr))
+ .proxy(ps)
.authenticator(auth)
.build();
HttpRequest req = HttpRequest.newBuilder(uri).GET().build();
@@ -87,6 +90,9 @@
if (!proxy.matched) {
throw new RuntimeException("Proxy authentication failed");
}
+ if (ps.count() > 1) {
+ throw new RuntimeException("CountingProxySelector. Expected 1, got " + ps.count());
+ }
}
}
@@ -102,6 +108,37 @@
}
}
+ /**
+ * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
+ * of times its select method has been invoked. This can be used to ensure
+ * that the Proxy Selector is invoked only once per HttpClient.sendXXX
+ * invocation.
+ */
+ static class CountingProxySelector extends ProxySelector {
+ private final ProxySelector proxySelector;
+ private volatile int count; // 0
+ private CountingProxySelector(InetSocketAddress proxyAddress) {
+ proxySelector = ProxySelector.of(proxyAddress);
+ }
+
+ public static CountingProxySelector of(InetSocketAddress proxyAddress) {
+ return new CountingProxySelector(proxyAddress);
+ }
+
+ int count() { return count; }
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ count++;
+ return proxySelector.select(uri);
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ proxySelector.connectFailed(uri, sa, ioe);
+ }
+ }
+
static class MyProxy implements Runnable {
final ServerSocket ss;
private volatile boolean matched;
--- a/test/jdk/java/net/httpclient/ProxyServer.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/ProxyServer.java Sun Nov 05 21:19:55 2017 +0000
@@ -207,7 +207,7 @@
} else {
doProxy(params[1], buf, p, cmd);
}
- } catch (IOException e) {
+ } catch (Throwable e) {
if (debug) {
System.out.println (e);
}
--- a/test/jdk/java/net/httpclient/ProxyTest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/ProxyTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -41,10 +41,12 @@
import java.net.ProxySelector;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
+import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -121,6 +123,37 @@
}
}
+ /**
+ * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
+ * of times its select method has been invoked. This can be used to ensure
+ * that the Proxy Selector is invoked only once per HttpClient.sendXXX
+ * invocation.
+ */
+ static class CountingProxySelector extends ProxySelector {
+ private final ProxySelector proxySelector;
+ private volatile int count; // 0
+ private CountingProxySelector(InetSocketAddress proxyAddress) {
+ proxySelector = ProxySelector.of(proxyAddress);
+ }
+
+ public static CountingProxySelector of(InetSocketAddress proxyAddress) {
+ return new CountingProxySelector(proxyAddress);
+ }
+
+ int count() { return count; }
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ count++;
+ return proxySelector.select(uri);
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ proxySelector.connectFailed(uri, sa, ioe);
+ }
+ }
+
public static void test(HttpServer server, HttpClient.Version version)
throws IOException,
URISyntaxException,
@@ -158,7 +191,7 @@
System.out.println("\nReal test begins here.");
System.out.println("Setting up request with HttpClient for version: "
+ version.name());
- ProxySelector ps = ProxySelector.of(
+ CountingProxySelector ps = CountingProxySelector.of(
InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
HttpClient client = HttpClient.newBuilder()
.version(version)
@@ -178,6 +211,9 @@
if (!RESPONSE.equals(resp)) {
throw new AssertionError("Unexpected response");
}
+ if (ps.count() > 1) {
+ throw new AssertionError("CountingProxySelector. Expected 1, got " + ps.count());
+ }
} finally {
System.out.println("Stopping proxy");
proxy.stop();
--- a/test/jdk/java/net/httpclient/RequestBuilderTest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/RequestBuilderTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -37,6 +37,7 @@
import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
import static jdk.incubator.http.HttpClient.Version.HTTP_2;
import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
+import static jdk.incubator.http.HttpRequest.noBody;
import static jdk.incubator.http.HttpRequest.newBuilder;
import static org.testng.Assert.*;
import org.testng.annotations.Test;
@@ -166,6 +167,18 @@
request = newBuilder(uri).GET().DELETE(fromString("")).build();
assertEquals(request.method(), "DELETE");
assertTrue(request.bodyPublisher().isPresent());
+
+ // CONNECT is disallowed in the implementation, since it is used for
+ // tunneling, and is handled separately for security checks.
+ assertThrows(IAE, () -> newBuilder(uri).method("CONNECT", noBody()).build());
+
+ request = newBuilder(uri).method("GET", noBody()).build();
+ assertEquals(request.method(), "GET");
+ assertTrue(request.bodyPublisher().isPresent());
+
+ request = newBuilder(uri).method("POST", fromString("")).build();
+ assertEquals(request.method(), "POST");
+ assertTrue(request.bodyPublisher().isPresent());
}
@Test
--- a/test/jdk/java/net/httpclient/SmokeTest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/SmokeTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -43,6 +43,8 @@
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
+import java.net.Proxy;
+import java.net.SocketAddress;
import java.util.concurrent.atomic.AtomicInteger;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
@@ -371,6 +373,37 @@
System.out.println(" OK");
}
+ /**
+ * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
+ * of times its select method has been invoked. This can be used to ensure
+ * that the Proxy Selector is invoked only once per HttpClient.sendXXX
+ * invocation.
+ */
+ static class CountingProxySelector extends ProxySelector {
+ private final ProxySelector proxySelector;
+ private volatile int count; // 0
+ private CountingProxySelector(InetSocketAddress proxyAddress) {
+ proxySelector = ProxySelector.of(proxyAddress);
+ }
+
+ public static CountingProxySelector of(InetSocketAddress proxyAddress) {
+ return new CountingProxySelector(proxyAddress);
+ }
+
+ int count() { return count; }
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ count++;
+ return proxySelector.select(uri);
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ proxySelector.connectFailed(uri, sa, ioe);
+ }
+ }
+
// Proxies
static void test4(String s) throws Exception {
System.out.print("test4: " + s);
@@ -380,9 +413,10 @@
ExecutorService e = Executors.newCachedThreadPool();
+ CountingProxySelector ps = CountingProxySelector.of(proxyAddr);
HttpClient cl = HttpClient.newBuilder()
.executor(e)
- .proxy(ProxySelector.of(proxyAddr))
+ .proxy(ps)
.sslContext(ctx)
.sslParameters(sslparams)
.build();
@@ -400,6 +434,9 @@
throw new RuntimeException(
"Body mismatch: expected [" + body + "], got [" + fc + "]");
}
+ if (ps.count() > 1) {
+ throw new RuntimeException("CountingProxySelector. Expected 1, got " + ps.count());
+ }
e.shutdownNow();
System.out.println(" OK");
}
--- a/test/jdk/java/net/httpclient/security/Driver.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/security/Driver.java Sun Nov 05 21:19:55 2017 +0000
@@ -52,8 +52,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
-
-import jdk.testlibrary.OutputAnalyzer;
import jdk.testlibrary.Utils;
/**
@@ -123,6 +121,8 @@
while (retval == 10) {
List<String> cmd = new ArrayList<>();
cmd.add(javaCmd);
+ cmd.add("-ea");
+ cmd.add("-esa");
cmd.add("-Dtest.jdk=" + testJdk);
cmd.add("-Dtest.src=" + testSrc);
cmd.add("-Dtest.classes=" + testClasses);
--- a/test/jdk/java/net/httpclient/security/Security.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/security/Security.java Sun Nov 05 21:19:55 2017 +0000
@@ -381,7 +381,7 @@
r.execute();
if (!succeeds) {
System.out.println("FAILED: expected security exception");
- throw new RuntimeException("Failed");
+ throw new RuntimeException("FAILED: expected security exception\"");
}
System.out.println (policy + " succeeded as expected");
} catch (BindException e) {
--- a/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java Sun Nov 05 21:19:55 2017 +0000
@@ -31,14 +31,10 @@
* @test
* @bug 8164625
* @summary Verifies HttpClient yields the connection to the WebSocket
+ * @build DummyWebSocketServer
* @run main/othervm -Djdk.httpclient.HttpClient.log=trace ConnectionHandover
*/
public class ConnectionHandover {
-
- static {
- LoggingHelper.setupLogging();
- }
-
/*
* An I/O channel associated with the connection is closed by WebSocket.abort().
* If this connection is returned to the connection pool, then the second
--- a/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java Sun Nov 05 21:19:55 2017 +0000
@@ -34,6 +34,7 @@
import java.nio.charset.CharacterCodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
@@ -47,9 +48,7 @@
import java.util.stream.Collectors;
import static java.lang.String.format;
-import static java.lang.System.Logger.Level.ERROR;
-import static java.lang.System.Logger.Level.INFO;
-import static java.lang.System.Logger.Level.TRACE;
+import static java.lang.System.err;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
@@ -83,7 +82,6 @@
*/
public final class DummyWebSocketServer implements Closeable {
- private final static System.Logger log = System.getLogger(DummyWebSocketServer.class.getName());
private final AtomicBoolean started = new AtomicBoolean();
private final Thread thread;
private volatile ServerSocketChannel ssc;
@@ -98,9 +96,9 @@
thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
- log.log(INFO, "Accepting next connection at: " + ssc);
+ err.println("Accepting next connection at: " + ssc);
SocketChannel channel = ssc.accept();
- log.log(INFO, "Accepted: " + channel);
+ err.println("Accepted: " + channel);
try {
channel.configureBlocking(true);
StringBuilder request = new StringBuilder();
@@ -117,18 +115,18 @@
b.clear();
}
} catch (IOException e) {
- log.log(TRACE, () -> "Error in connection: " + channel, e);
+ err.println("Error in connection: " + channel + ", " + e);
} finally {
- log.log(INFO, "Closed: " + channel);
+ err.println("Closed: " + channel);
close(channel);
}
}
} catch (ClosedByInterruptException ignored) {
} catch (IOException e) {
- log.log(ERROR, e);
+ err.println(e);
} finally {
close(ssc);
- log.log(INFO, "Stopped at: " + getURI());
+ err.println("Stopped at: " + getURI());
}
});
thread.setName("DummyWebSocketServer");
@@ -136,7 +134,7 @@
}
public void open() throws IOException {
- log.log(INFO, "Starting");
+ err.println("Starting");
if (!started.compareAndSet(false, true)) {
throw new IllegalStateException("Already started");
}
@@ -149,12 +147,12 @@
} catch (IOException e) {
close(ssc);
}
- log.log(INFO, "Started at: " + getURI());
+ err.println("Started at: " + getURI());
}
@Override
public void close() {
- log.log(INFO, "Stopping: " + getURI());
+ err.println("Stopping: " + getURI());
thread.interrupt();
close(ssc);
}
@@ -210,12 +208,13 @@
if (!iterator.hasNext()) {
throw new IllegalStateException("The request is empty");
}
- if (!"GET / HTTP/1.1".equals(iterator.next())) {
+ String statusLine = iterator.next();
+ if (!(statusLine.startsWith("GET /") && statusLine.endsWith(" HTTP/1.1"))) {
throw new IllegalStateException
("Unexpected status line: " + request.get(0));
}
response.add("HTTP/1.1 101 Switching Protocols");
- Map<String, String> requestHeaders = new HashMap<>();
+ Map<String, List<String>> requestHeaders = new HashMap<>();
while (iterator.hasNext()) {
String header = iterator.next();
String[] split = header.split(": ");
@@ -224,10 +223,8 @@
("Unexpected header: " + header
+ ", split=" + Arrays.toString(split));
}
- if (requestHeaders.put(split[0], split[1]) != null) {
- throw new IllegalStateException
- ("Duplicating headers: " + Arrays.toString(split));
- }
+ requestHeaders.computeIfAbsent(split[0], k -> new ArrayList<>()).add(split[1]);
+
}
if (requestHeaders.containsKey("Sec-WebSocket-Protocol")) {
throw new IllegalStateException("Subprotocols are not expected");
@@ -240,17 +237,20 @@
expectHeader(requestHeaders, "Upgrade", "websocket");
response.add("Upgrade: websocket");
expectHeader(requestHeaders, "Sec-WebSocket-Version", "13");
- String key = requestHeaders.get("Sec-WebSocket-Key");
- if (key == null) {
+ List<String> key = requestHeaders.get("Sec-WebSocket-Key");
+ if (key == null || key.isEmpty()) {
throw new IllegalStateException("Sec-WebSocket-Key is missing");
}
+ if (key.size() != 1) {
+ throw new IllegalStateException("Sec-WebSocket-Key has too many values : " + key);
+ }
MessageDigest sha1 = null;
try {
sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new InternalError(e);
}
- String x = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ String x = key.get(0) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
sha1.update(x.getBytes(ISO_8859_1));
String v = Base64.getEncoder().encodeToString(sha1.digest());
response.add("Sec-WebSocket-Accept: " + v);
@@ -258,17 +258,17 @@
};
}
- protected static String expectHeader(Map<String, String> headers,
+ protected static String expectHeader(Map<String, List<String>> headers,
String name,
String value) {
- String v = headers.get(name);
- if (!value.equals(v)) {
+ List<String> v = headers.get(name);
+ if (!v.contains(value)) {
throw new IllegalStateException(
format("Expected '%s: %s', actual: '%s: %s'",
name, value, name, v)
);
}
- return v;
+ return value;
}
private static void close(AutoCloseable... acs) {
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java Sun Nov 05 17:32:13 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2017, 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
@@ -89,9 +89,9 @@
@Test(dataProvider = "badURIs")
void illegalURI(String u) {
- WebSocket.Builder b = HttpClient.newHttpClient()
- .newWebSocketBuilder(URI.create(u), listener());
- assertCompletesExceptionally(IllegalArgumentException.class, b.buildAsync());
+ assertThrows(IllegalArgumentException.class,
+ () -> HttpClient.newHttpClient()
+ .newWebSocketBuilder(URI.create(u), listener()));
}
@Test
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java Sun Nov 05 21:19:55 2017 +0000
@@ -0,0 +1,579 @@
+/*
+ * Copyright (c) 2017, 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
+ * @summary Basic security checks for WebSocket URI from the Builder
+ * @compile ../DummyWebSocketServer.java ../../ProxyServer.java
+ * @run testng/othervm/java.security.policy=httpclient.policy WSURLPermissionTest
+ */
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URLPermission;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.Permissions;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.WebSocket;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+public class WSURLPermissionTest {
+
+ static AccessControlContext withPermissions(Permission... perms) {
+ Permissions p = new Permissions();
+ for (Permission perm : perms) {
+ p.add(perm);
+ }
+ ProtectionDomain pd = new ProtectionDomain(null, p);
+ return new AccessControlContext(new ProtectionDomain[]{ pd });
+ }
+
+ static AccessControlContext noPermissions() {
+ return withPermissions(/*empty*/);
+ }
+
+ URI wsURI;
+ DummyWebSocketServer webSocketServer;
+ InetSocketAddress proxyAddress;
+
+ @BeforeTest
+ public void setup() throws Exception {
+ ProxyServer proxyServer = new ProxyServer(0, true);
+ proxyAddress = new InetSocketAddress("127.0.0.1", proxyServer.getPort());
+ webSocketServer = new DummyWebSocketServer();
+ webSocketServer.open();
+ wsURI = webSocketServer.getURI();
+
+ System.out.println("Proxy Server: " + proxyAddress);
+ System.out.println("DummyWebSocketServer: " + wsURI);
+ }
+
+ @AfterTest
+ public void teardown() {
+ webSocketServer.close();
+ }
+
+ static class NoOpListener implements WebSocket.Listener {}
+ static final WebSocket.Listener noOpListener = new NoOpListener();
+
+ @DataProvider(name = "passingScenarios")
+ public Object[][] passingScenarios() {
+ HttpClient noProxyClient = HttpClient.newHttpClient();
+ return new Object[][]{
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // no actions
+ new URLPermission[] { new URLPermission(wsURI.toString()) },
+ "0" /* for log file identification */ },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // scheme wildcard
+ new URLPermission[] { new URLPermission("ws://*") },
+ "0.1" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // port wildcard
+ new URLPermission[] { new URLPermission("ws://"+wsURI.getHost()+":*") },
+ "0.2" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // empty actions
+ new URLPermission[] { new URLPermission(wsURI.toString(), "") },
+ "1" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // colon
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":") },
+ "2" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // wildcard
+ new URLPermission[] { new URLPermission(wsURI.toString(), "*:*") },
+ "3" },
+
+ // WS permission checking is agnostic of method, any/none will do
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // specific method
+ new URLPermission[] { new URLPermission(wsURI.toString(), "GET") },
+ "3.1" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // specific method
+ new URLPermission[] { new URLPermission(wsURI.toString(), "POST") },
+ "3.2" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // path
+ new URLPermission[] { new URLPermission(wsURI.resolve("/path/x").toString()) },
+ "4" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // same dir wildcard
+ new URLPermission[] { new URLPermission(wsURI.resolve("/path/*").toString()) },
+ "5" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // recursive
+ new URLPermission[] { new URLPermission(wsURI.resolve("/path/-").toString()) },
+ "6" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // recursive top
+ new URLPermission[] { new URLPermission(wsURI.resolve("/-").toString()) },
+ "7" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // header
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header") },
+ "8" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // header
+ .buildAsync().get().abort();
+ return null; }, // wildcard
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
+ "9" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // headers
+ .header("B-Header", "B-Value") // headers
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header,B-Header") },
+ "10" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // headers
+ .header("B-Header", "B-Value") // headers
+ .buildAsync().get().abort();
+ return null; }, // wildcard
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
+ "11" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // headers
+ .header("B-Header", "B-Value") // headers
+ .buildAsync().get().abort();
+ return null; }, // wildcards
+ new URLPermission[] { new URLPermission(wsURI.toString(), "*:*") },
+ "12" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // multi-value
+ .header("A-Header", "B-Value") // headers
+ .buildAsync().get().abort();
+ return null; }, // wildcard
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
+ "13" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value") // multi-value
+ .header("A-Header", "B-Value") // headers
+ .buildAsync().get().abort();
+ return null; }, // single grant
+ new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header") },
+ "14" },
+
+ // client with a DIRECT proxy
+ { (PrivilegedExceptionAction<?>)() -> {
+ ProxySelector ps = ProxySelector.of(null);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] { new URLPermission(wsURI.toString()) },
+ "15" },
+
+ // client with a SOCKS proxy! ( expect implementation to ignore SOCKS )
+ { (PrivilegedExceptionAction<?>)() -> {
+ ProxySelector ps = new ProxySelector() {
+ @Override public List<Proxy> select(URI uri) {
+ return List.of(new Proxy(Proxy.Type.SOCKS, proxyAddress)); }
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { }
+ };
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] { new URLPermission(wsURI.toString()) },
+ "16" },
+
+ // client with a HTTP/HTTPS proxy
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()), // CONNECT action string
+ new URLPermission("socket://"+proxyAddress.getHostName()
+ +":"+proxyAddress.getPort(), "CONNECT")},
+ "17" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()), // no action string
+ new URLPermission("socket://"+proxyAddress.getHostName()
+ +":"+proxyAddress.getPort())},
+ "18" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()), // wildcard headers
+ new URLPermission("socket://"+proxyAddress.getHostName()
+ +":"+proxyAddress.getPort(), "CONNECT:*")},
+ "19" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ CountingProxySelector ps = CountingProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ assertEquals(ps.count(), 1); // ps.select only invoked once
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()), // empty headers
+ new URLPermission("socket://"+proxyAddress.getHostName()
+ +":"+proxyAddress.getPort(), "CONNECT:")},
+ "20" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()),
+ new URLPermission("socket://*")}, // wildcard socket URL
+ "21" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission("ws://*"), // wildcard ws URL
+ new URLPermission("socket://*")}, // wildcard socket URL
+ "22" },
+
+ };
+ }
+
+ @Test(dataProvider = "passingScenarios")
+ public void testWithNoSecurityManager(PrivilegedExceptionAction<?> action,
+ URLPermission[] unused,
+ String dataProviderId)
+ throws Exception
+ {
+ // sanity ( no security manager )
+ System.setSecurityManager(null);
+ try {
+ AccessController.doPrivileged(action);
+ } finally {
+ System.setSecurityManager(new SecurityManager());
+ }
+ }
+
+ @Test(dataProvider = "passingScenarios")
+ public void testWithAllPermissions(PrivilegedExceptionAction<?> action,
+ URLPermission[] unused,
+ String dataProviderId)
+ throws Exception
+ {
+ // Run with all permissions, i.e. no further restrictions than test's AllPermission
+ assert System.getSecurityManager() != null;
+ AccessController.doPrivileged(action);
+ }
+
+ @Test(dataProvider = "passingScenarios")
+ public void testWithMinimalPermissions(PrivilegedExceptionAction<?> action,
+ URLPermission[] perms,
+ String dataProviderId)
+ throws Exception
+ {
+ // Run with minimal permissions, i.e. just what is required
+ assert System.getSecurityManager() != null;
+ AccessControlContext minimalACC = withPermissions(perms);
+ AccessController.doPrivileged(action, minimalACC);
+ }
+
+ @Test(dataProvider = "passingScenarios")
+ public void testWithNoPermissions(PrivilegedExceptionAction<?> action,
+ URLPermission[] unused,
+ String dataProviderId)
+ throws Exception
+ {
+ // Run with NO permissions, i.e. expect SecurityException
+ assert System.getSecurityManager() != null;
+ try {
+ AccessController.doPrivileged(action, noPermissions());
+ fail("EXPECTED SecurityException");
+ } catch (PrivilegedActionException expected) {
+ Throwable t = expected.getCause();
+ if (t instanceof ExecutionException)
+ t = t.getCause();
+
+ if (t instanceof SecurityException)
+ System.out.println("Caught expected SE:" + expected);
+ else
+ fail("Expected SecurityException, but got: " + t);
+ }
+ }
+
+ // --- Negative tests ---
+
+ @DataProvider(name = "failingScenarios")
+ public Object[][] failingScenarios() {
+ HttpClient noProxyClient = HttpClient.newHttpClient();
+ return new Object[][]{
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null;
+ },
+ new URLPermission[]{ /* no permissions */ },
+ "50" /* for log file identification */},
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null;
+ }, // wrong scheme
+ new URLPermission[]{ new URLPermission("http://*") },
+ "51" },
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null;
+ }, // wrong scheme
+ new URLPermission[]{ new URLPermission("socket://*") },
+ "52" },
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null;
+ }, // wrong host
+ new URLPermission[]{ new URLPermission("ws://foo.com/") },
+ "53" },
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null;
+ }, // wrong port
+ new URLPermission[]{ new URLPermission("ws://"+ wsURI.getHost()+":5") },
+ "54" },
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value")
+ .buildAsync().get().abort();
+ return null;
+ }, // only perm to set B not A
+ new URLPermission[] { new URLPermission(wsURI.toString(), "*:B-Header") },
+ "55" },
+
+ { (PrivilegedExceptionAction<?>) () -> {
+ noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+ .header("A-Header", "A-Value")
+ .header("B-Header", "B-Value")
+ .buildAsync().get().abort();
+ return null;
+ }, // only perm to set B not A
+ new URLPermission[] { new URLPermission(wsURI.toString(), "*:B-Header") },
+ "56" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // wrong path
+ new URLPermission[] { new URLPermission(wsURI.resolve("/aDiffPath/").toString()) },
+ "57" },
+
+ { (PrivilegedExceptionAction<?>)() -> {
+ URI uriWithPath = wsURI.resolve("/path/x");
+ noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // more specific path
+ new URLPermission[] { new URLPermission(wsURI.resolve("/path/x/y").toString()) },
+ "58" },
+
+ // client with a HTTP/HTTPS proxy
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; }, // missing proxy perm
+ new URLPermission[] { new URLPermission(wsURI.toString()) },
+ "100" },
+
+ // client with a HTTP/HTTPS proxy
+ { (PrivilegedExceptionAction<?>)() -> {
+ assert proxyAddress != null;
+ ProxySelector ps = ProxySelector.of(proxyAddress);
+ HttpClient client = HttpClient.newBuilder().proxy(ps).build();
+ client.newWebSocketBuilder(wsURI, noOpListener)
+ .buildAsync().get().abort();
+ return null; },
+ new URLPermission[] {
+ new URLPermission(wsURI.toString()), // missing proxy CONNECT
+ new URLPermission("socket://*", "GET") },
+ "101" },
+ };
+ }
+
+ @Test(dataProvider = "failingScenarios")
+ public void testWithoutEnoughPermissions(PrivilegedExceptionAction<?> action,
+ URLPermission[] perms,
+ String dataProviderId)
+ throws Exception
+ {
+ // Run without Enough permissions, i.e. expect SecurityException
+ assert System.getSecurityManager() != null;
+ AccessControlContext notEnoughPermsACC = withPermissions(perms);
+ try {
+ AccessController.doPrivileged(action, notEnoughPermsACC);
+ fail("EXPECTED SecurityException");
+ } catch (PrivilegedActionException expected) {
+ Throwable t = expected.getCause();
+ if (t instanceof ExecutionException)
+ t = t.getCause();
+
+ if (t instanceof SecurityException)
+ System.out.println("Caught expected SE:" + expected);
+ else
+ fail("Expected SecurityException, but got: " + t);
+ }
+ }
+
+ /**
+ * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
+ * of times its select method has been invoked. This can be used to ensure
+ * that the Proxy Selector is invoked only once per WebSocket.Builder::buildAsync
+ * invocation.
+ */
+ static class CountingProxySelector extends ProxySelector {
+ private final ProxySelector proxySelector;
+ private volatile int count; // 0
+ private CountingProxySelector(InetSocketAddress proxyAddress) {
+ proxySelector = ProxySelector.of(proxyAddress);
+ }
+
+ public static CountingProxySelector of(InetSocketAddress proxyAddress) {
+ return new CountingProxySelector(proxyAddress);
+ }
+
+ int count() { return count; }
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ System.out.println("PS: uri");
+ Throwable t = new Throwable();
+ t.printStackTrace(System.out);
+ count++;
+ return proxySelector.select(uri);
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ proxySelector.connectFailed(uri, sa, ioe);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/security/httpclient.policy Sun Nov 05 21:19:55 2017 +0000
@@ -0,0 +1,46 @@
+grant codeBase "jrt:/jdk.incubator.httpclient" {
+ permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
+ permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
+ permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
+ permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
+
+ // ## why is SP not good enough. Check API @throws signatures and impl
+ permission java.net.SocketPermission "*","connect,resolve";
+ permission java.net.URLPermission "http:*","*:*";
+ permission java.net.URLPermission "https:*","*:*";
+ permission java.net.URLPermission "ws:*","*:*";
+ permission java.net.URLPermission "wss:*","*:*";
+ permission java.net.URLPermission "socket:*","CONNECT"; // proxy
+
+ // For request/response body processors, fromFile, asFile
+ permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
+
+ // ## look at the different property names!
+ permission java.util.PropertyPermission "jdk.httpclient.HttpClient.log","read"; // name!
+ permission java.util.PropertyPermission "jdk.httpclient.auth.retrylimit","read";
+ permission java.util.PropertyPermission "jdk.httpclient.connectionWindowSize","read";
+ permission java.util.PropertyPermission "jdk.httpclient.enablepush","read";
+ permission java.util.PropertyPermission "jdk.httpclient.hpack.maxheadertablesize","read";
+ permission java.util.PropertyPermission "jdk.httpclient.keepalive.timeout","read";
+ permission java.util.PropertyPermission "jdk.httpclient.maxframesize","read";
+ permission java.util.PropertyPermission "jdk.httpclient.maxstreams","read";
+ permission java.util.PropertyPermission "jdk.httpclient.redirects.retrylimit","read";
+ permission java.util.PropertyPermission "jdk.httpclient.windowsize","read";
+ permission java.util.PropertyPermission "jdk.httpclient.bufsize","read";
+ permission java.util.PropertyPermission "jdk.httpclient.internal.selector.timeout","read";
+ permission java.util.PropertyPermission "jdk.internal.httpclient.debug","read";
+ permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.debug","read";
+ permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.log.level","read";
+
+ // ## these permissions do not appear in the NetPermission spec!!! JDK bug?
+ permission java.net.NetPermission "getSSLContext";
+ permission java.net.NetPermission "setSSLContext";
+
+ permission java.security.SecurityPermission "createAccessControlContext";
+};
+
+// bootstrap to get the test going, it will do its own restrictions
+grant codeBase "file:${test.classes}/*" {
+ permission java.security.AllPermission;
+};
+