Skip to content

Commit 8bd79d3

Browse files
committed
8170305: URLConnection doesn't handle HTTP/1.1 1xx (informational) messages
Reviewed-by: dfuchs, michaelm
1 parent 9cd3e35 commit 8bd79d3

File tree

2 files changed

+243
-1
lines changed

2 files changed

+243
-1
lines changed

src/java.base/share/classes/sun/net/www/http/HttpClient.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,21 @@ private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, Http
968968
code = Integer.parseInt(resp, ind, ind + 3, 10);
969969
} catch (Exception e) {}
970970

971-
if (code == HTTP_CONTINUE && ignoreContinue) {
971+
if (code == 101) {
972+
// We don't support protocol upgrade through the "Upgrade:" request header, so if a
973+
// server still unexpectedly sends a 101 response, we consider that a protocol violation
974+
// and close the connection.
975+
closeServer();
976+
logFinest("Closed connection due to unexpected 101 response");
977+
// clear off the response headers so that they don't get propagated
978+
// to the application
979+
responses.reset();
980+
throw new ProtocolException("Unexpected 101 response from server");
981+
}
982+
// ignore interim informational responses and continue to wait for final response.
983+
if ((code == HTTP_CONTINUE && ignoreContinue)
984+
|| (code >= 102 && code <= 199)) {
985+
logFinest("Ignoring interim informational 1xx response: " + code);
972986
responses.reset();
973987
return parseHTTPHeader(responses, pi, httpuc);
974988
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright (c) 2022, 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+
import java.io.IOException;
25+
import java.io.InputStream;
26+
import java.io.OutputStream;
27+
import java.net.HttpURLConnection;
28+
import java.net.InetAddress;
29+
import java.net.ProtocolException;
30+
import java.net.ServerSocket;
31+
import java.net.Socket;
32+
import java.net.URI;
33+
import java.nio.charset.StandardCharsets;
34+
35+
import jdk.test.lib.net.URIBuilder;
36+
import org.testng.Assert;
37+
import org.testng.annotations.AfterClass;
38+
import org.testng.annotations.BeforeClass;
39+
import org.testng.annotations.Test;
40+
41+
/**
42+
* @test
43+
* @bug 8170305
44+
* @summary Tests behaviour of HttpURLConnection when server responds with 1xx interim response status codes
45+
* @library /test/lib
46+
* @run testng Response1xxTest
47+
*/
48+
public class Response1xxTest {
49+
private static final String EXPECTED_RSP_BODY = "Hello World";
50+
51+
private ServerSocket serverSocket;
52+
private Http11Server server;
53+
private String requestURIBase;
54+
55+
56+
@BeforeClass
57+
public void setup() throws Exception {
58+
serverSocket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
59+
server = new Http11Server(serverSocket);
60+
new Thread(server).start();
61+
requestURIBase = URIBuilder.newBuilder().scheme("http").loopback()
62+
.port(serverSocket.getLocalPort()).build().toString();
63+
}
64+
65+
@AfterClass
66+
public void teardown() throws Exception {
67+
if (server != null) {
68+
server.stop = true;
69+
System.out.println("(HTTP 1.1) Server stop requested");
70+
}
71+
if (serverSocket != null) {
72+
serverSocket.close();
73+
System.out.println("Closed (HTTP 1.1) server socket");
74+
}
75+
}
76+
77+
private static final class Http11Server implements Runnable {
78+
private static final int CONTENT_LENGTH = EXPECTED_RSP_BODY.getBytes(StandardCharsets.UTF_8).length;
79+
80+
private static final String HTTP_1_1_RSP_200 = "HTTP/1.1 200 OK\r\n" +
81+
"Content-Length: " + CONTENT_LENGTH + "\r\n\r\n" +
82+
EXPECTED_RSP_BODY;
83+
84+
private static final String REQ_LINE_FOO = "GET /test/foo HTTP/1.1\r\n";
85+
private static final String REQ_LINE_BAR = "GET /test/bar HTTP/1.1\r\n";
86+
private static final String REQ_LINE_HELLO = "GET /test/hello HTTP/1.1\r\n";
87+
private static final String REQ_LINE_BYE = "GET /test/bye HTTP/1.1\r\n";
88+
89+
90+
private final ServerSocket serverSocket;
91+
private volatile boolean stop;
92+
93+
private Http11Server(final ServerSocket serverSocket) {
94+
this.serverSocket = serverSocket;
95+
}
96+
97+
@Override
98+
public void run() {
99+
System.out.println("Server running at " + serverSocket);
100+
while (!stop) {
101+
Socket socket = null;
102+
try {
103+
// accept a connection
104+
socket = serverSocket.accept();
105+
System.out.println("Accepted connection from client " + socket);
106+
// read request
107+
final String requestLine;
108+
try {
109+
requestLine = readRequestLine(socket);
110+
} catch (Throwable t) {
111+
// ignore connections from potential rogue client
112+
System.err.println("Ignoring connection/request from client " + socket
113+
+ " due to exception:");
114+
t.printStackTrace();
115+
// close the socket
116+
safeClose(socket);
117+
continue;
118+
}
119+
System.out.println("Received following request line from client " + socket
120+
+ " :\n" + requestLine);
121+
final int informationalResponseCode;
122+
if (requestLine.startsWith(REQ_LINE_FOO)) {
123+
// we will send intermediate/informational 102 response
124+
informationalResponseCode = 102;
125+
} else if (requestLine.startsWith(REQ_LINE_BAR)) {
126+
// we will send intermediate/informational 103 response
127+
informationalResponseCode = 103;
128+
} else if (requestLine.startsWith(REQ_LINE_HELLO)) {
129+
// we will send intermediate/informational 100 response
130+
informationalResponseCode = 100;
131+
} else if (requestLine.startsWith(REQ_LINE_BYE)) {
132+
// we will send intermediate/informational 101 response
133+
informationalResponseCode = 101;
134+
} else {
135+
// unexpected client. ignore and close the client
136+
System.err.println("Ignoring unexpected request from client " + socket);
137+
safeClose(socket);
138+
continue;
139+
}
140+
try (final OutputStream os = socket.getOutputStream()) {
141+
// send informational response headers a few times (spec allows them to
142+
// be sent multiple times)
143+
for (int i = 0; i < 3; i++) {
144+
// send 1xx response header
145+
os.write(("HTTP/1.1 " + informationalResponseCode + "\r\n\r\n")
146+
.getBytes(StandardCharsets.UTF_8));
147+
os.flush();
148+
System.out.println("Sent response code " + informationalResponseCode
149+
+ " to client " + socket);
150+
}
151+
// now send a final response
152+
System.out.println("Now sending 200 response code to client " + socket);
153+
os.write(HTTP_1_1_RSP_200.getBytes(StandardCharsets.UTF_8));
154+
os.flush();
155+
System.out.println("Sent 200 response code to client " + socket);
156+
}
157+
} catch (Throwable t) {
158+
// close the client connection
159+
safeClose(socket);
160+
// continue accepting any other client connections until we are asked to stop
161+
System.err.println("Ignoring exception in server:");
162+
t.printStackTrace();
163+
}
164+
}
165+
}
166+
167+
static String readRequestLine(final Socket sock) throws IOException {
168+
final InputStream is = sock.getInputStream();
169+
final StringBuilder sb = new StringBuilder("");
170+
byte[] buf = new byte[1024];
171+
while (!sb.toString().endsWith("\r\n\r\n")) {
172+
final int numRead = is.read(buf);
173+
if (numRead == -1) {
174+
return sb.toString();
175+
}
176+
final String part = new String(buf, 0, numRead, StandardCharsets.ISO_8859_1);
177+
sb.append(part);
178+
}
179+
return sb.toString();
180+
}
181+
182+
private static void safeClose(final Socket socket) {
183+
try {
184+
socket.close();
185+
} catch (Throwable t) {
186+
// ignore
187+
}
188+
}
189+
}
190+
191+
/**
192+
* Tests that when a HTTP/1.1 server sends intermediate 1xx response codes and then the final
193+
* response, the client (internally) will ignore those intermediate informational response codes
194+
* and only return the final response to the application
195+
*/
196+
@Test
197+
public void test1xx() throws Exception {
198+
final URI[] requestURIs = new URI[]{
199+
new URI(requestURIBase + "/test/foo"),
200+
new URI(requestURIBase + "/test/bar"),
201+
new URI(requestURIBase + "/test/hello")};
202+
for (final URI requestURI : requestURIs) {
203+
System.out.println("Issuing request to " + requestURI);
204+
final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection();
205+
final int responseCode = urlConnection.getResponseCode();
206+
Assert.assertEquals(responseCode, 200, "Unexpected response code");
207+
final String body;
208+
try (final InputStream is = urlConnection.getInputStream()) {
209+
final byte[] bytes = is.readAllBytes();
210+
body = new String(bytes, StandardCharsets.UTF_8);
211+
}
212+
Assert.assertEquals(body, EXPECTED_RSP_BODY, "Unexpected response body");
213+
}
214+
}
215+
216+
/**
217+
* Tests that when a HTTP/1.1 server sends 101 response code, when the client
218+
* didn't ask for a connection upgrade, then the request fails with an exception.
219+
*/
220+
@Test
221+
public void test101CausesRequestFailure() throws Exception {
222+
final URI requestURI = new URI(requestURIBase + "/test/bye");
223+
System.out.println("Issuing request to " + requestURI);
224+
final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection();
225+
// we expect the request to fail because the server unexpectedly sends a 101 response
226+
Assert.assertThrows(ProtocolException.class, () -> urlConnection.getResponseCode());
227+
}
228+
}

0 commit comments

Comments
 (0)