< prev index next >
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OpeningHandshake.java
Print this page
*** 26,49 ****
--- 26,56 ----
package jdk.incubator.http.internal.websocket;
import jdk.incubator.http.internal.common.MinimalFuture;
import java.io.IOException;
+ import java.net.InetSocketAddress;
+ import java.net.Proxy;
+ import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpClient.Version;
import jdk.incubator.http.HttpHeaders;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;
import jdk.incubator.http.HttpResponse.BodyHandler;
import jdk.incubator.http.WebSocketHandshakeException;
import jdk.incubator.http.internal.common.Pair;
+ import jdk.incubator.http.internal.common.Utils;
+ import java.net.URLPermission;
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;
import java.util.Collection;
import java.util.Collections;
*** 52,88 ****
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static jdk.incubator.http.internal.common.Utils.isValidName;
import static jdk.incubator.http.internal.common.Utils.stringOf;
! final class OpeningHandshake {
private static final String HEADER_CONNECTION = "Connection";
private static final String HEADER_UPGRADE = "Upgrade";
private static final String HEADER_ACCEPT = "Sec-WebSocket-Accept";
private static final String HEADER_EXTENSIONS = "Sec-WebSocket-Extensions";
private static final String HEADER_KEY = "Sec-WebSocket-Key";
private static final String HEADER_PROTOCOL = "Sec-WebSocket-Protocol";
private static final String HEADER_VERSION = "Sec-WebSocket-Version";
! private static final Set<String> FORBIDDEN_HEADERS;
static {
! FORBIDDEN_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
! FORBIDDEN_HEADERS.addAll(List.of(HEADER_ACCEPT,
HEADER_EXTENSIONS,
HEADER_KEY,
HEADER_PROTOCOL,
HEADER_VERSION));
}
! private static final SecureRandom srandom = new SecureRandom();
private final MessageDigest sha1;
private final HttpClient client;
{
--- 59,97 ----
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
+ import java.util.stream.Stream;
import static java.lang.String.format;
import static jdk.incubator.http.internal.common.Utils.isValidName;
+ import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
import static jdk.incubator.http.internal.common.Utils.stringOf;
! public class OpeningHandshake {
private static final String HEADER_CONNECTION = "Connection";
private static final String HEADER_UPGRADE = "Upgrade";
private static final String HEADER_ACCEPT = "Sec-WebSocket-Accept";
private static final String HEADER_EXTENSIONS = "Sec-WebSocket-Extensions";
private static final String HEADER_KEY = "Sec-WebSocket-Key";
private static final String HEADER_PROTOCOL = "Sec-WebSocket-Protocol";
private static final String HEADER_VERSION = "Sec-WebSocket-Version";
! private static final Set<String> ILLEGAL_HEADERS;
static {
! ILLEGAL_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
! ILLEGAL_HEADERS.addAll(List.of(HEADER_ACCEPT,
HEADER_EXTENSIONS,
HEADER_KEY,
HEADER_PROTOCOL,
HEADER_VERSION));
}
! private static final SecureRandom random = new SecureRandom();
private final MessageDigest sha1;
private final HttpClient client;
{
*** 97,116 ****
private final HttpRequest request;
private final Collection<String> subprotocols;
private final String nonce;
! OpeningHandshake(BuilderImpl b) {
this.client = b.getClient();
URI httpURI = createRequestURI(b.getUri());
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(httpURI);
Duration connectTimeout = b.getConnectTimeout();
if (connectTimeout != null) {
requestBuilder.timeout(connectTimeout);
}
for (Pair<String, String> p : b.getHeaders()) {
! if (FORBIDDEN_HEADERS.contains(p.first)) {
throw illegal("Illegal header: " + p.first);
}
requestBuilder.header(p.first, p.second);
}
this.subprotocols = createRequestSubprotocols(b.getSubprotocols());
--- 106,128 ----
private final HttpRequest request;
private final Collection<String> subprotocols;
private final String nonce;
! public OpeningHandshake(BuilderImpl b) {
! checkURI(b.getUri());
! Proxy proxy = proxyFor(b.getProxySelector(), b.getUri());
! checkPermissions(b, proxy);
this.client = b.getClient();
URI httpURI = createRequestURI(b.getUri());
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(httpURI);
Duration connectTimeout = b.getConnectTimeout();
if (connectTimeout != null) {
requestBuilder.timeout(connectTimeout);
}
for (Pair<String, String> p : b.getHeaders()) {
! if (ILLEGAL_HEADERS.contains(p.first)) {
throw illegal("Illegal header: " + p.first);
}
requestBuilder.header(p.first, p.second);
}
this.subprotocols = createRequestSubprotocols(b.getSubprotocols());
*** 128,137 ****
--- 140,150 ----
this.request = requestBuilder.version(Version.HTTP_1_1).GET().build();
WebSocketRequest r = (WebSocketRequest) this.request;
r.isWebSocket(true);
r.setSystemHeader(HEADER_UPGRADE, "websocket");
r.setSystemHeader(HEADER_CONNECTION, "Upgrade");
+ r.setProxy(proxy);
}
private static Collection<String> createRequestSubprotocols(
Collection<String> subprotocols)
{
*** 151,169 ****
* Checks the given URI for being a WebSocket URI and translates it into a
* target HTTP URI for the Opening Handshake.
*
* 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
! String s = uri.getScheme(); // The scheme might be null (i.e. undefined)
! if (!("ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s))
! || uri.getFragment() != null)
! {
! throw illegal("Bad URI: " + uri);
! }
String scheme = "ws".equalsIgnoreCase(s) ? "http" : "https";
try {
return new URI(scheme,
uri.getUserInfo(),
uri.getHost(),
--- 164,176 ----
* Checks the given URI for being a WebSocket URI and translates it into a
* target HTTP URI for the Opening Handshake.
*
* https://tools.ietf.org/html/rfc6455#section-3
*/
! static URI createRequestURI(URI uri) {
! String s = uri.getScheme();
! assert "ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s);
String scheme = "ws".equalsIgnoreCase(s) ? "http" : "https";
try {
return new URI(scheme,
uri.getUserInfo(),
uri.getHost(),
*** 175,200 ****
// Shouldn't happen: URI invariant
throw new InternalError(e);
}
}
! CompletableFuture<Result> send() {
! return client.sendAsync(this.request, BodyHandler.<Void>discard(null))
.thenCompose(this::resultFrom);
}
/*
* The result of the opening handshake.
*/
static final class Result {
final String subprotocol;
! final RawChannel channel;
! private Result(String subprotocol, RawChannel channel) {
this.subprotocol = subprotocol;
! this.channel = channel;
}
}
private CompletableFuture<Result> resultFrom(HttpResponse<?> response) {
// Do we need a special treatment for SSLHandshakeException?
--- 182,209 ----
// Shouldn't happen: URI invariant
throw new InternalError(e);
}
}
! public CompletableFuture<Result> send() {
! PrivilegedAction<CompletableFuture<Result>> pa = () ->
! client.sendAsync(this.request, BodyHandler.<Void>discard(null))
.thenCompose(this::resultFrom);
+ return AccessController.doPrivileged(pa);
}
/*
* The result of the opening handshake.
*/
static final class Result {
final String subprotocol;
! final TransportSupplier transport;
! private Result(String subprotocol, TransportSupplier transport) {
this.subprotocol = subprotocol;
! this.transport = transport;
}
}
private CompletableFuture<Result> resultFrom(HttpResponse<?> response) {
// Do we need a special treatment for SSLHandshakeException?
*** 249,259 ****
if (!actual.trim().equals(expected)) {
throw checkFailed("Bad " + HEADER_ACCEPT);
}
String subprotocol = checkAndReturnSubprotocol(headers);
RawChannel channel = ((RawChannel.Provider) response).rawChannel();
! return new Result(subprotocol, channel);
}
private String checkAndReturnSubprotocol(HttpHeaders responseHeaders)
throws CheckFailedException
{
--- 258,268 ----
if (!actual.trim().equals(expected)) {
throw checkFailed("Bad " + HEADER_ACCEPT);
}
String subprotocol = checkAndReturnSubprotocol(headers);
RawChannel channel = ((RawChannel.Provider) response).rawChannel();
! return new Result(subprotocol, new TransportSupplier(channel));
}
private String checkAndReturnSubprotocol(HttpHeaders responseHeaders)
throws CheckFailedException
{
*** 298,314 ****
return values.get(0);
}
private static String createNonce() {
byte[] bytes = new byte[16];
! OpeningHandshake.srandom.nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
private static IllegalArgumentException illegal(String message) {
return new IllegalArgumentException(message);
}
! private static CheckFailedException checkFailed(String message) {
! throw new CheckFailedException(message);
}
}
--- 307,377 ----
return values.get(0);
}
private static String createNonce() {
byte[] bytes = new byte[16];
! OpeningHandshake.random.nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
+ private static CheckFailedException checkFailed(String message) {
+ throw new CheckFailedException(message);
+ }
+
+ private static URI checkURI(URI uri) {
+ String scheme = uri.getScheme();
+ if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
+ throw illegal("invalid URI scheme: " + scheme);
+ if (uri.getHost() == null)
+ throw illegal("URI must contain a host: " + uri);
+ if (uri.getFragment() != null)
+ throw illegal("URI must not contain a fragment: " + uri);
+ return uri;
+ }
+
private static IllegalArgumentException illegal(String message) {
return new IllegalArgumentException(message);
}
! /**
! * Returns the proxy for the given URI when sent through the given client,
! * or {@code null} if none is required or applicable.
! */
! private static Proxy proxyFor(Optional<ProxySelector> selector, URI uri) {
! if (!selector.isPresent()) {
! return null;
! }
! URI requestURI = createRequestURI(uri); // Based on the HTTP scheme
! List<Proxy> pl = selector.get().select(requestURI);
! if (pl.isEmpty()) {
! return null;
! }
! Proxy proxy = pl.get(0);
! if (proxy.type() != 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) {
! return;
! }
! Stream<String> headers = b.getHeaders().stream().map(p -> p.first).distinct();
! URLPermission perm1 = Utils.permissionForServer(b.getUri(), "", headers);
! sm.checkPermission(perm1);
! if (proxy == null) {
! return;
! }
! URLPermission perm2 = permissionForProxy((InetSocketAddress) proxy.address());
! if (perm2 != null) {
! sm.checkPermission(perm2);
! }
}
}
< prev index next >