From d4bddd7d1fb7b1b7f0836648228235c6e4b56a18 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Thu, 16 Sep 2010 14:57:33 -0700 Subject: [PATCH] Fix two HTTP issues that came up when writing HttpURLConnection docs. We're now more careful about which headers are sent to HTTP proxies. And getContentEncoding() is better documented. Change-Id: I04241f99c2f32c25ba005fbd6ff9ef7236c3c9d3 --- luni/src/main/java/java/net/HttpURLConnection.java | 11 ++++ .../www/protocol/http/HttpURLConnectionImpl.java | 67 +++++++++++++++++----- .../java/libcore/java/net/URLConnectionTest.java | 60 +++++++++++++++++-- 3 files changed, 120 insertions(+), 18 deletions(-) diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java index 84251ac9..ab272c73 100644 --- a/luni/src/main/java/java/net/HttpURLConnection.java +++ b/luni/src/main/java/java/net/HttpURLConnection.java @@ -628,6 +628,17 @@ public abstract class HttpURLConnection extends URLConnection { public abstract boolean usingProxy(); /** + * Returns the encoding used to transmit the response body over the network. + * This is null or "identity" if the content was not encoded, or "gzip" if + * the body was gzip compressed. Most callers will be more interested in the + * {@link #getContentType() content type}, which may also include the + * content's character encoding. + */ + @Override public String getContentEncoding() { + return super.getContentEncoding(); // overridden for Javadoc only + } + + /** * Returns whether this connection follows redirects. * * @return {@code true} if this connection follows redirects, false diff --git a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java index cc62b028..f9f46ad8 100644 --- a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java +++ b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java @@ -771,13 +771,13 @@ public class HttpURLConnectionImpl extends HttpURLConnection { * value. */ private void writeRequestHeaders(OutputStream out) throws IOException { - prepareRequestHeaders(); + Header header = prepareRequestHeaders(); StringBuilder result = new StringBuilder(256); - result.append(requestHeader.getStatusLine()).append("\r\n"); - for (int i = 0; i < requestHeader.length(); i++) { - String key = requestHeader.getKey(i); - String value = requestHeader.get(i); + result.append(header.getStatusLine()).append("\r\n"); + for (int i = 0; i < header.length(); i++) { + String key = header.getKey(i); + String value = header.get(i); if (key != null) { result.append(key).append(": ").append(value).append("\r\n"); } @@ -794,16 +794,45 @@ public class HttpURLConnectionImpl extends HttpURLConnection { *

This client doesn't specify a default {@code Accept} header because it * doesn't know what content types the application is interested in. */ - private void prepareRequestHeaders() throws IOException { - String protocol = (httpVersion == 0) ? "HTTP/1.0" : "HTTP/1.1"; - requestHeader.setStatusLine(method + " " + requestString() + " " + protocol); + private Header prepareRequestHeaders() throws IOException { + /* + * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), + * send only the minimum set of headers. This avoids sending potentially + * sensitive data like HTTP cookies to the proxy unencrypted. + */ + if (method == CONNECT) { + Header proxyHeader = new Header(); + proxyHeader.setStatusLine(getStatusLine()); + + // always set Host and User-Agent + String host = requestHeader.get("Host"); + if (host == null) { + host = getOriginAddress(url); + } + proxyHeader.set("Host", host); - if (requestHeader.get("User-Agent") == null) { - String agent = getSystemProperty("http.agent"); - if (agent == null) { - agent = "Java" + getSystemProperty("java.version"); + String userAgent = requestHeader.get("User-Agent"); + if (userAgent == null) { + userAgent = getDefaultUserAgent(); } - requestHeader.add("User-Agent", agent); + proxyHeader.set("User-Agent", userAgent); + + // copy over the Proxy-Authorization header if it exists + String proxyAuthorization = requestHeader.get("Proxy-Authorization"); + if (proxyAuthorization != null) { + proxyHeader.set("Proxy-Authorization", proxyAuthorization); + } + + // Always set the Proxy-Connection to Keep-Alive for the benefit of + // HTTP/1.0 proxies like Squid. + proxyHeader.set("Proxy-Connection", "Keep-Alive"); + return proxyHeader; + } + + requestHeader.setStatusLine(getStatusLine()); + + if (requestHeader.get("User-Agent") == null) { + requestHeader.add("User-Agent", getDefaultUserAgent()); } if (requestHeader.get("Host") == null) { @@ -843,6 +872,18 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } } + + return requestHeader; + } + + private String getStatusLine() { + String protocol = (httpVersion == 0) ? "HTTP/1.0" : "HTTP/1.1"; + return method + " " + requestString() + " " + protocol; + } + + private String getDefaultUserAgent() { + String agent = getSystemProperty("http.agent"); + return agent != null ? agent : ("Java" + getSystemProperty("java.version")); } private boolean hasConnectionCloseHeader() { diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index ef857c82..9dc1cdfe 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -69,6 +69,12 @@ public class URLConnectionTest extends junit.framework.TestCase { } }; + private final HostnameVerifier ALWAYS_TRUST_VERIFIER = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + private MockWebServer server = new MockWebServer(); @Override protected void tearDown() throws Exception { @@ -485,11 +491,7 @@ public class URLConnectionTest extends junit.framework.TestCase { HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( server.toProxyAddress()); connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); - connection.setHostnameVerifier(new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); + connection.setHostnameVerifier(ALWAYS_TRUST_VERIFIER); assertContent("this response comes via a secure proxy", connection); @@ -503,6 +505,54 @@ public class URLConnectionTest extends junit.framework.TestCase { assertContains(get.getHeaders(), "Host: android.com"); } + /** + * Test which headers are sent unencrypted to the HTTP proxy. + */ + public void testProxyConnectIncludesProxyHeadersOnly() + throws IOException, InterruptedException { + TestSSLContext testSSLContext = TestSSLContext.create(); + + server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); + server.enqueue(new MockResponse().clearHeaders()); // for CONNECT + server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); + server.play(); + + URL url = new URL("https://android.com/foo"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( + server.toProxyAddress()); + connection.addRequestProperty("Private", "Secret"); + connection.addRequestProperty("Proxy-Authorization", "bar"); + connection.addRequestProperty("User-Agent", "baz"); + connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); + connection.setHostnameVerifier(ALWAYS_TRUST_VERIFIER); + assertContent("encrypted response from the origin server", connection); + + RecordedRequest connect = server.takeRequest(); + assertContainsNoneMatching(connect.getHeaders(), "Private.*"); + assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); + assertContains(connect.getHeaders(), "User-Agent: baz"); + assertContains(connect.getHeaders(), "Host: android.com"); + assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); + + RecordedRequest get = server.takeRequest(); + assertContains(get.getHeaders(), "Private: Secret"); + } + + public void testDisconnectedConnection() throws IOException { + server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); + server.play(); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + InputStream in = connection.getInputStream(); + assertEquals('A', (char) in.read()); + connection.disconnect(); + try { + in.read(); + fail("Expected a connection closed exception"); + } catch (IOException expected) { + } + } + public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { testResponseCaching(TransferKind.FIXED_LENGTH); } -- 2.11.0