-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
8, 11, 17, 24, 25
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
Java's HttpURLConnection class demonstrates inconsistent handling of HTTP responses with status codes that violate HTTP specifications. The class incorrectly processes some invalid status codes while rejecting others, and continues to process response messages even when status codes are detected as invalid.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Per HTTP specifications, a status code must be a 3-digit integer in the range 100-599. When encountering an invalid status code:
getResponseCode() should consistently return -1
getResponseMessage() should return null
and no Exception
ACTUAL -
******************
Test with response: >HTTP/1.1 2000 OK<
Server replying to >HTTP/1.1 2000 OK<
getResponseCode: 2000
getResponseMessage: OK
getResponseCode returned: 2000, expected: -1
******************
Test with response: >HTTP/1.1 000000000000000 OK<
Server replying to >HTTP/1.1 000000000000000 OK<
getResponseCode: 0
getResponseMessage: OK
getResponseCode returned: 0, expected: -1
******************
Test with response: >HTTP/1.1 200000000000000 OK<
Server replying to >HTTP/1.1 200000000000000 OK<
getResponseCode: -1
getResponseMessage: OK
getResponseMessage returned: OK, expected: null
Exception in thread "main" java.lang.Exception: 3 sub-test(s) failed.
at Responses.main(Responses.java:164)
---------- BEGIN SOURCE ----------
import java.net.*;
import java.io.*;
import static java.net.Proxy.NO_PROXY;
public class Responses {
/*
* Test cases :-
* "Response from server" expected getResponse() and
* getResponseMessage() results
*/
static Object[][] getTests() {
return new Object[][] {
{ "HTTP/1.1 2000 OK", "-1", null },
{ "HTTP/1.1 000000000000000 OK", "-1", null },
{ "HTTP/1.1 200000000000000 OK", "-1", null }
};
}
/*
* Simple http server used by test
*
* GET /<n> HTTP/1.x results in http response with the status line
* set to geTests()[<n>][0] -- eg: GET /2 results in a response of
* "HTTP/1.1 404 "
*/
static class HttpServer implements Runnable {
final ServerSocket ss;
volatile boolean shutdown;
public HttpServer() {
try {
InetAddress loopback = InetAddress.getLoopbackAddress();
ss = new ServerSocket();
ss.bind(new InetSocketAddress(loopback, 0));
} catch (IOException ioe) {
throw new Error("Unable to create ServerSocket: " + ioe);
}
}
public int port() {
return ss.getLocalPort();
}
public String authority() {
InetAddress address = ss.getInetAddress();
String hostaddr = address.isAnyLocalAddress()
? "localhost" : address.getHostAddress();
if (hostaddr.indexOf(':') > -1) {
hostaddr = "[" + hostaddr + "]";
}
return hostaddr + ":" + port();
}
public void shutdown() throws IOException {
shutdown = true;
ss.close();
}
public void run() {
Object[][] tests = getTests();
try {
while(!shutdown) {
Socket s = ss.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(
s.getInputStream()));
String req = in.readLine();
int pos1 = req.indexOf(' ');
int pos2 = req.indexOf(' ', pos1+1);
int i = Integer.parseInt(req.substring(pos1+2, pos2));
System.out.println("Server replying to >" + tests[i][0] + "<");
PrintStream out = new PrintStream(
new BufferedOutputStream(
s.getOutputStream() ));
out.print( tests[i][0] );
out.print("\r\n");
out.print("Content-Length: 0\r\n");
out.print("Connection: close\r\n");
out.print("\r\n");
out.flush();
s.shutdownOutput();
s.close();
}
} catch (Exception e) {
if (!shutdown) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]) throws Exception {
/* start the http server */
HttpServer svr = new HttpServer();
(new Thread(svr)).start();
String authority = svr.authority();
System.out.println("Server listening on: " + authority);
/*
* Iterate through each test case and check that getResponseCode
* returns the expected result.
*/
int failures = 0;
Object tests[][] = getTests();
for (int i=0; i<tests.length; i++) {
System.out.println("******************");
System.out.println("Test with response: >" + tests[i][0] + "<");
URL url = new URL("http://" + authority + "/" + i);
HttpURLConnection http = (HttpURLConnection)url.openConnection(NO_PROXY);
try {
// test getResponseCode
//
System.out.println("getResponseCode: " + http.getResponseCode());
System.out.println("getResponseMessage: " + http.getResponseMessage());
int expectedCode = Integer.parseInt((String)tests[i][1]);
int actualCode = http.getResponseCode();
if (actualCode != expectedCode) {
System.out.println("getResponseCode returned: " + actualCode +
", expected: " + expectedCode);
failures++;
continue;
}
// test getResponseMessage
//
String expectedPhrase = (String)tests[i][2];
String actualPhrase = http.getResponseMessage();
if (actualPhrase == null && expectedPhrase == null) {
continue;
}
if (!actualPhrase.equals(expectedPhrase)) {
failures++;
System.out.println("getResponseMessage returned: " +
actualPhrase + ", expected: " + expectedPhrase);
continue;
}
} catch (IOException e) {
System.err.println("Test failed for >" + tests[i][0] + "<: " + e);
e.printStackTrace();
failures++;
}
}
/* shutdown http server */
svr.shutdown();
if (failures > 0) {
throw new Exception(failures + " sub-test(s) failed.");
}
}
---------- END SOURCE ----------
Java's HttpURLConnection class demonstrates inconsistent handling of HTTP responses with status codes that violate HTTP specifications. The class incorrectly processes some invalid status codes while rejecting others, and continues to process response messages even when status codes are detected as invalid.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Per HTTP specifications, a status code must be a 3-digit integer in the range 100-599. When encountering an invalid status code:
getResponseCode() should consistently return -1
getResponseMessage() should return null
and no Exception
ACTUAL -
******************
Test with response: >HTTP/1.1 2000 OK<
Server replying to >HTTP/1.1 2000 OK<
getResponseCode: 2000
getResponseMessage: OK
getResponseCode returned: 2000, expected: -1
******************
Test with response: >HTTP/1.1 000000000000000 OK<
Server replying to >HTTP/1.1 000000000000000 OK<
getResponseCode: 0
getResponseMessage: OK
getResponseCode returned: 0, expected: -1
******************
Test with response: >HTTP/1.1 200000000000000 OK<
Server replying to >HTTP/1.1 200000000000000 OK<
getResponseCode: -1
getResponseMessage: OK
getResponseMessage returned: OK, expected: null
Exception in thread "main" java.lang.Exception: 3 sub-test(s) failed.
at Responses.main(Responses.java:164)
---------- BEGIN SOURCE ----------
import java.net.*;
import java.io.*;
import static java.net.Proxy.NO_PROXY;
public class Responses {
/*
* Test cases :-
* "Response from server" expected getResponse() and
* getResponseMessage() results
*/
static Object[][] getTests() {
return new Object[][] {
{ "HTTP/1.1 2000 OK", "-1", null },
{ "HTTP/1.1 000000000000000 OK", "-1", null },
{ "HTTP/1.1 200000000000000 OK", "-1", null }
};
}
/*
* Simple http server used by test
*
* GET /<n> HTTP/1.x results in http response with the status line
* set to geTests()[<n>][0] -- eg: GET /2 results in a response of
* "HTTP/1.1 404 "
*/
static class HttpServer implements Runnable {
final ServerSocket ss;
volatile boolean shutdown;
public HttpServer() {
try {
InetAddress loopback = InetAddress.getLoopbackAddress();
ss = new ServerSocket();
ss.bind(new InetSocketAddress(loopback, 0));
} catch (IOException ioe) {
throw new Error("Unable to create ServerSocket: " + ioe);
}
}
public int port() {
return ss.getLocalPort();
}
public String authority() {
InetAddress address = ss.getInetAddress();
String hostaddr = address.isAnyLocalAddress()
? "localhost" : address.getHostAddress();
if (hostaddr.indexOf(':') > -1) {
hostaddr = "[" + hostaddr + "]";
}
return hostaddr + ":" + port();
}
public void shutdown() throws IOException {
shutdown = true;
ss.close();
}
public void run() {
Object[][] tests = getTests();
try {
while(!shutdown) {
Socket s = ss.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(
s.getInputStream()));
String req = in.readLine();
int pos1 = req.indexOf(' ');
int pos2 = req.indexOf(' ', pos1+1);
int i = Integer.parseInt(req.substring(pos1+2, pos2));
System.out.println("Server replying to >" + tests[i][0] + "<");
PrintStream out = new PrintStream(
new BufferedOutputStream(
s.getOutputStream() ));
out.print( tests[i][0] );
out.print("\r\n");
out.print("Content-Length: 0\r\n");
out.print("Connection: close\r\n");
out.print("\r\n");
out.flush();
s.shutdownOutput();
s.close();
}
} catch (Exception e) {
if (!shutdown) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]) throws Exception {
/* start the http server */
HttpServer svr = new HttpServer();
(new Thread(svr)).start();
String authority = svr.authority();
System.out.println("Server listening on: " + authority);
/*
* Iterate through each test case and check that getResponseCode
* returns the expected result.
*/
int failures = 0;
Object tests[][] = getTests();
for (int i=0; i<tests.length; i++) {
System.out.println("******************");
System.out.println("Test with response: >" + tests[i][0] + "<");
URL url = new URL("http://" + authority + "/" + i);
HttpURLConnection http = (HttpURLConnection)url.openConnection(NO_PROXY);
try {
// test getResponseCode
//
System.out.println("getResponseCode: " + http.getResponseCode());
System.out.println("getResponseMessage: " + http.getResponseMessage());
int expectedCode = Integer.parseInt((String)tests[i][1]);
int actualCode = http.getResponseCode();
if (actualCode != expectedCode) {
System.out.println("getResponseCode returned: " + actualCode +
", expected: " + expectedCode);
failures++;
continue;
}
// test getResponseMessage
//
String expectedPhrase = (String)tests[i][2];
String actualPhrase = http.getResponseMessage();
if (actualPhrase == null && expectedPhrase == null) {
continue;
}
if (!actualPhrase.equals(expectedPhrase)) {
failures++;
System.out.println("getResponseMessage returned: " +
actualPhrase + ", expected: " + expectedPhrase);
continue;
}
} catch (IOException e) {
System.err.println("Test failed for >" + tests[i][0] + "<: " + e);
e.printStackTrace();
failures++;
}
}
/* shutdown http server */
svr.shutdown();
if (failures > 0) {
throw new Exception(failures + " sub-test(s) failed.");
}
}
---------- END SOURCE ----------