< 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 >