# HG changeset patch # User phh # Date 1618262670 0 # Mon Apr 12 21:24:30 2021 +0000 # Node ID f9c4aef089847ed2f1cdd2a80f928bea9767e318 # Parent b3cee5c1366d13847612bbd23805695290cb0bab 8265099: Revert backport to 11u of 8236859: WebSocket over authenticating proxy fails with NPE Summary: Revert https://hg.openjdk.java.net/jdk-updates/jdk11u-dev/rev/57e3fa3574ec Reviewed-by: diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -240,22 +240,19 @@ HttpHeaders hdrs = r.headers(); HttpRequestImpl req = r.request(); - if (status != PROXY_UNAUTHORIZED){ + if (status != UNAUTHORIZED && status != PROXY_UNAUTHORIZED) { + // check if any authentication succeeded for first time + if (exchange.serverauth != null && !exchange.serverauth.fromcache) { + AuthInfo au = exchange.serverauth; + cache.store(au.scheme, req.uri(), false, au.credentials); + } if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) { AuthInfo au = exchange.proxyauth; URI proxyURI = getProxyURI(req); if (proxyURI != null) { - exchange.proxyauth = null; cache.store(au.scheme, proxyURI, true, au.credentials); } } - if (status != UNAUTHORIZED) { - // check if any authentication succeeded for first time - if (exchange.serverauth != null && !exchange.serverauth.fromcache) { - AuthInfo au = exchange.serverauth; - cache.store(au.scheme, req.uri(), false, au.credentials); - } - } return null; } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java b/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -142,7 +142,6 @@ HttpConnection c = secure ? findConnection(key, sslPool) : findConnection(key, plainPool); //System.out.println ("getConnection returning: " + c); - assert c == null || c.isSecure() == secure; return c; } @@ -156,10 +155,6 @@ // Called also by whitebox tests void returnToPool(HttpConnection conn, Instant now, long keepAlive) { - assert (conn instanceof PlainHttpConnection) || conn.isSecure() - : "Attempting to return unsecure connection to SSL pool: " - + conn.getClass(); - // Don't call registerCleanupTrigger while holding a lock, // but register it before the connection is added to the pool, // since we don't want to trigger the cleanup if the connection @@ -455,7 +450,7 @@ if (c instanceof PlainHttpConnection) { removeFromPool(c, plainPool); } else { - assert c.isSecure() : "connection " + c + " is not secure!"; + assert c.isSecure(); removeFromPool(c, sslPool); } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -263,7 +263,7 @@ connection.close(); return MinimalFuture.completedFuture(null); // not treating as error } else { - return readBody(discarding(), !request.isWebSocket(), executor); + return readBody(discarding(), true, executor); } } @@ -387,14 +387,6 @@ public CompletableFuture readBody(HttpResponse.BodySubscriber p, boolean return2Cache, Executor executor) { - if (debug.on()) { - debug.log("readBody: return2Cache: " + return2Cache); - if (request.isWebSocket() && return2Cache && connection != null) { - debug.log("websocket connection will be returned to cache: " - + connection.getClass() + "/" + connection ); - } - } - assert !return2Cache || !request.isWebSocket(); this.return2Cache = return2Cache; final Http1BodySubscriber subscriber = new Http1BodySubscriber<>(p); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -160,26 +160,6 @@ return rawchan; } - /** - * Closes the RawChannel that may have been used for WebSocket protocol. - * - * @apiNote This method should be called to close the connection - * if an exception occurs during the websocket handshake, in cases where - * {@link #rawChannel() rawChannel().close()} would have been called. - * An unsuccessful handshake may prevent the creation of the RawChannel: - * if a RawChannel has already been created, this method wil close it. - * Otherwise, it will close the connection. - * - * @throws IOException if an I/O exception occurs while closing - * the channel. - */ - public synchronized void closeRawChannel() throws IOException { - // close the rawChannel, if created, or the - // connection, if not. - if (rawchan != null) rawchan.close(); - else connection.close(); - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -363,10 +363,6 @@ this.response = new HttpResponseImpl<>(currentreq, response, this.response, null, exch); Exchange oldExch = exch; - if (currentreq.isWebSocket()) { - // need to close the connection and open a new one. - exch.exchImpl.connection().close(); - } return exch.ignoreBody().handle((r,t) -> { previousreq = currentreq; currentreq = newrequest; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/RawChannelTube.java b/src/java.net.http/share/classes/jdk/internal/net/http/RawChannelTube.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/RawChannelTube.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/RawChannelTube.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,7 +73,7 @@ this.initial = initial; this.writePublisher = new WritePublisher(); this.readSubscriber = new ReadSubscriber(); - dbgTag = "[WebSocket] RawChannelTube(" + tube +")"; + dbgTag = "[WebSocket] RawChannelTube(" + tube.toString() +")"; debug = Utils.getWebSocketLogger(dbgTag::toString, Utils.DEBUG_WS); connection.client().webSocketOpen(); connectFlows(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,7 +41,7 @@ import javax.net.ssl.SSLParameters; /** - * -Djdk.httpclient.HttpClient.log= + * -Djava.net.HttpClient.log= * errors,requests,headers, * frames[:control:data:window:all..],content,ssl,trace,channel * diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -215,26 +215,19 @@ // // See https://tools.ietf.org/html/rfc6455#section-7.4.1 Result result = null; - Throwable exception = null; + Exception exception = null; try { result = handleResponse(response); } catch (IOException e) { exception = e; } catch (Exception e) { exception = new WebSocketHandshakeException(response).initCause(e); - } catch (Error e) { - // We should attempt to close the connection and relay - // the error through the completable future even in this - // case. - exception = e; } if (exception == null) { return MinimalFuture.completedFuture(result); } try { - // calling this method will close the rawChannel, if created, - // or the connection, if not. - ((RawChannel.Provider) response).closeRawChannel(); + ((RawChannel.Provider) response).rawChannel().close(); } catch (IOException e) { exception.addSuppressed(e); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,6 @@ interface Provider { RawChannel rawChannel() throws IOException; - void closeRawChannel() throws IOException; } interface RawEvent { diff --git a/test/jdk/java/net/httpclient/websocket/DummySecureWebSocketServer.java b/test/jdk/java/net/httpclient/websocket/DummySecureWebSocketServer.java deleted file mode 100644 --- a/test/jdk/java/net/httpclient/websocket/DummySecureWebSocketServer.java +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright (c) 2020, 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. - */ - -import javax.net.ServerSocketFactory; -import javax.net.ssl.SSLServerSocketFactory; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.ServerSocket; -import java.net.SocketAddress; -import java.net.SocketOption; -import java.net.StandardSocketOptions; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.channels.ClosedByInterruptException; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -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; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiFunction; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static java.lang.String.format; -import static java.lang.System.err; -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; -import static java.util.Objects.requireNonNull; - -/** - * Dummy WebSocket Server, which supports TLS. - * By default the dummy webserver uses a plain TCP connection, - * but it can use a TLS connection if secure() is called before - * open(). It will use the default SSL context. - * - * Performs simpler version of the WebSocket Opening Handshake over HTTP (i.e. - * no proxying, cookies, etc.) Supports sequential connections, one at a time, - * i.e. in order for a client to connect to the server the previous client must - * disconnect first. - * - * Expected client request: - * - * GET /chat HTTP/1.1 - * Host: server.example.com - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Origin: http://example.com - * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 13 - * - * This server response: - * - * HTTP/1.1 101 Switching Protocols - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= - * Sec-WebSocket-Protocol: chat - */ -public class DummySecureWebSocketServer implements Closeable { - - /** - * Emulates some of the SocketChannel APIs over a Socket - * instance. - */ - public static class WebSocketChannel implements AutoCloseable { - interface Reader { - int read(ByteBuffer buf) throws IOException; - } - interface Writer { - void write(ByteBuffer buf) throws IOException; - } - interface Config { - void setOption(SocketOption option, T value) throws IOException; - } - interface Closer { - void close() throws IOException; - } - final AutoCloseable channel; - final Reader reader; - final Writer writer; - final Config config; - final Closer closer; - WebSocketChannel(AutoCloseable channel, Reader reader, Writer writer, Config config, Closer closer) { - this.channel = channel; - this.reader = reader; - this.writer = writer; - this.config = config; - this.closer = closer; - } - public void close() throws IOException { - closer.close(); - } - public String toString() { - return channel.toString(); - } - public int read(ByteBuffer bb) throws IOException { - return reader.read(bb); - } - public void write(ByteBuffer bb) throws IOException { - writer.write(bb); - } - public void setOption(SocketOption option, T value) throws IOException { - config.setOption(option, value); - } - public static WebSocketChannel of(Socket s) { - Reader reader = (bb) -> DummySecureWebSocketServer.read(s.getInputStream(), bb); - Writer writer = (bb) -> DummySecureWebSocketServer.write(s.getOutputStream(), bb); - return new WebSocketChannel(s, reader, writer, s::setOption, s::close); - } - } - - /** - * Emulates some of the ServerSocketChannel APIs over a ServerSocket - * instance. - */ - public static class WebServerSocketChannel implements AutoCloseable { - interface Accepter { - WebSocketChannel accept() throws IOException; - } - interface Binder { - void bind(SocketAddress address) throws IOException; - } - interface Config { - void setOption(SocketOption option, T value) throws IOException; - } - interface Closer { - void close() throws IOException; - } - interface Addressable { - SocketAddress getLocalAddress() throws IOException; - } - final AutoCloseable server; - final Accepter accepter; - final Binder binder; - final Addressable address; - final Config config; - final Closer closer; - WebServerSocketChannel(AutoCloseable server, - Accepter accepter, - Binder binder, - Addressable address, - Config config, - Closer closer) { - this.server = server; - this.accepter = accepter; - this.binder = binder; - this.address = address; - this.config = config; - this.closer = closer; - } - public void close() throws IOException { - closer.close(); - } - public String toString() { - return server.toString(); - } - public WebSocketChannel accept() throws IOException { - return accepter.accept(); - } - public void bind(SocketAddress address) throws IOException { - binder.bind(address); - } - public void setOption(SocketOption option, T value) throws IOException { - config.setOption(option, value); - } - public SocketAddress getLocalAddress() throws IOException { - return address.getLocalAddress(); - } - public static WebServerSocketChannel of(ServerSocket ss) { - Accepter a = () -> WebSocketChannel.of(ss.accept()); - return new WebServerSocketChannel(ss, a, ss::bind, ss::getLocalSocketAddress, ss::setOption, ss::close); - } - } - - // Creates a secure WebServerSocketChannel - static WebServerSocketChannel openWSS() throws IOException { - return WebServerSocketChannel.of(SSLServerSocketFactory.getDefault().createServerSocket()); - } - - // Creates a plain WebServerSocketChannel - static WebServerSocketChannel openWS() throws IOException { - return WebServerSocketChannel.of(ServerSocketFactory.getDefault().createServerSocket()); - } - - - static int read(InputStream str, ByteBuffer buffer) throws IOException { - int len = Math.min(buffer.remaining(), 1024); - if (len <= 0) return 0; - byte[] bytes = new byte[len]; - int res = 0; - if (buffer.hasRemaining()) { - len = Math.min(len, buffer.remaining()); - int n = str.read(bytes, 0, len); - if (n > 0) { - buffer.put(bytes, 0, n); - res += n; - } else if (res > 0) { - return res; - } else { - return n; - } - } - return res; - } - - static void write(OutputStream str, ByteBuffer buffer) throws IOException { - int len = Math.min(buffer.remaining(), 1024); - if (len <= 0) return; - byte[] bytes = new byte[len]; - int res = 0; - int pos = buffer.position(); - while (buffer.hasRemaining()) { - len = Math.min(len, buffer.remaining()); - buffer.get(bytes, 0, len); - str.write(bytes, 0, len); - } - } - - private final AtomicBoolean started = new AtomicBoolean(); - private final Thread thread; - private volatile WebServerSocketChannel ss; - private volatile InetSocketAddress address; - private volatile boolean secure; - private ByteBuffer read = ByteBuffer.allocate(16384); - private final CountDownLatch readReady = new CountDownLatch(1); - private volatile boolean done; - - private static class Credentials { - private final String name; - private final String password; - private Credentials(String name, String password) { - this.name = name; - this.password = password; - } - public String name() { return name; } - public String password() { return password; } - } - - public DummySecureWebSocketServer() { - this(defaultMapping(), null, null); - } - - public DummySecureWebSocketServer(String username, String password) { - this(defaultMapping(), username, password); - } - - public DummySecureWebSocketServer(BiFunction,Credentials,List> mapping, - String username, - String password) { - requireNonNull(mapping); - Credentials credentials = username != null ? - new Credentials(username, password) : null; - - thread = new Thread(() -> { - try { - while (!Thread.currentThread().isInterrupted() && !done) { - err.println("Accepting next connection at: " + ss); - WebSocketChannel channel = ss.accept(); - err.println("Accepted: " + channel); - try { - channel.setOption(StandardSocketOptions.TCP_NODELAY, true); - while (!done) { - StringBuilder request = new StringBuilder(); - if (!readRequest(channel, request)) { - throw new IOException("Bad request:[" + request + "]"); - } - List strings = asList(request.toString().split("\r\n")); - List response = mapping.apply(strings, credentials); - writeResponse(channel, response); - - if (response.get(0).startsWith("HTTP/1.1 401")) { - err.println("Sent 401 Authentication response " + channel); - continue; - } else { - serve(channel); - break; - } - } - } catch (IOException e) { - if (!done) { - err.println("Error in connection: " + channel + ", " + e); - } - } finally { - err.println("Closed: " + channel); - close(channel); - readReady.countDown(); - } - } - } catch (ClosedByInterruptException ignored) { - } catch (Throwable e) { - if (!done) { - e.printStackTrace(err); - } - } finally { - done = true; - close(ss); - err.println("Stopped at: " + getURI()); - } - }); - thread.setName("DummySecureWebSocketServer"); - thread.setDaemon(false); - } - - // must be called before open() - public DummySecureWebSocketServer secure() { - secure = true; - return this; - } - - protected void read(WebSocketChannel ch) throws IOException { - // Read until the thread is interrupted or an error occurred - // or the input is shutdown - ByteBuffer b = ByteBuffer.allocate(65536); - while (ch.read(b) != -1) { - b.flip(); - if (read.remaining() < b.remaining()) { - int required = read.capacity() - read.remaining() + b.remaining(); - int log2required = 32 - Integer.numberOfLeadingZeros(required - 1); - ByteBuffer newBuffer = ByteBuffer.allocate(1 << log2required); - newBuffer.put(read.flip()); - read = newBuffer; - } - read.put(b); - b.clear(); - } - } - - protected void write(WebSocketChannel ch) throws IOException { } - - protected final void serve(WebSocketChannel channel) - throws InterruptedException - { - Thread reader = new Thread(() -> { - try { - read(channel); - } catch (IOException ignored) { } - }); - Thread writer = new Thread(() -> { - try { - write(channel); - } catch (IOException ignored) { } - }); - reader.start(); - writer.start(); - try { - while (!done) { - try { - reader.join(500); - } catch (InterruptedException x) { - if (done) { - close(channel); - break; - } - } - } - } finally { - reader.interrupt(); - try { - while (!done) { - try { - writer.join(500); - } catch (InterruptedException x) { - if (done) break; - } - } - } finally { - writer.interrupt(); - } - } - } - - public ByteBuffer read() throws InterruptedException { - readReady.await(); - return read.duplicate().asReadOnlyBuffer().flip(); - } - - public void open() throws IOException { - err.println("Starting"); - if (!started.compareAndSet(false, true)) { - throw new IllegalStateException("Already started"); - } - ss = secure ? openWSS() : openWS(); - try { - ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - address = (InetSocketAddress) ss.getLocalAddress(); - thread.start(); - } catch (IOException e) { - done = true; - close(ss); - throw e; - } - err.println("Started at: " + getURI()); - } - - @Override - public void close() { - err.println("Stopping: " + getURI()); - done = true; - thread.interrupt(); - close(ss); - } - - URI getURI() { - if (!started.get()) { - throw new IllegalStateException("Not yet started"); - } - if (!secure) { - return URI.create("ws://localhost:" + address.getPort()); - } else { - return URI.create("wss://localhost:" + address.getPort()); - } - } - - private boolean readRequest(WebSocketChannel channel, StringBuilder request) - throws IOException - { - ByteBuffer buffer = ByteBuffer.allocate(512); - while (channel.read(buffer) != -1) { - // read the complete HTTP request headers, there should be no body - CharBuffer decoded; - buffer.flip(); - try { - decoded = ISO_8859_1.newDecoder().decode(buffer); - } catch (CharacterCodingException e) { - throw new UncheckedIOException(e); - } - request.append(decoded); - if (Pattern.compile("\r\n\r\n").matcher(request).find()) - return true; - buffer.clear(); - } - return false; - } - - private void writeResponse(WebSocketChannel channel, List response) - throws IOException - { - String s = response.stream().collect(Collectors.joining("\r\n")) - + "\r\n\r\n"; - ByteBuffer encoded; - try { - encoded = ISO_8859_1.newEncoder().encode(CharBuffer.wrap(s)); - } catch (CharacterCodingException e) { - throw new UncheckedIOException(e); - } - while (encoded.hasRemaining()) { - channel.write(encoded); - } - } - - private static BiFunction,Credentials,List> defaultMapping() { - return (request, credentials) -> { - List response = new LinkedList<>(); - Iterator iterator = request.iterator(); - if (!iterator.hasNext()) { - throw new IllegalStateException("The request is empty"); - } - 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> requestHeaders = new HashMap<>(); - while (iterator.hasNext()) { - String header = iterator.next(); - String[] split = header.split(": "); - if (split.length != 2) { - throw new IllegalStateException - ("Unexpected header: " + header - + ", split=" + 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"); - } - if (requestHeaders.containsKey("Sec-WebSocket-Extensions")) { - throw new IllegalStateException("Extensions are not expected"); - } - expectHeader(requestHeaders, "Connection", "Upgrade"); - response.add("Connection: Upgrade"); - expectHeader(requestHeaders, "Upgrade", "websocket"); - response.add("Upgrade: websocket"); - expectHeader(requestHeaders, "Sec-WebSocket-Version", "13"); - List 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.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); - - // check authorization credentials, if required by the server - if (credentials != null && !authorized(credentials, requestHeaders)) { - response.clear(); - response.add("HTTP/1.1 401 Unauthorized"); - response.add("Content-Length: 0"); - response.add("WWW-Authenticate: Basic realm=\"dummy server realm\""); - } - - return response; - }; - } - - // Checks credentials in the request against those allowable by the server. - private static boolean authorized(Credentials credentials, - Map> requestHeaders) { - List authorization = requestHeaders.get("Authorization"); - if (authorization == null) - return false; - - if (authorization.size() != 1) { - throw new IllegalStateException("Authorization unexpected count:" + authorization); - } - String header = authorization.get(0); - if (!header.startsWith("Basic ")) - throw new IllegalStateException("Authorization not Basic: " + header); - - header = header.substring("Basic ".length()); - String values = new String(Base64.getDecoder().decode(header), UTF_8); - int sep = values.indexOf(':'); - if (sep < 1) { - throw new IllegalStateException("Authorization not colon: " + values); - } - String name = values.substring(0, sep); - String password = values.substring(sep + 1); - - if (name.equals(credentials.name()) && password.equals(credentials.password())) - return true; - - return false; - } - - protected static String expectHeader(Map> headers, - String name, - String value) { - List v = headers.get(name); - if (v == null) { - throw new IllegalStateException( - format("Expected '%s' header, not present in %s", - name, headers)); - } - if (!v.contains(value)) { - throw new IllegalStateException( - format("Expected '%s: %s', actual: '%s: %s'", - name, value, name, v) - ); - } - return value; - } - - private static void close(AutoCloseable... acs) { - for (AutoCloseable ac : acs) { - try { - ac.close(); - } catch (Exception ignored) { } - } - } -} diff --git a/test/jdk/java/net/httpclient/websocket/SecureSupport.java b/test/jdk/java/net/httpclient/websocket/SecureSupport.java deleted file mode 100644 --- a/test/jdk/java/net/httpclient/websocket/SecureSupport.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2020, 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. - */ - -import java.io.IOException; -import java.net.Socket; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static org.testng.Assert.assertThrows; - -/** - * Helper class to create instances of DummySecureWebSocketServer which - * can support both plain and secure connections. - * The caller should invoke DummySecureWebSocketServer::secure before - * DummySecureWebSocketServer::open in order to enable secure connection. - * When secure, the DummySecureWebSocketServer currently only support using the - * default SSLEngine through the default SSLSocketServerFacrtory. - */ -public class SecureSupport { - - private SecureSupport() { } - - public static DummySecureWebSocketServer serverWithCannedData(int... data) { - return serverWithCannedDataAndAuthentication(null, null, data); - } - - public static DummySecureWebSocketServer serverWithCannedDataAndAuthentication( - String username, - String password, - int... data) - { - byte[] copy = new byte[data.length]; - for (int i = 0; i < data.length; i++) { - copy[i] = (byte) data[i]; - } - return serverWithCannedDataAndAuthentication(username, password, copy); - } - - public static DummySecureWebSocketServer serverWithCannedData(byte... data) { - return serverWithCannedDataAndAuthentication(null, null, data); - } - - public static DummySecureWebSocketServer serverWithCannedDataAndAuthentication( - String username, - String password, - byte... data) - { - byte[] copy = Arrays.copyOf(data, data.length); - return new DummySecureWebSocketServer(username, password) { - @Override - protected void write(WebSocketChannel ch) throws IOException { - int off = 0; int n = 1; // 1 byte at a time - while (off + n < copy.length + n) { - int len = Math.min(copy.length - off, n); - ByteBuffer bytes = ByteBuffer.wrap(copy, off, len); - off += len; - ch.write(bytes); - } - super.write(ch); - } - }; - } - - /* - * This server does not read from the wire, allowing its client to fill up - * their send buffer. Used to test scenarios with outstanding send - * operations. - */ - public static DummySecureWebSocketServer notReadingServer() { - return new DummySecureWebSocketServer() { - @Override - protected void read(WebSocketChannel ch) throws IOException { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - }; - } - - public static DummySecureWebSocketServer writingServer(int... data) { - byte[] copy = new byte[data.length]; - for (int i = 0; i < data.length; i++) { - copy[i] = (byte) data[i]; - } - return new DummySecureWebSocketServer() { - - @Override - protected void read(WebSocketChannel ch) throws IOException { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - - @Override - protected void write(WebSocketChannel ch) throws IOException { - int off = 0; int n = 1; // 1 byte at a time - while (off + n < copy.length + n) { - int len = Math.min(copy.length - off, n); - ByteBuffer bytes = ByteBuffer.wrap(copy, off, len); - off += len; - ch.write(bytes); - } - super.write(ch); - } - }; - - } - - public static String stringWith2NBytes(int n) { - // -- Russian Alphabet (33 characters, 2 bytes per char) -- - char[] abc = { - 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416, - 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, - 0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, - 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, - 0x042F, - }; - // repeat cyclically - StringBuilder sb = new StringBuilder(n); - for (int i = 0, j = 0; i < n; i++, j = (j + 1) % abc.length) { - sb.append(abc[j]); - } - String s = sb.toString(); - assert s.length() == n && s.getBytes(StandardCharsets.UTF_8).length == 2 * n; - return s; - } - - public static String malformedString() { - return new String(new char[]{0xDC00, 0xD800}); - } - - public static String incompleteString() { - return new String(new char[]{0xD800}); - } - - public static String stringWithNBytes(int n) { - char[] chars = new char[n]; - Arrays.fill(chars, 'A'); - return new String(chars); - } -} diff --git a/test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java b/test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java --- a/test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java +++ b/test/jdk/java/net/httpclient/websocket/WebSocketProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -23,15 +23,10 @@ /* * @test - * @bug 8217429 8236859 + * @bug 8217429 * @summary WebSocket proxy tunneling tests - * @library /test/lib - * @compile SecureSupport.java DummySecureWebSocketServer.java ../ProxyServer.java - * @build jdk.test.lib.net.SimpleSSLContext WebSocketProxyTest + * @compile DummyWebSocketServer.java ../ProxyServer.java * @run testng/othervm - * -Djdk.internal.httpclient.debug=true - * -Djdk.internal.httpclient.websocket.debug=true - * -Djdk.httpclient.HttpClient.log=errors,requests,headers * -Djdk.http.auth.tunneling.disabledSchemes= * WebSocketProxyTest */ @@ -57,14 +52,9 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; - -import jdk.test.lib.net.SimpleSSLContext; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; - -import javax.net.ssl.SSLContext; - import static java.net.http.HttpClient.newBuilder; import static java.nio.charset.StandardCharsets.UTF_8; import static org.testng.Assert.assertEquals; @@ -76,14 +66,6 @@ private static final String USERNAME = "wally"; private static final String PASSWORD = "xyz987"; - static { - try { - SSLContext.setDefault(new SimpleSSLContext().get()); - } catch (IOException ex) { - throw new ExceptionInInitializerError(ex); - } - } - static class WSAuthenticator extends Authenticator { @Override protected PasswordAuthentication getPasswordAuthentication() { @@ -91,34 +73,20 @@ } } - static final Function SERVER_WITH_CANNED_DATA = + static final Function SERVER_WITH_CANNED_DATA = new Function<>() { - @Override public DummySecureWebSocketServer apply(int[] data) { - return SecureSupport.serverWithCannedData(data); } + @Override public DummyWebSocketServer apply(int[] data) { + return Support.serverWithCannedData(data); } @Override public String toString() { return "SERVER_WITH_CANNED_DATA"; } }; - static final Function SSL_SERVER_WITH_CANNED_DATA = - new Function<>() { - @Override public DummySecureWebSocketServer apply(int[] data) { - return SecureSupport.serverWithCannedData(data).secure(); } - @Override public String toString() { return "SSL_SERVER_WITH_CANNED_DATA"; } - }; - - static final Function AUTH_SERVER_WITH_CANNED_DATA = + static final Function AUTH_SERVER_WITH_CANNED_DATA = new Function<>() { - @Override public DummySecureWebSocketServer apply(int[] data) { - return SecureSupport.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data); } + @Override public DummyWebSocketServer apply(int[] data) { + return Support.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data); } @Override public String toString() { return "AUTH_SERVER_WITH_CANNED_DATA"; } }; - static final Function AUTH_SSL_SVR_WITH_CANNED_DATA = - new Function<>() { - @Override public DummySecureWebSocketServer apply(int[] data) { - return SecureSupport.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data).secure(); } - @Override public String toString() { return "AUTH_SSL_SVR_WITH_CANNED_DATA"; } - }; - static final Supplier TUNNELING_PROXY_SERVER = new Supplier<>() { @Override public ProxyServer get() { @@ -137,20 +105,15 @@ @DataProvider(name = "servers") public Object[][] servers() { return new Object[][] { - { SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, - { SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER }, - { SSL_SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, - { SSL_SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER }, - { AUTH_SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, - { AUTH_SSL_SVR_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, - { AUTH_SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER }, - { AUTH_SSL_SVR_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER }, + { SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, + { SERVER_WITH_CANNED_DATA, AUTH_TUNNELING_PROXY_SERVER }, + { AUTH_SERVER_WITH_CANNED_DATA, TUNNELING_PROXY_SERVER }, }; } @Test(dataProvider = "servers") public void simpleAggregatingBinaryMessages - (Function serverSupplier, + (Function serverSupplier, Supplier proxyServerSupplier) throws IOException { @@ -171,8 +134,6 @@ InetSocketAddress proxyAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), proxyServer.getPort()); server.open(); - System.out.println("Server: " + server.getURI()); - System.out.println("Proxy: " + proxyAddress); WebSocket.Listener listener = new WebSocket.Listener() { @@ -248,7 +209,7 @@ @Test public void clientAuthenticate() throws IOException { try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get(); - var server = new DummySecureWebSocketServer()){ + var server = new DummyWebSocketServer()){ server.open(); InetSocketAddress proxyAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), proxyServer.getPort()); @@ -269,7 +230,7 @@ @Test public void explicitAuthenticate() throws IOException { try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get(); - var server = new DummySecureWebSocketServer()) { + var server = new DummyWebSocketServer()) { server.open(); InetSocketAddress proxyAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), proxyServer.getPort()); @@ -287,36 +248,12 @@ } /* - * Ensures authentication succeeds when an `Authorization` header is explicitly set. - */ - @Test - public void explicitAuthenticate2() throws IOException { - try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get(); - var server = new DummySecureWebSocketServer(USERNAME, PASSWORD).secure()) { - server.open(); - InetSocketAddress proxyAddress = new InetSocketAddress( - InetAddress.getLoopbackAddress(), proxyServer.getPort()); - - String hv = "Basic " + Base64.getEncoder().encodeToString( - (USERNAME + ":" + PASSWORD).getBytes(UTF_8)); - - var webSocket = newBuilder() - .proxy(ProxySelector.of(proxyAddress)).build() - .newWebSocketBuilder() - .header("Proxy-Authorization", hv) - .header("Authorization", hv) - .buildAsync(server.getURI(), new WebSocket.Listener() { }) - .join(); - } - } - - /* * Ensures authentication does not succeed when no authenticator is present. */ @Test public void failNoAuthenticator() throws IOException { try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get(); - var server = new DummySecureWebSocketServer(USERNAME, PASSWORD)) { + var server = new DummyWebSocketServer(USERNAME, PASSWORD)) { server.open(); InetSocketAddress proxyAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), proxyServer.getPort()); @@ -344,7 +281,7 @@ @Test public void failBadCredentials() throws IOException { try (var proxyServer = AUTH_TUNNELING_PROXY_SERVER.get(); - var server = new DummySecureWebSocketServer(USERNAME, PASSWORD)) { + var server = new DummyWebSocketServer(USERNAME, PASSWORD)) { server.open(); InetSocketAddress proxyAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), proxyServer.getPort());