okhttp version dump
This commit is contained in:
1 parent
2897a2fe12
commit
dff16b3834
35 files changed
+613
-107
No files matched your search
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.tls.CertificateChainCleaner;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
import okio.ByteString;
|
||||
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
@@ -128,9 +130,11 @@ public final class CertificatePinner {
|
||||
public static final CertificatePinner DEFAULT = new Builder().build();
|
||||
|
||||
private final Map<String, Set<ByteString>> hostnameToPins;
|
||||
private final TrustRootIndex trustRootIndex;
|
||||
|
||||
private CertificatePinner(Builder builder) {
|
||||
hostnameToPins = Util.immutableMap(builder.hostnameToPins);
|
||||
this.hostnameToPins = Util.immutableMap(builder.hostnameToPins);
|
||||
this.trustRootIndex = builder.trustRootIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +147,9 @@ public final class CertificatePinner {
|
||||
*/
|
||||
public void check(String hostname, List<Certificate> peerCertificates)
|
||||
throws SSLPeerUnverifiedException {
|
||||
if (trustRootIndex != null) {
|
||||
peerCertificates = new CertificateChainCleaner(trustRootIndex).clean(peerCertificates);
|
||||
}
|
||||
|
||||
Set<ByteString> pins = findMatchingPins(hostname);
|
||||
|
||||
@@ -208,6 +215,10 @@ public final class CertificatePinner {
|
||||
return wildcardPins;
|
||||
}
|
||||
|
||||
Builder newBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SHA-1 of {@code certificate}'s public key. This uses the mechanism Moxie
|
||||
* Marlinspike describes in <a href="https://github.com/moxie0/AndroidPinning">Android
|
||||
@@ -227,6 +238,20 @@ public final class CertificatePinner {
|
||||
/** Builds a configured certificate pinner. */
|
||||
public static final class Builder {
|
||||
private final Map<String, Set<ByteString>> hostnameToPins = new LinkedHashMap<>();
|
||||
private TrustRootIndex trustRootIndex;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
Builder(CertificatePinner certificatePinner) {
|
||||
this.hostnameToPins.putAll(certificatePinner.hostnameToPins);
|
||||
this.trustRootIndex = certificatePinner.trustRootIndex;
|
||||
}
|
||||
|
||||
public Builder trustRootIndex(TrustRootIndex trustRootIndex) {
|
||||
this.trustRootIndex = trustRootIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins certificates for {@code hostname}.
|
||||
|
||||
@@ -117,9 +117,7 @@ public final class ConnectionPool {
|
||||
RealConnection get(Address address, StreamAllocation streamAllocation) {
|
||||
assert (Thread.holdsLock(this));
|
||||
for (RealConnection connection : connections) {
|
||||
// TODO(jwilson): this is awkward. We're already holding a lock on 'this', and
|
||||
// connection.allocationLimit() may also lock the FramedConnection.
|
||||
if (connection.allocations.size() < connection.allocationLimit()
|
||||
if (connection.allocations.size() < connection.allocationLimit
|
||||
&& address.equals(connection.route().address)
|
||||
&& !connection.noNewStreams) {
|
||||
streamAllocation.acquire(connection);
|
||||
|
||||
@@ -559,4 +559,32 @@ public final class Cookie {
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (!(other instanceof Cookie)) return false;
|
||||
Cookie that = (Cookie) other;
|
||||
return that.name.equals(name)
|
||||
&& that.value.equals(value)
|
||||
&& that.domain.equals(domain)
|
||||
&& that.path.equals(path)
|
||||
&& that.expiresAt == expiresAt
|
||||
&& that.secure == secure
|
||||
&& that.httpOnly == httpOnly
|
||||
&& that.persistent == persistent
|
||||
&& that.hostOnly == hostOnly;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int hash = 17;
|
||||
hash = 31 * hash + name.hashCode();
|
||||
hash = 31 * hash + value.hashCode();
|
||||
hash = 31 * hash + domain.hashCode();
|
||||
hash = 31 * hash + path.hashCode();
|
||||
hash = 31 * hash + (int) (expiresAt ^ (expiresAt >>> 32));
|
||||
hash = 31 * hash + (secure ? 0 : 1);
|
||||
hash = 31 * hash + (httpOnly ? 0 : 1);
|
||||
hash = 31 * hash + (persistent ? 0 : 1);
|
||||
hash = 31 * hash + (hostOnly ? 0 : 1);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
@@ -28,13 +28,16 @@ import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.internal.Internal;
|
||||
import okhttp3.internal.InternalCache;
|
||||
import okhttp3.internal.Platform;
|
||||
import okhttp3.internal.RouteDatabase;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.http.StreamAllocation;
|
||||
import okhttp3.internal.io.RealConnection;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
|
||||
/**
|
||||
* Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
|
||||
@@ -54,7 +57,7 @@ import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
* Response response = clientWith30sTimeout.newCall(request).execute();
|
||||
* }</pre>
|
||||
*/
|
||||
public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
public class OkHttpClient implements Cloneable, Call.Factory {
|
||||
private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
|
||||
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
|
||||
|
||||
@@ -130,6 +133,7 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
final InternalCache internalCache;
|
||||
final SocketFactory socketFactory;
|
||||
final SSLSocketFactory sslSocketFactory;
|
||||
final TrustRootIndex trustRootIndex;
|
||||
final HostnameVerifier hostnameVerifier;
|
||||
final CertificatePinner certificatePinner;
|
||||
final Authenticator proxyAuthenticator;
|
||||
@@ -159,7 +163,13 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
this.cache = builder.cache;
|
||||
this.internalCache = builder.internalCache;
|
||||
this.socketFactory = builder.socketFactory;
|
||||
if (builder.sslSocketFactory != null) {
|
||||
|
||||
boolean isTLS = false;
|
||||
for (ConnectionSpec spec : connectionSpecs) {
|
||||
isTLS = isTLS || spec.isTls();
|
||||
}
|
||||
|
||||
if (builder.sslSocketFactory != null || !isTLS) {
|
||||
this.sslSocketFactory = builder.sslSocketFactory;
|
||||
} else {
|
||||
try {
|
||||
@@ -170,8 +180,21 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
throw new AssertionError(); // The system has no TLS. Just give up.
|
||||
}
|
||||
}
|
||||
if (sslSocketFactory != null && builder.trustRootIndex == null) {
|
||||
X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
|
||||
if (trustManager == null) {
|
||||
throw new IllegalStateException("Unable to extract the trust manager on " + Platform.get()
|
||||
+ ", sslSocketFactory is " + sslSocketFactory.getClass());
|
||||
}
|
||||
this.trustRootIndex = Platform.get().trustRootIndex(trustManager);
|
||||
this.certificatePinner = builder.certificatePinner.newBuilder()
|
||||
.trustRootIndex(trustRootIndex)
|
||||
.build();
|
||||
} else {
|
||||
this.trustRootIndex = builder.trustRootIndex;
|
||||
this.certificatePinner = builder.certificatePinner;
|
||||
}
|
||||
this.hostnameVerifier = builder.hostnameVerifier;
|
||||
this.certificatePinner = builder.certificatePinner;
|
||||
this.proxyAuthenticator = builder.proxyAuthenticator;
|
||||
this.authenticator = builder.authenticator;
|
||||
this.connectionPool = builder.connectionPool;
|
||||
@@ -317,6 +340,7 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
InternalCache internalCache;
|
||||
SocketFactory socketFactory;
|
||||
SSLSocketFactory sslSocketFactory;
|
||||
TrustRootIndex trustRootIndex;
|
||||
HostnameVerifier hostnameVerifier;
|
||||
CertificatePinner certificatePinner;
|
||||
Authenticator proxyAuthenticator;
|
||||
@@ -364,6 +388,7 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
this.cache = okHttpClient.cache;
|
||||
this.socketFactory = okHttpClient.socketFactory;
|
||||
this.sslSocketFactory = okHttpClient.sslSocketFactory;
|
||||
this.trustRootIndex = okHttpClient.trustRootIndex;
|
||||
this.hostnameVerifier = okHttpClient.hostnameVerifier;
|
||||
this.certificatePinner = okHttpClient.certificatePinner;
|
||||
this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
|
||||
@@ -501,6 +526,7 @@ public final class OkHttpClient implements Cloneable, Call.Factory {
|
||||
public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) {
|
||||
if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null");
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
this.trustRootIndex = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 okhttp3.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal;
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ package okhttp3.internal;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -29,7 +30,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.internal.tls.AndroidTrustRootIndex;
|
||||
import okhttp3.internal.tls.RealTrustRootIndex;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
import okio.Buffer;
|
||||
|
||||
import static okhttp3.internal.Internal.logger;
|
||||
@@ -55,6 +61,11 @@ import static okhttp3.internal.Internal.logger;
|
||||
* unstable.
|
||||
*
|
||||
* Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
|
||||
*
|
||||
* <h3>Trust Manager Extraction</h3>
|
||||
*
|
||||
* <p>Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust
|
||||
* manager that was used to create an {@link SSLSocketFactory}.
|
||||
*/
|
||||
public class Platform {
|
||||
private static final Platform PLATFORM = findPlatform();
|
||||
@@ -78,6 +89,14 @@ public class Platform {
|
||||
public void untagSocket(Socket socket) throws SocketException {
|
||||
}
|
||||
|
||||
public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
|
||||
return new RealTrustRootIndex(trustManager.getAcceptedIssuers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure TLS extensions on {@code sslSocket} for {@code route}.
|
||||
*
|
||||
@@ -112,11 +131,13 @@ public class Platform {
|
||||
private static Platform findPlatform() {
|
||||
// Attempt to find Android 2.3+ APIs.
|
||||
try {
|
||||
Class<?> sslParametersClass;
|
||||
try {
|
||||
Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
|
||||
sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Older platform before being unbundled.
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
|
||||
sslParametersClass = Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
|
||||
}
|
||||
|
||||
OptionalMethod<Socket> setUseSessionTickets
|
||||
@@ -144,25 +165,34 @@ public class Platform {
|
||||
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
return new Android(setUseSessionTickets, setHostname, trafficStatsTagSocket,
|
||||
trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols);
|
||||
return new Android(sslParametersClass, setUseSessionTickets, setHostname,
|
||||
trafficStatsTagSocket, trafficStatsUntagSocket, getAlpnSelectedProtocol,
|
||||
setAlpnProtocols);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// This isn't an Android runtime.
|
||||
}
|
||||
|
||||
// Find Jetty's ALPN extension for OpenJDK.
|
||||
// Find an Oracle JDK.
|
||||
try {
|
||||
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
|
||||
Class<?> negoClass = Class.forName(negoClassName);
|
||||
Class<?> providerClass = Class.forName(negoClassName + "$Provider");
|
||||
Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
|
||||
Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
|
||||
Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
|
||||
Method getMethod = negoClass.getMethod("get", SSLSocket.class);
|
||||
Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
|
||||
return new JdkWithJettyBootPlatform(
|
||||
putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
|
||||
Class<?> sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl");
|
||||
|
||||
// Find Jetty's ALPN extension for OpenJDK.
|
||||
try {
|
||||
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
|
||||
Class<?> negoClass = Class.forName(negoClassName);
|
||||
Class<?> providerClass = Class.forName(negoClassName + "$Provider");
|
||||
Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
|
||||
Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
|
||||
Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
|
||||
Method getMethod = negoClass.getMethod("get", SSLSocket.class);
|
||||
Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
|
||||
return new JdkWithJettyBootPlatform(sslContextClass,
|
||||
putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
return new JdkPlatform(sslContextClass);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
return new Platform();
|
||||
@@ -172,6 +202,7 @@ public class Platform {
|
||||
private static class Android extends Platform {
|
||||
private static final int MAX_LOG_LENGTH = 4000;
|
||||
|
||||
private final Class<?> sslParametersClass;
|
||||
private final OptionalMethod<Socket> setUseSessionTickets;
|
||||
private final OptionalMethod<Socket> setHostname;
|
||||
|
||||
@@ -183,9 +214,11 @@ public class Platform {
|
||||
private final OptionalMethod<Socket> getAlpnSelectedProtocol;
|
||||
private final OptionalMethod<Socket> setAlpnProtocols;
|
||||
|
||||
public Android(OptionalMethod<Socket> setUseSessionTickets, OptionalMethod<Socket> setHostname,
|
||||
Method trafficStatsTagSocket, Method trafficStatsUntagSocket,
|
||||
OptionalMethod<Socket> getAlpnSelectedProtocol, OptionalMethod<Socket> setAlpnProtocols) {
|
||||
public Android(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,
|
||||
OptionalMethod<Socket> setHostname, Method trafficStatsTagSocket,
|
||||
Method trafficStatsUntagSocket, OptionalMethod<Socket> getAlpnSelectedProtocol,
|
||||
OptionalMethod<Socket> setAlpnProtocols) {
|
||||
this.sslParametersClass = sslParametersClass;
|
||||
this.setUseSessionTickets = setUseSessionTickets;
|
||||
this.setHostname = setHostname;
|
||||
this.trafficStatsTagSocket = trafficStatsTagSocket;
|
||||
@@ -210,6 +243,34 @@ public class Platform {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters");
|
||||
if (context == null) {
|
||||
// If that didn't work, try the Google Play Services SSL provider before giving up. This
|
||||
// must be loaded by the SSLSocketFactory's class loader.
|
||||
try {
|
||||
Class<?> gmsSslParametersClass = Class.forName(
|
||||
"com.google.android.gms.org.conscrypt.SSLParametersImpl", false,
|
||||
sslSocketFactory.getClass().getClassLoader());
|
||||
context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
X509TrustManager x509TrustManager = readFieldOrNull(
|
||||
context, X509TrustManager.class, "x509TrustManager");
|
||||
if (x509TrustManager != null) return x509TrustManager;
|
||||
|
||||
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
|
||||
}
|
||||
|
||||
@Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
|
||||
TrustRootIndex result = AndroidTrustRootIndex.get(trustManager);
|
||||
if (result != null) return result;
|
||||
return super.trustRootIndex(trustManager);
|
||||
}
|
||||
|
||||
@Override public void configureTlsExtensions(
|
||||
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
|
||||
// Enable SNI and session tickets.
|
||||
@@ -271,18 +332,34 @@ public class Platform {
|
||||
}
|
||||
}
|
||||
|
||||
/** JDK 1.7 or better. */
|
||||
private static class JdkPlatform extends Platform {
|
||||
private final Class<?> sslContextClass;
|
||||
|
||||
public JdkPlatform(Class<?> sslContextClass) {
|
||||
this.sslContextClass = sslContextClass;
|
||||
}
|
||||
|
||||
@Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context");
|
||||
if (context == null) return null;
|
||||
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
|
||||
*/
|
||||
private static class JdkWithJettyBootPlatform extends Platform {
|
||||
private static class JdkWithJettyBootPlatform extends JdkPlatform {
|
||||
private final Method putMethod;
|
||||
private final Method getMethod;
|
||||
private final Method removeMethod;
|
||||
private final Class<?> clientProviderClass;
|
||||
private final Class<?> serverProviderClass;
|
||||
|
||||
public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
|
||||
Class<?> clientProviderClass, Class<?> serverProviderClass) {
|
||||
public JdkWithJettyBootPlatform(Class<?> sslContextClass, Method putMethod, Method getMethod,
|
||||
Method removeMethod, Class<?> clientProviderClass, Class<?> serverProviderClass) {
|
||||
super(sslContextClass);
|
||||
this.putMethod = putMethod;
|
||||
this.getMethod = getMethod;
|
||||
this.removeMethod = removeMethod;
|
||||
@@ -394,4 +471,27 @@ public class Platform {
|
||||
}
|
||||
return result.readByteArray();
|
||||
}
|
||||
|
||||
static <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
|
||||
for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
|
||||
try {
|
||||
Field field = c.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(instance);
|
||||
if (value == null || !fieldType.isInstance(value)) return null;
|
||||
return fieldType.cast(value);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.
|
||||
if (!fieldName.equals("delegate")) {
|
||||
Object delegate = readFieldOrNull(instance, Object.class, "delegate");
|
||||
if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ package okhttp3.internal;
|
||||
|
||||
public final class Version {
|
||||
public static String userAgent() {
|
||||
return "okhttp/3.0.1";
|
||||
return "okhttp/3.1.1";
|
||||
}
|
||||
|
||||
private Version() {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.framed;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.framed;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class FramedConnection implements Closeable {
|
||||
*/
|
||||
private final Listener listener;
|
||||
private final Map<Integer, FramedStream> streams = new HashMap<>();
|
||||
private final String hostName;
|
||||
private final String hostname;
|
||||
private int lastGoodStreamId;
|
||||
private int nextStreamId;
|
||||
private boolean shutdown;
|
||||
@@ -148,14 +148,14 @@ public final class FramedConnection implements Closeable {
|
||||
okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, OKHTTP_CLIENT_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
hostName = builder.hostName;
|
||||
hostname = builder.hostname;
|
||||
|
||||
if (protocol == Protocol.HTTP_2) {
|
||||
variant = new Http2();
|
||||
// Like newSingleThreadExecutor, except lazy creates the thread.
|
||||
pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
Util.threadFactory(String.format("OkHttp %s Push Observer", hostName), true));
|
||||
Util.threadFactory(String.format("OkHttp %s Push Observer", hostname), true));
|
||||
// 1 less than SPDY http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.9.2
|
||||
peerSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535);
|
||||
peerSettings.set(Settings.MAX_FRAME_SIZE, 0, Http2.INITIAL_MAX_FRAME_SIZE);
|
||||
@@ -342,7 +342,7 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
|
||||
void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
|
||||
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
|
||||
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
writeSynReset(streamId, errorCode);
|
||||
@@ -357,7 +357,7 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
|
||||
void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
|
||||
executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostName, streamId) {
|
||||
executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
frameWriter.windowUpdate(streamId, unacknowledgedBytesRead);
|
||||
@@ -390,7 +390,7 @@ public final class FramedConnection implements Closeable {
|
||||
private void writePingLater(
|
||||
final boolean reply, final int payload1, final int payload2, final Ping ping) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s ping %08x%08x",
|
||||
hostName, payload1, payload2) {
|
||||
hostname, payload1, payload2) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
writePing(reply, payload1, payload2, ping);
|
||||
@@ -528,7 +528,7 @@ public final class FramedConnection implements Closeable {
|
||||
|
||||
public static class Builder {
|
||||
private Socket socket;
|
||||
private String hostName;
|
||||
private String hostname;
|
||||
private BufferedSource source;
|
||||
private BufferedSink sink;
|
||||
private Listener listener = Listener.REFUSE_INCOMING_STREAMS;
|
||||
@@ -550,9 +550,9 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
|
||||
public Builder socket(
|
||||
Socket socket, String hostName, BufferedSource source, BufferedSink sink) {
|
||||
Socket socket, String hostname, BufferedSource source, BufferedSink sink) {
|
||||
this.socket = socket;
|
||||
this.hostName = hostName;
|
||||
this.hostname = hostname;
|
||||
this.source = source;
|
||||
this.sink = sink;
|
||||
return this;
|
||||
@@ -586,7 +586,7 @@ public final class FramedConnection implements Closeable {
|
||||
final FrameReader frameReader;
|
||||
|
||||
private Reader(FrameReader frameReader) {
|
||||
super("OkHttp %s", hostName);
|
||||
super("OkHttp %s", hostname);
|
||||
this.frameReader = frameReader;
|
||||
}
|
||||
|
||||
@@ -663,12 +663,12 @@ public final class FramedConnection implements Closeable {
|
||||
inFinished, headerBlock);
|
||||
lastGoodStreamId = streamId;
|
||||
streams.put(streamId, newStream);
|
||||
executor.execute(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
listener.onStream(newStream);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.INFO, "FramedConnection.Listener failure for " + hostName, e);
|
||||
logger.log(Level.INFO, "FramedConnection.Listener failure for " + hostname, e);
|
||||
try {
|
||||
newStream.close(ErrorCode.PROTOCOL_ERROR);
|
||||
} catch (IOException ignored) {
|
||||
@@ -724,7 +724,7 @@ public final class FramedConnection implements Closeable {
|
||||
streamsToNotify = streams.values().toArray(new FramedStream[streams.size()]);
|
||||
}
|
||||
}
|
||||
executor.execute(new NamedRunnable("OkHttp %s settings", hostName) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s settings", hostname) {
|
||||
@Override public void execute() {
|
||||
listener.onSettings(FramedConnection.this);
|
||||
}
|
||||
@@ -740,7 +740,7 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
|
||||
private void ackSettingsLater(final Settings peerSettings) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostName) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostname) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
frameWriter.ackSettings(peerSettings);
|
||||
@@ -834,7 +834,7 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
currentPushRequests.add(streamId);
|
||||
}
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostName, streamId) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
|
||||
try {
|
||||
@@ -852,7 +852,7 @@ public final class FramedConnection implements Closeable {
|
||||
|
||||
private void pushHeadersLater(final int streamId, final List<Header> requestHeaders,
|
||||
final boolean inFinished) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostName, streamId) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
|
||||
try {
|
||||
@@ -878,7 +878,7 @@ public final class FramedConnection implements Closeable {
|
||||
source.require(byteCount); // Eagerly read the frame before firing client thread.
|
||||
source.read(buffer, byteCount);
|
||||
if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount);
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Data[%s]", hostName, streamId) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
|
||||
@@ -895,7 +895,7 @@ public final class FramedConnection implements Closeable {
|
||||
}
|
||||
|
||||
private void pushResetLater(final int streamId, final ErrorCode errorCode) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostName, streamId) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
pushObserver.onReset(streamId, errorCode);
|
||||
synchronized (FramedConnection.this) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.framed;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 okhttp3.internal.framed;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
@@ -294,7 +294,7 @@ public final class Http2 implements Variant {
|
||||
case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
|
||||
break; // Advisory only, so ignored.
|
||||
default:
|
||||
throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
|
||||
break; // Must ignore setting with unknown id.
|
||||
}
|
||||
settings.set(id, 0, value);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.http;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
public final class HeaderParser {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -51,6 +50,7 @@ import okio.Sink;
|
||||
import okio.Source;
|
||||
import okio.Timeout;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT;
|
||||
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
|
||||
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
|
||||
import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
|
||||
@@ -868,8 +868,8 @@ public final class HttpEngine {
|
||||
|
||||
/**
|
||||
* Figures out the HTTP request to make in response to receiving this engine's response. This will
|
||||
* either add authentication headers or follow redirects. If a follow-up is either unnecessary or
|
||||
* not applicable, this returns null.
|
||||
* either add authentication headers, follow redirects or handle a client request timeout. If a
|
||||
* follow-up is either unnecessary or not applicable, this returns null.
|
||||
*/
|
||||
public Request followUpRequest() throws IOException {
|
||||
if (userResponse == null) throw new IllegalStateException();
|
||||
@@ -940,6 +940,17 @@ public final class HttpEngine {
|
||||
|
||||
return requestBuilder.url(url).build();
|
||||
|
||||
case HTTP_CLIENT_TIMEOUT:
|
||||
// 408's are rare in practice, but some servers like HAProxy use this response code. The
|
||||
// spec says that we may repeat the request without modifications. Modern browsers also
|
||||
// repeat the request (even non-idempotent ones.)
|
||||
boolean retryableBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
|
||||
if (callerWritesRequestBody && !retryableBody) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return userRequest;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.http;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -44,7 +44,9 @@ import okhttp3.internal.ConnectionSpecSelector;
|
||||
import okhttp3.internal.Platform;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.Version;
|
||||
import okhttp3.internal.framed.ErrorCode;
|
||||
import okhttp3.internal.framed.FramedConnection;
|
||||
import okhttp3.internal.framed.FramedStream;
|
||||
import okhttp3.internal.http.Http1xStream;
|
||||
import okhttp3.internal.http.OkHeaders;
|
||||
import okhttp3.internal.http.RouteException;
|
||||
@@ -60,7 +62,7 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static okhttp3.internal.Util.closeQuietly;
|
||||
|
||||
public final class RealConnection implements Connection {
|
||||
public final class RealConnection extends FramedConnection.Listener implements Connection {
|
||||
private final Route route;
|
||||
|
||||
/** The low-level TCP socket. */
|
||||
@@ -77,6 +79,7 @@ public final class RealConnection implements Connection {
|
||||
public int successCount;
|
||||
public BufferedSource source;
|
||||
public BufferedSink sink;
|
||||
public int allocationLimit;
|
||||
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
|
||||
public boolean noNewStreams;
|
||||
public long idleAtNanos = Long.MAX_VALUE;
|
||||
@@ -154,11 +157,15 @@ public final class RealConnection implements Connection {
|
||||
FramedConnection framedConnection = new FramedConnection.Builder(true)
|
||||
.socket(socket, route.address().url().host(), source, sink)
|
||||
.protocol(protocol)
|
||||
.listener(this)
|
||||
.build();
|
||||
framedConnection.sendConnectionPreface();
|
||||
|
||||
// Only assign the framed connection once the preface has been sent successfully.
|
||||
this.allocationLimit = framedConnection.maxConcurrentStreams();
|
||||
this.framedConnection = framedConnection;
|
||||
} else {
|
||||
this.allocationLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,13 +315,6 @@ public final class RealConnection implements Connection {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public int allocationLimit() {
|
||||
FramedConnection framedConnection = this.framedConnection;
|
||||
return framedConnection != null
|
||||
? framedConnection.maxConcurrentStreams()
|
||||
: 1;
|
||||
}
|
||||
|
||||
/** Returns true if this connection is ready to host new streams. */
|
||||
public boolean isHealthy(boolean doExtensiveChecks) {
|
||||
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
|
||||
@@ -347,6 +347,16 @@ public final class RealConnection implements Connection {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Refuse incoming streams. */
|
||||
@Override public void onStream(FramedStream stream) throws IOException {
|
||||
stream.close(ErrorCode.REFUSED_STREAM);
|
||||
}
|
||||
|
||||
/** When settings are received, adjust the allocation limit. */
|
||||
@Override public void onSettings(FramedConnection connection) {
|
||||
allocationLimit = connection.maxConcurrentStreams();
|
||||
}
|
||||
|
||||
@Override public Handshake handshake() {
|
||||
return handshake;
|
||||
}
|
||||
@@ -360,7 +370,11 @@ public final class RealConnection implements Connection {
|
||||
}
|
||||
|
||||
@Override public Protocol protocol() {
|
||||
return protocol != null ? protocol : Protocol.HTTP_1_1;
|
||||
if (framedConnection == null) {
|
||||
return protocol != null ? protocol : Protocol.HTTP_1_1;
|
||||
} else {
|
||||
return framedConnection.getProtocol();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 okhttp3.internal.tls;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.cert.TrustAnchor;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
* A index of trusted root certificates that exploits knowledge of Android implementation details.
|
||||
* This class is potentially much faster to initialize than {@link RealTrustRootIndex} because
|
||||
* it doesn't need to load and index trusted CA certificates.
|
||||
*/
|
||||
public final class AndroidTrustRootIndex implements TrustRootIndex {
|
||||
private final X509TrustManager trustManager;
|
||||
private final Method findByIssuerAndSignatureMethod;
|
||||
|
||||
public AndroidTrustRootIndex(
|
||||
X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
|
||||
this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod;
|
||||
this.trustManager = trustManager;
|
||||
}
|
||||
|
||||
@Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
|
||||
try {
|
||||
TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
|
||||
trustManager, cert);
|
||||
return trustAnchor.getTrustedCert();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static TrustRootIndex get(X509TrustManager trustManager) {
|
||||
// From org.conscrypt.TrustManagerImpl, we want the method with this signature:
|
||||
// private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
|
||||
try {
|
||||
Method method = trustManager.getClass().getDeclaredMethod(
|
||||
"findTrustAnchorByIssuerAndSignature", X509Certificate.class);
|
||||
method.setAccessible(true);
|
||||
return new AndroidTrustRootIndex(trustManager, method);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 okhttp3.internal.tls;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
|
||||
/**
|
||||
* Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs.
|
||||
* Cleaning a chain returns a list of certificates where the first element is {@code chain[0]}, each
|
||||
* certificate is signed by the certificate that follows, and the last certificate is a trusted CA
|
||||
* certificate.
|
||||
*
|
||||
* <p>Use of the chain cleaner is necessary to omit unexpected certificates that aren't relevant to
|
||||
* the TLS handshake and to extract the trusted CA certificate for the benefit of certificate
|
||||
* pinning.
|
||||
*
|
||||
* <p>This class includes code from <a href="https://conscrypt.org/">Conscrypt's</a> {@code
|
||||
* TrustManagerImpl} and {@code TrustedCertificateIndex}.
|
||||
*/
|
||||
public final class CertificateChainCleaner {
|
||||
private final TrustRootIndex trustRootIndex;
|
||||
|
||||
public CertificateChainCleaner(TrustRootIndex trustRootIndex) {
|
||||
this.trustRootIndex = trustRootIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cleaned chain for {@code chain}.
|
||||
*
|
||||
* <p>This method throws if the complete chain to a trusted CA certificate cannot be constructed.
|
||||
* This is unexpected unless the trust root index in this class has a different trust manager than
|
||||
* what was used to establish {@code chain}.
|
||||
*/
|
||||
public List<Certificate> clean(List<Certificate> chain) throws SSLPeerUnverifiedException {
|
||||
Deque<Certificate> queue = new ArrayDeque<>(chain);
|
||||
List<Certificate> result = new ArrayList<>();
|
||||
result.add(queue.removeFirst());
|
||||
|
||||
followIssuerChain:
|
||||
while (true) {
|
||||
X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1);
|
||||
|
||||
// If this cert has been signed by a trusted CA cert, we're done. Add the trusted CA
|
||||
// certificate to the end of the chain, unless it's already present. (That would happen if the
|
||||
// first certificate in the chain is itself a self-signed and trusted CA certificate.)
|
||||
X509Certificate caCert = trustRootIndex.findByIssuerAndSignature(toVerify);
|
||||
if (caCert != null) {
|
||||
if (result.size() > 1 || !toVerify.equals(caCert)) {
|
||||
result.add(caCert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Search for the certificate in the chain that signed this certificate. This is typically the
|
||||
// next element in the chain, but it could be any element.
|
||||
for (Iterator<Certificate> i = queue.iterator(); i.hasNext(); ) {
|
||||
X509Certificate signingCert = (X509Certificate) i.next();
|
||||
if (toVerify.getIssuerDN().equals(signingCert.getSubjectDN())
|
||||
&& verifySignature(toVerify, signingCert)) {
|
||||
i.remove();
|
||||
result.add(signingCert);
|
||||
continue followIssuerChain;
|
||||
}
|
||||
}
|
||||
|
||||
throw new SSLPeerUnverifiedException("Failed to find a cert that signed " + toVerify);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */
|
||||
private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) {
|
||||
try {
|
||||
toVerify.verify(signingCert.getPublicKey());
|
||||
return true;
|
||||
} catch (GeneralSecurityException verifyFailed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.tls;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package okhttp3.internal.tls;
|
||||
|
||||
import java.security.cert.Certificate;
|
||||
@@ -57,7 +56,7 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
public boolean verify(String host, X509Certificate certificate) {
|
||||
return verifyAsIpAddress(host)
|
||||
? verifyIpAddress(host, certificate)
|
||||
: verifyHostName(host, certificate);
|
||||
: verifyHostname(host, certificate);
|
||||
}
|
||||
|
||||
/** Returns true if {@code certificate} matches {@code ipAddress}. */
|
||||
@@ -71,14 +70,14 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if {@code certificate} matches {@code hostName}. */
|
||||
private boolean verifyHostName(String hostName, X509Certificate certificate) {
|
||||
hostName = hostName.toLowerCase(Locale.US);
|
||||
/** Returns true if {@code certificate} matches {@code hostname}. */
|
||||
private boolean verifyHostname(String hostname, X509Certificate certificate) {
|
||||
hostname = hostname.toLowerCase(Locale.US);
|
||||
boolean hasDns = false;
|
||||
List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
|
||||
for (int i = 0, size = altNames.size(); i < size; i++) {
|
||||
hasDns = true;
|
||||
if (verifyHostName(hostName, altNames.get(i))) {
|
||||
if (verifyHostname(hostname, altNames.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +87,7 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
// RFC 2818 advises using the most specific name for matching.
|
||||
String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
|
||||
if (cn != null) {
|
||||
return verifyHostName(hostName, cn);
|
||||
return verifyHostname(hostname, cn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,17 +133,17 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
|
||||
* Returns {@code true} iff {@code hostname} matches the domain name {@code pattern}.
|
||||
*
|
||||
* @param hostName lower-case host name.
|
||||
* @param hostname lower-case host name.
|
||||
* @param pattern domain name pattern from certificate. May be a wildcard pattern such as {@code
|
||||
* *.android.com}.
|
||||
*/
|
||||
private boolean verifyHostName(String hostName, String pattern) {
|
||||
private boolean verifyHostname(String hostname, String pattern) {
|
||||
// Basic sanity checks
|
||||
// Check length == 0 instead of .isEmpty() to support Java 5.
|
||||
if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith("."))
|
||||
|| (hostName.endsWith(".."))) {
|
||||
if ((hostname == null) || (hostname.length() == 0) || (hostname.startsWith("."))
|
||||
|| (hostname.endsWith(".."))) {
|
||||
// Invalid domain name
|
||||
return false;
|
||||
}
|
||||
@@ -154,29 +153,29 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize hostName and pattern by turning them into absolute domain names if they are not
|
||||
// Normalize hostname and pattern by turning them into absolute domain names if they are not
|
||||
// yet absolute. This is needed because server certificates do not normally contain absolute
|
||||
// names or patterns, but they should be treated as absolute. At the same time, any hostName
|
||||
// names or patterns, but they should be treated as absolute. At the same time, any hostname
|
||||
// presented to this method should also be treated as absolute for the purposes of matching
|
||||
// to the server certificate.
|
||||
// www.android.com matches www.android.com
|
||||
// www.android.com matches www.android.com.
|
||||
// www.android.com. matches www.android.com.
|
||||
// www.android.com. matches www.android.com
|
||||
if (!hostName.endsWith(".")) {
|
||||
hostName += '.';
|
||||
if (!hostname.endsWith(".")) {
|
||||
hostname += '.';
|
||||
}
|
||||
if (!pattern.endsWith(".")) {
|
||||
pattern += '.';
|
||||
}
|
||||
// hostName and pattern are now absolute domain names.
|
||||
// hostname and pattern are now absolute domain names.
|
||||
|
||||
pattern = pattern.toLowerCase(Locale.US);
|
||||
// hostName and pattern are now in lower case -- domain names are case-insensitive.
|
||||
// hostname and pattern are now in lower case -- domain names are case-insensitive.
|
||||
|
||||
if (!pattern.contains("*")) {
|
||||
// Not a wildcard pattern -- hostName and pattern must match exactly.
|
||||
return hostName.equals(pattern);
|
||||
// Not a wildcard pattern -- hostname and pattern must match exactly.
|
||||
return hostname.equals(pattern);
|
||||
}
|
||||
// Wildcard pattern
|
||||
|
||||
@@ -196,11 +195,11 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optimization: check whether hostName is too short to match the pattern. hostName must be at
|
||||
// Optimization: check whether hostname is too short to match the pattern. hostName must be at
|
||||
// least as long as the pattern because asterisk must match the whole left-most label and
|
||||
// hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
|
||||
if (hostName.length() < pattern.length()) {
|
||||
// hostName too short to match the pattern.
|
||||
// hostname starts with a non-empty label. Thus, asterisk has to match one or more characters.
|
||||
if (hostname.length() < pattern.length()) {
|
||||
// hostname too short to match the pattern.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -209,22 +208,22 @@ public final class OkHostnameVerifier implements HostnameVerifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
// hostName must end with the region of pattern following the asterisk.
|
||||
// hostname must end with the region of pattern following the asterisk.
|
||||
String suffix = pattern.substring(1);
|
||||
if (!hostName.endsWith(suffix)) {
|
||||
// hostName does not end with the suffix
|
||||
if (!hostname.endsWith(suffix)) {
|
||||
// hostname does not end with the suffix
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that asterisk did not match across domain name labels.
|
||||
int suffixStartIndexInHostName = hostName.length() - suffix.length();
|
||||
if ((suffixStartIndexInHostName > 0)
|
||||
&& (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
|
||||
int suffixStartIndexInHostname = hostname.length() - suffix.length();
|
||||
if ((suffixStartIndexInHostname > 0)
|
||||
&& (hostname.lastIndexOf('.', suffixStartIndexInHostname - 1) != -1)) {
|
||||
// Asterisk is matching across domain name labels -- not permitted.
|
||||
return false;
|
||||
}
|
||||
|
||||
// hostName matches pattern
|
||||
// hostname matches pattern
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 okhttp3.internal.tls;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
public final class RealTrustRootIndex implements TrustRootIndex {
|
||||
private final Map<X500Principal, List<X509Certificate>> subjectToCaCerts;
|
||||
|
||||
public RealTrustRootIndex(X509Certificate... caCerts) {
|
||||
subjectToCaCerts = new LinkedHashMap<>();
|
||||
for (X509Certificate caCert : caCerts) {
|
||||
X500Principal subject = caCert.getSubjectX500Principal();
|
||||
List<X509Certificate> subjectCaCerts = subjectToCaCerts.get(subject);
|
||||
if (subjectCaCerts == null) {
|
||||
subjectCaCerts = new ArrayList<>(1);
|
||||
subjectToCaCerts.put(subject, subjectCaCerts);
|
||||
}
|
||||
subjectCaCerts.add(caCert);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
|
||||
X500Principal issuer = cert.getIssuerX500Principal();
|
||||
List<X509Certificate> subjectCaCerts = subjectToCaCerts.get(issuer);
|
||||
if (subjectCaCerts == null) return null;
|
||||
|
||||
for (X509Certificate caCert : subjectCaCerts) {
|
||||
PublicKey publicKey = caCert.getPublicKey();
|
||||
try {
|
||||
cert.verify(publicKey);
|
||||
return caCert;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 okhttp3.internal.tls;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public interface TrustRootIndex {
|
||||
/** Returns the trusted CA certificate that signed {@code cert}. */
|
||||
X509Certificate findByIssuerAndSignature(X509Certificate cert);
|
||||
}
|
||||
Reference in new issue
Block a user