diff --git a/okhttp/src/main/java/com/squareup/okhttp/Address.java b/okhttp/src/main/java/com/squareup/okhttp/Address.java index cd41ac99c..a787a94a6 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Address.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Address.java @@ -38,16 +38,20 @@ public final class Address { final int uriPort; final SSLSocketFactory sslSocketFactory; final HostnameVerifier hostnameVerifier; + final OkAuthenticator authenticator; public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException { + HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy) + throws UnknownHostException { if (uriHost == null) throw new NullPointerException("uriHost == null"); if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort); + if (authenticator == null) throw new IllegalArgumentException("authenticator == null"); this.proxy = proxy; this.uriHost = uriHost; this.uriPort = uriPort; this.sslSocketFactory = sslSocketFactory; this.hostnameVerifier = hostnameVerifier; + this.authenticator = authenticator; } /** Returns the hostname of the origin server. */ @@ -79,6 +83,14 @@ public final class Address { return hostnameVerifier; } + + /** + * Returns the client's authenticator. This method never returns null. + */ + public OkAuthenticator getAuthenticator() { + return authenticator; + } + /** * Returns this address's explicitly-specified HTTP proxy, or null to * delegate to the HTTP client's proxy selector. @@ -94,7 +106,8 @@ public final class Address { && this.uriHost.equals(that.uriHost) && this.uriPort == that.uriPort && equal(this.sslSocketFactory, that.sslSocketFactory) - && equal(this.hostnameVerifier, that.hostnameVerifier); + && equal(this.hostnameVerifier, that.hostnameVerifier) + && equal(this.authenticator, that.authenticator); } return false; } @@ -105,6 +118,7 @@ public final class Address { result = 31 * result + uriPort; result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0); + result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0); result = 31 * result + (proxy != null ? proxy.hashCode() : 0); return result; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index 6a6c84dc7..e9ccb9806 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -275,8 +275,9 @@ public final class Connection implements Closeable { case HTTP_PROXY_AUTH: requestHeaders = new RawHeaders(requestHeaders); URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/"); - boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH, - responseHeaders, requestHeaders, route.proxy, url); + boolean credentialsFound = HttpAuthenticator.processAuthHeader( + route.address.authenticator, HTTP_PROXY_AUTH, responseHeaders, requestHeaders, + route.proxy, url); if (credentialsFound) { continue; } else { diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkAuthenticator.java b/okhttp/src/main/java/com/squareup/okhttp/OkAuthenticator.java new file mode 100644 index 000000000..a50541924 --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/OkAuthenticator.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.okhttp; + +import com.squareup.okhttp.internal.Base64; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.util.List; + +/** + * Responds to authentication challenges from the remote web or proxy server by + * returning credentials. + */ +public interface OkAuthenticator { + /** + * Returns a credential that satisfies the authentication challenge made by + * {@code url}. Returns null if the challenge cannot be satisfied. This method + * is called in response to an HTTP 401 unauthorized status code sent by the + * origin server. + * + * @param challenges parsed "WWW-Authenticate" challenge headers from the HTTP + * response. + */ + Credential authenticate(Proxy proxy, URL url, List challenges) throws IOException; + + /** + * Returns a credential that satisfies the authentication challenge made by + * {@code proxy}. Returns null if the challenge cannot be satisfied. This + * method is called in response to an HTTP 401 unauthorized status code sent + * by the proxy server. + * + * @param challenges parsed "Proxy-Authenticate" challenge headers from the + * HTTP response. + */ + Credential authenticateProxy(Proxy proxy, URL url, List challenges) throws IOException; + + /** An RFC 2617 challenge. */ + public final class Challenge { + private final String scheme; + private final String realm; + + public Challenge(String scheme, String realm) { + this.scheme = scheme; + this.realm = realm; + } + + /** Returns the authentication scheme, like {@code Basic}. */ + public String getScheme() { + return scheme; + } + + /** Returns the protection space. */ + public String getRealm() { + return realm; + } + + @Override public boolean equals(Object o) { + return o instanceof Challenge + && ((Challenge) o).scheme.equals(scheme) + && ((Challenge) o).realm.equals(realm); + } + + @Override public int hashCode() { + return scheme.hashCode() + 31 * realm.hashCode(); + } + + @Override public String toString() { + return scheme + " realm=\"" + realm + "\""; + } + } + + /** An RFC 2617 credential. */ + public final class Credential { + private final String headerValue; + + private Credential(String headerValue) { + this.headerValue = headerValue; + } + + /** Returns an auth credential for the Basic scheme. */ + public static Credential basic(String userName, String password) { + try { + String usernameAndPassword = userName + ":" + password; + byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1"); + String encoded = Base64.encode(bytes); + return new Credential("Basic " + encoded); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(); + } + } + + public String getHeaderValue() { + return headerValue; + } + + @Override public boolean equals(Object o) { + return o instanceof Credential && ((Credential) o).headerValue.equals(headerValue); + } + + @Override public int hashCode() { + return headerValue.hashCode(); + } + + @Override public String toString() { + return headerValue; + } + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index 7834bd6b2..41c65e195 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -15,10 +15,12 @@ */ package com.squareup.okhttp; +import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; import com.squareup.okhttp.internal.http.OkResponseCache; import com.squareup.okhttp.internal.http.OkResponseCacheAdapter; +import java.net.Authenticator; import java.net.CookieHandler; import java.net.HttpURLConnection; import java.net.Proxy; @@ -41,6 +43,7 @@ public final class OkHttpClient { private ResponseCache responseCache; private SSLSocketFactory sslSocketFactory; private HostnameVerifier hostnameVerifier; + private OkAuthenticator authenticator; private ConnectionPool connectionPool; private boolean followProtocolRedirects = true; @@ -149,6 +152,22 @@ public final class OkHttpClient { return hostnameVerifier; } + /** + * Sets the authenticator used to respond to challenges from the remote web + * server or proxy server. + * + *

If unset, the {@link Authenticator#setDefault system-wide default} + * authenticator will be used. + */ + public OkHttpClient setAuthenticator(OkAuthenticator authenticator) { + this.authenticator = authenticator; + return this; + } + + public OkAuthenticator getAuthenticator() { + return authenticator; + } + /** * Sets the connection pool used to recycle HTTP and HTTPS connections. * @@ -209,6 +228,9 @@ public final class OkHttpClient { result.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : HttpsURLConnection.getDefaultHostnameVerifier(); + result.authenticator = authenticator != null + ? authenticator + : HttpAuthenticator.SYSTEM_DEFAULT; result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault(); result.followProtocolRedirects = followProtocolRedirects; return result; diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java index 4ccd12aa9..566431e33 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java @@ -16,7 +16,8 @@ */ package com.squareup.okhttp.internal.http; -import com.squareup.okhttp.internal.Base64; +import com.squareup.okhttp.OkAuthenticator; +import com.squareup.okhttp.OkAuthenticator.Challenge; import java.io.IOException; import java.net.Authenticator; import java.net.InetAddress; @@ -27,11 +28,49 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import static com.squareup.okhttp.OkAuthenticator.Credential; import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; /** Handles HTTP authentication headers from origin and proxy servers. */ public final class HttpAuthenticator { + /** Uses the global authenticator to get the password. */ + public static final OkAuthenticator SYSTEM_DEFAULT = new OkAuthenticator() { + @Override public Credential authenticate( + Proxy proxy, URL url, List challenges) throws IOException { + for (Challenge challenge : challenges) { + PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(url.getHost(), + getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), + challenge.getRealm(), challenge.getScheme(), url, Authenticator.RequestorType.SERVER); + if (auth != null) { + return Credential.basic(auth.getUserName(), new String(auth.getPassword())); + } + } + return null; + } + + @Override public Credential authenticateProxy( + Proxy proxy, URL url, List challenges) throws IOException { + for (Challenge challenge : challenges) { + InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); + PasswordAuthentication auth = Authenticator.requestPasswordAuthentication( + proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(), + url.getProtocol(), challenge.getRealm(), challenge.getScheme(), url, + Authenticator.RequestorType.PROXY); + if (auth != null) { + return Credential.basic(auth.getUserName(), new String(auth.getPassword())); + } + } + return null; + } + + private InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException { + return (proxy != null && proxy.type() != Proxy.Type.DIRECT) + ? ((InetSocketAddress) proxy.address()).getAddress() + : InetAddress.getByName(url.getHost()); + } + }; + private HttpAuthenticator() { } @@ -41,68 +80,33 @@ public final class HttpAuthenticator { * @return true if credentials have been added to successorRequestHeaders * and another request should be attempted. */ - public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders, - RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException { - if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) { - throw new IllegalArgumentException(); + public static boolean processAuthHeader(OkAuthenticator authenticator, int responseCode, + RawHeaders responseHeaders, RawHeaders successorRequestHeaders, Proxy proxy, URL url) + throws IOException { + String responseField; + String requestField; + if (responseCode == HTTP_UNAUTHORIZED) { + responseField = "WWW-Authenticate"; + requestField = "Authorization"; + } else if (responseCode == HTTP_PROXY_AUTH) { + responseField = "Proxy-Authenticate"; + requestField = "Proxy-Authorization"; + } else { + throw new IllegalArgumentException(); // TODO: ProtocolException? } - - // Keep asking for username/password until authorized. - String challengeHeader = - responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate"; - String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url); - if (credentials == null) { - return false; // Could not find credentials so end the request cycle. - } - - // Add authorization credentials, bypassing the already-connected check. - String fieldName = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authorization" : "Authorization"; - successorRequestHeaders.set(fieldName, credentials); - return true; - } - - /** - * Returns the authorization credentials that may satisfy the challenge. - * Returns null if a challenge header was not provided or if credentials - * were not available. - */ - private static String getCredentials(RawHeaders responseHeaders, String challengeHeader, - Proxy proxy, URL url) throws IOException { - List challenges = parseChallenges(responseHeaders, challengeHeader); + List challenges = parseChallenges(responseHeaders, responseField); if (challenges.isEmpty()) { - return null; + return false; // Could not find a challenge so end the request cycle. } - - for (Challenge challenge : challenges) { - // Use the global authenticator to get the password. - PasswordAuthentication auth; - if (responseHeaders.getResponseCode() == HTTP_PROXY_AUTH) { - InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); - auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(), - getConnectToInetAddress(proxy, url), proxyAddress.getPort(), url.getProtocol(), - challenge.realm, challenge.scheme, url, Authenticator.RequestorType.PROXY); - } else { - auth = Authenticator.requestPasswordAuthentication(url.getHost(), - getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), challenge.realm, - challenge.scheme, url, Authenticator.RequestorType.SERVER); - } - if (auth == null) { - continue; - } - - // Use base64 to encode the username and password. - String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword()); - byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1"); - String encoded = Base64.encode(bytes); - return challenge.scheme + " " + encoded; + Credential credential = responseHeaders.getResponseCode() == HTTP_PROXY_AUTH + ? authenticator.authenticateProxy(proxy, url, challenges) + : authenticator.authenticate(proxy, url, challenges); + if (credential == null) { + return false; // Could not satisfy the challenge so end the request cycle. } - - return null; - } - - private static InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException { - return (proxy != null && proxy.type() != Proxy.Type.DIRECT) - ? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.getHost()); + // Add authorization credentials, bypassing the already-connected check. + successorRequestHeaders.set(requestField, credential.getHeaderValue()); + return true; } /** @@ -151,25 +155,4 @@ public final class HttpAuthenticator { } return result; } - - /** An RFC 2617 challenge. */ - private static final class Challenge { - final String scheme; - final String realm; - - Challenge(String scheme, String realm) { - this.scheme = scheme; - this.realm = realm; - } - - @Override public boolean equals(Object o) { - return o instanceof Challenge - && ((Challenge) o).scheme.equals(scheme) - && ((Challenge) o).realm.equals(realm); - } - - @Override public int hashCode() { - return scheme.hashCode() + 31 * realm.hashCode(); - } - } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index 7a06dca53..3a93384f4 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -278,7 +278,7 @@ public class HttpEngine { hostnameVerifier = policy.hostnameVerifier; } Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory, - hostnameVerifier, policy.requestedProxy); + hostnameVerifier, policy.authenticator, policy.requestedProxy); routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT, policy.getFailedRoutes()); } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index 1da7cf513..c39cf098e 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ b/okhttp/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.OkAuthenticator; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Route; import com.squareup.okhttp.internal.Util; @@ -83,6 +84,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */ SSLSocketFactory sslSocketFactory; HostnameVerifier hostnameVerifier; + OkAuthenticator authenticator; final Set failedRoutes; @@ -104,6 +106,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { this.connectionPool = client.getConnectionPool(); this.sslSocketFactory = client.getSslSocketFactory(); this.hostnameVerifier = client.getHostnameVerifier(); + this.authenticator = client.getAuthenticator(); this.responseCache = responseCache; } @@ -421,8 +424,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } // fall-through case HTTP_UNAUTHORIZED: - boolean credentialsFound = HttpAuthenticator.processAuthHeader(getResponseCode(), - httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, selectedProxy, url); + boolean credentialsFound = HttpAuthenticator.processAuthHeader(authenticator, + getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, + selectedProxy, url); return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE; case HTTP_MULT_CHOICE: diff --git a/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java index a1acf6d2e..28d19f430 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java @@ -19,6 +19,7 @@ import com.google.mockwebserver.MockWebServer; import com.squareup.okhttp.internal.RecordingHostnameVerifier; import com.squareup.okhttp.internal.SslContextBuilder; import com.squareup.okhttp.internal.Util; +import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer; import java.io.IOException; import java.net.InetAddress; @@ -70,14 +71,15 @@ public final class ConnectionPoolTest { @Before public void setUp() throws Exception { httpServer.play(); - httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, null); + httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, + HttpAuthenticator.SYSTEM_DEFAULT, null); httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()), httpServer.getPort()); spdyServer.play(); spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(), sslContext.getSocketFactory(), - new RecordingHostnameVerifier(), null); + new RecordingHostnameVerifier(), HttpAuthenticator.SYSTEM_DEFAULT, null); spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), spdyServer.getPort()); diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java b/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java new file mode 100644 index 000000000..636acbde1 --- /dev/null +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.okhttp.internal; + +import com.squareup.okhttp.OkAuthenticator; +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public final class RecordingOkAuthenticator implements OkAuthenticator { + public final List calls = new ArrayList(); + public final Credential credential; + + public RecordingOkAuthenticator(Credential credential) { + this.credential = credential; + } + + @Override public Credential authenticate(Proxy proxy, URL url, List challenges) + throws IOException { + calls.add("authenticate" + + " proxy=" + proxy.type() + + " url=" + url + + " challenges=" + challenges); + return credential; + } + + @Override public Credential authenticateProxy(Proxy proxy, URL url, List challenges) + throws IOException { + calls.add("authenticateProxy" + + " proxy=" + proxy.type() + + " url=" + url + + " challenges=" + challenges); + return credential; + } +} diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java index 2a241dba8..67724ee47 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java @@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.Address; import com.squareup.okhttp.Connection; import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.OkAuthenticator; import com.squareup.okhttp.Route; import com.squareup.okhttp.internal.Dns; import com.squareup.okhttp.internal.SslContextBuilder; @@ -78,11 +79,12 @@ public final class RouteSelectorTest { } } + private final OkAuthenticator authenticator = HttpAuthenticator.SYSTEM_DEFAULT; private final FakeDns dns = new FakeDns(); private final FakeProxySelector proxySelector = new FakeProxySelector(); @Test public void singleRoute() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, Collections.EMPTY_SET); @@ -100,7 +102,7 @@ public final class RouteSelectorTest { } @Test public void singleRouteReturnsFailedRoute() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, Collections.EMPTY_SET); @@ -121,7 +123,7 @@ public final class RouteSelectorTest { } @Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, proxyA); + Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, Collections.EMPTY_SET); @@ -138,7 +140,7 @@ public final class RouteSelectorTest { } @Test public void explicitDirectProxy() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, NO_PROXY); + Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, Collections.EMPTY_SET); @@ -153,7 +155,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsNull() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); proxySelector.proxies = null; RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -169,7 +171,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsNoProxies() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, Collections.EMPTY_SET); @@ -184,7 +186,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorReturnsMultipleProxies() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); @@ -218,7 +220,7 @@ public final class RouteSelectorTest { } @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); proxySelector.proxies.add(NO_PROXY); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -235,7 +237,7 @@ public final class RouteSelectorTest { } @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception { - Address address = new Address(uriHost, uriPort, null, null, null); + Address address = new Address(uriHost, uriPort, null, null, authenticator, null); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); @@ -274,8 +276,8 @@ public final class RouteSelectorTest { } @Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception { - Address address = - new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY); + Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, + Proxy.NO_PROXY); Set failedRoutes = new LinkedHashSet(); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, failedRoutes); @@ -287,8 +289,8 @@ public final class RouteSelectorTest { } @Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception { - Address address = - new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY); + Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, + Proxy.NO_PROXY); Set failedRoutes = new LinkedHashSet(); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, failedRoutes); @@ -300,7 +302,8 @@ public final class RouteSelectorTest { } @Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception { - Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, null); + Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, + null); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, @@ -338,8 +341,8 @@ public final class RouteSelectorTest { } @Test public void failedRoutesAreLast() throws Exception { - Address address = - new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY); + Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator, + Proxy.NO_PROXY); Set failedRoutes = new LinkedHashSet(1); RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index 332de5eba..82ddef03f 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -24,6 +24,7 @@ import com.squareup.okhttp.HttpResponseCache; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.internal.RecordingAuthenticator; import com.squareup.okhttp.internal.RecordingHostnameVerifier; +import com.squareup.okhttp.internal.RecordingOkAuthenticator; import com.squareup.okhttp.internal.SslContextBuilder; import java.io.ByteArrayOutputStream; import java.io.File; @@ -78,6 +79,7 @@ import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; +import static com.squareup.okhttp.OkAuthenticator.Credential; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -2358,6 +2360,30 @@ public final class URLConnectionTest { fail("TODO"); } + @Test public void customAuthenticator() throws Exception { + MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) + .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") + .setBody("Please authenticate."); + server.enqueue(pleaseAuthenticate); + server.enqueue(new MockResponse().setBody("A")); + server.play(); + + Credential credential = Credential.basic("jesse", "peanutbutter"); + RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(credential); + client.setAuthenticator(authenticator); + assertContent("A", client.open(server.getUrl("/private"))); + + assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); + assertContains(server.takeRequest().getHeaders(), + "Authorization: " + credential.getHeaderValue()); + + assertEquals(1, authenticator.calls.size()); + String call = authenticator.calls.get(0); + assertTrue(call, call.contains("proxy=DIRECT")); + assertTrue(call, call.contains("url=" + server.getUrl("/private"))); + assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]")); + } + /** Returns a gzipped copy of {@code bytes}. */ public byte[] gzip(byte[] bytes) throws IOException { ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();