Skip to content

Commit ce10688

Browse files
KarmPaul Hohensee
authored andcommitted
8245245: WebSocket can lose the URL encoding of URI query parameters
8298588: WebSockets: HandshakeUrlEncodingTest unnecessarily depends on a response body The fix updates jdk.internal.net.http.websocket.OpeningHandshake to avoid double encoding and decoding of URL Reviewed-by: phh, sgehwolf Backport-of: c07ce7e
1 parent c46dcc5 commit ce10688

File tree

2 files changed

+222
-8
lines changed

2 files changed

+222
-8
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,15 @@ private static Collection<String> createRequestSubprotocols(
171171
static URI createRequestURI(URI uri) {
172172
String s = uri.getScheme();
173173
assert "ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s);
174-
String scheme = "ws".equalsIgnoreCase(s) ? "http" : "https";
174+
String newUri = uri.toString();
175+
if (s.equalsIgnoreCase("ws")) {
176+
newUri = "http" + newUri.substring(2);
177+
}
178+
else {
179+
newUri = "https" + newUri.substring(3);
180+
}
175181
try {
176-
return new URI(scheme,
177-
uri.getUserInfo(),
178-
uri.getHost(),
179-
uri.getPort(),
180-
uri.getPath(),
181-
uri.getQuery(),
182-
null); // No fragment
182+
return new URI(newUri);
183183
} catch (URISyntaxException e) {
184184
// Shouldn't happen: URI invariant
185185
throw new InternalError(e);
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8245245
27+
* @summary Test for Websocket URI encoding during HandShake
28+
* @library /lib/testlibrary
29+
* @library /test/lib
30+
* @build jdk.testlibrary.SimpleSSLContext
31+
* @modules java.net.http
32+
* jdk.httpserver
33+
* @run testng/othervm -Djdk.internal.httpclient.debug=true HandshakeUrlEncodingTest
34+
*/
35+
36+
import com.sun.net.httpserver.HttpHandler;
37+
import com.sun.net.httpserver.HttpServer;
38+
import com.sun.net.httpserver.HttpsConfigurator;
39+
import com.sun.net.httpserver.HttpsServer;
40+
import com.sun.net.httpserver.HttpExchange;
41+
import jdk.test.lib.net.URIBuilder;
42+
import jdk.testlibrary.SimpleSSLContext;
43+
import org.testng.annotations.AfterTest;
44+
import org.testng.annotations.BeforeTest;
45+
import org.testng.annotations.DataProvider;
46+
import org.testng.annotations.Test;
47+
48+
import javax.net.ssl.SSLContext;
49+
import java.io.IOException;
50+
import java.io.InputStream;
51+
import java.io.OutputStream;
52+
import java.net.InetAddress;
53+
import java.net.InetSocketAddress;
54+
import java.net.URI;
55+
import java.net.http.HttpClient;
56+
import java.net.http.WebSocket;
57+
import java.net.http.WebSocketHandshakeException;
58+
import java.util.concurrent.CompletionException;
59+
import java.util.concurrent.ExecutionException;
60+
import java.util.concurrent.ExecutorService;
61+
import java.util.concurrent.Executors;
62+
63+
import static java.net.http.HttpClient.Builder.NO_PROXY;
64+
import static org.testng.Assert.assertEquals;
65+
import static org.testng.Assert.assertNotNull;
66+
import static org.testng.Assert.fail;
67+
import static java.lang.System.out;
68+
69+
public class HandshakeUrlEncodingTest {
70+
71+
SSLContext sslContext;
72+
HttpServer httpTestServer;
73+
HttpsServer httpsTestServer;
74+
String httpURI;
75+
String httpsURI;
76+
77+
static String queryPart;
78+
79+
static final int ITERATION_COUNT = 10;
80+
// a shared executor helps reduce the amount of threads created by the test
81+
static final ExecutorService executor = Executors.newCachedThreadPool();
82+
83+
@DataProvider(name = "variants")
84+
public Object[][] variants() {
85+
return new Object[][]{
86+
{ httpURI, false },
87+
{ httpsURI, false },
88+
{ httpURI, true },
89+
{ httpsURI, true }
90+
};
91+
}
92+
93+
HttpClient newHttpClient() {
94+
return HttpClient.newBuilder()
95+
.proxy(NO_PROXY)
96+
.executor(executor)
97+
.sslContext(sslContext)
98+
.build();
99+
}
100+
101+
@Test(dataProvider = "variants")
102+
public void test(String uri, boolean sameClient) {
103+
HttpClient client = null;
104+
out.println("The url is " + uri);
105+
for (int i = 0; i < ITERATION_COUNT; i++) {
106+
System.out.printf("iteration %s%n", i);
107+
if (!sameClient || client == null)
108+
client = newHttpClient();
109+
110+
try {
111+
client.newWebSocketBuilder()
112+
.buildAsync(URI.create(uri), new WebSocket.Listener() { })
113+
.join();
114+
fail("Expected to throw");
115+
} catch (CompletionException ce) {
116+
final Throwable t = getCompletionCause(ce);
117+
if (!(t instanceof WebSocketHandshakeException)) {
118+
throw new AssertionError("Unexpected exception", t);
119+
}
120+
final WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
121+
assertNotNull(wse.getResponse());
122+
assertNotNull(wse.getResponse().uri());
123+
assertNotNull(wse.getResponse().statusCode());
124+
final String rawQuery = wse.getResponse().uri().getRawQuery();
125+
final String expectedRawQuery = "&raw=abc+def/ghi=xyz&encoded=abc%2Bdef%2Fghi%3Dxyz";
126+
assertEquals(rawQuery, expectedRawQuery);
127+
// Unlike later JDKs, 11u does not have JDK-8240666 patched currently.
128+
// We need to check whether a body is present. This is OK as previous assertions verify the fix of JDK-8245245.
129+
if (wse.getResponse().body() != null &&
130+
(wse.getResponse().body().getClass().equals(String.class))) {
131+
final String body = (String) wse.getResponse().body();
132+
final String expectedBody = "/?" + expectedRawQuery;
133+
assertEquals(body, expectedBody);
134+
}
135+
out.println("Status code is " + wse.getResponse().statusCode());
136+
out.println("Response is " + wse.getResponse());
137+
assertEquals(wse.getResponse().statusCode(), 400);
138+
}
139+
}
140+
}
141+
142+
@BeforeTest
143+
public void setup() throws Exception {
144+
sslContext = new SimpleSSLContext().get();
145+
if (sslContext == null)
146+
throw new AssertionError("Unexpected null sslContext");
147+
148+
149+
InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
150+
queryPart = "?&raw=abc+def/ghi=xyz&encoded=abc%2Bdef%2Fghi%3Dxyz";
151+
httpTestServer = HttpServer.create(sa, 10);
152+
httpURI = URIBuilder.newBuilder()
153+
.scheme("ws")
154+
.host("localhost")
155+
.port(httpTestServer.getAddress().getPort())
156+
.path("/")
157+
.build()
158+
.toString() + queryPart;
159+
160+
httpTestServer.createContext("/", new UrlHandler());
161+
162+
httpsTestServer = HttpsServer.create(sa, 10);
163+
httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
164+
httpsURI = URIBuilder.newBuilder()
165+
.scheme("wss")
166+
.host("localhost")
167+
.port(httpsTestServer.getAddress().getPort())
168+
.path("/")
169+
.build()
170+
.toString() + queryPart;
171+
172+
httpsTestServer.createContext("/", new UrlHandler());
173+
174+
httpTestServer.start();
175+
httpsTestServer.start();
176+
}
177+
178+
@AfterTest
179+
public void teardown() {
180+
httpTestServer.stop(0);
181+
httpsTestServer.stop(0);
182+
executor.shutdownNow();
183+
}
184+
185+
private static Throwable getCompletionCause(Throwable x) {
186+
if (!(x instanceof CompletionException)
187+
&& !(x instanceof ExecutionException)) return x;
188+
final Throwable cause = x.getCause();
189+
if (cause == null) {
190+
throw new InternalError("Unexpected null cause", x);
191+
}
192+
return cause;
193+
}
194+
195+
static class UrlHandler implements HttpHandler {
196+
197+
@Override
198+
public void handle(HttpExchange e) throws IOException {
199+
try(InputStream is = e.getRequestBody();
200+
OutputStream os = e.getResponseBody()) {
201+
String testUri = "/?&raw=abc+def/ghi=xyz&encoded=abc%2Bdef%2Fghi%3Dxyz";
202+
URI uri = e.getRequestURI();
203+
byte[] bytes = is.readAllBytes();
204+
if (uri.toString().equals(testUri)) {
205+
bytes = testUri.getBytes();
206+
}
207+
e.sendResponseHeaders(400, bytes.length);
208+
os.write(bytes);
209+
210+
}
211+
}
212+
}
213+
}
214+

0 commit comments

Comments
 (0)