| /* |
| * Copyright (C) 2012 Square, Inc. |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * 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.Protocol; |
| import com.squareup.okhttp.internal.tls.RealTrustRootIndex; |
| import com.squareup.okhttp.internal.tls.TrustRootIndex; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.net.InetSocketAddress; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicReference; |
| import javax.net.ssl.SSLSocket; |
| import javax.net.ssl.SSLSocketFactory; |
| import javax.net.ssl.X509TrustManager; |
| |
| import dalvik.system.SocketTagger; |
| import okio.Buffer; |
| |
| /** |
| * Access to proprietary Android APIs. Avoids use of reflection where possible. |
| */ |
| // only tests should extend this class |
| public class Platform { |
| private static final AtomicReference<Platform> INSTANCE_HOLDER |
| = new AtomicReference<>(new Platform()); |
| |
| // only for private use and in tests |
| protected Platform() { |
| } |
| |
| public static Platform get() { |
| return INSTANCE_HOLDER.get(); |
| } |
| |
| /** |
| * Atomically replaces the Platform instance returned by future |
| * invocations of {@link #get()}, for use in tests. |
| * Invocations of this method should typically be followed by |
| * a try/finally block to reset the previous value: |
| * |
| * <pre> |
| * Platform p = getAndSetForTest(...); |
| * try { |
| * ... |
| * } finally { |
| * getAndSetForTest(p); |
| * } |
| * </pre> |
| * |
| * @return the previous value of {@link #get()}. |
| */ |
| public static Platform getAndSetForTest(Platform platform) { |
| if (platform == null) { |
| throw new NullPointerException(); |
| } |
| return INSTANCE_HOLDER.getAndSet(platform); |
| } |
| |
| /** setUseSessionTickets(boolean) */ |
| private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS = |
| new OptionalMethod<Socket>(null, "setUseSessionTickets", Boolean.TYPE); |
| /** setHostname(String) */ |
| private static final OptionalMethod<Socket> SET_HOSTNAME = |
| new OptionalMethod<Socket>(null, "setHostname", String.class); |
| /** byte[] getAlpnSelectedProtocol() */ |
| private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL = |
| new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol"); |
| /** setAlpnSelectedProtocol(byte[]) */ |
| private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS = |
| new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class ); |
| |
| public void logW(String warning) { |
| System.logW(warning); |
| } |
| |
| public void tagSocket(Socket socket) throws SocketException { |
| SocketTagger.get().tag(socket); |
| } |
| |
| public void untagSocket(Socket socket) throws SocketException { |
| SocketTagger.get().untag(socket); |
| } |
| |
| public void configureTlsExtensions( |
| SSLSocket sslSocket, String hostname, List<Protocol> protocols) { |
| // Enable SNI and session tickets. |
| if (hostname != null) { |
| SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true); |
| SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname); |
| } |
| |
| // Enable ALPN. |
| boolean alpnSupported = SET_ALPN_PROTOCOLS.isSupported(sslSocket); |
| if (!alpnSupported) { |
| return; |
| } |
| |
| Object[] parameters = { concatLengthPrefixed(protocols) }; |
| if (alpnSupported) { |
| SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters); |
| } |
| } |
| |
| /** |
| * Called after the TLS handshake to release resources allocated by {@link |
| * #configureTlsExtensions}. |
| */ |
| public void afterHandshake(SSLSocket sslSocket) { |
| } |
| |
| public String getSelectedProtocol(SSLSocket socket) { |
| boolean alpnSupported = GET_ALPN_SELECTED_PROTOCOL.isSupported(socket); |
| if (!alpnSupported) { |
| return null; |
| } |
| |
| byte[] alpnResult = |
| (byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket); |
| if (alpnResult != null) { |
| return new String(alpnResult, Util.UTF_8); |
| } |
| return null; |
| } |
| |
| public void connectSocket(Socket socket, InetSocketAddress address, |
| int connectTimeout) throws IOException { |
| socket.connect(address, connectTimeout); |
| } |
| |
| /** Prefix used on custom headers. */ |
| public String getPrefix() { |
| return "X-Android"; |
| } |
| |
| /** |
| * Stripped down/adapted from OkHttp's {@code Platform.Android.trustManager()}. |
| * OkHttp 2.7.5 uses this only for certificate pinning logic that we don't use |
| * on Android, so this method should never be called outside of OkHttp's tests. |
| * This method has been stripped down to the minimum for OkHttp's tests to pass. |
| */ |
| public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { |
| Class sslParametersClass; |
| try { |
| sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters"); |
| return readFieldOrNull(context, X509TrustManager.class, "x509TrustManager"); |
| } |
| |
| /** |
| * Stripped down from OkHttp's implementation to the minimum to get OkHttp's tests |
| * to pass. OkHttp 2.7.5 uses this for certificate pinning logic which is unused |
| * in Android. This method should never be called outside of OkHttp's tests. |
| */ |
| public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { |
| return new RealTrustRootIndex(trustManager.getAcceptedIssuers()); |
| } |
| |
| // Helper method from OkHttp's Platform.java |
| private 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; |
| } |
| |
| /** |
| * Returns the concatenation of 8-bit, length prefixed protocol names. |
| * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 |
| */ |
| static byte[] concatLengthPrefixed(List<Protocol> protocols) { |
| Buffer result = new Buffer(); |
| for (int i = 0, size = protocols.size(); i < size; i++) { |
| Protocol protocol = protocols.get(i); |
| if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. |
| result.writeByte(protocol.toString().length()); |
| result.writeUtf8(protocol.toString()); |
| } |
| return result.readByteArray(); |
| } |
| } |