From 38575673e4726ea68fa4f4fde22603a8927ca09b Mon Sep 17 00:00:00 2001 From: jwilson Date: Mon, 18 Feb 2013 04:18:53 -0500 Subject: [PATCH] Optionally follow redirects from HTTPS to HTTP and vice versa. The API is imperfect because HttpURLConnection doesn't expose any SSL information, and because HttpsURLConnection won't necessarily have a secure final request. --- .../com/squareup/okhttp/OkHttpClient.java | 87 ++++++-- .../okhttp/internal/http/HttpEngine.java | 35 +--- .../internal/http/HttpURLConnectionImpl.java | 61 +++--- .../internal/http/HttpsURLConnectionImpl.java | 191 +++++++++--------- .../internal/http/URLConnectionTest.java | 45 +++++ 5 files changed, 256 insertions(+), 163 deletions(-) diff --git a/src/main/java/com/squareup/okhttp/OkHttpClient.java b/src/main/java/com/squareup/okhttp/OkHttpClient.java index 4cc5ec62b..d21cdb710 100644 --- a/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -36,6 +36,7 @@ public final class OkHttpClient { private SSLSocketFactory sslSocketFactory; private HostnameVerifier hostnameVerifier; private ConnectionPool connectionPool; + private boolean followProtocolRedirects = true; /** * Sets the HTTP proxy that will be used by connections created by this @@ -48,6 +49,10 @@ public final class OkHttpClient { return this; } + public Proxy getProxy() { + return proxy; + } + /** * Sets the proxy selection policy to be used if no {@link #setProxy proxy} * is specified explicitly. The proxy selector may return multiple proxies; @@ -62,6 +67,10 @@ public final class OkHttpClient { return this; } + public ProxySelector getProxySelector() { + return proxySelector; + } + /** * Sets the cookie handler to be used to read outgoing cookies and write * incoming cookies. @@ -74,6 +83,10 @@ public final class OkHttpClient { return this; } + public CookieHandler getCookieHandler() { + return cookieHandler; + } + /** * Sets the response cache to be used to read and write cached responses. * @@ -85,6 +98,10 @@ public final class OkHttpClient { return this; } + public ResponseCache getResponseCache() { + return responseCache; + } + /** * Sets the socket factory used to secure HTTPS connections. * @@ -96,6 +113,10 @@ public final class OkHttpClient { return this; } + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + /** * Sets the verifier used to confirm that response certificates apply to * requested hostnames for HTTPS connections. @@ -108,6 +129,10 @@ public final class OkHttpClient { return this; } + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + /** * Sets the connection pool used to recycle HTTP and HTTPS connections. * @@ -119,31 +144,55 @@ public final class OkHttpClient { return this; } - public HttpURLConnection open(URL url) { - ProxySelector proxySelector = - this.proxySelector != null ? this.proxySelector : ProxySelector.getDefault(); - CookieHandler cookieHandler = - this.cookieHandler != null ? this.cookieHandler : CookieHandler.getDefault(); - ResponseCache responseCache = - this.responseCache != null ? this.responseCache : ResponseCache.getDefault(); - ConnectionPool connectionPool = - this.connectionPool != null ? this.connectionPool : ConnectionPool.getDefault(); + public ConnectionPool getConnectionPool() { + return connectionPool; + } + /** + * Configure this client to follow redirects from HTTPS to HTTP and from HTTP + * to HTTPS. + * + *

If unset, protocol redirects will be followed. This is different than + * the built-in {@code HttpURLConnection}'s default. + */ + public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) { + this.followProtocolRedirects = followProtocolRedirects; + return this; + } + + public boolean getFollowProtocolRedirects() { + return followProtocolRedirects; + } + + public HttpURLConnection open(URL url) { String protocol = url.getProtocol(); if (protocol.equals("http")) { - return new HttpURLConnectionImpl(url, 80, proxy, proxySelector, cookieHandler, responseCache, - connectionPool); + return new HttpURLConnectionImpl(url, copyWithDefaults()); } else if (protocol.equals("https")) { - HttpsURLConnectionImpl result = - new HttpsURLConnectionImpl(url, 443, proxy, proxySelector, cookieHandler, responseCache, - connectionPool); - result.setSSLSocketFactory(this.sslSocketFactory != null ? this.sslSocketFactory - : HttpsURLConnection.getDefaultSSLSocketFactory()); - result.setHostnameVerifier(this.hostnameVerifier != null ? this.hostnameVerifier - : HttpsURLConnection.getDefaultHostnameVerifier()); - return result; + return new HttpsURLConnectionImpl(url, copyWithDefaults()); } else { throw new IllegalArgumentException("Unexpected protocol: " + protocol); } } + + /** + * Returns a copy of this OkHttpClient that uses the system-wide default for + * each field that hasn't been explicitly configured. + */ + private OkHttpClient copyWithDefaults() { + OkHttpClient result = new OkHttpClient(); + result.proxy = proxy; + result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault(); + result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault(); + result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault(); + result.sslSocketFactory = sslSocketFactory != null + ? sslSocketFactory + : HttpsURLConnection.getDefaultSSLSocketFactory(); + result.hostnameVerifier = hostnameVerifier != null + ? hostnameVerifier + : HttpsURLConnection.getDefaultHostnameVerifier(); + result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault(); + result.followProtocolRedirects = followProtocolRedirects; + return result; + } } diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index 947bb690d..8816bc41a 100644 --- a/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -32,7 +32,6 @@ import java.io.OutputStream; import java.net.CacheRequest; import java.net.CacheResponse; import java.net.CookieHandler; -import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; @@ -272,8 +271,14 @@ public class HttpEngine { if (uriHost == null) { throw new UnknownHostException(uri.toString()); } - Address address = new Address(uriHost, getEffectivePort(uri), getSslSocketFactory(), - getHostnameVerifier(), policy.requestedProxy); + SSLSocketFactory sslSocketFactory = null; + HostnameVerifier hostnameVerifier = null; + if (uri.getScheme().equalsIgnoreCase("https")) { + sslSocketFactory = policy.sslSocketFactory; + hostnameVerifier = policy.hostnameVerifier; + } + Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory, + hostnameVerifier, policy.requestedProxy); routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT); } @@ -391,11 +396,7 @@ public class HttpEngine { } // Offer this request to the cache. - cacheRequest = policy.responseCache.put(uri, getHttpConnectionToCache()); - } - - protected HttpURLConnection getHttpConnectionToCache() { - return policy; + cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache()); } /** @@ -576,22 +577,6 @@ public class HttpEngine { : connection.getProxy().type() == Proxy.Type.HTTP; // A proxy was selected. } - /** - * Returns the SSL configuration for connections created by this engine. - * We cannot reuse HTTPS connections if the socket factory has changed. - */ - protected SSLSocketFactory getSslSocketFactory() { - return null; - } - - /** - * Returns the hostname verifier for connections created by this engine. We - * cannot reuse HTTPS connections if the hostname verifier has changed. - */ - protected HostnameVerifier getHostnameVerifier() { - return null; - } - public static String getDefaultUserAgent() { String agent = System.getProperty("http.agent"); return agent != null ? agent : ("Java" + System.getProperty("java.version")); @@ -651,7 +636,7 @@ public class HttpEngine { if (policy.responseCache instanceof OkResponseCache) { OkResponseCache httpResponseCache = (OkResponseCache) policy.responseCache; httpResponseCache.trackConditionalCacheHit(); - httpResponseCache.update(cacheResponse, getHttpConnectionToCache()); + httpResponseCache.update(cacheResponse, policy.getHttpConnectionToCache()); } return; } else { diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index 051869d28..ff0a2c421 100644 --- a/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java @@ -19,6 +19,7 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.Connection; import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.internal.Util; import java.io.FileNotFoundException; import java.io.IOException; @@ -38,7 +39,9 @@ import java.security.Permission; import java.security.cert.CertificateException; import java.util.List; import java.util.Map; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocketFactory; import static com.squareup.okhttp.internal.Util.getEffectivePort; @@ -63,7 +66,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { */ private static final int MAX_REDIRECTS = 20; - private final int defaultPort; + private final boolean followProtocolRedirects; /** The proxy requested by the client, or null for a proxy to be selected automatically. */ final Proxy requestedProxy; @@ -72,6 +75,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection { final CookieHandler cookieHandler; final ResponseCache responseCache; final ConnectionPool connectionPool; + /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */ + SSLSocketFactory sslSocketFactory; + HostnameVerifier hostnameVerifier; private final RawHeaders rawRequestHeaders = new RawHeaders(); @@ -80,15 +86,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection { protected IOException httpEngineFailure; protected HttpEngine httpEngine; - public HttpURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector, - CookieHandler cookieHandler, ResponseCache responseCache, ConnectionPool connectionPool) { + public HttpURLConnectionImpl(URL url, OkHttpClient client) { super(url); - this.defaultPort = defaultPort; - this.requestedProxy = proxy; - this.proxySelector = proxySelector; - this.cookieHandler = cookieHandler; - this.responseCache = responseCache; - this.connectionPool = connectionPool; + this.followProtocolRedirects = client.getFollowProtocolRedirects(); + this.requestedProxy = client.getProxy(); + this.proxySelector = client.getProxySelector(); + this.cookieHandler = client.getCookieHandler(); + this.responseCache = client.getResponseCache(); + this.connectionPool = client.getConnectionPool(); + this.sslSocketFactory = client.getSslSocketFactory(); + this.hostnameVerifier = client.getHostnameVerifier(); } @Override public final void connect() throws IOException { @@ -258,13 +265,20 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } - /** - * Create a new HTTP engine. This hook method is non-final so it can be - * overridden by HttpsURLConnectionImpl. - */ - protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, + protected HttpURLConnection getHttpConnectionToCache() { + return this; + } + + private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, Connection connection, RetryableOutputStream requestBody) throws IOException { - return new HttpEngine(this, method, requestHeaders, connection, requestBody); + if (url.getProtocol().equals("http")) { + return new HttpEngine(this, method, requestHeaders, connection, requestBody); + } else if (url.getProtocol().equals("https")) { + return new HttpsURLConnectionImpl.HttpsEngine( + this, method, requestHeaders, connection, requestBody); + } else { + throw new AssertionError(); + } } /** @@ -413,11 +427,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } URL previousUrl = url; url = new URL(previousUrl, location); - if (!previousUrl.getProtocol().equals(url.getProtocol())) { - return Retry.NONE; // the scheme changed; don't retry. + if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) { + return Retry.NONE; // Don't follow redirects to unsupported protocols. } - if (previousUrl.getHost().equals(url.getHost()) - && getEffectivePort(previousUrl) == getEffectivePort(url)) { + boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol()); + if (!sameProtocol && !followProtocolRedirects) { + return Retry.NONE; // This client doesn't follow redirects across protocols. + } + boolean sameHost = previousUrl.getHost().equals(url.getHost()); + boolean samePort = getEffectivePort(previousUrl) == getEffectivePort(url); + if (sameHost && samePort && sameProtocol) { return Retry.SAME_CONNECTION; } else { return Retry.DIFFERENT_CONNECTION; @@ -428,10 +447,6 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } - final int getDefaultPort() { - return defaultPort; - } - /** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */ final int getFixedContentLength() { return fixedContentLength; diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java index 821290311..c224270b0 100644 --- a/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java @@ -17,18 +17,14 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.Connection; -import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.TunnelRequest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.CacheResponse; -import java.net.CookieHandler; import java.net.HttpURLConnection; import java.net.ProtocolException; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.ResponseCache; import java.net.SecureCacheResponse; import java.net.URL; import java.security.Permission; @@ -49,73 +45,84 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection { /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */ private final HttpUrlConnectionDelegate delegate; - public HttpsURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector, - CookieHandler cookieHandler, ResponseCache responseCache, ConnectionPool connectionPool) { + public HttpsURLConnectionImpl(URL url, OkHttpClient client) { super(url); - delegate = new HttpUrlConnectionDelegate(url, defaultPort, proxy, proxySelector, cookieHandler, - responseCache, connectionPool); + delegate = new HttpUrlConnectionDelegate(url, client); } - private void checkConnected() { - if (delegate.getSSLSocket() == null) { - throw new IllegalStateException("Connection has not yet been established"); + @Override public String getCipherSuite() { + SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); + if (cacheResponse != null) { + return cacheResponse.getCipherSuite(); } + SSLSocket sslSocket = getSslSocket(); + if (sslSocket != null) { + return sslSocket.getSession().getCipherSuite(); + } + return null; + } + + @Override public Certificate[] getLocalCertificates() { + SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); + if (cacheResponse != null) { + List result = cacheResponse.getLocalCertificateChain(); + return result != null ? result.toArray(new Certificate[result.size()]) : null; + } + SSLSocket sslSocket = getSslSocket(); + if (sslSocket != null) { + return sslSocket.getSession().getLocalCertificates(); + } + return null; + } + + @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { + SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); + if (cacheResponse != null) { + List result = cacheResponse.getServerCertificateChain(); + return result != null ? result.toArray(new Certificate[result.size()]) : null; + } + SSLSocket sslSocket = getSslSocket(); + if (sslSocket != null) { + return sslSocket.getSession().getPeerCertificates(); + } + return null; + } + + @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); + if (cacheResponse != null) { + return cacheResponse.getPeerPrincipal(); + } + SSLSocket sslSocket = getSslSocket(); + if (sslSocket != null) { + return sslSocket.getSession().getPeerPrincipal(); + } + return null; + } + + @Override public Principal getLocalPrincipal() { + SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); + if (cacheResponse != null) { + return cacheResponse.getLocalPrincipal(); + } + SSLSocket sslSocket = getSslSocket(); + if (sslSocket != null) { + return sslSocket.getSession().getLocalPrincipal(); + } + return null; } HttpEngine getHttpEngine() { return delegate.getHttpEngine(); } - @Override - public String getCipherSuite() { - SecureCacheResponse cacheResponse = delegate.getCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getCipherSuite(); + private SSLSocket getSslSocket() { + if (delegate.httpEngine == null || delegate.httpEngine.sentRequestMillis == -1) { + throw new IllegalStateException("Connection has not yet been established"); } - checkConnected(); - return delegate.getSSLSocket().getSession().getCipherSuite(); - } - - @Override - public Certificate[] getLocalCertificates() { - SecureCacheResponse cacheResponse = delegate.getCacheResponse(); - if (cacheResponse != null) { - List result = cacheResponse.getLocalCertificateChain(); - return result != null ? result.toArray(new Certificate[result.size()]) : null; - } - checkConnected(); - return delegate.getSSLSocket().getSession().getLocalCertificates(); - } - - @Override - public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { - SecureCacheResponse cacheResponse = delegate.getCacheResponse(); - if (cacheResponse != null) { - List result = cacheResponse.getServerCertificateChain(); - return result != null ? result.toArray(new Certificate[result.size()]) : null; - } - checkConnected(); - return delegate.getSSLSocket().getSession().getPeerCertificates(); - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - SecureCacheResponse cacheResponse = delegate.getCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getPeerPrincipal(); - } - checkConnected(); - return delegate.getSSLSocket().getSession().getPeerPrincipal(); - } - - @Override - public Principal getLocalPrincipal() { - SecureCacheResponse cacheResponse = delegate.getCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getLocalPrincipal(); - } - checkConnected(); - return delegate.getSSLSocket().getSession().getLocalPrincipal(); + return delegate.httpEngine instanceof HttpsEngine + ? ((HttpsEngine) delegate.httpEngine).sslSocket + : null; // Not HTTPS! Probably an https:// to http:// redirect. } @Override @@ -375,49 +382,53 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection { delegate.setChunkedStreamingMode(chunkLength); } + @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + delegate.hostnameVerifier = hostnameVerifier; + } + + @Override public HostnameVerifier getHostnameVerifier() { + return delegate.hostnameVerifier; + } + + @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { + delegate.sslSocketFactory = sslSocketFactory; + } + + @Override public SSLSocketFactory getSSLSocketFactory() { + return delegate.sslSocketFactory; + } + private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { - private HttpUrlConnectionDelegate(URL url, int defaultPort, Proxy proxy, - ProxySelector proxySelector, CookieHandler cookieHandler, ResponseCache responseCache, - ConnectionPool connectionPool) { - super(url, defaultPort, proxy, proxySelector, cookieHandler, responseCache, connectionPool); + private HttpUrlConnectionDelegate(URL url, OkHttpClient client) { + super(url, client); } - @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, - Connection connection, RetryableOutputStream requestBody) throws IOException { - return new HttpsEngine(this, method, requestHeaders, connection, requestBody, - HttpsURLConnectionImpl.this); + @Override protected HttpURLConnection getHttpConnectionToCache() { + return HttpsURLConnectionImpl.this; } - public SecureCacheResponse getCacheResponse() { - HttpsEngine engine = (HttpsEngine) httpEngine; - return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null; - } - - public SSLSocket getSSLSocket() { - HttpsEngine engine = (HttpsEngine) httpEngine; - return engine != null ? engine.sslSocket : null; + public SecureCacheResponse getSecureCacheResponse() { + return httpEngine instanceof HttpsEngine + ? (SecureCacheResponse) httpEngine.getCacheResponse() + : null; } } - private static final class HttpsEngine extends HttpEngine { + public static final class HttpsEngine extends HttpEngine { /** * Stash of HttpsEngine.connection.socket to implement requests like * {@link #getCipherSuite} even after the connection has been recycled. */ private SSLSocket sslSocket; - private final HttpsURLConnectionImpl enclosing; - /** * @param policy the HttpURLConnectionImpl with connection configuration - * @param enclosing the HttpsURLConnection with HTTPS features */ - private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, - Connection connection, RetryableOutputStream requestBody, HttpsURLConnectionImpl enclosing) + public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, + Connection connection, RetryableOutputStream requestBody) throws IOException { super(policy, method, requestHeaders, connection, requestBody); this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null; - this.enclosing = enclosing; } @Override protected void connected(Connection connection) { @@ -433,18 +444,6 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection { return false; } - @Override protected SSLSocketFactory getSslSocketFactory() { - return enclosing.getSSLSocketFactory(); - } - - @Override protected HostnameVerifier getHostnameVerifier() { - return enclosing.getHostnameVerifier(); - } - - @Override protected HttpURLConnection getHttpConnectionToCache() { - return enclosing; - } - @Override protected TunnelRequest getTunnelConfig() { String userAgent = requestHeaders.getUserAgent(); if (userAgent == null) { diff --git a/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index 6b348c241..9a3572aaa 100644 --- a/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -61,6 +61,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; @@ -1540,6 +1541,7 @@ public final class URLConnectionTest { .setBody("This page has moved!")); server.play(); + client.setFollowProtocolRedirects(false); client.setSSLSocketFactory(sslContext.getSocketFactory()); client.setHostnameVerifier(new RecordingHostnameVerifier()); HttpURLConnection connection = client.open(server.getUrl("/")); @@ -1552,10 +1554,53 @@ public final class URLConnectionTest { .setBody("This page has moved!")); server.play(); + client.setFollowProtocolRedirects(false); HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); } + @Test public void redirectedFromHttpsToHttpFollowingProtocolRedirects() throws Exception { + server2 = new MockWebServer(); + server2.enqueue(new MockResponse().setBody("This is insecure HTTP!")); + server2.play(); + + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: " + server2.getUrl("/")) + .setBody("This page has moved!")); + server.play(); + + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setFollowProtocolRedirects(true); + HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/")); + assertContent("This is insecure HTTP!", connection); + assertNull(connection.getCipherSuite()); + assertNull(connection.getLocalCertificates()); + assertNull(connection.getServerCertificates()); + assertNull(connection.getPeerPrincipal()); + assertNull(connection.getLocalPrincipal()); + } + + @Test public void redirectedFromHttpToHttpsFollowingProtocolRedirects() throws Exception { + server2 = new MockWebServer(); + server2.useHttps(sslContext.getSocketFactory(), false); + server2.enqueue(new MockResponse().setBody("This is secure HTTPS!")); + server2.play(); + + server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: " + server2.getUrl("/")) + .setBody("This page has moved!")); + server.play(); + + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setFollowProtocolRedirects(true); + HttpURLConnection connection = client.open(server.getUrl("/")); + assertContent("This is secure HTTPS!", connection); + assertFalse(connection instanceof HttpsURLConnection); + } + @Test public void redirectToAnotherOriginServer() throws Exception { redirectToAnotherOriginServer(false); }