Merge "Use generated source not jarjar"
am: fbdaa4eb83
Change-Id: Ia6ff9f4b8e8cdd7d2035c2d8d6bc16e06b9cd58c
diff --git a/Android.bp b/Android.bp
index eb6d503..720d605 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,7 +17,9 @@
// The source files that contribute to Android's core library APIs.
filegroup {
name: "okhttp_api_files",
- srcs: ["android/src/main/java/com/android/okhttp/internalandroidapi/**/*.java"],
+ // Use the repackaged version of android as that is what is used by Android core library
+ // APIs.
+ srcs: ["repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/**/*.java"],
}
// non-jarjar'd version of okhttp to compile the tests against
@@ -44,8 +46,16 @@
java_library {
name: "okhttp",
- static_libs: ["okhttp-nojarjar"],
- jarjar_rules: "jarjar-rules.txt",
+ srcs: [
+ // Although some of the classes in the android/ directory are already in the correct
+ // package and do not need to be moved to another package they are transformed as they
+ // reference other classes that do require repackaging.
+ "repackaged/android/src/main/java/**/*.java",
+ "repackaged/okhttp/src/main/java/**/*.java",
+ "repackaged/okhttp-urlconnection/src/main/java/**/*.java",
+ "repackaged/okhttp-android-support/src/main/java/**/*.java",
+ "repackaged/okio/okio/src/main/java/**/*.java",
+ ],
hostdex: true,
installable: true,
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
deleted file mode 100644
index c84813d..0000000
--- a/jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule com.squareup.** com.android.@1
-rule okio.** com.android.okhttp.okio.@1
diff --git a/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java b/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java
new file mode 100644
index 0000000..1414133
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java
@@ -0,0 +1,102 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import libcore.net.event.NetworkEventDispatcher;
+import libcore.net.event.NetworkEventListener;
+
+/**
+ * A provider of the shared Android {@link ConnectionPool}. This class is aware of network
+ * configuration change events: When the network configuration changes the pool object is discarded
+ * and a later calls to {@link #get()} will return a new pool.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class ConfigAwareConnectionPool {
+
+ private static final long CONNECTION_POOL_DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+ private static final int CONNECTION_POOL_MAX_IDLE_CONNECTIONS;
+ private static final long CONNECTION_POOL_KEEP_ALIVE_DURATION_MS;
+ static {
+ String keepAliveProperty = System.getProperty("http.keepAlive");
+ String keepAliveDurationProperty = System.getProperty("http.keepAliveDuration");
+ String maxIdleConnectionsProperty = System.getProperty("http.maxConnections");
+ CONNECTION_POOL_KEEP_ALIVE_DURATION_MS = (keepAliveDurationProperty != null
+ ? Long.parseLong(keepAliveDurationProperty)
+ : CONNECTION_POOL_DEFAULT_KEEP_ALIVE_DURATION_MS);
+ if (keepAliveProperty != null && !Boolean.parseBoolean(keepAliveProperty)) {
+ CONNECTION_POOL_MAX_IDLE_CONNECTIONS = 0;
+ } else if (maxIdleConnectionsProperty != null) {
+ CONNECTION_POOL_MAX_IDLE_CONNECTIONS = Integer.parseInt(maxIdleConnectionsProperty);
+ } else {
+ CONNECTION_POOL_MAX_IDLE_CONNECTIONS = 5;
+ }
+ }
+
+ private static final ConfigAwareConnectionPool instance = new ConfigAwareConnectionPool();
+
+ private final NetworkEventDispatcher networkEventDispatcher;
+
+ /**
+ * {@code true} if the ConnectionPool reset has been registered with the
+ * {@link NetworkEventDispatcher}.
+ */
+ private boolean networkEventListenerRegistered;
+
+ private ConnectionPool connectionPool;
+
+ /** Visible for testing. Use {@link #getInstance()} */
+ protected ConfigAwareConnectionPool(NetworkEventDispatcher networkEventDispatcher) {
+ this.networkEventDispatcher = networkEventDispatcher;
+ }
+
+ private ConfigAwareConnectionPool() {
+ networkEventDispatcher = NetworkEventDispatcher.getInstance();
+ }
+
+ public static ConfigAwareConnectionPool getInstance() {
+ return instance;
+ }
+
+ /**
+ * Returns the current {@link ConnectionPool} to use.
+ */
+ public synchronized ConnectionPool get() {
+ if (connectionPool == null) {
+ // Only register the listener once the first time a ConnectionPool is created.
+ if (!networkEventListenerRegistered) {
+ networkEventDispatcher.addListener(new NetworkEventListener() {
+ @Override
+ public void onNetworkConfigurationChanged() {
+ synchronized (ConfigAwareConnectionPool.this) {
+ // If the network config has changed then existing pooled connections should not be
+ // re-used. By setting connectionPool to null it ensures that the next time
+ // getConnectionPool() is called a new pool will be created.
+ connectionPool = null;
+ }
+ }
+ });
+ networkEventListenerRegistered = true;
+ }
+ connectionPool = new ConnectionPool(
+ CONNECTION_POOL_MAX_IDLE_CONNECTIONS, CONNECTION_POOL_KEEP_ALIVE_DURATION_MS);
+ }
+ return connectionPool;
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java b/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java
new file mode 100644
index 0000000..2684dd6
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java
@@ -0,0 +1,35 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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.android.okhttp;
+
+/**
+ * Exposes nonpublic {@link ConnectionSpec} API for internal use by libcore.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public class ConnectionSpecs {
+ /** uninstantiable */
+ private ConnectionSpecs() {
+ }
+
+ public static ConnectionSpec.Builder builder(boolean tls) {
+ return new ConnectionSpec.Builder(tls);
+ }
+
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java b/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java
new file mode 100644
index 0000000..920824b
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java
@@ -0,0 +1,123 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+import libcore.net.NetworkSecurityPolicy;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ResponseCache;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class HttpHandler extends URLStreamHandler {
+
+ private final static List<ConnectionSpec> CLEARTEXT_ONLY =
+ Collections.singletonList(ConnectionSpec.CLEARTEXT);
+
+ private static final CleartextURLFilter CLEARTEXT_FILTER = new CleartextURLFilter();
+
+ private final ConfigAwareConnectionPool configAwareConnectionPool =
+ ConfigAwareConnectionPool.getInstance();
+
+ @Override protected URLConnection openConnection(URL url) throws IOException {
+ return newOkUrlFactory(null /* proxy */).open(url);
+ }
+
+ @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+ if (url == null || proxy == null) {
+ throw new IllegalArgumentException("url == null || proxy == null");
+ }
+ return newOkUrlFactory(proxy).open(url);
+ }
+
+ @Override protected int getDefaultPort() {
+ return 80;
+ }
+
+ protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
+ OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
+ // For HttpURLConnections created through java.net.URL Android uses a connection pool that
+ // is aware when the default network changes so that pooled connections are not re-used when
+ // the default network changes.
+ okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
+ return okUrlFactory;
+ }
+
+ /**
+ * Creates an OkHttpClient suitable for creating {@link java.net.HttpURLConnection} instances on
+ * Android.
+ */
+ // Visible for android.net.Network.
+ public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
+ OkHttpClient client = new OkHttpClient();
+
+ // Explicitly set the timeouts to infinity.
+ client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
+ client.setReadTimeout(0, TimeUnit.MILLISECONDS);
+ client.setWriteTimeout(0, TimeUnit.MILLISECONDS);
+
+ // Set the default (same protocol) redirect behavior. The default can be overridden for
+ // each instance using HttpURLConnection.setInstanceFollowRedirects().
+ client.setFollowRedirects(HttpURLConnection.getFollowRedirects());
+
+ // Do not permit http -> https and https -> http redirects.
+ client.setFollowSslRedirects(false);
+
+ // Permit cleartext traffic only (this is a handler for HTTP, not for HTTPS).
+ client.setConnectionSpecs(CLEARTEXT_ONLY);
+
+ // When we do not set the Proxy explicitly OkHttp picks up a ProxySelector using
+ // ProxySelector.getDefault().
+ if (proxy != null) {
+ client.setProxy(proxy);
+ }
+
+ // OkHttp requires that we explicitly set the response cache.
+ OkUrlFactory okUrlFactory = new OkUrlFactory(client);
+
+ // Use the installed NetworkSecurityPolicy to determine which requests are permitted over
+ // http.
+ OkUrlFactories.setUrlFilter(okUrlFactory, CLEARTEXT_FILTER);
+
+ ResponseCache responseCache = ResponseCache.getDefault();
+ if (responseCache != null) {
+ AndroidInternal.setResponseCache(okUrlFactory, responseCache);
+ }
+ return okUrlFactory;
+ }
+
+ private static final class CleartextURLFilter implements URLFilter {
+ @Override
+ public void checkURLPermitted(URL url) throws IOException {
+ String host = url.getHost();
+ if (!NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(host)) {
+ throw new IOException("Cleartext HTTP traffic to " + host + " not permitted");
+ }
+ }
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java b/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java
new file mode 100644
index 0000000..299274d
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.net.Proxy;
+import java.util.Collections;
+import java.util.List;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpsHandler extends HttpHandler {
+
+ /**
+ * The connection spec to use when connecting to an https:// server. Note that Android does
+ * not set the cipher suites or TLS versions to use so the socket's defaults will be used
+ * instead. When the SSLSocketFactory is provided by the app or GMS core we will not
+ * override the enabled ciphers or TLS versions set on the sockets it produces with a
+ * list hardcoded at release time. This is deliberate.
+ */
+ private static final ConnectionSpec TLS_CONNECTION_SPEC = ConnectionSpecs.builder(true)
+ .allEnabledCipherSuites()
+ .allEnabledTlsVersions()
+ .supportsTlsExtensions(true)
+ .build();
+
+ private static final List<Protocol> HTTP_1_1_ONLY =
+ Collections.singletonList(Protocol.HTTP_1_1);
+
+ private final ConfigAwareConnectionPool configAwareConnectionPool =
+ ConfigAwareConnectionPool.getInstance();
+
+ @Override protected int getDefaultPort() {
+ return 443;
+ }
+
+ @Override
+ protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
+ OkUrlFactory okUrlFactory = createHttpsOkUrlFactory(proxy);
+ // For HttpsURLConnections created through java.net.URL Android uses a connection pool that
+ // is aware when the default network changes so that pooled connections are not re-used when
+ // the default network changes.
+ okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
+ return okUrlFactory;
+ }
+
+ /**
+ * Creates an OkHttpClient suitable for creating {@link HttpsURLConnection} instances on
+ * Android.
+ */
+ // Visible for android.net.Network.
+ public static OkUrlFactory createHttpsOkUrlFactory(Proxy proxy) {
+ // The HTTPS OkHttpClient is an HTTP OkHttpClient with extra configuration.
+ OkUrlFactory okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);
+
+ // All HTTPS requests are allowed.
+ OkUrlFactories.setUrlFilter(okUrlFactory, null);
+
+ OkHttpClient okHttpClient = okUrlFactory.client();
+
+ // Only enable HTTP/1.1 (implies HTTP/1.0). Disable SPDY / HTTP/2.0.
+ okHttpClient.setProtocols(HTTP_1_1_ONLY);
+
+ okHttpClient.setConnectionSpecs(Collections.singletonList(TLS_CONNECTION_SPEC));
+
+ // Android support certificate pinning via NetworkSecurityConfig so there is no need to
+ // also expose OkHttp's mechanism. The OkHttpClient underlying https HttpsURLConnections
+ // in Android should therefore always use the default certificate pinner, whose set of
+ // {@code hostNamesToPin} is empty.
+ okHttpClient.setCertificatePinner(CertificatePinner.DEFAULT);
+
+ // OkHttp does not automatically honor the system-wide HostnameVerifier set with
+ // HttpsURLConnection.setDefaultHostnameVerifier().
+ okUrlFactory.client().setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
+ // OkHttp does not automatically honor the system-wide SSLSocketFactory set with
+ // HttpsURLConnection.setDefaultSSLSocketFactory().
+ // See https://github.com/square/okhttp/issues/184 for details.
+ okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
+
+ return okUrlFactory;
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java b/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java
new file mode 100644
index 0000000..1b01f44
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java
@@ -0,0 +1,46 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+
+/**
+ * Exposes nonpublic {@link OkUrlFactory} API for internal use by libcore.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OkUrlFactories {
+
+ /** uninstantiable */
+ private OkUrlFactories() {
+ }
+
+ public static HttpURLConnection open(OkUrlFactory okUrlFactory, URL url, Proxy proxy) {
+ return okUrlFactory.open(url, proxy);
+ }
+
+ public static void setUrlFilter(OkUrlFactory okUrlFactory, URLFilter urlFilter) {
+ okUrlFactory.setUrlFilter(urlFilter);
+ }
+
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java b/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java
new file mode 100644
index 0000000..ad2559a
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java
@@ -0,0 +1,219 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.tls.RealTrustRootIndex;
+import com.android.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 com.android.okhttp.okio.Buffer;
+
+/**
+ * Access to proprietary Android APIs. Avoids use of reflection where possible.
+ * @hide This class is not part of the Android public SDK API
+ */
+// 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();
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java b/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java
new file mode 100644
index 0000000..f186a62
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java
@@ -0,0 +1,30 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Version {
+ public static String userAgent() {
+ String agent = System.getProperty("http.agent");
+ return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+ }
+
+ private Version() {
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java
new file mode 100644
index 0000000..b7f5002
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java
@@ -0,0 +1,172 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder;
+import com.android.okhttp.Cache;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.huc.JavaApiConverter;
+
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A modified copy of {@link com.android.okhttp.AndroidShimResponseCache} that exposes a
+ * {@link CacheHolder} instead of a {@link Cache}. We want to keep the framework code that interacts
+ * with OkHttp at arms length. By delegating to this class the Android HttpResponseCache class has
+ * no knowledge of OkHttp internal classes at class resolution time and there are no internal
+ * classes appearing on method signatures.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public final class AndroidResponseCacheAdapter {
+
+ private final CacheHolder cacheHolder;
+ private final Cache okHttpCache;
+
+ @libcore.api.CorePlatformApi
+ public AndroidResponseCacheAdapter(CacheHolder cacheHolder) {
+ this.cacheHolder = cacheHolder;
+ // Avoid one level of dereferencing by storing the reference to the OkHttp cache for later.
+ this.okHttpCache = cacheHolder.getCache();
+ }
+
+ /**
+ * Returns the {@link CacheHolder} associated with this instance and can be used by OkHttp
+ * internal code to obtain the underlying OkHttp Cache object.
+ */
+ @libcore.api.CorePlatformApi
+ public CacheHolder getCacheHolder() {
+ return cacheHolder;
+ }
+
+ /**
+ * Used to implement {@link java.net.ResponseCache#get(URI, String, Map)}. See that method for
+ * details.
+ */
+ @libcore.api.CorePlatformApi
+ public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders);
+ Response okResponse = okHttpCache.internalCache.get(okRequest);
+ if (okResponse == null) {
+ return null;
+ }
+ return JavaApiConverter.createJavaCacheResponse(okResponse);
+ }
+
+ /**
+ * Used to implement {@link java.net.ResponseCache#put(URI, URLConnection)}. See that method for
+ * details.
+ */
+ @libcore.api.CorePlatformApi
+ public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+ Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection);
+ if (okResponse == null) {
+ // The URLConnection is not cacheable or could not be converted. Stop.
+ return null;
+ }
+ com.android.okhttp.internal.http.CacheRequest okCacheRequest =
+ okHttpCache.internalCache.put(okResponse);
+ if (okCacheRequest == null) {
+ return null;
+ }
+ return JavaApiConverter.createJavaCacheRequest(okCacheRequest);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the {@link #getMaxSize()} if a background
+ * deletion is pending. IOException is thrown if the size cannot be determined.
+ */
+ @libcore.api.CorePlatformApi
+ public long getSize() throws IOException {
+ return okHttpCache.getSize();
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ @libcore.api.CorePlatformApi
+ public long getMaxSize() {
+ return okHttpCache.getMaxSize();
+ }
+
+ /**
+ * Force buffered operations to the filesystem. This ensures that responses
+ * written to the cache will be available the next time the cache is opened,
+ * even if this process is killed. IOException is thrown if the flush fails.
+ */
+ @libcore.api.CorePlatformApi
+ public void flush() throws IOException {
+ okHttpCache.flush();
+ }
+
+ /**
+ * Returns the number of HTTP requests that required the network to either
+ * supply a response or validate a locally cached response.
+ */
+ @libcore.api.CorePlatformApi
+ public int getNetworkCount() {
+ return okHttpCache.getNetworkCount();
+ }
+
+ /**
+ * Returns the number of HTTP requests whose response was provided by the
+ * cache. This may include conditional {@code GET} requests that were
+ * validated over the network.
+ */
+ @libcore.api.CorePlatformApi
+ public int getHitCount() {
+ return okHttpCache.getHitCount();
+ }
+
+ /**
+ * Returns the total number of HTTP requests that were made. This includes
+ * both client requests and requests that were made on the client's behalf
+ * to handle a redirects and retries.
+ */
+ @libcore.api.CorePlatformApi
+ public int getRequestCount() {
+ return okHttpCache.getRequestCount();
+ }
+
+ /** Closes this cache. Stored values will remain on the filesystem. */
+ @libcore.api.CorePlatformApi
+ public void close() throws IOException {
+ okHttpCache.close();
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ @libcore.api.CorePlatformApi
+ public void delete() throws IOException {
+ okHttpCache.delete();
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..859ea80
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,37 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public interface Dns {
+ /**
+ * Returns the IP addresses of {@code hostname}, in the order they should
+ * be attempted.
+ */
+ @libcore.api.CorePlatformApi
+ List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java
new file mode 100644
index 0000000..aa90bf3
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java
@@ -0,0 +1,91 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2018 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.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.Cache;
+
+import java.io.File;
+
+/**
+ * An interface used to indicate a class can return a {@link CacheHolder} object.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public interface HasCacheHolder {
+
+ /**
+ * Returns the {@link CacheHolder} object.
+ */
+ @libcore.api.CorePlatformApi
+ CacheHolder getCacheHolder();
+
+ /**
+ * A holder for an OkHttp internal Cache object. This class exists as an opaque layer over
+ * OkHttp internal classes.
+ * @hide
+ */
+ @libcore.api.CorePlatformApi
+ final class CacheHolder {
+
+ private final Cache okHttpCache;
+
+ private CacheHolder(Cache okHttpCache) {
+ this.okHttpCache = okHttpCache;
+ }
+
+ // This constructor is required for @libcore.api.CorePlatformApi stubs generation. Without
+ // it the constructor above is included in the stubs code which adds a dependency on
+ // okhttp.Cache that we don't want.
+ @SuppressWarnings("unused")
+ private CacheHolder() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the underlying {@link Cache} object.
+ * @hide
+ */
+ public Cache getCache() {
+ return okHttpCache;
+ }
+
+ /**
+ * Returns a new {@link CacheHolder} containing an OKHttp Cache with the specified settings.
+ *
+ * @param directory a writable directory
+ * @param maxSizeBytes the maximum number of bytes this cache should use to store
+ */
+ @libcore.api.CorePlatformApi
+ public static CacheHolder create(File directory, long maxSizeBytes) {
+ Cache cache = new Cache(directory, maxSizeBytes);
+ return new CacheHolder(cache);
+ }
+
+ /**
+ * Returns true if the arguments supplied would result in an equivalent cache to this one
+ * being created if they were passed to {@link #create(File, long)}.
+ */
+ @libcore.api.CorePlatformApi
+ public boolean isEquivalent(File directory, long maxSizeBytes) {
+ return (okHttpCache.getDirectory().equals(directory)
+ && okHttpCache.getMaxSize() == maxSizeBytes
+ && !okHttpCache.isClosed());
+ }
+ }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java
new file mode 100644
index 0000000..38722fe
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java
@@ -0,0 +1,181 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.HttpHandler;
+import com.android.okhttp.HttpsHandler;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.OkUrlFactories;
+import com.android.okhttp.OkUrlFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import javax.net.SocketFactory;
+
+/**
+ * A way to construct {@link java.net.HttpURLConnection}s that supports some
+ * configuration on a per-factory or per-connection basis rather than only via
+ * global static state such as {@link java.net.CookieHandler#setDefault(java.net.CookieHandler)}.
+ * The per-factory configuration is <b>optional</b>; if not set, global
+ * configuration or default behavior is used.
+ *
+ * This facade prevents tight coupling with the underlying implementation (on
+ * top of a particular version of OkHttp). Android code outside of libcore
+ * should never depend directly on OkHttp.
+ *
+ * This abstraction is not suitable for general use. Talk to the maintainers of
+ * this class before modifying it or adding additional dependencies.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public final class HttpURLConnectionFactory {
+
+ private ConnectionPool connectionPool;
+ private com.android.okhttp.Dns dns;
+
+ /**
+ * Sets a new ConnectionPool, specific to this URLFactory and not shared with
+ * any other connections, with the given configuration.
+ */
+ @libcore.api.CorePlatformApi
+ public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration,
+ TimeUnit timeUnit) {
+ this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
+ }
+
+ @libcore.api.CorePlatformApi
+ public void setDns(Dns dns) {
+ Objects.requireNonNull(dns);
+ this.dns = new DnsAdapter(dns);
+ }
+
+ /**
+ * Opens a connection that uses the system default proxy settings and SocketFactory.
+ */
+ public URLConnection openConnection(URL url) throws IOException {
+ return internalOpenConnection(url, null /* socketFactory */, null /* proxy */);
+ }
+
+ /**
+ * Opens a connection that uses the system default SocketFactory and the specified
+ * proxy settings.
+ */
+ public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+ Objects.requireNonNull(proxy);
+ return internalOpenConnection(url, null /* socketFactory */, proxy);
+ }
+
+ /**
+ * Opens a connection that uses the specified SocketFactory and the system default
+ * proxy settings.
+ */
+ public URLConnection openConnection(URL url, SocketFactory socketFactory) throws IOException {
+ Objects.requireNonNull(socketFactory);
+ return internalOpenConnection(url, socketFactory, null /* proxy */);
+ }
+
+ /**
+ * Opens a connection using the specified SocketFactory and the specified proxy
+ * settings, overriding any system wide configuration.
+ */
+ @libcore.api.CorePlatformApi
+ public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy)
+ throws IOException {
+ Objects.requireNonNull(socketFactory);
+ Objects.requireNonNull(proxy);
+ return internalOpenConnection(url, socketFactory, proxy);
+ }
+
+ private URLConnection internalOpenConnection(URL url, SocketFactory socketFactoryOrNull,
+ Proxy proxyOrNull) throws IOException {
+ String protocol = url.getProtocol();
+ OkUrlFactory okUrlFactory;
+ // TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.
+ // Could this cause unexpected behavior?
+ if (protocol.equals("http")) {
+ okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxyOrNull);
+ } else if (protocol.equals("https")) {
+ okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxyOrNull);
+ } else {
+ // OkHttp only supports HTTP and HTTPS.
+ throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
+ }
+
+ OkHttpClient client = okUrlFactory.client();
+ if (connectionPool != null) {
+ client.setConnectionPool(connectionPool);
+ }
+ if (dns != null) {
+ client.setDns(dns);
+ }
+ if (socketFactoryOrNull != null) {
+ client.setSocketFactory(socketFactoryOrNull);
+ }
+ if (proxyOrNull == null) {
+ return okUrlFactory.open(url);
+ } else {
+ return OkUrlFactories.open(okUrlFactory, url, proxyOrNull);
+ }
+ }
+
+ /**
+ * Adapts a {@link Dns} as a {@link com.android.okhttp.Dns}.
+ */
+ static final class DnsAdapter implements com.android.okhttp.Dns {
+ private final Dns adaptee;
+
+ DnsAdapter(Dns adaptee) {
+ this.adaptee = Objects.requireNonNull(adaptee);
+ }
+
+ @Override
+ public List<InetAddress> lookup(String hostname) throws UnknownHostException {
+ return adaptee.lookup(hostname);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * DnsAdapter.class.hashCode() + adaptee.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DnsAdapter)) {
+ return false;
+ }
+ return adaptee.equals(((DnsAdapter) obj).adaptee);
+ }
+
+ @Override
+ public String toString() {
+ return adaptee.toString();
+ }
+ }
+
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java
new file mode 100644
index 0000000..746f63c
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internalandroidapi.HasCacheHolder;
+import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder;
+import com.android.okhttp.internal.huc.CacheAdapter;
+
+import java.net.ResponseCache;
+
+/**
+ * Back doors to enable the use of OkHttp within the Android platform libraries. OkHttp is used to
+ * provide the default {@link java.net.HttpURLConnection} / {@link javax.net.ssl.HttpsURLConnection}
+ * implementation including support for a custom {@link ResponseCache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AndroidInternal {
+
+ private AndroidInternal() {
+ }
+
+ /** Sets the response cache to be used to read and write cached responses. */
+ public static void setResponseCache(OkUrlFactory okUrlFactory, ResponseCache responseCache) {
+ OkHttpClient client = okUrlFactory.client();
+ if (responseCache instanceof OkCacheContainer) {
+ // Avoid adding layers of wrappers. Rather than wrap the ResponseCache in yet another layer to
+ // make the ResponseCache look like an InternalCache, we can unwrap the Cache instead.
+ // This means that Cache stats will be correctly updated.
+ OkCacheContainer okCacheContainer = (OkCacheContainer) responseCache;
+ client.setCache(okCacheContainer.getCache());
+ // BEGIN Android-added: Recognize internalapi.HasCacheHolder.
+ } else if (responseCache instanceof HasCacheHolder) {
+ // Avoid adding layers of wrappers. Rather than wrap the ResponseCache in yet another layer to
+ // make the ResponseCache look like an InternalCache using CacheAdapter, we can unwrap the
+ // held Cache instead. This means that Cache stats will be correctly updated by OkHttp.
+ HasCacheHolder hasCacheHolder = (HasCacheHolder) responseCache;
+ CacheHolder cacheHolder = hasCacheHolder.getCacheHolder();
+ client.setCache(cacheHolder.getCache());
+ // END Android-added: Recognize internalapi.HasCacheHolder.
+ } else {
+ client.setInternalCache(responseCache != null ? new CacheAdapter(responseCache) : null);
+ }
+ }
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java
new file mode 100644
index 0000000..b0f504a
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java
@@ -0,0 +1,151 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.huc.JavaApiConverter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class provided for use by Android so that it can continue supporting a {@link ResponseCache}
+ * with stats.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AndroidShimResponseCache extends ResponseCache {
+
+ private final Cache delegate;
+
+ private AndroidShimResponseCache(Cache delegate) {
+ this.delegate = delegate;
+ }
+
+ public static AndroidShimResponseCache create(File directory, long maxSize) throws IOException {
+ Cache cache = new Cache(directory, maxSize);
+ return new AndroidShimResponseCache(cache);
+ }
+
+ public boolean isEquivalent(File directory, long maxSize) {
+ Cache installedCache = getCache();
+ return (installedCache.getDirectory().equals(directory)
+ && installedCache.getMaxSize() == maxSize
+ && !installedCache.isClosed());
+ }
+
+ public Cache getCache() {
+ return delegate;
+ }
+
+ @Override public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders);
+ Response okResponse = delegate.internalCache.get(okRequest);
+ if (okResponse == null) {
+ return null;
+ }
+ return JavaApiConverter.createJavaCacheResponse(okResponse);
+ }
+
+ @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+ Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection);
+ if (okResponse == null) {
+ // The URLConnection is not cacheable or could not be converted. Stop.
+ return null;
+ }
+ com.android.okhttp.internal.http.CacheRequest okCacheRequest =
+ delegate.internalCache.put(okResponse);
+ if (okCacheRequest == null) {
+ return null;
+ }
+ return JavaApiConverter.createJavaCacheRequest(okCacheRequest);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the {@link #maxSize} if a background
+ * deletion is pending.
+ */
+ public long size() throws IOException {
+ return delegate.getSize();
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public long maxSize() {
+ return delegate.getMaxSize();
+ }
+
+ /**
+ * Force buffered operations to the filesystem. This ensures that responses
+ * written to the cache will be available the next time the cache is opened,
+ * even if this process is killed.
+ */
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ /**
+ * Returns the number of HTTP requests that required the network to either
+ * supply a response or validate a locally cached response.
+ */
+ public int getNetworkCount() {
+ return delegate.getNetworkCount();
+ }
+
+ /**
+ * Returns the number of HTTP requests whose response was provided by the
+ * cache. This may include conditional {@code GET} requests that were
+ * validated over the network.
+ */
+ public int getHitCount() {
+ return delegate.getHitCount();
+ }
+
+ /**
+ * Returns the total number of HTTP requests that were made. This includes
+ * both client requests and requests that were made on the client's behalf
+ * to handle a redirects and retries.
+ */
+ public int getRequestCount() {
+ return delegate.getRequestCount();
+ }
+
+ /**
+ * Uninstalls the cache and releases any active resources. Stored contents
+ * will remain on the filesystem.
+ */
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ /**
+ * Uninstalls the cache and deletes all of its stored contents.
+ */
+ public void delete() throws IOException {
+ delegate.delete();
+ }
+
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java
new file mode 100644
index 0000000..72e71b1
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java
@@ -0,0 +1,26 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+/**
+ * An interface that allows OkHttp to detect that a {@link java.net.ResponseCache} contains a
+ * {@link Cache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface OkCacheContainer {
+ Cache getCache();
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java
new file mode 100644
index 0000000..97751d2
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java
@@ -0,0 +1,107 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+
+/** Adapts {@link ResponseCache} to {@link InternalCache}.
+ * @hide This class is not part of the Android public SDK API*/
+public final class CacheAdapter implements InternalCache {
+ private final ResponseCache delegate;
+
+ public CacheAdapter(ResponseCache delegate) {
+ this.delegate = delegate;
+ }
+
+ public ResponseCache getDelegate() {
+ return delegate;
+ }
+
+ @Override public Response get(Request request) throws IOException {
+ CacheResponse javaResponse = getJavaCachedResponse(request);
+ if (javaResponse == null) {
+ return null;
+ }
+ return JavaApiConverter.createOkResponseForCacheGet(request, javaResponse);
+ }
+
+ @Override public CacheRequest put(Response response) throws IOException {
+ URI uri = response.request().uri();
+ HttpURLConnection connection = JavaApiConverter.createJavaUrlConnectionForCachePut(response);
+ final java.net.CacheRequest request = delegate.put(uri, connection);
+ if (request == null) {
+ return null;
+ }
+ return new CacheRequest() {
+ @Override public Sink body() throws IOException {
+ OutputStream body = request.getBody();
+ return body != null ? Okio.sink(body) : null;
+ }
+
+ @Override public void abort() {
+ request.abort();
+ }
+ };
+ }
+
+ @Override public void remove(Request request) throws IOException {
+ // This method is treated as optional and there is no obvious way of implementing it with
+ // ResponseCache. Removing items from the cache due to modifications made from this client is
+ // not essential given that modifications could be made from any other client. We have to assume
+ // that it's ok to keep using the cached data. Otherwise the server shouldn't declare it as
+ // cacheable or the client should be careful about caching it.
+ }
+
+ @Override public void update(Response cached, Response network) throws IOException {
+ // This method is treated as optional and there is no obvious way of implementing it with
+ // ResponseCache. Updating headers is useful if the server changes the metadata for a resource
+ // (e.g. max age) to extend or truncate the life of that resource in the cache. If the metadata
+ // is not updated the caching behavior may not be optimal, but will obey the metadata sent
+ // with the original cached response.
+ }
+
+ @Override public void trackConditionalCacheHit() {
+ // This method is optional.
+ }
+
+ @Override public void trackResponse(CacheStrategy cacheStrategy) {
+ // This method is optional.
+ }
+
+ /**
+ * Returns the {@link CacheResponse} from the delegate by converting the
+ * OkHttp {@link Request} into the arguments required by the {@link ResponseCache}.
+ */
+ private CacheResponse getJavaCachedResponse(Request request) throws IOException {
+ Map<String, List<String>> headers = JavaApiConverter.extractJavaHeaders(request);
+ return delegate.get(request.uri(), request.method(), headers);
+ }
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java
new file mode 100644
index 0000000..cbd3b83
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java
@@ -0,0 +1,867 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import com.android.okhttp.Headers;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.Request;
+import com.android.okhttp.RequestBody;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.StatusLine;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * Helper methods that convert between Java and OkHttp representations.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class JavaApiConverter {
+ private static final RequestBody EMPTY_REQUEST_BODY = RequestBody.create(null, new byte[0]);
+
+ private JavaApiConverter() {
+ }
+
+ /**
+ * Creates an OkHttp {@link Response} using the supplied {@link URI} and {@link URLConnection}
+ * to supply the data. The URLConnection is assumed to already be connected. If this method
+ * returns {@code null} the response is uncacheable.
+ */
+ public static Response createOkResponseForCachePut(URI uri, URLConnection urlConnection)
+ throws IOException {
+
+ HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
+
+ Response.Builder okResponseBuilder = new Response.Builder();
+
+ // Request: Create one from the URL connection.
+ Headers responseHeaders = createHeaders(urlConnection.getHeaderFields());
+ // Some request headers are needed for Vary caching.
+ Headers varyHeaders = varyHeaders(urlConnection, responseHeaders);
+ if (varyHeaders == null) {
+ return null;
+ }
+
+ // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+ String requestMethod = httpUrlConnection.getRequestMethod();
+ RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod)
+ ? EMPTY_REQUEST_BODY
+ : null;
+
+ Request okRequest = new Request.Builder()
+ .url(uri.toString())
+ .method(requestMethod, placeholderBody)
+ .headers(varyHeaders)
+ .build();
+ okResponseBuilder.request(okRequest);
+
+ // Status line
+ StatusLine statusLine = StatusLine.parse(extractStatusLine(httpUrlConnection));
+ okResponseBuilder.protocol(statusLine.protocol);
+ okResponseBuilder.code(statusLine.code);
+ okResponseBuilder.message(statusLine.message);
+
+ // A network response is required for the Cache to find any Vary headers it needs.
+ Response networkResponse = okResponseBuilder.build();
+ okResponseBuilder.networkResponse(networkResponse);
+
+ // Response headers
+ Headers okHeaders = extractOkResponseHeaders(httpUrlConnection);
+ okResponseBuilder.headers(okHeaders);
+
+ // Response body
+ ResponseBody okBody = createOkBody(urlConnection);
+ okResponseBuilder.body(okBody);
+
+ // Handle SSL handshake information as needed.
+ if (httpUrlConnection instanceof HttpsURLConnection) {
+ HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) httpUrlConnection;
+
+ Certificate[] peerCertificates;
+ try {
+ peerCertificates = httpsUrlConnection.getServerCertificates();
+ } catch (SSLPeerUnverifiedException e) {
+ peerCertificates = null;
+ }
+
+ Certificate[] localCertificates = httpsUrlConnection.getLocalCertificates();
+
+ Handshake handshake = Handshake.get(
+ httpsUrlConnection.getCipherSuite(), nullSafeImmutableList(peerCertificates),
+ nullSafeImmutableList(localCertificates));
+ okResponseBuilder.handshake(handshake);
+ }
+
+ return okResponseBuilder.build();
+ }
+
+ /**
+ * Returns headers for the header names and values in the {@link Map}.
+ */
+ private static Headers createHeaders(Map<String, List<String>> headers) {
+ Headers.Builder builder = new Headers.Builder();
+ for (Map.Entry<String, List<String>> header : headers.entrySet()) {
+ if (header.getKey() == null || header.getValue() == null) {
+ continue;
+ }
+ String name = header.getKey().trim();
+ for (String value : header.getValue()) {
+ String trimmedValue = value.trim();
+ Internal.instance.addLenient(builder, name, trimmedValue);
+ }
+ }
+ return builder.build();
+ }
+
+ private static Headers varyHeaders(URLConnection urlConnection, Headers responseHeaders) {
+ if (OkHeaders.hasVaryAll(responseHeaders)) {
+ // "*" means that this will be treated as uncacheable anyway.
+ return null;
+ }
+ Set<String> varyFields = OkHeaders.varyFields(responseHeaders);
+ if (varyFields.isEmpty()) {
+ return new Headers.Builder().build();
+ }
+
+ // This probably indicates another HTTP stack is trying to use the shared ResponseCache.
+ // We cannot guarantee this case will work properly because we cannot reliably extract *all*
+ // the request header values, and we can't get multiple Vary request header values.
+ // We also can't be sure about the Accept-Encoding behavior of other stacks.
+ if (!(urlConnection instanceof CacheHttpURLConnection
+ || urlConnection instanceof CacheHttpsURLConnection)) {
+ return null;
+ }
+
+ // This is the case we expect: The URLConnection is from a call to
+ // JavaApiConverter.createJavaUrlConnection() and we have access to the user's request headers.
+ Map<String, List<String>> requestProperties = urlConnection.getRequestProperties();
+ Headers.Builder result = new Headers.Builder();
+ for (String fieldName : varyFields) {
+ List<String> fieldValues = requestProperties.get(fieldName);
+ if (fieldValues == null) {
+ if (fieldName.equals("Accept-Encoding")) {
+ // Accept-Encoding is special. If OkHttp sees Accept-Encoding is unset it will add
+ // "gzip". We don't have access to the request that was actually made so we must do the
+ // same.
+ result.add("Accept-Encoding", "gzip");
+ }
+ } else {
+ for (String fieldValue : fieldValues) {
+ Internal.instance.addLenient(result, fieldName, fieldValue);
+ }
+ }
+ }
+ return result.build();
+ }
+
+ /**
+ * Creates an OkHttp {@link Response} using the supplied {@link Request} and {@link CacheResponse}
+ * to supply the data.
+ */
+ static Response createOkResponseForCacheGet(Request request, CacheResponse javaResponse)
+ throws IOException {
+
+ // Build a cache request for the response to use.
+ Headers responseHeaders = createHeaders(javaResponse.getHeaders());
+ Headers varyHeaders;
+ if (OkHeaders.hasVaryAll(responseHeaders)) {
+ // "*" means that this will be treated as uncacheable anyway.
+ varyHeaders = new Headers.Builder().build();
+ } else {
+ varyHeaders = OkHeaders.varyHeaders(request.headers(), responseHeaders);
+ }
+
+ Request cacheRequest = new Request.Builder()
+ .url(request.httpUrl())
+ .method(request.method(), null)
+ .headers(varyHeaders)
+ .build();
+
+ Response.Builder okResponseBuilder = new Response.Builder();
+
+ // Request: Use the cacheRequest we built.
+ okResponseBuilder.request(cacheRequest);
+
+ // Status line: Java has this as one of the headers.
+ StatusLine statusLine = StatusLine.parse(extractStatusLine(javaResponse));
+ okResponseBuilder.protocol(statusLine.protocol);
+ okResponseBuilder.code(statusLine.code);
+ okResponseBuilder.message(statusLine.message);
+
+ // Response headers
+ Headers okHeaders = extractOkHeaders(javaResponse);
+ okResponseBuilder.headers(okHeaders);
+
+ // Response body
+ ResponseBody okBody = createOkBody(okHeaders, javaResponse);
+ okResponseBuilder.body(okBody);
+
+ // Handle SSL handshake information as needed.
+ if (javaResponse instanceof SecureCacheResponse) {
+ SecureCacheResponse javaSecureCacheResponse = (SecureCacheResponse) javaResponse;
+
+ // Handshake doesn't support null lists.
+ List<Certificate> peerCertificates;
+ try {
+ peerCertificates = javaSecureCacheResponse.getServerCertificateChain();
+ } catch (SSLPeerUnverifiedException e) {
+ peerCertificates = Collections.emptyList();
+ }
+ List<Certificate> localCertificates = javaSecureCacheResponse.getLocalCertificateChain();
+ if (localCertificates == null) {
+ localCertificates = Collections.emptyList();
+ }
+ Handshake handshake = Handshake.get(
+ javaSecureCacheResponse.getCipherSuite(), peerCertificates, localCertificates);
+ okResponseBuilder.handshake(handshake);
+ }
+
+ return okResponseBuilder.build();
+ }
+
+ /**
+ * Creates an OkHttp {@link Request} from the supplied information.
+ *
+ * <p>This method allows a {@code null} value for {@code requestHeaders} for situations
+ * where a connection is already connected and access to the headers has been lost.
+ * See {@link java.net.HttpURLConnection#getRequestProperties()} for details.
+ */
+ public static Request createOkRequest(
+ URI uri, String requestMethod, Map<String, List<String>> requestHeaders) {
+ // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+ RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod)
+ ? EMPTY_REQUEST_BODY
+ : null;
+
+ Request.Builder builder = new Request.Builder()
+ .url(uri.toString())
+ .method(requestMethod, placeholderBody);
+
+ if (requestHeaders != null) {
+ Headers headers = extractOkHeaders(requestHeaders);
+ builder.headers(headers);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link java.net.CacheResponse} of the correct (sub)type using information
+ * gathered from the supplied {@link Response}.
+ */
+ public static CacheResponse createJavaCacheResponse(final Response response) {
+ final Headers headers = response.headers();
+ final ResponseBody body = response.body();
+ if (response.request().isHttps()) {
+ final Handshake handshake = response.handshake();
+ return new SecureCacheResponse() {
+ @Override
+ public String getCipherSuite() {
+ return handshake != null ? handshake.cipherSuite() : null;
+ }
+
+ @Override
+ public List<Certificate> getLocalCertificateChain() {
+ if (handshake == null) return null;
+ // Java requires null, not an empty list here.
+ List<Certificate> certificates = handshake.localCertificates();
+ return certificates.size() > 0 ? certificates : null;
+ }
+
+ @Override
+ public List<Certificate> getServerCertificateChain() throws SSLPeerUnverifiedException {
+ if (handshake == null) return null;
+ // Java requires null, not an empty list here.
+ List<Certificate> certificates = handshake.peerCertificates();
+ return certificates.size() > 0 ? certificates : null;
+ }
+
+ @Override
+ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ if (handshake == null) return null;
+ return handshake.peerPrincipal();
+ }
+
+ @Override
+ public Principal getLocalPrincipal() {
+ if (handshake == null) return null;
+ return handshake.localPrincipal();
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaders() throws IOException {
+ // Java requires that the entry with a null key be the status line.
+ return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ if (body == null) return null;
+ return body.byteStream();
+ }
+ };
+ } else {
+ return new CacheResponse() {
+ @Override
+ public Map<String, List<String>> getHeaders() throws IOException {
+ // Java requires that the entry with a null key be the status line.
+ return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ if (body == null) return null;
+ return body.byteStream();
+ }
+ };
+ }
+ }
+
+ public static java.net.CacheRequest createJavaCacheRequest(final CacheRequest okCacheRequest) {
+ return new java.net.CacheRequest() {
+ @Override
+ public void abort() {
+ okCacheRequest.abort();
+ }
+ @Override
+ public OutputStream getBody() throws IOException {
+ Sink body = okCacheRequest.body();
+ if (body == null) {
+ return null;
+ }
+ return Okio.buffer(body).outputStream();
+ }
+ };
+ }
+
+ /**
+ * Creates an {@link java.net.HttpURLConnection} of the correct subclass from the supplied OkHttp
+ * {@link Response}.
+ */
+ static HttpURLConnection createJavaUrlConnectionForCachePut(Response okResponse) {
+ Request request = okResponse.request();
+ // Create an object of the correct class in case the ResponseCache uses instanceof.
+ if (request.isHttps()) {
+ return new CacheHttpsURLConnection(new CacheHttpURLConnection(okResponse));
+ } else {
+ return new CacheHttpURLConnection(okResponse);
+ }
+ }
+
+ /**
+ * Extracts an immutable request header map from the supplied {@link com.android.okhttp.Headers}.
+ */
+ static Map<String, List<String>> extractJavaHeaders(Request request) {
+ return OkHeaders.toMultimap(request.headers(), null);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link java.net.CacheResponse}. Only real headers are
+ * extracted. See {@link #extractStatusLine(java.net.CacheResponse)}.
+ */
+ private static Headers extractOkHeaders(CacheResponse javaResponse) throws IOException {
+ Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders();
+ return extractOkHeaders(javaResponseHeaders);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link java.net.HttpURLConnection}. Only real headers
+ * are extracted. See {@link #extractStatusLine(java.net.HttpURLConnection)}.
+ */
+ private static Headers extractOkResponseHeaders(HttpURLConnection httpUrlConnection) {
+ Map<String, List<String>> javaResponseHeaders = httpUrlConnection.getHeaderFields();
+ return extractOkHeaders(javaResponseHeaders);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link Map}. Only real headers are
+ * extracted. Any entry (one with a {@code null} key) is discarded.
+ */
+ // @VisibleForTesting
+ static Headers extractOkHeaders(Map<String, List<String>> javaHeaders) {
+ Headers.Builder okHeadersBuilder = new Headers.Builder();
+ for (Map.Entry<String, List<String>> javaHeader : javaHeaders.entrySet()) {
+ String name = javaHeader.getKey();
+ if (name == null) {
+ // The Java API uses the null key to store the status line in responses.
+ // Earlier versions of OkHttp would use the null key to store the "request line" in
+ // requests. e.g. "GET / HTTP 1.1". Although this is no longer the case it must be
+ // explicitly ignored because Headers.Builder does not support null keys.
+ continue;
+ }
+ for (String value : javaHeader.getValue()) {
+ Internal.instance.addLenient(okHeadersBuilder, name, value);
+ }
+ }
+ return okHeadersBuilder.build();
+ }
+
+ /**
+ * Extracts the status line from the supplied Java API {@link java.net.HttpURLConnection}.
+ * As per the spec, the status line is held as the header with the null key. Returns {@code null}
+ * if there is no status line.
+ */
+ private static String extractStatusLine(HttpURLConnection httpUrlConnection) {
+ // Java specifies that this will be be response header with a null key.
+ return httpUrlConnection.getHeaderField(null);
+ }
+
+ /**
+ * Extracts the status line from the supplied Java API {@link java.net.CacheResponse}.
+ * As per the spec, the status line is held as the header with the null key. Throws a
+ * {@link ProtocolException} if there is no status line.
+ */
+ private static String extractStatusLine(CacheResponse javaResponse) throws IOException {
+ Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders();
+ return extractStatusLine(javaResponseHeaders);
+ }
+
+ // VisibleForTesting
+ static String extractStatusLine(Map<String, List<String>> javaResponseHeaders)
+ throws ProtocolException {
+ List<String> values = javaResponseHeaders.get(null);
+ if (values == null || values.size() == 0) {
+ // The status line is missing. This suggests a badly behaving cache.
+ throw new ProtocolException(
+ "CacheResponse is missing a \'null\' header containing the status line. Headers="
+ + javaResponseHeaders);
+ }
+ return values.get(0);
+ }
+
+ /**
+ * Creates an OkHttp Response.Body containing the supplied information.
+ */
+ private static ResponseBody createOkBody(final Headers okHeaders,
+ final CacheResponse cacheResponse) {
+ return new ResponseBody() {
+ private BufferedSource body;
+
+ @Override
+ public MediaType contentType() {
+ String contentTypeHeader = okHeaders.get("Content-Type");
+ return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader);
+ }
+
+ @Override
+ public long contentLength() {
+ return OkHeaders.contentLength(okHeaders);
+ }
+ @Override public BufferedSource source() throws IOException {
+ if (body == null) {
+ InputStream is = cacheResponse.getBody();
+ body = Okio.buffer(Okio.source(is));
+ }
+ return body;
+ }
+ };
+ }
+
+ /**
+ * Creates an OkHttp Response.Body containing the supplied information.
+ */
+ private static ResponseBody createOkBody(final URLConnection urlConnection) {
+ if (!urlConnection.getDoInput()) {
+ return null;
+ }
+ return new ResponseBody() {
+ private BufferedSource body;
+
+ @Override public MediaType contentType() {
+ String contentTypeHeader = urlConnection.getContentType();
+ return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader);
+ }
+ @Override public long contentLength() {
+ String s = urlConnection.getHeaderField("Content-Length");
+ return stringToLong(s);
+ }
+ @Override public BufferedSource source() throws IOException {
+ if (body == null) {
+ InputStream is = urlConnection.getInputStream();
+ body = Okio.buffer(Okio.source(is));
+ }
+ return body;
+ }
+ };
+ }
+
+ /**
+ * An {@link java.net.HttpURLConnection} that represents an HTTP request at the point where
+ * the request has been made, and the response headers have been received, but the body content,
+ * if present, has not been read yet. This intended to provide enough information for
+ * {@link java.net.ResponseCache} subclasses and no more.
+ *
+ * <p>Much of the method implementations are overrides to delegate to the OkHttp request and
+ * response, or to deny access to information as a real HttpURLConnection would after connection.
+ */
+ private static final class CacheHttpURLConnection extends HttpURLConnection {
+
+ private final Request request;
+ private final Response response;
+
+ public CacheHttpURLConnection(Response response) {
+ super(response.request().url());
+ this.request = response.request();
+ this.response = response;
+
+ // Configure URLConnection inherited fields.
+ this.connected = true;
+ this.doOutput = request.body() != null;
+ this.doInput = true;
+ this.useCaches = true;
+
+ // Configure HttpUrlConnection inherited fields.
+ this.method = request.method();
+ }
+
+ // HTTP connection lifecycle methods
+
+ @Override
+ public void connect() throws IOException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void disconnect() {
+ throw throwRequestModificationException();
+ }
+
+ // HTTP Request methods
+
+ @Override
+ public void setRequestProperty(String key, String value) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void addRequestProperty(String key, String value) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public String getRequestProperty(String key) {
+ return request.header(key);
+ }
+
+ @Override
+ public Map<String, List<String>> getRequestProperties() {
+ // The RI and OkHttp's HttpURLConnectionImpl fail this call after connect() as required by the
+ // spec. There seems no good reason why this should fail while getRequestProperty() is ok.
+ // We don't fail here, because we need all request header values for caching Vary responses
+ // correctly.
+ return OkHeaders.toMultimap(request.headers(), null);
+ }
+
+ @Override
+ public void setFixedLengthStreamingMode(int contentLength) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setFixedLengthStreamingMode(long contentLength) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setChunkedStreamingMode(int chunklen) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setInstanceFollowRedirects(boolean followRedirects) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getInstanceFollowRedirects() {
+ // Return the platform default.
+ return super.getInstanceFollowRedirects();
+ }
+
+ @Override
+ public void setRequestMethod(String method) throws ProtocolException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public String getRequestMethod() {
+ return request.method();
+ }
+
+ // HTTP Response methods
+
+ @Override
+ public String getHeaderFieldKey(int position) {
+ // Deal with index 0 meaning "status line"
+ if (position < 0) {
+ throw new IllegalArgumentException("Invalid header index: " + position);
+ }
+ if (position == 0) {
+ return null;
+ }
+ return response.headers().name(position - 1);
+ }
+
+ @Override
+ public String getHeaderField(int position) {
+ // Deal with index 0 meaning "status line"
+ if (position < 0) {
+ throw new IllegalArgumentException("Invalid header index: " + position);
+ }
+ if (position == 0) {
+ return StatusLine.get(response).toString();
+ }
+ return response.headers().value(position - 1);
+ }
+
+ @Override
+ public String getHeaderField(String fieldName) {
+ return fieldName == null
+ ? StatusLine.get(response).toString()
+ : response.headers().get(fieldName);
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaderFields() {
+ return OkHeaders.toMultimap(response.headers(), StatusLine.get(response).toString());
+ }
+
+ @Override
+ public int getResponseCode() throws IOException {
+ return response.code();
+ }
+
+ @Override
+ public String getResponseMessage() throws IOException {
+ return response.message();
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return null;
+ }
+
+ // HTTP miscellaneous methods
+
+ @Override
+ public boolean usingProxy() {
+ // It's safe to return false here, even if a proxy is in use. The problem is we don't
+ // necessarily know if we're going to use a proxy by the time we ask the cache for a response.
+ return false;
+ }
+
+ // URLConnection methods
+
+ @Override
+ public void setConnectTimeout(int timeout) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public int getConnectTimeout() {
+ // Impossible to say.
+ return 0;
+ }
+
+ @Override
+ public void setReadTimeout(int timeout) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public int getReadTimeout() {
+ // Impossible to say.
+ return 0;
+ }
+
+ @Override
+ public Object getContent() throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public Object getContent(Class[] classes) throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setDoInput(boolean doInput) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getDoInput() {
+ return doInput;
+ }
+
+ @Override
+ public void setDoOutput(boolean doOutput) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getDoOutput() {
+ return doOutput;
+ }
+
+ @Override
+ public void setAllowUserInteraction(boolean allowUserInteraction) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getAllowUserInteraction() {
+ return false;
+ }
+
+ @Override
+ public void setUseCaches(boolean useCaches) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getUseCaches() {
+ return super.getUseCaches();
+ }
+
+ @Override
+ public void setIfModifiedSince(long ifModifiedSince) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public long getIfModifiedSince() {
+ return stringToLong(request.headers().get("If-Modified-Since"));
+ }
+
+ @Override
+ public boolean getDefaultUseCaches() {
+ return super.getDefaultUseCaches();
+ }
+
+ @Override
+ public void setDefaultUseCaches(boolean defaultUseCaches) {
+ super.setDefaultUseCaches(defaultUseCaches);
+ }
+ }
+
+ /** An HttpsURLConnection to offer to the cache. */
+ private static final class CacheHttpsURLConnection extends DelegatingHttpsURLConnection {
+ private final CacheHttpURLConnection delegate;
+
+ public CacheHttpsURLConnection(CacheHttpURLConnection delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ @Override protected Handshake handshake() {
+ return delegate.response.handshake();
+ }
+
+ @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ throw throwRequestModificationException();
+ }
+
+ @Override public HostnameVerifier getHostnameVerifier() {
+ throw throwRequestSslAccessException();
+ }
+
+ @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) {
+ throw throwRequestModificationException();
+ }
+
+ @Override public SSLSocketFactory getSSLSocketFactory() {
+ throw throwRequestSslAccessException();
+ }
+
+ @Override public long getContentLengthLong() {
+ return delegate.getContentLengthLong();
+ }
+
+ @Override public void setFixedLengthStreamingMode(long contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+ }
+
+ @Override public long getHeaderFieldLong(String field, long defaultValue) {
+ return delegate.getHeaderFieldLong(field, defaultValue);
+ }
+ }
+
+ private static RuntimeException throwRequestModificationException() {
+ throw new UnsupportedOperationException("ResponseCache cannot modify the request.");
+ }
+
+ private static RuntimeException throwRequestHeaderAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access request headers");
+ }
+
+ private static RuntimeException throwRequestSslAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access SSL internals");
+ }
+
+ private static RuntimeException throwResponseBodyAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access the response body.");
+ }
+
+ private static <T> List<T> nullSafeImmutableList(T[] elements) {
+ return elements == null ? Collections.<T>emptyList() : Util.immutableList(elements);
+ }
+
+ private static long stringToLong(String s) {
+ if (s == null) return -1;
+ try {
+ return Long.parseLong(s);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java
new file mode 100644
index 0000000..43e8259
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+import com.android.okhttp.internal.huc.HttpURLConnectionImpl;
+import com.android.okhttp.internal.huc.HttpsURLConnectionImpl;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable {
+ private final OkHttpClient client;
+ private URLFilter urlFilter;
+
+ public OkUrlFactory(OkHttpClient client) {
+ this.client = client;
+ }
+
+ public OkHttpClient client() {
+ return client;
+ }
+
+ void setUrlFilter(URLFilter filter) {
+ urlFilter = filter;
+ }
+
+ /**
+ * Returns a copy of this stream handler factory that includes a shallow copy
+ * of the internal {@linkplain OkHttpClient HTTP client}.
+ */
+ @Override public OkUrlFactory clone() {
+ return new OkUrlFactory(client.clone());
+ }
+
+ public HttpURLConnection open(URL url) {
+ return open(url, client.getProxy());
+ }
+
+ HttpURLConnection open(URL url, Proxy proxy) {
+ String protocol = url.getProtocol();
+ OkHttpClient copy = client.copyWithDefaults();
+ copy.setProxy(proxy);
+
+ if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
+ if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
+ throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+ }
+
+ /**
+ * Creates a URLStreamHandler as a {@link java.net.URL#setURLStreamHandlerFactory}.
+ *
+ * <p>This code configures OkHttp to handle all HTTP and HTTPS connections
+ * created with {@link java.net.URL#openConnection()}: <pre> {@code
+ *
+ * OkHttpClient okHttpClient = new OkHttpClient();
+ * URL.setURLStreamHandlerFactory(new OkUrlFactory(okHttpClient));
+ * }</pre>
+ */
+ @Override public URLStreamHandler createURLStreamHandler(final String protocol) {
+ if (!protocol.equals("http") && !protocol.equals("https")) return null;
+
+ return new URLStreamHandler() {
+ @Override protected URLConnection openConnection(URL url) {
+ return open(url);
+ }
+
+ @Override protected URLConnection openConnection(URL url, Proxy proxy) {
+ return open(url, proxy);
+ }
+
+ @Override protected int getDefaultPort() {
+ if (protocol.equals("http")) return 80;
+ if (protocol.equals("https")) return 443;
+ throw new AssertionError();
+ }
+ };
+ }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java
new file mode 100644
index 0000000..9f69a0f
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java
@@ -0,0 +1,34 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Request filter based on the request's URL.
+ *
+ * @deprecated use {@link okhttp3.Interceptor} for non-HttpURLConnection filtering.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface URLFilter {
+ /**
+ * Check whether request to the provided URL is permitted to be issued.
+ *
+ * @throws IOException if the request to the provided URL is not permitted.
+ */
+ void checkURLPermitted(URL url) throws IOException;
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java
new file mode 100644
index 0000000..25cf6fb
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java
@@ -0,0 +1,293 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Implement an HTTPS connection by delegating to an HTTP connection for
+ * everything but the HTTPS-specific stuff.
+ */
+abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
+ private final HttpURLConnection delegate;
+
+ public DelegatingHttpsURLConnection(HttpURLConnection delegate) {
+ super(delegate.getURL());
+ this.delegate = delegate;
+ }
+
+ protected abstract Handshake handshake();
+
+ @Override public abstract void setHostnameVerifier(HostnameVerifier hostnameVerifier);
+
+ @Override public abstract HostnameVerifier getHostnameVerifier();
+
+ @Override public abstract void setSSLSocketFactory(SSLSocketFactory sslSocketFactory);
+
+ @Override public abstract SSLSocketFactory getSSLSocketFactory();
+
+ @Override public String getCipherSuite() {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.cipherSuite() : null;
+ }
+
+ @Override public Certificate[] getLocalCertificates() {
+ Handshake handshake = handshake();
+ if (handshake == null) return null;
+ List<Certificate> result = handshake.localCertificates();
+ return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+ }
+
+ @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
+ Handshake handshake = handshake();
+ if (handshake == null) return null;
+ List<Certificate> result = handshake.peerCertificates();
+ return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+ }
+
+ @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.peerPrincipal() : null;
+ }
+
+ @Override public Principal getLocalPrincipal() {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.localPrincipal() : null;
+ }
+
+ @Override public void connect() throws IOException {
+ connected = true;
+ delegate.connect();
+ }
+
+ @Override public void disconnect() {
+ delegate.disconnect();
+ }
+
+ @Override public InputStream getErrorStream() {
+ return delegate.getErrorStream();
+ }
+
+ @Override public String getRequestMethod() {
+ return delegate.getRequestMethod();
+ }
+
+ @Override public int getResponseCode() throws IOException {
+ return delegate.getResponseCode();
+ }
+
+ @Override public String getResponseMessage() throws IOException {
+ return delegate.getResponseMessage();
+ }
+
+ @Override public void setRequestMethod(String method) throws ProtocolException {
+ delegate.setRequestMethod(method);
+ }
+
+ @Override public boolean usingProxy() {
+ return delegate.usingProxy();
+ }
+
+ @Override public boolean getInstanceFollowRedirects() {
+ return delegate.getInstanceFollowRedirects();
+ }
+
+ @Override public void setInstanceFollowRedirects(boolean followRedirects) {
+ delegate.setInstanceFollowRedirects(followRedirects);
+ }
+
+ @Override public boolean getAllowUserInteraction() {
+ return delegate.getAllowUserInteraction();
+ }
+
+ @Override public Object getContent() throws IOException {
+ return delegate.getContent();
+ }
+
+ @SuppressWarnings("unchecked") // Spec does not generify
+ @Override public Object getContent(Class[] types) throws IOException {
+ return delegate.getContent(types);
+ }
+
+ @Override public String getContentEncoding() {
+ return delegate.getContentEncoding();
+ }
+
+ @Override public int getContentLength() {
+ return delegate.getContentLength();
+ }
+
+ @Override public String getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override public long getDate() {
+ return delegate.getDate();
+ }
+
+ @Override public boolean getDefaultUseCaches() {
+ return delegate.getDefaultUseCaches();
+ }
+
+ @Override public boolean getDoInput() {
+ return delegate.getDoInput();
+ }
+
+ @Override public boolean getDoOutput() {
+ return delegate.getDoOutput();
+ }
+
+ @Override public long getExpiration() {
+ return delegate.getExpiration();
+ }
+
+ @Override public String getHeaderField(int pos) {
+ return delegate.getHeaderField(pos);
+ }
+
+ @Override public Map<String, List<String>> getHeaderFields() {
+ return delegate.getHeaderFields();
+ }
+
+ @Override public Map<String, List<String>> getRequestProperties() {
+ return delegate.getRequestProperties();
+ }
+
+ @Override public void addRequestProperty(String field, String newValue) {
+ delegate.addRequestProperty(field, newValue);
+ }
+
+ @Override public String getHeaderField(String key) {
+ return delegate.getHeaderField(key);
+ }
+
+ @Override public long getHeaderFieldDate(String field, long defaultValue) {
+ return delegate.getHeaderFieldDate(field, defaultValue);
+ }
+
+ @Override public int getHeaderFieldInt(String field, int defaultValue) {
+ return delegate.getHeaderFieldInt(field, defaultValue);
+ }
+
+ @Override public String getHeaderFieldKey(int position) {
+ return delegate.getHeaderFieldKey(position);
+ }
+
+ @Override public long getIfModifiedSince() {
+ return delegate.getIfModifiedSince();
+ }
+
+ @Override public InputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override public long getLastModified() {
+ return delegate.getLastModified();
+ }
+
+ @Override public OutputStream getOutputStream() throws IOException {
+ return delegate.getOutputStream();
+ }
+
+ @Override public Permission getPermission() throws IOException {
+ return delegate.getPermission();
+ }
+
+ @Override public String getRequestProperty(String field) {
+ return delegate.getRequestProperty(field);
+ }
+
+ @Override public URL getURL() {
+ return delegate.getURL();
+ }
+
+ @Override public boolean getUseCaches() {
+ return delegate.getUseCaches();
+ }
+
+ @Override public void setAllowUserInteraction(boolean newValue) {
+ delegate.setAllowUserInteraction(newValue);
+ }
+
+ @Override public void setDefaultUseCaches(boolean newValue) {
+ delegate.setDefaultUseCaches(newValue);
+ }
+
+ @Override public void setDoInput(boolean newValue) {
+ delegate.setDoInput(newValue);
+ }
+
+ @Override public void setDoOutput(boolean newValue) {
+ delegate.setDoOutput(newValue);
+ }
+
+ @Override public void setIfModifiedSince(long newValue) {
+ delegate.setIfModifiedSince(newValue);
+ }
+
+ @Override public void setRequestProperty(String field, String newValue) {
+ delegate.setRequestProperty(field, newValue);
+ }
+
+ @Override public void setUseCaches(boolean newValue) {
+ delegate.setUseCaches(newValue);
+ }
+
+ @Override public void setConnectTimeout(int timeoutMillis) {
+ delegate.setConnectTimeout(timeoutMillis);
+ }
+
+ @Override public int getConnectTimeout() {
+ return delegate.getConnectTimeout();
+ }
+
+ @Override public void setReadTimeout(int timeoutMillis) {
+ delegate.setReadTimeout(timeoutMillis);
+ }
+
+ @Override public int getReadTimeout() {
+ return delegate.getReadTimeout();
+ }
+
+ @Override public String toString() {
+ return delegate.toString();
+ }
+
+ @Override public void setFixedLengthStreamingMode(int contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+ }
+
+ @Override public void setChunkedStreamingMode(int chunkLength) {
+ delegate.setChunkedStreamingMode(chunkLength);
+ }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java
new file mode 100644
index 0000000..962bc99
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java
@@ -0,0 +1,641 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Connection;
+import com.android.okhttp.Handshake;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.RequestBody;
+import com.android.okhttp.Response;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Platform;
+import com.android.okhttp.internal.URLFilter;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import com.android.okhttp.internal.http.HttpDate;
+import com.android.okhttp.internal.http.HttpEngine;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.RequestException;
+import com.android.okhttp.internal.http.RetryableSink;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StatusLine;
+import com.android.okhttp.internal.http.StreamAllocation;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpRetryException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * This implementation uses HttpEngine to send requests and receive responses.
+ * This class may use multiple HttpEngines to follow redirects, authentication
+ * retries, etc. to retrieve the final response body.
+ *
+ * <h3>What does 'connected' mean?</h3>
+ * This class inherits a {@code connected} field from the superclass. That field
+ * is <strong>not</strong> used to indicate whether this URLConnection is
+ * currently connected. Instead, it indicates whether a connection has ever been
+ * attempted. Once a connection has been attempted, certain properties (request
+ * header fields, request method, etc.) are immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class HttpURLConnectionImpl extends HttpURLConnection {
+ private static final Set<String> METHODS = new LinkedHashSet<>(
+ Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH"));
+ private static final RequestBody EMPTY_REQUEST_BODY = RequestBody.create(null, new byte[0]);
+
+ final OkHttpClient client;
+
+ private Headers.Builder requestHeaders = new Headers.Builder();
+
+ /** Like the superclass field of the same name, but a long and available on all platforms. */
+ private long fixedContentLength = -1;
+ private int followUpCount;
+ protected IOException httpEngineFailure;
+ protected HttpEngine httpEngine;
+ /** Lazily created (with synthetic headers) on first call to getHeaders(). */
+ private Headers responseHeaders;
+
+ /**
+ * The most recently attempted route. This will be null if we haven't sent a
+ * request yet, or if the response comes from a cache.
+ */
+ private Route route;
+
+ /**
+ * The most recently received TLS handshake. This will be null if we haven't
+ * connected yet, or if the most recent connection was HTTP (and not HTTPS).
+ */
+ Handshake handshake;
+
+ private URLFilter urlFilter;
+
+ public HttpURLConnectionImpl(URL url, OkHttpClient client) {
+ super(url);
+ this.client = client;
+ }
+
+ public HttpURLConnectionImpl(URL url, OkHttpClient client, URLFilter urlFilter) {
+ this(url, client);
+ this.urlFilter = urlFilter;
+ }
+
+ @Override public final void connect() throws IOException {
+ initHttpEngine();
+ boolean success;
+ do {
+ success = execute(false);
+ } while (!success);
+ }
+
+ @Override public final void disconnect() {
+ // Calling disconnect() before a connection exists should have no effect.
+ if (httpEngine == null) return;
+
+ httpEngine.cancel();
+
+ // This doesn't close the stream because doing so would require all stream
+ // access to be synchronized. It's expected that the thread using the
+ // connection will close its streams directly. If it doesn't, the worst
+ // case is that the GzipSource's Inflater won't be released until it's
+ // finalized. (This logs a warning on Android.)
+ }
+
+ /**
+ * Returns an input stream from the server in the case of error such as the
+ * requested file (txt, htm, html) is not found on the remote server.
+ */
+ @Override public final InputStream getErrorStream() {
+ try {
+ HttpEngine response = getResponse();
+ if (HttpEngine.hasBody(response.getResponse())
+ && response.getResponse().code() >= HTTP_BAD_REQUEST) {
+ return response.getResponse().body().byteStream();
+ }
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private Headers getHeaders() throws IOException {
+ if (responseHeaders == null) {
+ Response response = getResponse().getResponse();
+ Headers headers = response.headers();
+ responseHeaders = headers.newBuilder()
+ .add(OkHeaders.SELECTED_PROTOCOL, response.protocol().toString())
+ .add(OkHeaders.RESPONSE_SOURCE, responseSourceHeader(response))
+ .build();
+ }
+ return responseHeaders;
+ }
+
+ private static String responseSourceHeader(Response response) {
+ if (response.networkResponse() == null) {
+ if (response.cacheResponse() == null) {
+ return "NONE";
+ }
+ return "CACHE " + response.code();
+ }
+ if (response.cacheResponse() == null) {
+ return "NETWORK " + response.code();
+ }
+ return "CONDITIONAL_CACHE " + response.networkResponse().code();
+ }
+
+ /**
+ * Returns the value of the field at {@code position}. Returns null if there
+ * are fewer than {@code position} headers.
+ */
+ @Override public final String getHeaderField(int position) {
+ try {
+ return getHeaders().value(position);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value of the field corresponding to the {@code fieldName}, or
+ * null if there is no such field. If the field has multiple values, the
+ * last value is returned.
+ */
+ @Override public final String getHeaderField(String fieldName) {
+ try {
+ return fieldName == null
+ ? StatusLine.get(getResponse().getResponse()).toString()
+ : getHeaders().get(fieldName);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Override public final String getHeaderFieldKey(int position) {
+ try {
+ return getHeaders().name(position);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Override public final Map<String, List<String>> getHeaderFields() {
+ try {
+ return OkHeaders.toMultimap(getHeaders(),
+ StatusLine.get(getResponse().getResponse()).toString());
+ } catch (IOException e) {
+ return Collections.emptyMap();
+ }
+ }
+
+ @Override public final Map<String, List<String>> getRequestProperties() {
+ if (connected) {
+ throw new IllegalStateException(
+ "Cannot access request header fields after connection is set");
+ }
+
+ return OkHeaders.toMultimap(requestHeaders.build(), null);
+ }
+
+ @Override public final InputStream getInputStream() throws IOException {
+ if (!doInput) {
+ throw new ProtocolException("This protocol does not support input");
+ }
+
+ HttpEngine response = getResponse();
+
+ // if the requested file does not exist, throw an exception formerly the
+ // Error page from the server was returned if the requested file was
+ // text/html this has changed to return FileNotFoundException for all
+ // file types
+ if (getResponseCode() >= HTTP_BAD_REQUEST) {
+ throw new FileNotFoundException(url.toString());
+ }
+
+ return response.getResponse().body().byteStream();
+ }
+
+ @Override public final OutputStream getOutputStream() throws IOException {
+ connect();
+
+ BufferedSink sink = httpEngine.getBufferedRequestBody();
+ if (sink == null) {
+ throw new ProtocolException("method does not support a request body: " + method);
+ } else if (httpEngine.hasResponse()) {
+ throw new ProtocolException("cannot write request body after response has been read");
+ }
+
+ return sink.outputStream();
+ }
+
+ @Override public final Permission getPermission() throws IOException {
+ URL url = getURL();
+ String hostName = url.getHost();
+ int hostPort = url.getPort() != -1
+ ? url.getPort()
+ : HttpUrl.defaultPort(url.getProtocol());
+ if (usingProxy()) {
+ InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
+ hostName = proxyAddress.getHostName();
+ hostPort = proxyAddress.getPort();
+ }
+ return new SocketPermission(hostName + ":" + hostPort, "connect, resolve");
+ }
+
+ @Override public final String getRequestProperty(String field) {
+ if (field == null) return null;
+ return requestHeaders.get(field);
+ }
+
+ @Override public void setConnectTimeout(int timeoutMillis) {
+ client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void setInstanceFollowRedirects(boolean followRedirects) {
+ client.setFollowRedirects(followRedirects);
+ }
+
+ @Override public boolean getInstanceFollowRedirects() {
+ return client.getFollowRedirects();
+ }
+
+ @Override public int getConnectTimeout() {
+ return client.getConnectTimeout();
+ }
+
+ @Override public void setReadTimeout(int timeoutMillis) {
+ client.setReadTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override public int getReadTimeout() {
+ return client.getReadTimeout();
+ }
+
+ private void initHttpEngine() throws IOException {
+ if (httpEngineFailure != null) {
+ throw httpEngineFailure;
+ } else if (httpEngine != null) {
+ return;
+ }
+
+ connected = true;
+ try {
+ if (doOutput) {
+ if (method.equals("GET")) {
+ // they are requesting a stream to write to. This implies a POST method
+ method = "POST";
+ } else if (!HttpMethod.permitsRequestBody(method)) {
+ throw new ProtocolException(method + " does not support writing");
+ }
+ }
+ // If the user set content length to zero, we know there will not be a request body.
+ httpEngine = newHttpEngine(method, null, null, null);
+ } catch (IOException e) {
+ httpEngineFailure = e;
+ throw e;
+ }
+ }
+
+ private HttpEngine newHttpEngine(String method, StreamAllocation streamAllocation,
+ RetryableSink requestBody, Response priorResponse)
+ throws MalformedURLException, UnknownHostException {
+ // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+ RequestBody placeholderBody = HttpMethod.requiresRequestBody(method)
+ ? EMPTY_REQUEST_BODY
+ : null;
+ URL url = getURL();
+ HttpUrl httpUrl = Internal.instance.getHttpUrlChecked(url.toString());
+ Request.Builder builder = new Request.Builder()
+ .url(httpUrl)
+ .method(method, placeholderBody);
+ Headers headers = requestHeaders.build();
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ builder.addHeader(headers.name(i), headers.value(i));
+ }
+
+ boolean bufferRequestBody = false;
+ if (HttpMethod.permitsRequestBody(method)) {
+ // Specify how the request body is terminated.
+ if (fixedContentLength != -1) {
+ builder.header("Content-Length", Long.toString(fixedContentLength));
+ } else if (chunkLength > 0) {
+ builder.header("Transfer-Encoding", "chunked");
+ } else {
+ bufferRequestBody = true;
+ }
+
+ // Add a content type for the request body, if one isn't already present.
+ if (headers.get("Content-Type") == null) {
+ builder.header("Content-Type", "application/x-www-form-urlencoded");
+ }
+ }
+
+ if (headers.get("User-Agent") == null) {
+ builder.header("User-Agent", defaultUserAgent());
+ }
+
+ Request request = builder.build();
+
+ // If we're currently not using caches, make sure the engine's client doesn't have one.
+ OkHttpClient engineClient = client;
+ if (Internal.instance.internalCache(engineClient) != null && !getUseCaches()) {
+ engineClient = client.clone().setCache(null);
+ }
+
+ return new HttpEngine(engineClient, request, bufferRequestBody, true, false, streamAllocation,
+ requestBody, priorResponse);
+ }
+
+ private String defaultUserAgent() {
+ String agent = System.getProperty("http.agent");
+ return agent != null ? Util.toHumanReadableAscii(agent) : Version.userAgent();
+ }
+
+ /**
+ * Aggressively tries to get the final HTTP response, potentially making
+ * many HTTP requests in the process in order to cope with redirects and
+ * authentication.
+ */
+ private HttpEngine getResponse() throws IOException {
+ initHttpEngine();
+
+ if (httpEngine.hasResponse()) {
+ return httpEngine;
+ }
+
+ while (true) {
+ if (!execute(true)) {
+ continue;
+ }
+
+ Response response = httpEngine.getResponse();
+ Request followUp = httpEngine.followUpRequest();
+
+ if (followUp == null) {
+ httpEngine.releaseStreamAllocation();
+ return httpEngine;
+ }
+
+ if (++followUpCount > HttpEngine.MAX_FOLLOW_UPS) {
+ throw new ProtocolException("Too many follow-up requests: " + followUpCount);
+ }
+
+ // The first request was insufficient. Prepare for another...
+ url = followUp.url();
+ requestHeaders = followUp.headers().newBuilder();
+
+ // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM redirect
+ // should keep the same method, Chrome, Firefox and the RI all issue GETs
+ // when following any redirect.
+ Sink requestBody = httpEngine.getRequestBody();
+ if (!followUp.method().equals(method)) {
+ requestBody = null;
+ }
+
+ if (requestBody != null && !(requestBody instanceof RetryableSink)) {
+ throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
+ }
+
+ StreamAllocation streamAllocation = httpEngine.close();
+ if (!httpEngine.sameConnection(followUp.httpUrl())) {
+ streamAllocation.release();
+ streamAllocation = null;
+ }
+
+ httpEngine = newHttpEngine(followUp.method(), streamAllocation, (RetryableSink) requestBody,
+ response);
+ }
+ }
+
+ /**
+ * Sends a request and optionally reads a response. Returns true if the
+ * request was successfully executed, and false if the request can be
+ * retried. Throws an exception if the request failed permanently.
+ */
+ private boolean execute(boolean readResponse) throws IOException {
+ boolean releaseConnection = true;
+ if (urlFilter != null) {
+ urlFilter.checkURLPermitted(httpEngine.getRequest().url());
+ }
+ try {
+ httpEngine.sendRequest();
+ Connection connection = httpEngine.getConnection();
+ if (connection != null) {
+ route = connection.getRoute();
+ handshake = connection.getHandshake();
+ } else {
+ route = null;
+ handshake = null;
+ }
+ if (readResponse) {
+ httpEngine.readResponse();
+ }
+ releaseConnection = false;
+
+ return true;
+ } catch (RequestException e) {
+ // An attempt to interpret a request failed.
+ IOException toThrow = e.getCause();
+ httpEngineFailure = toThrow;
+ throw toThrow;
+ } catch (RouteException e) {
+ // The attempt to connect via a route failed. The request will not have been sent.
+ HttpEngine retryEngine = httpEngine.recover(e);
+ if (retryEngine != null) {
+ releaseConnection = false;
+ httpEngine = retryEngine;
+ return false;
+ }
+
+ // Give up; recovery is not possible.
+ IOException toThrow = e.getLastConnectException();
+ httpEngineFailure = toThrow;
+ throw toThrow;
+ } catch (IOException e) {
+ // An attempt to communicate with a server failed. The request may have been sent.
+ HttpEngine retryEngine = httpEngine.recover(e);
+ if (retryEngine != null) {
+ releaseConnection = false;
+ httpEngine = retryEngine;
+ return false;
+ }
+
+ // Give up; recovery is not possible.
+ httpEngineFailure = e;
+ throw e;
+ } finally {
+ // We're throwing an unchecked exception. Release any resources.
+ if (releaseConnection) {
+ StreamAllocation streamAllocation = httpEngine.close();
+ streamAllocation.release();
+ }
+ }
+ }
+
+ /**
+ * Returns true if either:
+ * <ul>
+ * <li>A specific proxy was explicitly configured for this connection.
+ * <li>The response has already been retrieved, and a proxy was {@link
+ * java.net.ProxySelector selected} in order to get it.
+ * </ul>
+ *
+ * <p><strong>Warning:</strong> This method may return false before attempting
+ * to connect and true afterwards.
+ */
+ @Override public final boolean usingProxy() {
+ Proxy proxy = route != null
+ ? route.getProxy()
+ : client.getProxy();
+ return proxy != null && proxy.type() != Proxy.Type.DIRECT;
+ }
+
+ @Override public String getResponseMessage() throws IOException {
+ return getResponse().getResponse().message();
+ }
+
+ @Override public final int getResponseCode() throws IOException {
+ return getResponse().getResponse().code();
+ }
+
+ @Override public final void setRequestProperty(String field, String newValue) {
+ if (connected) {
+ throw new IllegalStateException("Cannot set request property after connection is made");
+ }
+ if (field == null) {
+ throw new NullPointerException("field == null");
+ }
+ if (newValue == null) {
+ // Silently ignore null header values for backwards compatibility with older
+ // android versions as well as with other URLConnection implementations.
+ //
+ // Some implementations send a malformed HTTP header when faced with
+ // such requests, we respect the spec and ignore the header.
+ Platform.get().logW("Ignoring header " + field + " because its value was null.");
+ return;
+ }
+
+ // TODO: Deprecate use of X-Android-Transports header?
+ if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+ setProtocols(newValue, false /* append */);
+ } else {
+ requestHeaders.set(field, newValue);
+ }
+ }
+
+ @Override public void setIfModifiedSince(long newValue) {
+ super.setIfModifiedSince(newValue);
+ if (ifModifiedSince != 0) {
+ requestHeaders.set("If-Modified-Since", HttpDate.format(new Date(ifModifiedSince)));
+ } else {
+ requestHeaders.removeAll("If-Modified-Since");
+ }
+ }
+
+ @Override public final void addRequestProperty(String field, String value) {
+ if (connected) {
+ throw new IllegalStateException("Cannot add request property after connection is made");
+ }
+ if (field == null) {
+ throw new NullPointerException("field == null");
+ }
+ if (value == null) {
+ // Silently ignore null header values for backwards compatibility with older
+ // android versions as well as with other URLConnection implementations.
+ //
+ // Some implementations send a malformed HTTP header when faced with
+ // such requests, we respect the spec and ignore the header.
+ Platform.get().logW("Ignoring header " + field + " because its value was null.");
+ return;
+ }
+
+ // TODO: Deprecate use of X-Android-Transports header?
+ if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+ setProtocols(value, true /* append */);
+ } else {
+ requestHeaders.add(field, value);
+ }
+ }
+
+ /*
+ * Splits and validates a comma-separated string of protocols.
+ * When append == false, we require that the transport list contains "http/1.1".
+ * Throws {@link IllegalStateException} when one of the protocols isn't
+ * defined in {@link Protocol OkHttp's protocol enumeration}.
+ */
+ private void setProtocols(String protocolsString, boolean append) {
+ List<Protocol> protocolsList = new ArrayList<>();
+ if (append) {
+ protocolsList.addAll(client.getProtocols());
+ }
+ for (String protocol : protocolsString.split(",", -1)) {
+ try {
+ protocolsList.add(Protocol.get(protocol));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ client.setProtocols(protocolsList);
+ }
+
+ @Override public void setRequestMethod(String method) throws ProtocolException {
+ if (!METHODS.contains(method)) {
+ throw new ProtocolException("Expected one of " + METHODS + " but was " + method);
+ }
+ this.method = method;
+ }
+
+ @Override public void setFixedLengthStreamingMode(int contentLength) {
+ setFixedLengthStreamingMode((long) contentLength);
+ }
+
+ @Override public void setFixedLengthStreamingMode(long contentLength) {
+ if (super.connected) throw new IllegalStateException("Already connected");
+ if (chunkLength > 0) throw new IllegalStateException("Already in chunked mode");
+ if (contentLength < 0) throw new IllegalArgumentException("contentLength < 0");
+ this.fixedContentLength = contentLength;
+ super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);
+ }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java
new file mode 100644
index 0000000..5d7e738
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java
@@ -0,0 +1,95 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.internal.URLFilter;
+import java.net.URL;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpsURLConnectionImpl extends DelegatingHttpsURLConnection {
+ private final HttpURLConnectionImpl delegate;
+
+ public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
+ this(new HttpURLConnectionImpl(url, client));
+ }
+
+ public HttpsURLConnectionImpl(URL url, OkHttpClient client, URLFilter filter) {
+ this(new HttpURLConnectionImpl(url, client, filter));
+ }
+
+ public HttpsURLConnectionImpl(HttpURLConnectionImpl delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ @Override protected Handshake handshake() {
+ if (delegate.httpEngine == null) {
+ throw new IllegalStateException("Connection has not yet been established");
+ }
+
+ // If there's a response, get the handshake from there so that caching
+ // works. Otherwise get the handshake from the connection because we might
+ // have not connected yet.
+ return delegate.httpEngine.hasResponse()
+ ? delegate.httpEngine.getResponse().handshake()
+ : delegate.handshake;
+ }
+
+ @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ delegate.client.setHostnameVerifier(hostnameVerifier);
+ }
+
+ @Override public HostnameVerifier getHostnameVerifier() {
+ return delegate.client.getHostnameVerifier();
+ }
+
+ @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+ // BEGIN Android-added: Integrate upstream change: Let setSSLSocketFactory(null) throw.
+ // https://github.com/square/okhttp/commit/f704f9d30e941ebdbdc95843c931cfc9d34bcba6
+ // This method is documented to throw if sslSocketFactory == null. Setting the client's
+ // sslSocketFactory to null instead would cause it to fall back to the default factory.
+ // http://b/73702052
+ if (sslSocketFactory == null) {
+ throw new IllegalArgumentException("sslSocketFactory == null");
+ }
+ // END Android-added: Integrate upstream change: Let setSSLSocketFactory(null) throw.
+ delegate.client.setSslSocketFactory(sslSocketFactory);
+ }
+
+ @Override public SSLSocketFactory getSSLSocketFactory() {
+ return delegate.client.getSslSocketFactory();
+ }
+
+ @Override public long getContentLengthLong() {
+ return delegate.getContentLengthLong();
+ }
+
+ @Override public void setFixedLengthStreamingMode(long contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+ }
+
+ @Override public long getHeaderFieldLong(String field, long defaultValue) {
+ return delegate.getHeaderFieldLong(field, defaultValue);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java
new file mode 100644
index 0000000..5331969
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java
@@ -0,0 +1,205 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.util.List;
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+import static com.android.okhttp.internal.Util.equal;
+
+/**
+ * A specification for a connection to an origin server. For simple connections,
+ * this is the server's hostname and port. If an explicit proxy is requested (or
+ * {@linkplain Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
+ * that proxy information. For secure connections the address also includes the
+ * SSL socket factory, hostname verifier, and certificate pinner.
+ *
+ * <p>HTTP requests that share the same {@code Address} may also share the same
+ * {@link Connection}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Address {
+ final HttpUrl url;
+ final Dns dns;
+ final SocketFactory socketFactory;
+ final Authenticator authenticator;
+ final List<Protocol> protocols;
+ final List<ConnectionSpec> connectionSpecs;
+ final ProxySelector proxySelector;
+ final Proxy proxy;
+ final SSLSocketFactory sslSocketFactory;
+ final HostnameVerifier hostnameVerifier;
+ final CertificatePinner certificatePinner;
+
+ public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
+ SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
+ CertificatePinner certificatePinner, Authenticator authenticator, Proxy proxy,
+ List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {
+ this.url = new HttpUrl.Builder()
+ .scheme(sslSocketFactory != null ? "https" : "http")
+ .host(uriHost)
+ .port(uriPort)
+ .build();
+
+ if (dns == null) throw new IllegalArgumentException("dns == null");
+ this.dns = dns;
+
+ if (socketFactory == null) throw new IllegalArgumentException("socketFactory == null");
+ this.socketFactory = socketFactory;
+
+ if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
+ this.authenticator = authenticator;
+
+ if (protocols == null) throw new IllegalArgumentException("protocols == null");
+ this.protocols = Util.immutableList(protocols);
+
+ if (connectionSpecs == null) throw new IllegalArgumentException("connectionSpecs == null");
+ this.connectionSpecs = Util.immutableList(connectionSpecs);
+
+ if (proxySelector == null) throw new IllegalArgumentException("proxySelector == null");
+ this.proxySelector = proxySelector;
+
+ this.proxy = proxy;
+ this.sslSocketFactory = sslSocketFactory;
+ this.hostnameVerifier = hostnameVerifier;
+ this.certificatePinner = certificatePinner;
+ }
+
+ /**
+ * Returns a URL with the hostname and port of the origin server. The path, query, and fragment of
+ * this URL are always empty, since they are not significant for planning a route.
+ */
+ public HttpUrl url() {
+ return url;
+ }
+
+ /**
+ * Returns the hostname of the origin server.
+ *
+ * @deprecated prefer {@code address.url().host()}.
+ */
+ @Deprecated
+ public String getUriHost() {
+ return url.host();
+ }
+
+ /**
+ * Returns the port of the origin server; typically 80 or 443. Unlike
+ * may {@code getPort()} accessors, this method never returns -1.
+ *
+ * @deprecated prefer {@code address.url().port()}.
+ */
+ @Deprecated
+ public int getUriPort() {
+ return url.port();
+ }
+
+ /** Returns the service that will be used to resolve IP addresses for hostnames. */
+ public Dns getDns() {
+ return dns;
+ }
+
+ /** Returns the socket factory for new connections. */
+ public SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /** Returns the client's authenticator. */
+ public Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+ /**
+ * Returns the protocols the client supports. This method always returns a
+ * non-null list that contains minimally {@link Protocol#HTTP_1_1}.
+ */
+ public List<Protocol> getProtocols() {
+ return protocols;
+ }
+
+ public List<ConnectionSpec> getConnectionSpecs() {
+ return connectionSpecs;
+ }
+
+ /**
+ * Returns this address's proxy selector. Only used if the proxy is null. If none of this
+ * selector's proxies are reachable, a direct connection will be attempted.
+ */
+ public ProxySelector getProxySelector() {
+ return proxySelector;
+ }
+
+ /**
+ * Returns this address's explicitly-specified HTTP proxy, or null to
+ * delegate to the {@linkplain #getProxySelector proxy selector}.
+ */
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ /** Returns the SSL socket factory, or null if this is not an HTTPS address. */
+ public SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /** Returns the hostname verifier, or null if this is not an HTTPS address. */
+ public HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ /** Returns this address's certificate pinner, or null if this is not an HTTPS address. */
+ public CertificatePinner getCertificatePinner() {
+ return certificatePinner;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Address) {
+ Address that = (Address) other;
+ return this.url.equals(that.url)
+ && this.dns.equals(that.dns)
+ && this.authenticator.equals(that.authenticator)
+ && this.protocols.equals(that.protocols)
+ && this.connectionSpecs.equals(that.connectionSpecs)
+ && this.proxySelector.equals(that.proxySelector)
+ && equal(this.proxy, that.proxy)
+ && equal(this.sslSocketFactory, that.sslSocketFactory)
+ && equal(this.hostnameVerifier, that.hostnameVerifier)
+ && equal(this.certificatePinner, that.certificatePinner);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + url.hashCode();
+ result = 31 * result + dns.hashCode();
+ result = 31 * result + authenticator.hashCode();
+ result = 31 * result + protocols.hashCode();
+ result = 31 * result + connectionSpecs.hashCode();
+ result = 31 * result + proxySelector.hashCode();
+ result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
+ result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
+ result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
+ result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java
new file mode 100644
index 0000000..ae23d30
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java
@@ -0,0 +1,62 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import java.io.IOException;
+import java.net.Proxy;
+
+/**
+ * Responds to authentication challenges from the remote web or proxy server.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Authenticator {
+ /**
+ * Returns a request that includes a credential to satisfy an authentication
+ * challenge in {@code response}. 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.
+ *
+ * <p>Typical implementations will look up a credential and create a request
+ * derived from the initial request by setting the "Authorization" header.
+ * <pre> {@code
+ *
+ * String credential = Credentials.basic(...)
+ * return response.request().newBuilder()
+ * .header("Authorization", credential)
+ * .build();
+ * }</pre>
+ */
+ Request authenticate(Proxy proxy, Response response) throws IOException;
+
+ /**
+ * Returns a request that includes a credential to satisfy an authentication
+ * challenge made by {@code response}. Returns null if the challenge cannot be
+ * satisfied. This method is called in response to an HTTP 407 unauthorized
+ * status code sent by the proxy server.
+ *
+ * <p>Typical implementations will look up a credential and create a request
+ * derived from the initial request by setting the "Proxy-Authorization"
+ * header. <pre> {@code
+ *
+ * String credential = Credentials.basic(...)
+ * return response.request().newBuilder()
+ * .header("Proxy-Authorization", credential)
+ * .build();
+ * }</pre>
+ */
+ Request authenticateProxy(Proxy proxy, Response response) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java
new file mode 100644
index 0000000..1e8f583
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java
@@ -0,0 +1,732 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2010 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.android.okhttp;
+
+import com.android.okhttp.internal.DiskLruCache;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.StatusLine;
+import com.android.okhttp.internal.io.FileSystem;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSink;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and
+ * bandwidth.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ * <li><strong>{@linkplain #getRequestCount() Request Count:}</strong> the number of HTTP
+ * requests issued since this cache was created.
+ * <li><strong>{@linkplain #getNetworkCount() Network Count:}</strong> the number of those
+ * requests that required network use.
+ * <li><strong>{@linkplain #getHitCount() Hit Count:}</strong> the number of those requests whose
+ * responses were served by the cache.
+ * </ul>
+ *
+ * Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of
+ * the response, the client will issue a conditional {@code GET}. The server will then send either
+ * the updated response if it has changed, or a short 'not modified' response if the client's copy
+ * is still valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web server to return
+ * cacheable responses. Although this client honors all <a
+ * href="http://tools.ietf.org/html/rfc7234">HTTP/1.1 (RFC 7234)</a> cache headers, it doesn't cache
+ * partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip
+ * the cache, and fetch data directly from the server. To force a full refresh, add the {@code
+ * no-cache} directive: <pre> {@code
+ *
+ * Request request = new Request.Builder()
+ * .cacheControl(new CacheControl.Builder().noCache().build())
+ * .url("http://publicobject.com/helloworld.txt")
+ * .build();
+ * }</pre>
+ *
+ * If it is only necessary to force a cached response to be validated by the server, use the more
+ * efficient {@code max-age=0} directive instead: <pre> {@code
+ *
+ * Request request = new Request.Builder()
+ * .cacheControl(new CacheControl.Builder()
+ * .maxAge(0, TimeUnit.SECONDS)
+ * .build())
+ * .url("http://publicobject.com/helloworld.txt")
+ * .build();
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately, but not otherwise.
+ * This can be used so your application can show <i>something</i> while waiting for the latest data
+ * to be downloaded. To restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre> {@code
+ *
+ * Request request = new Request.Builder()
+ * .cacheControl(new CacheControl.Builder()
+ * .onlyIfCached()
+ * .build())
+ * .url("http://publicobject.com/helloworld.txt")
+ * .build();
+ * Response forceCacheResponse = client.newCall(request).execute();
+ * if (forceCacheResponse.code() != 504) {
+ * // The resource was cached! Show it.
+ * } else {
+ * // The resource was not cached.
+ * }
+ * }</pre>
+ * This technique works even better in situations where a stale response is better than no response.
+ * To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness
+ * in seconds: <pre> {@code
+ *
+ * Request request = new Request.Builder()
+ * .cacheControl(new CacheControl.Builder()
+ * .maxStale(365, TimeUnit.DAYS)
+ * .build())
+ * .url("http://publicobject.com/helloworld.txt")
+ * .build();
+ * }</pre>
+ *
+ * <p>The {@link CacheControl} class can configure request caching directives and parse response
+ * caching directives. It even offers convenient constants {@link CacheControl#FORCE_NETWORK} and
+ * {@link CacheControl#FORCE_CACHE} that address the use cases above.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Cache {
+ private static final int VERSION = 201105;
+ private static final int ENTRY_METADATA = 0;
+ private static final int ENTRY_BODY = 1;
+ private static final int ENTRY_COUNT = 2;
+
+ // Android-changed: internalCache made public so it can be used from Android internalapi package.
+ public final InternalCache internalCache = new InternalCache() {
+ @Override public Response get(Request request) throws IOException {
+ return Cache.this.get(request);
+ }
+ @Override public CacheRequest put(Response response) throws IOException {
+ return Cache.this.put(response);
+ }
+ @Override public void remove(Request request) throws IOException {
+ Cache.this.remove(request);
+ }
+ @Override public void update(Response cached, Response network) throws IOException {
+ Cache.this.update(cached, network);
+ }
+ @Override public void trackConditionalCacheHit() {
+ Cache.this.trackConditionalCacheHit();
+ }
+ @Override public void trackResponse(CacheStrategy cacheStrategy) {
+ Cache.this.trackResponse(cacheStrategy);
+ }
+ };
+
+ private final DiskLruCache cache;
+
+ /* read and write statistics, all guarded by 'this' */
+ private int writeSuccessCount;
+ private int writeAbortCount;
+ private int networkCount;
+ private int hitCount;
+ private int requestCount;
+
+ public Cache(File directory, long maxSize) {
+ this(directory, maxSize, FileSystem.SYSTEM);
+ }
+
+ Cache(File directory, long maxSize, FileSystem fileSystem) {
+ this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
+ }
+
+ private static String urlToKey(Request request) {
+ return Util.md5Hex(request.urlString());
+ }
+
+ Response get(Request request) {
+ String key = urlToKey(request);
+ DiskLruCache.Snapshot snapshot;
+ Entry entry;
+ try {
+ snapshot = cache.get(key);
+ if (snapshot == null) {
+ return null;
+ }
+ } catch (IOException e) {
+ // Give up because the cache cannot be read.
+ return null;
+ }
+
+ try {
+ entry = new Entry(snapshot.getSource(ENTRY_METADATA));
+ } catch (IOException e) {
+ Util.closeQuietly(snapshot);
+ return null;
+ }
+
+ Response response = entry.response(request, snapshot);
+
+ if (!entry.matches(request, response)) {
+ Util.closeQuietly(response.body());
+ return null;
+ }
+
+ return response;
+ }
+
+ private CacheRequest put(Response response) throws IOException {
+ String requestMethod = response.request().method();
+
+ if (HttpMethod.invalidatesCache(response.request().method())) {
+ try {
+ remove(response.request());
+ } catch (IOException ignored) {
+ // The cache cannot be written.
+ }
+ return null;
+ }
+ if (!requestMethod.equals("GET")) {
+ // Don't cache non-GET responses. We're technically allowed to cache
+ // HEAD requests and some POST requests, but the complexity of doing
+ // so is high and the benefit is low.
+ return null;
+ }
+
+ if (OkHeaders.hasVaryAll(response)) {
+ return null;
+ }
+
+ Entry entry = new Entry(response);
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = cache.edit(urlToKey(response.request()));
+ if (editor == null) {
+ return null;
+ }
+ entry.writeTo(editor);
+ return new CacheRequestImpl(editor);
+ } catch (IOException e) {
+ abortQuietly(editor);
+ return null;
+ }
+ }
+
+ private void remove(Request request) throws IOException {
+ cache.remove(urlToKey(request));
+ }
+
+ private void update(Response cached, Response network) {
+ Entry entry = new Entry(network);
+ DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = snapshot.edit(); // Returns null if snapshot is not current.
+ if (editor != null) {
+ entry.writeTo(editor);
+ editor.commit();
+ }
+ } catch (IOException e) {
+ abortQuietly(editor);
+ }
+ }
+
+ private void abortQuietly(DiskLruCache.Editor editor) {
+ // Give up because the cache cannot be written.
+ try {
+ if (editor != null) {
+ editor.abort();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ /**
+ * Initialize the cache. This will include reading the journal files from
+ * the storage and building up the necessary in-memory cache information.
+ * <p>
+ * The initialization time may vary depending on the journal file size and
+ * the current actual cache size. The application needs to be aware of calling
+ * this function during the initialization phase and preferably in a background
+ * worker thread.
+ * <p>
+ * Note that if the application chooses to not call this method to initialize
+ * the cache. By default, the okhttp will perform lazy initialization upon the
+ * first usage of the cache.
+ */
+ public void initialize() throws IOException {
+ cache.initialize();
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ cache.delete();
+ }
+
+ /**
+ * Deletes all values stored in the cache. In-flight writes to the cache will
+ * complete normally, but the corresponding responses will not be stored.
+ */
+ public void evictAll() throws IOException {
+ cache.evictAll();
+ }
+
+ /**
+ * Returns an iterator over the URLs in this cache. This iterator doesn't throw {@code
+ * ConcurrentModificationException}, but if new responses are added while iterating, their URLs
+ * will not be returned. If existing responses are evicted during iteration, they will be absent
+ * (unless they were already returned).
+ *
+ * <p>The iterator supports {@linkplain Iterator#remove}. Removing a URL from the iterator evicts
+ * the corresponding response from the cache. Use this to evict selected responses.
+ */
+ public Iterator<String> urls() throws IOException {
+ return new Iterator<String>() {
+ final Iterator<DiskLruCache.Snapshot> delegate = cache.snapshots();
+
+ String nextUrl;
+ boolean canRemove;
+
+ @Override public boolean hasNext() {
+ if (nextUrl != null) return true;
+
+ canRemove = false; // Prevent delegate.remove() on the wrong item!
+ while (delegate.hasNext()) {
+ DiskLruCache.Snapshot snapshot = delegate.next();
+ try {
+ BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA));
+ nextUrl = metadata.readUtf8LineStrict();
+ return true;
+ } catch (IOException ignored) {
+ // We couldn't read the metadata for this snapshot; possibly because the host filesystem
+ // has disappeared! Skip it.
+ } finally {
+ snapshot.close();
+ }
+ }
+
+ return false;
+ }
+
+ @Override public String next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ String result = nextUrl;
+ nextUrl = null;
+ canRemove = true;
+ return result;
+ }
+
+ @Override public void remove() {
+ if (!canRemove) throw new IllegalStateException("remove() before next()");
+ delegate.remove();
+ }
+ };
+ }
+
+ public synchronized int getWriteAbortCount() {
+ return writeAbortCount;
+ }
+
+ public synchronized int getWriteSuccessCount() {
+ return writeSuccessCount;
+ }
+
+ public long getSize() throws IOException {
+ return cache.size();
+ }
+
+ public long getMaxSize() {
+ return cache.getMaxSize();
+ }
+
+ public void flush() throws IOException {
+ cache.flush();
+ }
+
+ public void close() throws IOException {
+ cache.close();
+ }
+
+ public File getDirectory() {
+ return cache.getDirectory();
+ }
+
+ public boolean isClosed() {
+ return cache.isClosed();
+ }
+
+ private synchronized void trackResponse(CacheStrategy cacheStrategy) {
+ requestCount++;
+
+ if (cacheStrategy.networkRequest != null) {
+ // If this is a conditional request, we'll increment hitCount if/when it hits.
+ networkCount++;
+
+ } else if (cacheStrategy.cacheResponse != null) {
+ // This response uses the cache and not the network. That's a cache hit.
+ hitCount++;
+ }
+ }
+
+ private synchronized void trackConditionalCacheHit() {
+ hitCount++;
+ }
+
+ public synchronized int getNetworkCount() {
+ return networkCount;
+ }
+
+ public synchronized int getHitCount() {
+ return hitCount;
+ }
+
+ public synchronized int getRequestCount() {
+ return requestCount;
+ }
+
+ private final class CacheRequestImpl implements CacheRequest {
+ private final DiskLruCache.Editor editor;
+ private Sink cacheOut;
+ private boolean done;
+ private Sink body;
+
+ public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
+ this.editor = editor;
+ this.cacheOut = editor.newSink(ENTRY_BODY);
+ this.body = new ForwardingSink(cacheOut) {
+ @Override public void close() throws IOException {
+ synchronized (Cache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeSuccessCount++;
+ }
+ super.close();
+ editor.commit();
+ }
+ };
+ }
+
+ @Override public void abort() {
+ synchronized (Cache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeAbortCount++;
+ }
+ Util.closeQuietly(cacheOut);
+ try {
+ editor.abort();
+ } catch (IOException ignored) {
+ }
+ }
+
+ @Override public Sink body() {
+ return body;
+ }
+ }
+
+ private static final class Entry {
+ private final String url;
+ private final Headers varyHeaders;
+ private final String requestMethod;
+ private final Protocol protocol;
+ private final int code;
+ private final String message;
+ private final Headers responseHeaders;
+ private final Handshake handshake;
+
+ /**
+ * Reads an entry from an input stream. A typical entry looks like this:
+ * <pre>{@code
+ * http://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ * }</pre>
+ *
+ * <p>A typical HTTPS file looks like this:
+ * <pre>{@code
+ * https://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ *
+ * AES_256_WITH_MD5
+ * 2
+ * base64-encoded peerCertificate[0]
+ * base64-encoded peerCertificate[1]
+ * -1
+ * }</pre>
+ * The file is newline separated. The first two lines are the URL and
+ * the request method. Next is the number of HTTP Vary request header
+ * lines, followed by those lines.
+ *
+ * <p>Next is the response status line, followed by the number of HTTP
+ * response header lines, followed by those lines.
+ *
+ * <p>HTTPS responses also contain SSL session information. This begins
+ * with a blank line, and then a line containing the cipher suite. Next
+ * is the length of the peer certificate chain. These certificates are
+ * base64-encoded and appear each on their own line. The next line
+ * contains the length of the local certificate chain. These
+ * certificates are also base64-encoded and appear each on their own
+ * line. A length of -1 is used to encode a null array.
+ */
+ public Entry(Source in) throws IOException {
+ try {
+ BufferedSource source = Okio.buffer(in);
+ url = source.readUtf8LineStrict();
+ requestMethod = source.readUtf8LineStrict();
+ Headers.Builder varyHeadersBuilder = new Headers.Builder();
+ int varyRequestHeaderLineCount = readInt(source);
+ for (int i = 0; i < varyRequestHeaderLineCount; i++) {
+ varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
+ }
+ varyHeaders = varyHeadersBuilder.build();
+
+ StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+ protocol = statusLine.protocol;
+ code = statusLine.code;
+ message = statusLine.message;
+ Headers.Builder responseHeadersBuilder = new Headers.Builder();
+ int responseHeaderLineCount = readInt(source);
+ for (int i = 0; i < responseHeaderLineCount; i++) {
+ responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
+ }
+ responseHeaders = responseHeadersBuilder.build();
+
+ if (isHttps()) {
+ String blank = source.readUtf8LineStrict();
+ if (blank.length() > 0) {
+ throw new IOException("expected \"\" but was \"" + blank + "\"");
+ }
+ String cipherSuite = source.readUtf8LineStrict();
+ List<Certificate> peerCertificates = readCertificateList(source);
+ List<Certificate> localCertificates = readCertificateList(source);
+ handshake = Handshake.get(cipherSuite, peerCertificates, localCertificates);
+ } else {
+ handshake = null;
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public Entry(Response response) {
+ this.url = response.request().urlString();
+ this.varyHeaders = OkHeaders.varyHeaders(response);
+ this.requestMethod = response.request().method();
+ this.protocol = response.protocol();
+ this.code = response.code();
+ this.message = response.message();
+ this.responseHeaders = response.headers();
+ this.handshake = response.handshake();
+ }
+
+ public void writeTo(DiskLruCache.Editor editor) throws IOException {
+ BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
+
+ sink.writeUtf8(url);
+ sink.writeByte('\n');
+ sink.writeUtf8(requestMethod);
+ sink.writeByte('\n');
+ sink.writeDecimalLong(varyHeaders.size());
+ sink.writeByte('\n');
+ for (int i = 0, size = varyHeaders.size(); i < size; i++) {
+ sink.writeUtf8(varyHeaders.name(i));
+ sink.writeUtf8(": ");
+ sink.writeUtf8(varyHeaders.value(i));
+ sink.writeByte('\n');
+ }
+
+ sink.writeUtf8(new StatusLine(protocol, code, message).toString());
+ sink.writeByte('\n');
+ sink.writeDecimalLong(responseHeaders.size());
+ sink.writeByte('\n');
+ for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+ sink.writeUtf8(responseHeaders.name(i));
+ sink.writeUtf8(": ");
+ sink.writeUtf8(responseHeaders.value(i));
+ sink.writeByte('\n');
+ }
+
+ if (isHttps()) {
+ sink.writeByte('\n');
+ sink.writeUtf8(handshake.cipherSuite());
+ sink.writeByte('\n');
+ writeCertList(sink, handshake.peerCertificates());
+ writeCertList(sink, handshake.localCertificates());
+ }
+ sink.close();
+ }
+
+ private boolean isHttps() {
+ return url.startsWith("https://");
+ }
+
+ private List<Certificate> readCertificateList(BufferedSource source) throws IOException {
+ int length = readInt(source);
+ if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null.
+
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ List<Certificate> result = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ String line = source.readUtf8LineStrict();
+ Buffer bytes = new Buffer();
+ bytes.write(ByteString.decodeBase64(line));
+ result.add(certificateFactory.generateCertificate(bytes.inputStream()));
+ }
+ return result;
+ } catch (CertificateException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private void writeCertList(BufferedSink sink, List<Certificate> certificates)
+ throws IOException {
+ try {
+ sink.writeDecimalLong(certificates.size());
+ sink.writeByte('\n');
+ for (int i = 0, size = certificates.size(); i < size; i++) {
+ byte[] bytes = certificates.get(i).getEncoded();
+ String line = ByteString.of(bytes).base64();
+ sink.writeUtf8(line);
+ sink.writeByte('\n');
+ }
+ } catch (CertificateEncodingException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public boolean matches(Request request, Response response) {
+ return url.equals(request.urlString())
+ && requestMethod.equals(request.method())
+ && OkHeaders.varyMatches(response, varyHeaders, request);
+ }
+
+ public Response response(Request request, DiskLruCache.Snapshot snapshot) {
+ String contentType = responseHeaders.get("Content-Type");
+ String contentLength = responseHeaders.get("Content-Length");
+ Request cacheRequest = new Request.Builder()
+ .url(url)
+ .method(requestMethod, null)
+ .headers(varyHeaders)
+ .build();
+ return new Response.Builder()
+ .request(cacheRequest)
+ .protocol(protocol)
+ .code(code)
+ .message(message)
+ .headers(responseHeaders)
+ .body(new CacheResponseBody(snapshot, contentType, contentLength))
+ .handshake(handshake)
+ .build();
+ }
+ }
+
+ private static int readInt(BufferedSource source) throws IOException {
+ try {
+ long result = source.readDecimalLong();
+ String line = source.readUtf8LineStrict();
+ if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) {
+ throw new IOException("expected an int but was \"" + result + line + "\"");
+ }
+ return (int) result;
+ } catch (NumberFormatException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private static class CacheResponseBody extends ResponseBody {
+ private final DiskLruCache.Snapshot snapshot;
+ private final BufferedSource bodySource;
+ private final String contentType;
+ private final String contentLength;
+
+ public CacheResponseBody(final DiskLruCache.Snapshot snapshot,
+ String contentType, String contentLength) {
+ this.snapshot = snapshot;
+ this.contentType = contentType;
+ this.contentLength = contentLength;
+
+ Source source = snapshot.getSource(ENTRY_BODY);
+ bodySource = Okio.buffer(new ForwardingSource(source) {
+ @Override public void close() throws IOException {
+ snapshot.close();
+ super.close();
+ }
+ });
+ }
+
+ @Override public MediaType contentType() {
+ return contentType != null ? MediaType.parse(contentType) : null;
+ }
+
+ @Override public long contentLength() {
+ try {
+ return contentLength != null ? Long.parseLong(contentLength) : -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ @Override public BufferedSource source() {
+ return bodySource;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java
new file mode 100644
index 0000000..27e2e4a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java
@@ -0,0 +1,369 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp;
+
+import com.android.okhttp.internal.http.HeaderParser;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Cache-Control header with cache directives from a server or client. These
+ * directives set policy on what responses can be stored, and which requests can
+ * be satisfied by those stored responses.
+ *
+ * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC
+ * 2616, 14.9</a>.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CacheControl {
+ /**
+ * Cache control request directives that require network validation of
+ * responses. Note that such requests may be assisted by the cache via
+ * conditional GET requests.
+ */
+ public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
+
+ /**
+ * Cache control request directives that uses the cache only, even if the
+ * cached response is stale. If the response isn't available in the cache or
+ * requires server validation, the call will fail with a {@code 504
+ * Unsatisfiable Request}.
+ */
+ public static final CacheControl FORCE_CACHE = new Builder()
+ .onlyIfCached()
+ .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
+ .build();
+
+ private final boolean noCache;
+ private final boolean noStore;
+ private final int maxAgeSeconds;
+ private final int sMaxAgeSeconds;
+ private final boolean isPrivate;
+ private final boolean isPublic;
+ private final boolean mustRevalidate;
+ private final int maxStaleSeconds;
+ private final int minFreshSeconds;
+ private final boolean onlyIfCached;
+ private final boolean noTransform;
+
+ String headerValue; // Lazily computed, if absent.
+
+ private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
+ boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
+ int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) {
+ this.noCache = noCache;
+ this.noStore = noStore;
+ this.maxAgeSeconds = maxAgeSeconds;
+ this.sMaxAgeSeconds = sMaxAgeSeconds;
+ this.isPrivate = isPrivate;
+ this.isPublic = isPublic;
+ this.mustRevalidate = mustRevalidate;
+ this.maxStaleSeconds = maxStaleSeconds;
+ this.minFreshSeconds = minFreshSeconds;
+ this.onlyIfCached = onlyIfCached;
+ this.noTransform = noTransform;
+ this.headerValue = headerValue;
+ }
+
+ private CacheControl(Builder builder) {
+ this.noCache = builder.noCache;
+ this.noStore = builder.noStore;
+ this.maxAgeSeconds = builder.maxAgeSeconds;
+ this.sMaxAgeSeconds = -1;
+ this.isPrivate = false;
+ this.isPublic = false;
+ this.mustRevalidate = false;
+ this.maxStaleSeconds = builder.maxStaleSeconds;
+ this.minFreshSeconds = builder.minFreshSeconds;
+ this.onlyIfCached = builder.onlyIfCached;
+ this.noTransform = builder.noTransform;
+ }
+
+ /**
+ * In a response, this field's name "no-cache" is misleading. It doesn't
+ * prevent us from caching the response; it only means we have to validate the
+ * response with the origin server before returning it. We can do this with a
+ * conditional GET.
+ *
+ * <p>In a request, it means do not use a cache to satisfy the request.
+ */
+ public boolean noCache() {
+ return noCache;
+ }
+
+ /** If true, this response should not be cached. */
+ public boolean noStore() {
+ return noStore;
+ }
+
+ /**
+ * The duration past the response's served date that it can be served without
+ * validation.
+ */
+ public int maxAgeSeconds() {
+ return maxAgeSeconds;
+ }
+
+ /**
+ * The "s-maxage" directive is the max age for shared caches. Not to be
+ * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
+ * this directive is not honored by this cache.
+ */
+ public int sMaxAgeSeconds() {
+ return sMaxAgeSeconds;
+ }
+
+ public boolean isPrivate() {
+ return isPrivate;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+ public boolean mustRevalidate() {
+ return mustRevalidate;
+ }
+
+ public int maxStaleSeconds() {
+ return maxStaleSeconds;
+ }
+
+ public int minFreshSeconds() {
+ return minFreshSeconds;
+ }
+
+ /**
+ * This field's name "only-if-cached" is misleading. It actually means "do
+ * not use the network". It is set by a client who only wants to make a
+ * request if it can be fully satisfied by the cache. Cached responses that
+ * would require validation (ie. conditional gets) are not permitted if this
+ * header is set.
+ */
+ public boolean onlyIfCached() {
+ return onlyIfCached;
+ }
+
+ public boolean noTransform() {
+ return noTransform;
+ }
+
+ /**
+ * Returns the cache directives of {@code headers}. This honors both
+ * Cache-Control and Pragma headers if they are present.
+ */
+ public static CacheControl parse(Headers headers) {
+ boolean noCache = false;
+ boolean noStore = false;
+ int maxAgeSeconds = -1;
+ int sMaxAgeSeconds = -1;
+ boolean isPrivate = false;
+ boolean isPublic = false;
+ boolean mustRevalidate = false;
+ int maxStaleSeconds = -1;
+ int minFreshSeconds = -1;
+ boolean onlyIfCached = false;
+ boolean noTransform = false;
+
+ boolean canUseHeaderValue = true;
+ String headerValue = null;
+
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ String name = headers.name(i);
+ String value = headers.value(i);
+
+ if (name.equalsIgnoreCase("Cache-Control")) {
+ if (headerValue != null) {
+ // Multiple cache-control headers means we can't use the raw value.
+ canUseHeaderValue = false;
+ } else {
+ headerValue = value;
+ }
+ } else if (name.equalsIgnoreCase("Pragma")) {
+ // Might specify additional cache-control params. We invalidate just in case.
+ canUseHeaderValue = false;
+ } else {
+ continue;
+ }
+
+ int pos = 0;
+ while (pos < value.length()) {
+ int tokenStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, "=,;");
+ String directive = value.substring(tokenStart, pos).trim();
+ String parameter;
+
+ if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
+ pos++; // consume ',' or ';' (if necessary)
+ parameter = null;
+ } else {
+ pos++; // consume '='
+ pos = HeaderParser.skipWhitespace(value, pos);
+
+ // quoted string
+ if (pos < value.length() && value.charAt(pos) == '\"') {
+ pos++; // consume '"' open quote
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, "\"");
+ parameter = value.substring(parameterStart, pos);
+ pos++; // consume '"' close quote (if necessary)
+
+ // unquoted string
+ } else {
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, ",;");
+ parameter = value.substring(parameterStart, pos).trim();
+ }
+ }
+
+ if ("no-cache".equalsIgnoreCase(directive)) {
+ noCache = true;
+ } else if ("no-store".equalsIgnoreCase(directive)) {
+ noStore = true;
+ } else if ("max-age".equalsIgnoreCase(directive)) {
+ maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
+ } else if ("s-maxage".equalsIgnoreCase(directive)) {
+ sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
+ } else if ("private".equalsIgnoreCase(directive)) {
+ isPrivate = true;
+ } else if ("public".equalsIgnoreCase(directive)) {
+ isPublic = true;
+ } else if ("must-revalidate".equalsIgnoreCase(directive)) {
+ mustRevalidate = true;
+ } else if ("max-stale".equalsIgnoreCase(directive)) {
+ maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE);
+ } else if ("min-fresh".equalsIgnoreCase(directive)) {
+ minFreshSeconds = HeaderParser.parseSeconds(parameter, -1);
+ } else if ("only-if-cached".equalsIgnoreCase(directive)) {
+ onlyIfCached = true;
+ } else if ("no-transform".equalsIgnoreCase(directive)) {
+ noTransform = true;
+ }
+ }
+ }
+
+ if (!canUseHeaderValue) {
+ headerValue = null;
+ }
+ return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
+ mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue);
+ }
+
+ @Override public String toString() {
+ String result = headerValue;
+ return result != null ? result : (headerValue = headerValue());
+ }
+
+ private String headerValue() {
+ StringBuilder result = new StringBuilder();
+ if (noCache) result.append("no-cache, ");
+ if (noStore) result.append("no-store, ");
+ if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", ");
+ if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
+ if (isPrivate) result.append("private, ");
+ if (isPublic) result.append("public, ");
+ if (mustRevalidate) result.append("must-revalidate, ");
+ if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", ");
+ if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", ");
+ if (onlyIfCached) result.append("only-if-cached, ");
+ if (noTransform) result.append("no-transform, ");
+ if (result.length() == 0) return "";
+ result.delete(result.length() - 2, result.length());
+ return result.toString();
+ }
+
+ /** Builds a {@code Cache-Control} request header.
+ * @hide This class is not part of the Android public SDK API*/
+ public static final class Builder {
+ boolean noCache;
+ boolean noStore;
+ int maxAgeSeconds = -1;
+ int maxStaleSeconds = -1;
+ int minFreshSeconds = -1;
+ boolean onlyIfCached;
+ boolean noTransform;
+
+ /** Don't accept an unvalidated cached response. */
+ public Builder noCache() {
+ this.noCache = true;
+ return this;
+ }
+
+ /** Don't store the server's response in any cache. */
+ public Builder noStore() {
+ this.noStore = true;
+ return this;
+ }
+
+ /**
+ * Sets the maximum age of a cached response. If the cache response's age
+ * exceeds {@code maxAge}, it will not be used and a network request will
+ * be made.
+ *
+ * @param maxAge a non-negative integer. This is stored and transmitted with
+ * {@link TimeUnit#SECONDS} precision; finer precision will be lost.
+ */
+ public Builder maxAge(int maxAge, TimeUnit timeUnit) {
+ if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge);
+ long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
+ this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE
+ ? Integer.MAX_VALUE
+ : (int) maxAgeSecondsLong;
+ return this;
+ }
+
+ /**
+ * Accept cached responses that have exceeded their freshness lifetime by
+ * up to {@code maxStale}. If unspecified, stale cache responses will not be
+ * used.
+ *
+ * @param maxStale a non-negative integer. This is stored and transmitted
+ * with {@link TimeUnit#SECONDS} precision; finer precision will be
+ * lost.
+ */
+ public Builder maxStale(int maxStale, TimeUnit timeUnit) {
+ if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale);
+ long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
+ this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE
+ ? Integer.MAX_VALUE
+ : (int) maxStaleSecondsLong;
+ return this;
+ }
+
+ /**
+ * Sets the minimum number of seconds that a response will continue to be
+ * fresh for. If the response will be stale when {@code minFresh} have
+ * elapsed, the cached response will not be used and a network request will
+ * be made.
+ *
+ * @param minFresh a non-negative integer. This is stored and transmitted
+ * with {@link TimeUnit#SECONDS} precision; finer precision will be
+ * lost.
+ */
+ public Builder minFresh(int minFresh, TimeUnit timeUnit) {
+ if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh);
+ long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
+ this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE
+ ? Integer.MAX_VALUE
+ : (int) minFreshSecondsLong;
+ return this;
+ }
+
+ /**
+ * Only accept the response if it is in the cache. If the response isn't
+ * cached, a {@code 504 Unsatisfiable Request} response will be returned.
+ */
+ public Builder onlyIfCached() {
+ this.onlyIfCached = true;
+ return this;
+ }
+
+ /** Don't accept a transformed response. */
+ public Builder noTransform() {
+ this.noTransform = true;
+ return this;
+ }
+
+ public CacheControl build() {
+ return new CacheControl(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java
new file mode 100644
index 0000000..25cddd0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java
@@ -0,0 +1,350 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.NamedRunnable;
+import com.android.okhttp.internal.http.HttpEngine;
+import com.android.okhttp.internal.http.RequestException;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StreamAllocation;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.logging.Level;
+
+import static com.android.okhttp.internal.Internal.logger;
+import static com.android.okhttp.internal.http.HttpEngine.MAX_FOLLOW_UPS;
+
+/**
+ * A call is a request that has been prepared for execution. A call can be
+ * canceled. As this object represents a single request/response pair (stream),
+ * it cannot be executed twice.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class Call {
+ private final OkHttpClient client;
+
+ // Guarded by this.
+ private boolean executed;
+ volatile boolean canceled;
+
+ /** The application's original request unadulterated by redirects or auth headers. */
+ Request originalRequest;
+ HttpEngine engine;
+
+ protected Call(OkHttpClient client, Request originalRequest) {
+ // Copy the client. Otherwise changes (socket factory, redirect policy,
+ // etc.) may incorrectly be reflected in the request when it is executed.
+ this.client = client.copyWithDefaults();
+ this.originalRequest = originalRequest;
+ }
+
+ /**
+ * Invokes the request immediately, and blocks until the response can be
+ * processed or is in error.
+ *
+ * <p>The caller may read the response body with the response's
+ * {@link Response#body} method. To facilitate connection recycling, callers
+ * should always {@link ResponseBody#close() close the response body}.
+ *
+ * <p>Note that transport-layer success (receiving a HTTP response code,
+ * headers and body) does not necessarily indicate application-layer success:
+ * {@code response} may still indicate an unhappy HTTP response code like 404
+ * or 500.
+ *
+ * @throws IOException if the request could not be executed due to
+ * cancellation, a connectivity problem or timeout. Because networks can
+ * fail during an exchange, it is possible that the remote server
+ * accepted the request before the failure.
+ *
+ * @throws IllegalStateException when the call has already been executed.
+ */
+ public Response execute() throws IOException {
+ synchronized (this) {
+ if (executed) throw new IllegalStateException("Already Executed");
+ executed = true;
+ }
+ try {
+ client.getDispatcher().executed(this);
+ Response result = getResponseWithInterceptorChain(false);
+ if (result == null) throw new IOException("Canceled");
+ return result;
+ } finally {
+ client.getDispatcher().finished(this);
+ }
+ }
+
+ Object tag() {
+ return originalRequest.tag();
+ }
+
+ /**
+ * Schedules the request to be executed at some point in the future.
+ *
+ * <p>The {@link OkHttpClient#getDispatcher dispatcher} defines when the
+ * request will run: usually immediately unless there are several other
+ * requests currently being executed.
+ *
+ * <p>This client will later call back {@code responseCallback} with either
+ * an HTTP response or a failure exception. If you {@link #cancel} a request
+ * before it completes the callback will not be invoked.
+ *
+ * @throws IllegalStateException when the call has already been executed.
+ */
+ public void enqueue(Callback responseCallback) {
+ enqueue(responseCallback, false);
+ }
+
+ void enqueue(Callback responseCallback, boolean forWebSocket) {
+ synchronized (this) {
+ if (executed) throw new IllegalStateException("Already Executed");
+ executed = true;
+ }
+ client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
+ }
+
+ /**
+ * Cancels the request, if possible. Requests that are already complete
+ * cannot be canceled.
+ */
+ public void cancel() {
+ canceled = true;
+ if (engine != null) engine.cancel();
+ }
+
+ /**
+ * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
+ * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
+ */
+ public synchronized boolean isExecuted() {
+ return executed;
+ }
+
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ final class AsyncCall extends NamedRunnable {
+ private final Callback responseCallback;
+ private final boolean forWebSocket;
+
+ private AsyncCall(Callback responseCallback, boolean forWebSocket) {
+ super("OkHttp %s", originalRequest.urlString());
+ this.responseCallback = responseCallback;
+ this.forWebSocket = forWebSocket;
+ }
+
+ String host() {
+ return originalRequest.httpUrl().host();
+ }
+
+ Request request() {
+ return originalRequest;
+ }
+
+ Object tag() {
+ return originalRequest.tag();
+ }
+
+ void cancel() {
+ Call.this.cancel();
+ }
+
+ Call get() {
+ return Call.this;
+ }
+
+ @Override protected void execute() {
+ boolean signalledCallback = false;
+ try {
+ Response response = getResponseWithInterceptorChain(forWebSocket);
+ if (canceled) {
+ signalledCallback = true;
+ responseCallback.onFailure(originalRequest, new IOException("Canceled"));
+ } else {
+ signalledCallback = true;
+ responseCallback.onResponse(response);
+ }
+ } catch (IOException e) {
+ if (signalledCallback) {
+ // Do not signal the callback twice!
+ logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
+ } else {
+ Request request = engine == null ? originalRequest : engine.getRequest();
+ responseCallback.onFailure(request, e);
+ }
+ } finally {
+ client.getDispatcher().finished(this);
+ }
+ }
+ }
+
+ /**
+ * Returns a string that describes this call. Doesn't include a full URL as that might contain
+ * sensitive information.
+ */
+ private String toLoggableString() {
+ String string = canceled ? "canceled call" : "call";
+ HttpUrl redactedUrl = originalRequest.httpUrl().resolve("/...");
+ return string + " to " + redactedUrl;
+ }
+
+ private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
+ Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
+ return chain.proceed(originalRequest);
+ }
+
+ class ApplicationInterceptorChain implements Interceptor.Chain {
+ private final int index;
+ private final Request request;
+ private final boolean forWebSocket;
+
+ ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
+ this.index = index;
+ this.request = request;
+ this.forWebSocket = forWebSocket;
+ }
+
+ @Override public Connection connection() {
+ return null;
+ }
+
+ @Override public Request request() {
+ return request;
+ }
+
+ @Override public Response proceed(Request request) throws IOException {
+ // If there's another interceptor in the chain, call that.
+ if (index < client.interceptors().size()) {
+ Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
+ Interceptor interceptor = client.interceptors().get(index);
+ Response interceptedResponse = interceptor.intercept(chain);
+
+ if (interceptedResponse == null) {
+ throw new NullPointerException("application interceptor " + interceptor
+ + " returned null");
+ }
+
+ return interceptedResponse;
+ }
+
+ // No more interceptors. Do HTTP.
+ return getResponse(request, forWebSocket);
+ }
+ }
+
+ /**
+ * Performs the request and returns the response. May return null if this
+ * call was canceled.
+ */
+ Response getResponse(Request request, boolean forWebSocket) throws IOException {
+ // Copy body metadata to the appropriate request headers.
+ RequestBody body = request.body();
+ if (body != null) {
+ Request.Builder requestBuilder = request.newBuilder();
+
+ MediaType contentType = body.contentType();
+ if (contentType != null) {
+ requestBuilder.header("Content-Type", contentType.toString());
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength != -1) {
+ requestBuilder.header("Content-Length", Long.toString(contentLength));
+ requestBuilder.removeHeader("Transfer-Encoding");
+ } else {
+ requestBuilder.header("Transfer-Encoding", "chunked");
+ requestBuilder.removeHeader("Content-Length");
+ }
+
+ request = requestBuilder.build();
+ }
+
+ // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
+ engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
+
+ int followUpCount = 0;
+ while (true) {
+ if (canceled) {
+ engine.releaseStreamAllocation();
+ throw new IOException("Canceled");
+ }
+
+ boolean releaseConnection = true;
+ try {
+ engine.sendRequest();
+ engine.readResponse();
+ releaseConnection = false;
+ } catch (RequestException e) {
+ // The attempt to interpret the request failed. Give up.
+ throw e.getCause();
+ } catch (RouteException e) {
+ // The attempt to connect via a route failed. The request will not have been sent.
+ HttpEngine retryEngine = engine.recover(e);
+ if (retryEngine != null) {
+ releaseConnection = false;
+ engine = retryEngine;
+ continue;
+ }
+ // Give up; recovery is not possible.
+ throw e.getLastConnectException();
+ } catch (IOException e) {
+ // An attempt to communicate with a server failed. The request may have been sent.
+ HttpEngine retryEngine = engine.recover(e, null);
+ if (retryEngine != null) {
+ releaseConnection = false;
+ engine = retryEngine;
+ continue;
+ }
+
+ // Give up; recovery is not possible.
+ throw e;
+ } finally {
+ // We're throwing an unchecked exception. Release any resources.
+ if (releaseConnection) {
+ StreamAllocation streamAllocation = engine.close();
+ streamAllocation.release();
+ }
+ }
+
+ Response response = engine.getResponse();
+ Request followUp = engine.followUpRequest();
+
+ if (followUp == null) {
+ if (!forWebSocket) {
+ engine.releaseStreamAllocation();
+ }
+ return response;
+ }
+
+ StreamAllocation streamAllocation = engine.close();
+
+ if (++followUpCount > MAX_FOLLOW_UPS) {
+ streamAllocation.release();
+ throw new ProtocolException("Too many follow-up requests: " + followUpCount);
+ }
+
+ if (!engine.sameConnection(followUp.httpUrl())) {
+ streamAllocation.release();
+ streamAllocation = null;
+ }
+
+ request = followUp;
+ engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
+ response);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java
new file mode 100644
index 0000000..10d78c6
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java
@@ -0,0 +1,46 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Callback {
+ /**
+ * Called when the request could not be executed due to cancellation, a
+ * connectivity problem or timeout. Because networks can fail during an
+ * exchange, it is possible that the remote server accepted the request
+ * before the failure.
+ */
+ void onFailure(Request request, IOException e);
+
+ /**
+ * Called when the HTTP response was successfully returned by the remote
+ * server. The callback may proceed to read the response body with {@link
+ * Response#body}. The response is still live until its response body is
+ * closed with {@code response.body().close()}. The recipient of the callback
+ * may even consume the response body on another thread.
+ *
+ * <p>Note that transport-layer success (receiving a HTTP response code,
+ * headers and body) does not necessarily indicate application-layer
+ * success: {@code response} may still indicate an unhappy HTTP response
+ * code like 404 or 500.
+ */
+ void onResponse(Response response) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java
new file mode 100644
index 0000000..638dea0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java
@@ -0,0 +1,275 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import com.android.okhttp.okio.ByteString;
+
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Constrains which certificates are trusted. Pinning certificates defends
+ * against attacks on certificate authorities. It also prevents connections
+ * through man-in-the-middle certificate authorities either known or unknown to
+ * the application's user.
+ *
+ * <p>This class currently pins a certificate's Subject Public Key Info as
+ * described on <a href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins
+ * are base-64 SHA-1 hashes, consistent with the format Chromium uses for <a
+ * href="http://goo.gl/XDh6je">static certificates</a>. See Chromium's <a
+ * href="http://goo.gl/4CCnGs">pinsets</a> for hostnames that are pinned in that
+ * browser.
+ *
+ * <h3>Setting up Certificate Pinning</h3>
+ * The easiest way to pin a host is turn on pinning with a broken configuration
+ * and read the expected configuration when the connection fails. Be sure to
+ * do this on a trusted network, and without man-in-the-middle tools like <a
+ * href="http://charlesproxy.com">Charles</a> or <a
+ * href="http://fiddlertool.com">Fiddler</a>.
+ *
+ * <p>For example, to pin {@code https://publicobject.com}, start with a broken
+ * configuration: <pre> {@code
+ *
+ * String hostname = "publicobject.com";
+ * CertificatePinner certificatePinner = new CertificatePinner.Builder()
+ * .add(hostname, "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
+ * .build();
+ * OkHttpClient client = new OkHttpClient();
+ * client.setCertificatePinner(certificatePinner);
+ *
+ * Request request = new Request.Builder()
+ * .url("https://" + hostname)
+ * .build();
+ * client.newCall(request).execute();
+ * }</pre>
+ *
+ * As expected, this fails with a certificate pinning exception: <pre> {@code
+ *
+ * javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
+ * Peer certificate chain:
+ * sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL
+ * sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA
+ * sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority
+ * sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root
+ * Pinned certificates for publicobject.com:
+ * sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ * at com.squareup.okhttp.CertificatePinner.check(CertificatePinner.java)
+ * at com.squareup.okhttp.Connection.upgradeToTls(Connection.java)
+ * at com.squareup.okhttp.Connection.connect(Connection.java)
+ * at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java)
+ * }</pre>
+ *
+ * Follow up by pasting the public key hashes from the exception into the
+ * certificate pinner's configuration: <pre> {@code
+ *
+ * CertificatePinner certificatePinner = new CertificatePinner.Builder()
+ * .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
+ * .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
+ * .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
+ * .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
+ * .build();
+ * }</pre>
+ *
+ * Pinning is per-hostname and/or per-wildcard pattern. To pin both
+ * {@code publicobject.com} and {@code www.publicobject.com}, you must
+ * configure both hostnames.
+ *
+ * <p>Wildcard pattern rules:
+ * <ol>
+ * <li>Asterisk {@code *} is only permitted in the left-most
+ * domain name label and must be the only character in that label
+ * (i.e., must match the whole left-most label). For example,
+ * {@code *.example.com} is permitted, while {@code *a.example.com},
+ * {@code a*.example.com}, {@code a*b.example.com}, {@code a.*.example.com}
+ * are not permitted.
+ * <li>Asterisk {@code *} cannot match across domain name labels.
+ * For example, {@code *.example.com} matches {@code test.example.com}
+ * but does not match {@code sub.test.example.com}.
+ * <li>Wildcard patterns for single-label domain names are not permitted.
+ * </ol>
+ *
+ * If hostname pinned directly and via wildcard pattern, both
+ * direct and wildcard pins will be used. For example: {@code *.example.com} pinned
+ * with {@code pin1} and {@code a.example.com} pinned with {@code pin2},
+ * to check {@code a.example.com} both {@code pin1} and {@code pin2} will be used.
+ *
+ * <h3>Warning: Certificate Pinning is Dangerous!</h3>
+ * Pinning certificates limits your server team's abilities to update their TLS
+ * certificates. By pinning certificates, you take on additional operational
+ * complexity and limit your ability to migrate between certificate authorities.
+ * Do not use certificate pinning without the blessing of your server's TLS
+ * administrator!
+ *
+ * <h4>Note about self-signed certificates</h4>
+ * {@link CertificatePinner} can not be used to pin self-signed certificate
+ * if such certificate is not accepted by {@link javax.net.ssl.TrustManager}.
+ *
+ * @see <a href="https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning">
+ * OWASP: Certificate and Public Key Pinning</a>
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CertificatePinner {
+ public static final CertificatePinner DEFAULT = new Builder().build();
+
+ private final Map<String, Set<ByteString>> hostnameToPins;
+
+ private CertificatePinner(Builder builder) {
+ this.hostnameToPins = Util.immutableMap(builder.hostnameToPins);
+ }
+
+ /**
+ * Confirms that at least one of the certificates pinned for {@code hostname}
+ * is in {@code peerCertificates}. Does nothing if there are no certificates
+ * pinned for {@code hostname}. OkHttp calls this after a successful TLS
+ * handshake, but before the connection is used.
+ *
+ * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match
+ * the certificates pinned for {@code hostname}.
+ */
+ public void check(String hostname, List<Certificate> peerCertificates)
+ throws SSLPeerUnverifiedException {
+
+ Set<ByteString> pins = findMatchingPins(hostname);
+
+ if (pins == null) return;
+
+ for (int i = 0, size = peerCertificates.size(); i < size; i++) {
+ X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
+ if (pins.contains(sha1(x509Certificate))) return; // Success!
+ }
+
+ // If we couldn't find a matching pin, format a nice exception.
+ StringBuilder message = new StringBuilder()
+ .append("Certificate pinning failure!")
+ .append("\n Peer certificate chain:");
+ for (int i = 0, size = peerCertificates.size(); i < size; i++) {
+ X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
+ message.append("\n ").append(pin(x509Certificate))
+ .append(": ").append(x509Certificate.getSubjectDN().getName());
+ }
+ message.append("\n Pinned certificates for ").append(hostname).append(":");
+ for (ByteString pin : pins) {
+ message.append("\n sha1/").append(pin.base64());
+ }
+ throw new SSLPeerUnverifiedException(message.toString());
+ }
+
+ /** @deprecated replaced with {@link #check(String, List)}. */
+ public void check(String hostname, Certificate... peerCertificates)
+ throws SSLPeerUnverifiedException {
+ check(hostname, Arrays.asList(peerCertificates));
+ }
+
+ /**
+ * Returns list of matching certificates' pins for the hostname
+ * or {@code null} if hostname does not have pinned certificates.
+ */
+ Set<ByteString> findMatchingPins(String hostname) {
+ Set<ByteString> directPins = hostnameToPins.get(hostname);
+ Set<ByteString> wildcardPins = null;
+
+ int indexOfFirstDot = hostname.indexOf('.');
+ int indexOfLastDot = hostname.lastIndexOf('.');
+
+ // Skip hostnames with one dot symbol for wildcard pattern search
+ // example.com will be skipped
+ // a.example.com won't be skipped
+ if (indexOfFirstDot != indexOfLastDot) {
+ // a.example.com -> search for wildcard pattern *.example.com
+ wildcardPins = hostnameToPins.get("*." + hostname.substring(indexOfFirstDot + 1));
+ }
+
+ if (directPins == null && wildcardPins == null) return null;
+
+ if (directPins != null && wildcardPins != null) {
+ Set<ByteString> pins = new LinkedHashSet<>();
+ pins.addAll(directPins);
+ pins.addAll(wildcardPins);
+ return pins;
+ }
+
+ if (directPins != null) return directPins;
+
+ return wildcardPins;
+ }
+
+ /**
+ * 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 Pinning</a>.
+ */
+ public static String pin(Certificate certificate) {
+ if (!(certificate instanceof X509Certificate)) {
+ throw new IllegalArgumentException("Certificate pinning requires X509 certificates");
+ }
+ return "sha1/" + sha1((X509Certificate) certificate).base64();
+ }
+
+ private static ByteString sha1(X509Certificate x509Certificate) {
+ return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
+ }
+
+ /** Builds a configured certificate pinner.
+ * @hide This class is not part of the Android public SDK API*/
+ public static final class Builder {
+ private final Map<String, Set<ByteString>> hostnameToPins = new LinkedHashMap<>();
+
+ /**
+ * Pins certificates for {@code hostname}.
+ *
+ * @param hostname lower-case host name or wildcard pattern such as {@code *.example.com}.
+ * @param pins SHA-1 hashes. Each pin is a SHA-1 hash of a
+ * certificate's Subject Public Key Info, base64-encoded and prefixed with
+ * {@code sha1/}.
+ */
+ public Builder add(String hostname, String... pins) {
+ if (hostname == null) throw new IllegalArgumentException("hostname == null");
+
+ Set<ByteString> hostPins = new LinkedHashSet<>();
+ Set<ByteString> previousPins = hostnameToPins.put(hostname, unmodifiableSet(hostPins));
+ if (previousPins != null) {
+ hostPins.addAll(previousPins);
+ }
+
+ for (String pin : pins) {
+ if (!pin.startsWith("sha1/")) {
+ throw new IllegalArgumentException("pins must start with 'sha1/': " + pin);
+ }
+ ByteString decodedPin = ByteString.decodeBase64(pin.substring("sha1/".length()));
+ if (decodedPin == null) {
+ throw new IllegalArgumentException("pins must be base64: " + pin);
+ }
+ hostPins.add(decodedPin);
+ }
+
+ return this;
+ }
+
+ public CertificatePinner build() {
+ return new CertificatePinner(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java
new file mode 100644
index 0000000..20f11d2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import static com.android.okhttp.internal.Util.equal;
+
+/** An RFC 2617 challenge.
+ * @hide This class is not part of the Android public SDK API*/
+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
+ && equal(scheme, ((Challenge) o).scheme)
+ && equal(realm, ((Challenge) o).realm);
+ }
+
+ @Override public int hashCode() {
+ int result = 29;
+ result = 31 * result + (realm != null ? realm.hashCode() : 0);
+ result = 31 * result + (scheme != null ? scheme.hashCode() : 0);
+ return result;
+ }
+
+ @Override public String toString() {
+ return scheme + " realm=\"" + realm + "\"";
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java
new file mode 100644
index 0000000..bc15fb7
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java
@@ -0,0 +1,377 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import static java.lang.Integer.MAX_VALUE;
+
+/**
+ * <a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS cipher
+ * suites</a>.
+ *
+ * <p><strong>Not all cipher suites are supported on all platforms.</strong> As newer cipher suites
+ * are created (for stronger privacy, better performance, etc.) they will be adopted by the platform
+ * and then exposed here. Cipher suites that are not available on either Android (through API level
+ * 20) or Java (through JDK 8) are omitted for brevity.
+ *
+ * <p>See also <a href="https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/NativeCrypto.java">NativeCrypto.java</a>
+ * from conscrypt, which lists the cipher suites supported by Android.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum CipherSuite {
+ // Last updated 2014-11-11 using cipher suites from Android 21 and Java 8.
+
+ // TLS_NULL_WITH_NULL_NULL("TLS_NULL_WITH_NULL_NULL", 0x0000, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_RSA_WITH_NULL_MD5("SSL_RSA_WITH_NULL_MD5", 0x0001, 5246, 6, 10),
+ TLS_RSA_WITH_NULL_SHA("SSL_RSA_WITH_NULL_SHA", 0x0002, 5246, 6, 10),
+ TLS_RSA_EXPORT_WITH_RC4_40_MD5("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, 4346, 6, 10),
+ TLS_RSA_WITH_RC4_128_MD5("SSL_RSA_WITH_RC4_128_MD5", 0x0004, 5246, 6, 10),
+ TLS_RSA_WITH_RC4_128_SHA("SSL_RSA_WITH_RC4_128_SHA", 0x0005, 5246, 6, 10),
+ // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006, 4346, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_IDEA_CBC_SHA("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007, 5469, MAX_VALUE, MAX_VALUE),
+ TLS_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, 4346, 6, 10),
+ TLS_RSA_WITH_DES_CBC_SHA("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, 5469, 6, 10),
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, 5246, 6, 10),
+ // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b, 4346, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_DES_CBC_SHA("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c, 5469, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d, 5246, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e, 4346, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_DES_CBC_SHA("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f, 5469, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, 4346, 6, 10),
+ TLS_DHE_DSS_WITH_DES_CBC_SHA("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, 5469, 6, 10),
+ TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, 5246, 6, 10),
+ TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, 4346, 6, 10),
+ TLS_DHE_RSA_WITH_DES_CBC_SHA("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, 5469, 6, 10),
+ TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, 5246, 6, 10),
+ TLS_DH_anon_EXPORT_WITH_RC4_40_MD5("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017, 4346, 6, 10),
+ TLS_DH_anon_WITH_RC4_128_MD5("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018, 5246, 6, 10),
+ TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019, 4346, 6, 10),
+ TLS_DH_anon_WITH_DES_CBC_SHA("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a, 5469, 6, 10),
+ TLS_DH_anon_WITH_3DES_EDE_CBC_SHA("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b, 5246, 6, 10),
+ TLS_KRB5_WITH_DES_CBC_SHA("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e, 2712, 6, MAX_VALUE),
+ TLS_KRB5_WITH_3DES_EDE_CBC_SHA("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f, 2712, 6, MAX_VALUE),
+ TLS_KRB5_WITH_RC4_128_SHA("TLS_KRB5_WITH_RC4_128_SHA", 0x0020, 2712, 6, MAX_VALUE),
+ // TLS_KRB5_WITH_IDEA_CBC_SHA("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021, 2712, MAX_VALUE, MAX_VALUE),
+ TLS_KRB5_WITH_DES_CBC_MD5("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022, 2712, 6, MAX_VALUE),
+ TLS_KRB5_WITH_3DES_EDE_CBC_MD5("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023, 2712, 6, MAX_VALUE),
+ TLS_KRB5_WITH_RC4_128_MD5("TLS_KRB5_WITH_RC4_128_MD5", 0x0024, 2712, 6, MAX_VALUE),
+ // TLS_KRB5_WITH_IDEA_CBC_MD5("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025, 2712, MAX_VALUE, MAX_VALUE),
+ TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026, 2712, 6, MAX_VALUE),
+ // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027, 2712, MAX_VALUE, MAX_VALUE),
+ TLS_KRB5_EXPORT_WITH_RC4_40_SHA("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028, 2712, 6, MAX_VALUE),
+ TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029, 2712, 6, MAX_VALUE),
+ // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a, 2712, MAX_VALUE, MAX_VALUE),
+ TLS_KRB5_EXPORT_WITH_RC4_40_MD5("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b, 2712, 6, MAX_VALUE),
+ // TLS_PSK_WITH_NULL_SHA("TLS_PSK_WITH_NULL_SHA", 0x002c, 4785, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_NULL_SHA("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d, 4785, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_NULL_SHA("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e, 4785, MAX_VALUE, MAX_VALUE),
+ TLS_RSA_WITH_AES_128_CBC_SHA("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, 5246, 6, 10),
+ // TLS_DH_DSS_WITH_AES_128_CBC_SHA("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030, 5246, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_AES_128_CBC_SHA("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_WITH_AES_128_CBC_SHA("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, 5246, 6, 10),
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, 5246, 6, 10),
+ TLS_DH_anon_WITH_AES_128_CBC_SHA("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034, 5246, 6, 10),
+ TLS_RSA_WITH_AES_256_CBC_SHA("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, 5246, 6, 10),
+ // TLS_DH_DSS_WITH_AES_256_CBC_SHA("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036, 5246, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_AES_256_CBC_SHA("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_WITH_AES_256_CBC_SHA("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, 5246, 6, 10),
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, 5246, 6, 10),
+ TLS_DH_anon_WITH_AES_256_CBC_SHA("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a, 5246, 6, 10),
+ TLS_RSA_WITH_NULL_SHA256("TLS_RSA_WITH_NULL_SHA256", 0x003b, 5246, 7, 21),
+ TLS_RSA_WITH_AES_128_CBC_SHA256("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c, 5246, 7, 21),
+ TLS_RSA_WITH_AES_256_CBC_SHA256("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d, 5246, 7, 21),
+ // TLS_DH_DSS_WITH_AES_128_CBC_SHA256("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e, 5246, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_AES_128_CBC_SHA256("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_WITH_AES_128_CBC_SHA256("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040, 5246, 7, 21),
+ // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046, 5932, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA256("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067, 5246, 7, 21),
+ // TLS_DH_DSS_WITH_AES_256_CBC_SHA256("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068, 5246, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_AES_256_CBC_SHA256("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069, 5246, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_WITH_AES_256_CBC_SHA256("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a, 5246, 7, 21),
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA256("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b, 5246, 7, 21),
+ TLS_DH_anon_WITH_AES_128_CBC_SHA256("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c, 5246, 7, 21),
+ TLS_DH_anon_WITH_AES_256_CBC_SHA256("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d, 5246, 7, 21),
+ // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_RC4_128_SHA("TLS_PSK_WITH_RC4_128_SHA", 0x008a, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_3DES_EDE_CBC_SHA("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_128_CBC_SHA("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_256_CBC_SHA("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_RC4_128_SHA("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_128_CBC_SHA("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_256_CBC_SHA("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_RC4_128_SHA("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_128_CBC_SHA("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_256_CBC_SHA("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095, 4279, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_SEED_CBC_SHA("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096, 4162, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_SEED_CBC_SHA("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097, 4162, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_SEED_CBC_SHA("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098, 4162, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_SEED_CBC_SHA("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099, 4162, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_SEED_CBC_SHA("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a, 4162, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_SEED_CBC_SHA("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b, 4162, MAX_VALUE, MAX_VALUE),
+ TLS_RSA_WITH_AES_128_GCM_SHA256("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c, 5288, 8, 21),
+ TLS_RSA_WITH_AES_256_GCM_SHA384("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d, 5288, 8, 21),
+ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e, 5288, 8, 21),
+ TLS_DHE_RSA_WITH_AES_256_GCM_SHA384("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f, 5288, 8, 21),
+ // TLS_DH_RSA_WITH_AES_128_GCM_SHA256("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0, 5288, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_AES_256_GCM_SHA384("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1, 5288, MAX_VALUE, MAX_VALUE),
+ TLS_DHE_DSS_WITH_AES_128_GCM_SHA256("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2, 5288, 8, 21),
+ TLS_DHE_DSS_WITH_AES_256_GCM_SHA384("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3, 5288, 8, 21),
+ // TLS_DH_DSS_WITH_AES_128_GCM_SHA256("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4, 5288, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_AES_256_GCM_SHA384("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5, 5288, MAX_VALUE, MAX_VALUE),
+ TLS_DH_anon_WITH_AES_128_GCM_SHA256("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6, 5288, 8, 21),
+ TLS_DH_anon_WITH_AES_256_GCM_SHA384("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7, 5288, 8, 21),
+ // TLS_PSK_WITH_AES_128_GCM_SHA256("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_256_GCM_SHA384("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_128_GCM_SHA256("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_256_GCM_SHA384("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_128_GCM_SHA256("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_256_GCM_SHA384("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_128_CBC_SHA256("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_256_CBC_SHA384("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_NULL_SHA256("TLS_PSK_WITH_NULL_SHA256", 0x00b0, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_NULL_SHA384("TLS_PSK_WITH_NULL_SHA384", 0x00b1, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_128_CBC_SHA256("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_256_CBC_SHA384("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_NULL_SHA256("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_NULL_SHA384("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_128_CBC_SHA256("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_AES_256_CBC_SHA384("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_NULL_SHA256("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_NULL_SHA384("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9, 5487, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4, 5932, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5, 5932, MAX_VALUE, MAX_VALUE),
+ TLS_EMPTY_RENEGOTIATION_INFO_SCSV("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff, 5746, 6, 14),
+ TLS_ECDH_ECDSA_WITH_NULL_SHA("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001, 4492, 7, 14),
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002, 4492, 7, 14),
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003, 4492, 7, 14),
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004, 4492, 7, 14),
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005, 4492, 7, 14),
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006, 4492, 7, 14),
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007, 4492, 7, 14),
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008, 4492, 7, 14),
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009, 4492, 7, 14),
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a, 4492, 7, 14),
+ TLS_ECDH_RSA_WITH_NULL_SHA("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b, 4492, 7, 14),
+ TLS_ECDH_RSA_WITH_RC4_128_SHA("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c, 4492, 7, 14),
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d, 4492, 7, 14),
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e, 4492, 7, 14),
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f, 4492, 7, 14),
+ TLS_ECDHE_RSA_WITH_NULL_SHA("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010, 4492, 7, 14),
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011, 4492, 7, 14),
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012, 4492, 7, 14),
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013, 4492, 7, 14),
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014, 4492, 7, 14),
+ TLS_ECDH_anon_WITH_NULL_SHA("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015, 4492, 7, 14),
+ TLS_ECDH_anon_WITH_RC4_128_SHA("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016, 4492, 7, 14),
+ TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017, 4492, 7, 14),
+ TLS_ECDH_anon_WITH_AES_128_CBC_SHA("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018, 4492, 7, 14),
+ TLS_ECDH_anon_WITH_AES_256_CBC_SHA("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019, 4492, 7, 14),
+ // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021, 5054, MAX_VALUE, MAX_VALUE),
+ // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022, 5054, MAX_VALUE, MAX_VALUE),
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023, 5289, 7, 21),
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024, 5289, 7, 21),
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025, 5289, 7, 21),
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026, 5289, 7, 21),
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027, 5289, 7, 21),
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028, 5289, 7, 21),
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029, 5289, 7, 21),
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a, 5289, 7, 21),
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b, 5289, 8, 21),
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c, 5289, 8, 21),
+ TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d, 5289, 8, 21),
+ TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e, 5289, 8, 21),
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f, 5289, 8, 21),
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030, 5289, 8, 21),
+ TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031, 5289, 8, 21),
+ TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032, 5289, 8, 21),
+ // TLS_ECDHE_PSK_WITH_RC4_128_SHA("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_NULL_SHA("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_NULL_SHA256("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_NULL_SHA384("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b, 5489, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_ARIA_128_CBC_SHA256("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_ARIA_256_CBC_SHA384("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_ARIA_128_CBC_SHA256("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_ARIA_256_CBC_SHA384("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_ARIA_128_GCM_SHA256("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_ARIA_256_GCM_SHA384("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_ARIA_128_GCM_SHA256("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_ARIA_256_GCM_SHA384("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_ARIA_128_CBC_SHA256("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_ARIA_256_CBC_SHA384("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_ARIA_128_GCM_SHA256("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_ARIA_256_GCM_SHA384("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071, 6209, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b, 6367, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_AES_128_CCM("TLS_RSA_WITH_AES_128_CCM", 0xc09c, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_AES_256_CCM("TLS_RSA_WITH_AES_256_CCM", 0xc09d, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_AES_128_CCM("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_AES_256_CCM("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_AES_128_CCM_8("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_RSA_WITH_AES_256_CCM_8("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_AES_128_CCM_8("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_RSA_WITH_AES_256_CCM_8("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_128_CCM("TLS_PSK_WITH_AES_128_CCM", 0xc0a4, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_256_CCM("TLS_PSK_WITH_AES_256_CCM", 0xc0a5, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_128_CCM("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_DHE_PSK_WITH_AES_256_CCM("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_128_CCM_8("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_WITH_AES_256_CCM_8("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_DHE_WITH_AES_128_CCM_8("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_PSK_DHE_WITH_AES_256_CCM_8("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab, 6655, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_AES_128_CCM("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac, 7251, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE),
+ // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE),
+ ;
+
+ final String javaName;
+
+ /**
+ * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA name
+ * for older cipher suites because the prefix is {@code SSL_} instead of {@code TLS_}.
+ * @param value the integer identifier for this cipher suite. (Documentation only.)
+ * @param rfc the RFC describing this cipher suite. (Documentation only.)
+ * @param sinceJavaVersion the first major Java release supporting this cipher suite.
+ * @param sinceAndroidVersion the first Android SDK version supporting this cipher suite.
+ */
+ private CipherSuite(
+ String javaName, int value, int rfc, int sinceJavaVersion, int sinceAndroidVersion) {
+ this.javaName = javaName;
+ }
+
+ public static CipherSuite forJavaName(String javaName) {
+ return javaName.startsWith("SSL_")
+ ? valueOf("TLS_" + javaName.substring(4))
+ : valueOf(javaName);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java
new file mode 100644
index 0000000..89f2546
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java
@@ -0,0 +1,86 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.net.Socket;
+
+/**
+ * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be used for multiple
+ * HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy.
+ *
+ * <p>Typically instances of this class are created, connected and exercised automatically by the
+ * HTTP client. Applications may use this class to monitor HTTP connections as members of a
+ * {@linkplain ConnectionPool connection pool}.
+ *
+ * <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, which isn't so much a
+ * connection as a single request/response exchange.
+ *
+ * <h3>Modern TLS</h3>
+ * There are tradeoffs when selecting which options to include when negotiating a secure connection
+ * to a remote host. Newer TLS options are quite useful:
+ * <ul>
+ * <li>Server Name Indication (SNI) enables one IP address to negotiate secure connections for
+ * multiple domain names.
+ * <li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port (443) to be used for
+ * different HTTP and SPDY protocols.
+ * </ul>
+ * Unfortunately, older HTTPS servers refuse to connect when such options are presented. Rather than
+ * avoiding these options entirely, this class allows a connection to be attempted with modern
+ * options and then retried without them should the attempt fail.
+ *
+ * <h3>Connection Reuse</h3>
+ * <p>Each connection can carry a varying number streams, depending on the underlying protocol being
+ * used. HTTP/1.x connections can carry either zero or one streams. HTTP/2 connections can carry any
+ * number of streams, dynamically configured with {@code SETTINGS_MAX_CONCURRENT_STREAMS}. A
+ * connection currently carrying zero streams is an idle stream. We keep it alive because reusing an
+ * existing connection is typically faster than establishing a new one.
+ *
+ * <p>When a single logical call requires multiple streams due to redirects or authorization
+ * challenges, we prefer to use the same physical connection for all streams in the sequence. There
+ * are potential performance and behavior consequences to this preference. To support this feature,
+ * this class separates <i>allocations</i> from <i>streams</i>. An allocation is created by a call,
+ * used for one or more streams, and then released. An allocated connection won't be stolen by
+ * other calls while a redirect or authorization challenge is being handled.
+ *
+ * <p>When the maximum concurrent streams limit is reduced, some allocations will be rescinded.
+ * Attempting to create new streams on these allocations will fail.
+ *
+ * <p>Note that an allocation may be released before its stream is completed. This is intended to
+ * make bookkeeping easier for the caller: releasing the allocation as soon as the terminal stream
+ * has been found. But only complete the stream once its data stream has been exhausted.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Connection {
+ /** Returns the route used by this connection. */
+ Route getRoute();
+
+ /**
+ * Returns the socket that this connection uses, or null if the connection
+ * is not currently connected.
+ */
+ Socket getSocket();
+
+ Handshake getHandshake();
+
+ /**
+ * Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol
+ * has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is
+ * using {@link Protocol#HTTP_1_0}.
+ */
+ Protocol getProtocol();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java
new file mode 100644
index 0000000..657751b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java
@@ -0,0 +1,329 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import java.lang.ref.Reference;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
+ * requests that share the same {@link com.android.okhttp.Address} may share a
+ * {@link Connection}. This class implements the policy of which connections to
+ * keep open for future use.
+ *
+ * <p>The {@link #getDefault() system-wide default} uses system properties for
+ * tuning parameters:
+ * <ul>
+ * <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
+ * pooled at all. Default is true.
+ * <li>{@code http.maxConnections} maximum number of idle connections to
+ * each to keep in the pool. Default is 5.
+ * <li>{@code http.keepAliveDuration} Time in milliseconds to keep the
+ * connection alive in the pool before closing it. Default is 5 minutes.
+ * This property isn't used by {@code HttpURLConnection}.
+ * </ul>
+ *
+ * <p>The default instance <i>doesn't</i> adjust its configuration as system
+ * properties are changed. This assumes that the applications that set these
+ * parameters do so before making HTTP connections, and that this class is
+ * initialized lazily.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionPool {
+ private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+ private static final ConnectionPool systemDefault;
+
+ static {
+ String keepAlive = System.getProperty("http.keepAlive");
+ String keepAliveDuration = System.getProperty("http.keepAliveDuration");
+ String maxIdleConnections = System.getProperty("http.maxConnections");
+ long keepAliveDurationMs = keepAliveDuration != null
+ ? Long.parseLong(keepAliveDuration)
+ : DEFAULT_KEEP_ALIVE_DURATION_MS;
+ if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
+ systemDefault = new ConnectionPool(0, keepAliveDurationMs);
+ } else if (maxIdleConnections != null) {
+ systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);
+ } else {
+ systemDefault = new ConnectionPool(5, keepAliveDurationMs);
+ }
+ }
+
+ /**
+ * A background thread is used to cleanup expired connections. There will be, at most, a single
+ * thread running per connection pool. We use a thread pool executor because it can shrink to
+ * zero threads, permitting this pool to be garbage collected.
+ */
+ private final Executor executor = new ThreadPoolExecutor(
+ 0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
+
+ /** The maximum number of idle connections for each address. */
+ private final int maxIdleConnections;
+ private final long keepAliveDurationNs;
+ private Runnable cleanupRunnable = new Runnable() {
+ @Override public void run() {
+ while (true) {
+ long waitNanos = cleanup(System.nanoTime());
+ if (waitNanos == -1) return;
+ if (waitNanos > 0) {
+ long waitMillis = waitNanos / 1000000L;
+ waitNanos -= (waitMillis * 1000000L);
+ synchronized (ConnectionPool.this) {
+ try {
+ ConnectionPool.this.wait(waitMillis, (int) waitNanos);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private final Deque<RealConnection> connections = new ArrayDeque<>();
+ final RouteDatabase routeDatabase = new RouteDatabase();
+
+ public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
+ this(maxIdleConnections, keepAliveDurationMs, TimeUnit.MILLISECONDS);
+ }
+
+ public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
+ this.maxIdleConnections = maxIdleConnections;
+ this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
+
+ // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
+ if (keepAliveDuration <= 0) {
+ throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
+ }
+ }
+
+ public static ConnectionPool getDefault() {
+ return systemDefault;
+ }
+
+ /** Returns the number of idle connections in the pool. */
+ public synchronized int getIdleConnectionCount() {
+ int total = 0;
+ for (RealConnection connection : connections) {
+ if (connection.allocations.isEmpty()) total++;
+ }
+ return total;
+ }
+
+ /**
+ * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included
+ * only idle connections and SPDY connections. In OkHttp 2.7 this includes all connections, both
+ * active and inactive. Use {@link #getIdleConnectionCount()} to count connections not currently
+ * in use.
+ */
+ public synchronized int getConnectionCount() {
+ return connections.size();
+ }
+
+ /** @deprecated Use {@link #getMultiplexedConnectionCount()}. */
+ @Deprecated
+ public synchronized int getSpdyConnectionCount() {
+ return getMultiplexedConnectionCount();
+ }
+
+ /** Returns total number of multiplexed connections in the pool. */
+ public synchronized int getMultiplexedConnectionCount() {
+ int total = 0;
+ for (RealConnection connection : connections) {
+ if (connection.isMultiplexed()) total++;
+ }
+ return total;
+ }
+
+ /** Returns total number of http connections in the pool. */
+ public synchronized int getHttpConnectionCount() {
+ return connections.size() - getMultiplexedConnectionCount();
+ }
+
+ /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
+ 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()
+ && address.equals(connection.getRoute().address)
+ && !connection.noNewStreams) {
+ streamAllocation.acquire(connection);
+ return connection;
+ }
+ }
+ return null;
+ }
+
+ void put(RealConnection connection) {
+ assert (Thread.holdsLock(this));
+ if (connections.isEmpty()) {
+ executor.execute(cleanupRunnable);
+ }
+ connections.add(connection);
+ }
+
+ /**
+ * Notify this pool that {@code connection} has become idle. Returns true if the connection
+ * has been removed from the pool and should be closed.
+ */
+ boolean connectionBecameIdle(RealConnection connection) {
+ assert (Thread.holdsLock(this));
+ if (connection.noNewStreams || maxIdleConnections == 0) {
+ connections.remove(connection);
+ return true;
+ } else {
+ notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
+ return false;
+ }
+ }
+
+ /** Close and remove all idle connections in the pool. */
+ public void evictAll() {
+ List<RealConnection> evictedConnections = new ArrayList<>();
+ synchronized (this) {
+ for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
+ RealConnection connection = i.next();
+ if (connection.allocations.isEmpty()) {
+ connection.noNewStreams = true;
+ evictedConnections.add(connection);
+ i.remove();
+ }
+ }
+ }
+
+ for (RealConnection connection : evictedConnections) {
+ Util.closeQuietly(connection.getSocket());
+ }
+ }
+
+ /**
+ * Performs maintenance on this pool, evicting the connection that has been idle the longest if
+ * either it has exceeded the keep alive limit or the idle connections limit.
+ *
+ * <p>Returns the duration in nanos to sleep until the next scheduled call to this method.
+ * Returns -1 if no further cleanups are required.
+ */
+ long cleanup(long now) {
+ int inUseConnectionCount = 0;
+ int idleConnectionCount = 0;
+ RealConnection longestIdleConnection = null;
+ long longestIdleDurationNs = Long.MIN_VALUE;
+
+ // Find either a connection to evict, or the time that the next eviction is due.
+ synchronized (this) {
+ for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
+ RealConnection connection = i.next();
+
+ // If the connection is in use, keep searching.
+ if (pruneAndGetAllocationCount(connection, now) > 0) {
+ inUseConnectionCount++;
+ continue;
+ }
+
+ idleConnectionCount++;
+
+ // If the connection is ready to be evicted, we're done.
+ long idleDurationNs = now - connection.idleAtNanos;
+ if (idleDurationNs > longestIdleDurationNs) {
+ longestIdleDurationNs = idleDurationNs;
+ longestIdleConnection = connection;
+ }
+ }
+
+ if (longestIdleDurationNs >= this.keepAliveDurationNs
+ || idleConnectionCount > this.maxIdleConnections) {
+ // We've found a connection to evict. Remove it from the list, then close it below (outside
+ // of the synchronized block).
+ connections.remove(longestIdleConnection);
+
+ } else if (idleConnectionCount > 0) {
+ // A connection will be ready to evict soon.
+ return keepAliveDurationNs - longestIdleDurationNs;
+
+ } else if (inUseConnectionCount > 0) {
+ // All connections are in use. It'll be at least the keep alive duration 'til we run again.
+ return keepAliveDurationNs;
+
+ } else {
+ // No connections, idle or in use.
+ return -1;
+ }
+ }
+
+ Util.closeQuietly(longestIdleConnection.getSocket());
+
+ // Cleanup again immediately.
+ return 0;
+ }
+
+ /**
+ * Prunes any leaked allocations and then returns the number of remaining live allocations on
+ * {@code connection}. Allocations are leaked if the connection is tracking them but the
+ * application code has abandoned them. Leak detection is imprecise and relies on garbage
+ * collection.
+ */
+ private int pruneAndGetAllocationCount(RealConnection connection, long now) {
+ List<Reference<StreamAllocation>> references = connection.allocations;
+ for (int i = 0; i < references.size(); ) {
+ Reference<StreamAllocation> reference = references.get(i);
+
+ if (reference.get() != null) {
+ i++;
+ continue;
+ }
+
+ // Android-removed: Drop warning about a leak that may not be the app's fault.
+ // We can't tell here whether the app accessed the response body (InputStream) or
+ // only the header fields; at least in the latter case, the app has done nothing
+ // wrong so we shouldn't warn. http://b/64789755
+ // // We've discovered a leaked allocation. This is an application bug.
+ // Internal.logger.warning("A connection to " + connection.getRoute().getAddress().url()
+ // + " was leaked. Did you forget to close a response body?");
+ references.remove(i);
+ connection.noNewStreams = true;
+
+ // If this was the last allocation, the connection is eligible for immediate eviction.
+ if (references.isEmpty()) {
+ connection.idleAtNanos = now - keepAliveDurationNs;
+ return 0;
+ }
+ }
+
+ return references.size();
+ }
+
+ void setCleanupRunnableForTest(Runnable cleanupRunnable) {
+ this.cleanupRunnable = cleanupRunnable;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java
new file mode 100644
index 0000000..516d596
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java
@@ -0,0 +1,336 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLSocket;
+
+import static com.android.okhttp.internal.Util.concat;
+import static com.android.okhttp.internal.Util.contains;
+
+/**
+ * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
+ * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
+ * connection.
+ *
+ * <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
+ * the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used
+ * even if it is present on the connection spec. The same policy also applies to cipher suites.
+ *
+ * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
+ * defer all feature selection to the underlying SSL socket.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionSpec {
+
+ // This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05.
+ // All of these suites are available on Android 5.0; earlier releases support a subset of
+ // these suites. https://github.com/square/okhttp/issues/330
+ private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+
+ // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
+ // continue to include them until better suites are commonly available. For example, none
+ // of the better cipher suites listed above shipped with Android 4.4 or Java 7.
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ };
+
+ /** A modern TLS connection with extensions like SNI and ALPN available. */
+ public static final ConnectionSpec MODERN_TLS = new Builder(true)
+ .cipherSuites(APPROVED_CIPHER_SUITES)
+ .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
+ .supportsTlsExtensions(true)
+ .build();
+
+ /** A backwards-compatible fallback connection for interop with obsolete servers. */
+ public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
+ .tlsVersions(TlsVersion.TLS_1_0)
+ .supportsTlsExtensions(true)
+ .build();
+
+ /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
+ public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
+
+ private final boolean tls;
+ private final boolean supportsTlsExtensions;
+ private final String[] cipherSuites;
+ private final String[] tlsVersions;
+
+ private ConnectionSpec(Builder builder) {
+ this.tls = builder.tls;
+ this.cipherSuites = builder.cipherSuites;
+ this.tlsVersions = builder.tlsVersions;
+ this.supportsTlsExtensions = builder.supportsTlsExtensions;
+ }
+
+ public boolean isTls() {
+ return tls;
+ }
+
+ /**
+ * Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
+ * socket's enabled cipher suites should be used.
+ */
+ public List<CipherSuite> cipherSuites() {
+ if (cipherSuites == null) return null;
+
+ CipherSuite[] result = new CipherSuite[cipherSuites.length];
+ for (int i = 0; i < cipherSuites.length; i++) {
+ result[i] = CipherSuite.forJavaName(cipherSuites[i]);
+ }
+ return Util.immutableList(result);
+ }
+
+ /**
+ * Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
+ * the SSL socket's enabled TLS versions should be used.
+ */
+ public List<TlsVersion> tlsVersions() {
+ if (tlsVersions == null) return null;
+
+ TlsVersion[] result = new TlsVersion[tlsVersions.length];
+ for (int i = 0; i < tlsVersions.length; i++) {
+ result[i] = TlsVersion.forJavaName(tlsVersions[i]);
+ }
+ return Util.immutableList(result);
+ }
+
+ public boolean supportsTlsExtensions() {
+ return supportsTlsExtensions;
+ }
+
+ /** Applies this spec to {@code sslSocket}. */
+ void apply(SSLSocket sslSocket, boolean isFallback) {
+ ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
+
+ if (specToApply.tlsVersions != null) {
+ sslSocket.setEnabledProtocols(specToApply.tlsVersions);
+ }
+ if (specToApply.cipherSuites != null) {
+ sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
+ }
+ }
+
+ /**
+ * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
+ * sslSocket}.
+ */
+ private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
+ String[] cipherSuitesIntersection = cipherSuites != null
+ ? Util.intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
+ : sslSocket.getEnabledCipherSuites();
+ String[] tlsVersionsIntersection = tlsVersions != null
+ ? Util.intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
+ : sslSocket.getEnabledProtocols();
+
+ // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
+ // the SCSV cipher is added to signal that a protocol fallback has taken place.
+ if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
+ cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
+ }
+
+ return new Builder(this)
+ .cipherSuites(cipherSuitesIntersection)
+ .tlsVersions(tlsVersionsIntersection)
+ .build();
+ }
+
+ /**
+ * Returns {@code true} if the socket, as currently configured, supports this connection spec.
+ * In order for a socket to be compatible the enabled cipher suites and protocols must intersect.
+ *
+ * <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
+ * match the socket's enabled cipher suites. If there are no required cipher suites the socket
+ * must have at least one cipher suite enabled.
+ *
+ * <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
+ * socket's enabled protocols.
+ */
+ public boolean isCompatible(SSLSocket socket) {
+ if (!tls) {
+ return false;
+ }
+
+ if (tlsVersions != null
+ && !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {
+ return false;
+ }
+
+ if (cipherSuites != null
+ && !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * An N*M intersection that terminates if any intersection is found. The sizes of both
+ * arguments are assumed to be so small, and the likelihood of an intersection so great, that it
+ * is not worth the CPU cost of sorting or the memory cost of hashing.
+ */
+ private static boolean nonEmptyIntersection(String[] a, String[] b) {
+ if (a == null || b == null || a.length == 0 || b.length == 0) {
+ return false;
+ }
+ for (String toFind : a) {
+ if (contains(b, toFind)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (!(other instanceof ConnectionSpec)) return false;
+ if (other == this) return true;
+
+ ConnectionSpec that = (ConnectionSpec) other;
+ if (this.tls != that.tls) return false;
+
+ if (tls) {
+ if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
+ if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
+ if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
+ }
+
+ return true;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ if (tls) {
+ result = 31 * result + Arrays.hashCode(cipherSuites);
+ result = 31 * result + Arrays.hashCode(tlsVersions);
+ result = 31 * result + (supportsTlsExtensions ? 0 : 1);
+ }
+ return result;
+ }
+
+ @Override public String toString() {
+ if (!tls) {
+ return "ConnectionSpec()";
+ }
+
+ String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
+ String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
+ return "ConnectionSpec("
+ + "cipherSuites=" + cipherSuitesString
+ + ", tlsVersions=" + tlsVersionsString
+ + ", supportsTlsExtensions=" + supportsTlsExtensions
+ + ")";
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static final class Builder {
+ private boolean tls;
+ private String[] cipherSuites;
+ private String[] tlsVersions;
+ private boolean supportsTlsExtensions;
+
+ Builder(boolean tls) {
+ this.tls = tls;
+ }
+
+ public Builder(ConnectionSpec connectionSpec) {
+ this.tls = connectionSpec.tls;
+ this.cipherSuites = connectionSpec.cipherSuites;
+ this.tlsVersions = connectionSpec.tlsVersions;
+ this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
+ }
+
+ public Builder allEnabledCipherSuites() {
+ if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+ this.cipherSuites = null;
+ return this;
+ }
+
+ public Builder cipherSuites(CipherSuite... cipherSuites) {
+ if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+ String[] strings = new String[cipherSuites.length];
+ for (int i = 0; i < cipherSuites.length; i++) {
+ strings[i] = cipherSuites[i].javaName;
+ }
+ return cipherSuites(strings);
+ }
+
+ public Builder cipherSuites(String... cipherSuites) {
+ if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+ if (cipherSuites.length == 0) {
+ throw new IllegalArgumentException("At least one cipher suite is required");
+ }
+
+ this.cipherSuites = cipherSuites.clone(); // Defensive copy.
+ return this;
+ }
+
+ public Builder allEnabledTlsVersions() {
+ if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+ this.tlsVersions = null;
+ return this;
+ }
+
+ public Builder tlsVersions(TlsVersion... tlsVersions) {
+ if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+
+ String[] strings = new String[tlsVersions.length];
+ for (int i = 0; i < tlsVersions.length; i++) {
+ strings[i] = tlsVersions[i].javaName;
+ }
+
+ return tlsVersions(strings);
+ }
+
+ public Builder tlsVersions(String... tlsVersions) {
+ if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+
+ if (tlsVersions.length == 0) {
+ throw new IllegalArgumentException("At least one TLS version is required");
+ }
+
+ this.tlsVersions = tlsVersions.clone(); // Defensive copy.
+ return this;
+ }
+
+ public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
+ if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
+ this.supportsTlsExtensions = supportsTlsExtensions;
+ return this;
+ }
+
+ public ConnectionSpec build() {
+ return new ConnectionSpec(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java
new file mode 100644
index 0000000..dd758be
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java
@@ -0,0 +1,39 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.io.UnsupportedEncodingException;
+import com.android.okhttp.okio.ByteString;
+
+/** Factory for HTTP authorization credentials.
+ * @hide This class is not part of the Android public SDK API*/
+public final class Credentials {
+ private Credentials() {
+ }
+
+ /** Returns an auth credential for the Basic scheme. */
+ public static String basic(String userName, String password) {
+ try {
+ String usernameAndPassword = userName + ":" + password;
+ byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
+ String encoded = ByteString.of(bytes).base64();
+ return "Basic " + encoded;
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java
new file mode 100644
index 0000000..ef518c2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java
@@ -0,0 +1,190 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import com.android.okhttp.Call.AsyncCall;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.HttpEngine;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Policy on when async requests are executed.
+ *
+ * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you
+ * supply your own executor, it should be able to run {@linkplain #getMaxRequests the
+ * configured maximum} number of calls concurrently.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Dispatcher {
+ private int maxRequests = 64;
+ private int maxRequestsPerHost = 5;
+
+ /** Executes calls. Created lazily. */
+ private ExecutorService executorService;
+
+ /** Ready calls in the order they'll be run. */
+ private final Deque<AsyncCall> readyCalls = new ArrayDeque<>();
+
+ /** Running calls. Includes canceled calls that haven't finished yet. */
+ private final Deque<AsyncCall> runningCalls = new ArrayDeque<>();
+
+ /** In-flight synchronous calls. Includes canceled calls that haven't finished yet. */
+ private final Deque<Call> executedCalls = new ArrayDeque<>();
+
+ public Dispatcher(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ public Dispatcher() {
+ }
+
+ public synchronized ExecutorService getExecutorService() {
+ if (executorService == null) {
+ executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
+ }
+ return executorService;
+ }
+
+ /**
+ * Set the maximum number of requests to execute concurrently. Above this
+ * requests queue in memory, waiting for the running calls to complete.
+ *
+ * <p>If more than {@code maxRequests} requests are in flight when this is
+ * invoked, those requests will remain in flight.
+ */
+ public synchronized void setMaxRequests(int maxRequests) {
+ if (maxRequests < 1) {
+ throw new IllegalArgumentException("max < 1: " + maxRequests);
+ }
+ this.maxRequests = maxRequests;
+ promoteCalls();
+ }
+
+ public synchronized int getMaxRequests() {
+ return maxRequests;
+ }
+
+ /**
+ * Set the maximum number of requests for each host to execute concurrently.
+ * This limits requests by the URL's host name. Note that concurrent requests
+ * to a single IP address may still exceed this limit: multiple hostnames may
+ * share an IP address or be routed through the same HTTP proxy.
+ *
+ * <p>If more than {@code maxRequestsPerHost} requests are in flight when this
+ * is invoked, those requests will remain in flight.
+ */
+ public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
+ if (maxRequestsPerHost < 1) {
+ throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
+ }
+ this.maxRequestsPerHost = maxRequestsPerHost;
+ promoteCalls();
+ }
+
+ public synchronized int getMaxRequestsPerHost() {
+ return maxRequestsPerHost;
+ }
+
+ synchronized void enqueue(AsyncCall call) {
+ if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
+ runningCalls.add(call);
+ getExecutorService().execute(call);
+ } else {
+ readyCalls.add(call);
+ }
+ }
+
+ /** Cancel all calls with the tag {@code tag}. */
+ public synchronized void cancel(Object tag) {
+ for (AsyncCall call : readyCalls) {
+ if (Util.equal(tag, call.tag())) {
+ call.cancel();
+ }
+ }
+
+ for (AsyncCall call : runningCalls) {
+ if (Util.equal(tag, call.tag())) {
+ call.get().canceled = true;
+ HttpEngine engine = call.get().engine;
+ if (engine != null) engine.cancel();
+ }
+ }
+
+ for (Call call : executedCalls) {
+ if (Util.equal(tag, call.tag())) {
+ call.cancel();
+ }
+ }
+ }
+
+ /** Used by {@code AsyncCall#run} to signal completion. */
+ synchronized void finished(AsyncCall call) {
+ if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
+ promoteCalls();
+ }
+
+ private void promoteCalls() {
+ if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
+ if (readyCalls.isEmpty()) return; // No ready calls to promote.
+
+ for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
+ AsyncCall call = i.next();
+
+ if (runningCallsForHost(call) < maxRequestsPerHost) {
+ i.remove();
+ runningCalls.add(call);
+ getExecutorService().execute(call);
+ }
+
+ if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
+ }
+ }
+
+ /** Returns the number of running calls that share a host with {@code call}. */
+ private int runningCallsForHost(AsyncCall call) {
+ int result = 0;
+ for (AsyncCall c : runningCalls) {
+ if (c.host().equals(call.host())) result++;
+ }
+ return result;
+ }
+
+ /** Used by {@code Call#execute} to signal it is in-flight. */
+ synchronized void executed(Call call) {
+ executedCalls.add(call);
+ }
+
+ /** Used by {@code Call#execute} to signal completion. */
+ synchronized void finished(Call call) {
+ if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
+ }
+
+ public synchronized int getRunningCallCount() {
+ return runningCalls.size();
+ }
+
+ public synchronized int getQueuedCallCount() {
+ return readyCalls.size();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java
new file mode 100644
index 0000000..20610bc
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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.android.okhttp;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names. Most applications will use the
+ * {@linkplain #SYSTEM system DNS service}, which is the default. Some applications may provide
+ * their own implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4
+ * addresses, or to force a specific known IP address.
+ *
+ * <p>Implementations of this interface must be safe for concurrent use.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+ /**
+ * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
+ * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
+ */
+ Dns SYSTEM = new Dns() {
+ @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
+ if (hostname == null) throw new UnknownHostException("hostname == null");
+ return Arrays.asList(InetAddress.getAllByName(hostname));
+ }
+ };
+
+ /**
+ * Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp.
+ * If a connection to an address fails, OkHttp will retry the connection with the next address
+ * until either a connection is made, the set of IP addresses is exhausted, or a limit is
+ * exceeded.
+ */
+ List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java b/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java
new file mode 100644
index 0000000..b95deed
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java
@@ -0,0 +1,61 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.okio.Buffer;
+
+/**
+ * Fluent API to build <a href="http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1">HTML
+ * 2.0</a>-compliant form data.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class FormEncodingBuilder {
+ private static final MediaType CONTENT_TYPE =
+ MediaType.parse("application/x-www-form-urlencoded");
+
+ private final Buffer content = new Buffer();
+
+ /** Add new key-value pair. */
+ public FormEncodingBuilder add(String name, String value) {
+ if (content.size() > 0) {
+ content.writeByte('&');
+ }
+ HttpUrl.canonicalize(content, name, 0, name.length(),
+ HttpUrl.FORM_ENCODE_SET, false, false, true, true);
+ content.writeByte('=');
+ HttpUrl.canonicalize(content, value, 0, value.length(),
+ HttpUrl.FORM_ENCODE_SET, false, false, true, true);
+ return this;
+ }
+
+ /** Add new key-value pair. */
+ public FormEncodingBuilder addEncoded(String name, String value) {
+ if (content.size() > 0) {
+ content.writeByte('&');
+ }
+ HttpUrl.canonicalize(content, name, 0, name.length(),
+ HttpUrl.FORM_ENCODE_SET, true, false, true, true);
+ content.writeByte('=');
+ HttpUrl.canonicalize(content, value, 0, value.length(),
+ HttpUrl.FORM_ENCODE_SET, true, false, true, true);
+ return this;
+ }
+
+ public RequestBody build() {
+ return RequestBody.create(CONTENT_TYPE, content.snapshot());
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java
new file mode 100644
index 0000000..4f39576
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java
@@ -0,0 +1,122 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A record of a TLS handshake. For HTTPS clients, the client is <i>local</i>
+ * and the remote server is its <i>peer</i>.
+ *
+ * <p>This value object describes a completed handshake. Use {@link
+ * javax.net.ssl.SSLSocketFactory} to set policy for new handshakes.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Handshake {
+ private final String cipherSuite;
+ private final List<Certificate> peerCertificates;
+ private final List<Certificate> localCertificates;
+
+ private Handshake(
+ String cipherSuite, List<Certificate> peerCertificates, List<Certificate> localCertificates) {
+ this.cipherSuite = cipherSuite;
+ this.peerCertificates = peerCertificates;
+ this.localCertificates = localCertificates;
+ }
+
+ public static Handshake get(SSLSession session) {
+ String cipherSuite = session.getCipherSuite();
+ if (cipherSuite == null) throw new IllegalStateException("cipherSuite == null");
+
+ Certificate[] peerCertificates;
+ try {
+ peerCertificates = session.getPeerCertificates();
+ } catch (SSLPeerUnverifiedException ignored) {
+ peerCertificates = null;
+ }
+ List<Certificate> peerCertificatesList = peerCertificates != null
+ ? Util.immutableList(peerCertificates)
+ : Collections.<Certificate>emptyList();
+
+ Certificate[] localCertificates = session.getLocalCertificates();
+ List<Certificate> localCertificatesList = localCertificates != null
+ ? Util.immutableList(localCertificates)
+ : Collections.<Certificate>emptyList();
+
+ return new Handshake(cipherSuite, peerCertificatesList, localCertificatesList);
+ }
+
+ public static Handshake get(
+ String cipherSuite, List<Certificate> peerCertificates, List<Certificate> localCertificates) {
+ if (cipherSuite == null) throw new IllegalArgumentException("cipherSuite == null");
+ return new Handshake(cipherSuite, Util.immutableList(peerCertificates),
+ Util.immutableList(localCertificates));
+ }
+
+ /** Returns a cipher suite name like "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA". */
+ public String cipherSuite() {
+ return cipherSuite;
+ }
+
+ /** Returns a possibly-empty list of certificates that identify the remote peer. */
+ public List<Certificate> peerCertificates() {
+ return peerCertificates;
+ }
+
+ /** Returns the remote peer's principle, or null if that peer is anonymous. */
+ public Principal peerPrincipal() {
+ return !peerCertificates.isEmpty()
+ ? ((X509Certificate) peerCertificates.get(0)).getSubjectX500Principal()
+ : null;
+ }
+
+ /** Returns a possibly-empty list of certificates that identify this peer. */
+ public List<Certificate> localCertificates() {
+ return localCertificates;
+ }
+
+ /** Returns the local principle, or null if this peer is anonymous. */
+ public Principal localPrincipal() {
+ return !localCertificates.isEmpty()
+ ? ((X509Certificate) localCertificates.get(0)).getSubjectX500Principal()
+ : null;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (!(other instanceof Handshake)) return false;
+ Handshake that = (Handshake) other;
+ return cipherSuite.equals(that.cipherSuite)
+ && peerCertificates.equals(that.peerCertificates)
+ && localCertificates.equals(that.localCertificates);
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + cipherSuite.hashCode();
+ result = 31 * result + peerCertificates.hashCode();
+ result = 31 * result + localCertificates.hashCode();
+ return result;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java
new file mode 100644
index 0000000..ec740a1
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java
@@ -0,0 +1,339 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.http.HttpDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * The header fields of a single HTTP message. Values are uninterpreted strings;
+ * use {@code Request} and {@code Response} for interpreted headers. This class
+ * maintains the order of the header fields within the HTTP message.
+ *
+ * <p>This class tracks header values line-by-line. A field with multiple comma-
+ * separated values on the same line will be treated as a field with a single
+ * value by this class. It is the caller's responsibility to detect and split
+ * on commas if their field permits multiple values. This simplifies use of
+ * single-valued fields whose values routinely contain commas, such as cookies
+ * or dates.
+ *
+ * <p>This class trims whitespace from values. It never returns values with
+ * leading or trailing whitespace.
+ *
+ * <p>Instances of this class are immutable. Use {@link Builder} to create
+ * instances.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Headers {
+ private final String[] namesAndValues;
+
+ private Headers(Builder builder) {
+ this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
+ }
+
+ private Headers(String[] namesAndValues) {
+ this.namesAndValues = namesAndValues;
+ }
+
+ /** Returns the last value corresponding to the specified field, or null. */
+ public String get(String name) {
+ return get(namesAndValues, name);
+ }
+
+ /**
+ * Returns the last value corresponding to the specified field parsed as an
+ * HTTP date, or null if either the field is absent or cannot be parsed as a
+ * date.
+ */
+ public Date getDate(String name) {
+ String value = get(name);
+ return value != null ? HttpDate.parse(value) : null;
+ }
+
+ /** Returns the number of field values. */
+ public int size() {
+ return namesAndValues.length / 2;
+ }
+
+ /** Returns the field at {@code position} or null if that is out of range. */
+ public String name(int index) {
+ int nameIndex = index * 2;
+ if (nameIndex < 0 || nameIndex >= namesAndValues.length) {
+ return null;
+ }
+ return namesAndValues[nameIndex];
+ }
+
+ /** Returns the value at {@code index} or null if that is out of range. */
+ public String value(int index) {
+ int valueIndex = index * 2 + 1;
+ if (valueIndex < 0 || valueIndex >= namesAndValues.length) {
+ return null;
+ }
+ return namesAndValues[valueIndex];
+ }
+
+ /** Returns an immutable case-insensitive set of header names. */
+ public Set<String> names() {
+ TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0, size = size(); i < size; i++) {
+ result.add(name(i));
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ /** Returns an immutable list of the header values for {@code name}. */
+ public List<String> values(String name) {
+ List<String> result = null;
+ for (int i = 0, size = size(); i < size; i++) {
+ if (name.equalsIgnoreCase(name(i))) {
+ if (result == null) result = new ArrayList<>(2);
+ result.add(value(i));
+ }
+ }
+ return result != null
+ ? Collections.unmodifiableList(result)
+ : Collections.<String>emptyList();
+ }
+
+ public Builder newBuilder() {
+ Builder result = new Builder();
+ Collections.addAll(result.namesAndValues, namesAndValues);
+ return result;
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0, size = size(); i < size; i++) {
+ result.append(name(i)).append(": ").append(value(i)).append("\n");
+ }
+ return result.toString();
+ }
+
+ public Map<String, List<String>> toMultimap() {
+ Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
+ for (int i = 0, size = size(); i < size; i++) {
+ String name = name(i);
+ List<String> values = result.get(name);
+ if (values == null) {
+ values = new ArrayList<>(2);
+ result.put(name, values);
+ }
+ values.add(value(i));
+ }
+ return result;
+ }
+
+ private static String get(String[] namesAndValues, String name) {
+ for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
+ if (name.equalsIgnoreCase(namesAndValues[i])) {
+ return namesAndValues[i + 1];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns headers for the alternating header names and values. There must be
+ * an even number of arguments, and they must alternate between header names
+ * and values.
+ */
+ public static Headers of(String... namesAndValues) {
+ if (namesAndValues == null || namesAndValues.length % 2 != 0) {
+ throw new IllegalArgumentException("Expected alternating header names and values");
+ }
+
+ // Make a defensive copy and clean it up.
+ namesAndValues = namesAndValues.clone();
+ for (int i = 0; i < namesAndValues.length; i++) {
+ if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
+ namesAndValues[i] = namesAndValues[i].trim();
+ }
+
+ // Check for malformed headers.
+ for (int i = 0; i < namesAndValues.length; i += 2) {
+ String name = namesAndValues[i];
+ String value = namesAndValues[i + 1];
+ if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+ throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+ }
+ }
+
+ return new Headers(namesAndValues);
+ }
+
+ /**
+ * Returns headers for the header names and values in the {@link Map}.
+ */
+ public static Headers of(Map<String, String> headers) {
+ if (headers == null) {
+ throw new IllegalArgumentException("Expected map with header names and values");
+ }
+
+ // Make a defensive copy and clean it up.
+ String[] namesAndValues = new String[headers.size() * 2];
+ int i = 0;
+ for (Map.Entry<String, String> header : headers.entrySet()) {
+ if (header.getKey() == null || header.getValue() == null) {
+ throw new IllegalArgumentException("Headers cannot be null");
+ }
+ String name = header.getKey().trim();
+ String value = header.getValue().trim();
+ if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+ throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+ }
+ namesAndValues[i] = name;
+ namesAndValues[i + 1] = value;
+ i += 2;
+ }
+
+ return new Headers(namesAndValues);
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static final class Builder {
+ private final List<String> namesAndValues = new ArrayList<>(20);
+
+ /**
+ * Add a header line without any validation. Only appropriate for headers from the remote peer
+ * or cache.
+ */
+ Builder addLenient(String line) {
+ int index = line.indexOf(":", 1);
+ if (index != -1) {
+ return addLenient(line.substring(0, index), line.substring(index + 1));
+ } else if (line.startsWith(":")) {
+ // Work around empty header names and header names that start with a
+ // colon (created by old broken SPDY versions of the response cache).
+ return addLenient("", line.substring(1)); // Empty header name.
+ } else {
+ return addLenient("", line); // No header name.
+ }
+ }
+
+ /** Add an header line containing a field name, a literal colon, and a value. */
+ public Builder add(String line) {
+ int index = line.indexOf(":");
+ if (index == -1) {
+ throw new IllegalArgumentException("Unexpected header: " + line);
+ }
+ return add(line.substring(0, index).trim(), line.substring(index + 1));
+ }
+
+ /** Add a field with the specified value. */
+ public Builder add(String name, String value) {
+ checkNameAndValue(name, value);
+ return addLenient(name, value);
+ }
+
+ /**
+ * Add a field with the specified value without any validation. Only
+ * appropriate for headers from the remote peer or cache.
+ */
+ Builder addLenient(String name, String value) {
+ namesAndValues.add(name);
+ namesAndValues.add(value.trim());
+ return this;
+ }
+
+ public Builder removeAll(String name) {
+ for (int i = 0; i < namesAndValues.size(); i += 2) {
+ if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+ namesAndValues.remove(i); // name
+ namesAndValues.remove(i); // value
+ i -= 2;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set a field with the specified value. If the field is not found, it is
+ * added. If the field is found, the existing values are replaced.
+ */
+ public Builder set(String name, String value) {
+ checkNameAndValue(name, value);
+ removeAll(name);
+ addLenient(name, value);
+ return this;
+ }
+
+ private void checkNameAndValue(String name, String value) {
+ if (name == null) throw new IllegalArgumentException("name == null");
+ if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
+ for (int i = 0, length = name.length(); i < length; i++) {
+ char c = name.charAt(i);
+ if (c <= '\u001f' || c >= '\u007f') {
+ throw new IllegalArgumentException(String.format(
+ "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
+ }
+ }
+ if (value == null) throw new IllegalArgumentException("value == null");
+
+ // Workaround for applications that set trailing "\r", "\n" or "\r\n" on header values.
+ // http://b/26422335, http://b/26889631 Android used to allow anything except '\0'.
+ int valueLen = value.length();
+ if (valueLen >= 2 && value.charAt(valueLen - 2) == '\r'
+ && value.charAt(valueLen - 1) == '\n') {
+ value = value.substring(0, value.length() - 2);
+ } else if (valueLen > 0
+ && (value.charAt(valueLen - 1) == '\n'
+ || value.charAt(valueLen - 1) == '\r')) {
+ value = value.substring(0, valueLen - 1);
+ }
+ // End of workaround.
+
+ for (int i = 0, length = value.length(); i < length; i++) {
+ char c = value.charAt(i);
+ // ANDROID-BEGIN
+ // http://b/28867041 - keep things working for apps that rely on Android's (out of spec)
+ // UTF-8 header encoding behavior.
+ // if ((c <= '\u001f' && c != '\u0009' /* htab */) || c >= '\u007f') {
+ if ((c <= '\u001f' && c != '\u0009' /* htab */) || c == '\u007f') {
+ // ANDROID-END
+ throw new IllegalArgumentException(String.format(
+ "Unexpected char %#04x at %d in header value: %s", (int) c, i, value));
+ }
+ }
+ }
+
+ /** Equivalent to {@code build().get(name)}, but potentially faster. */
+ public String get(String name) {
+ for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
+ if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+ return namesAndValues.get(i + 1);
+ }
+ }
+ return null;
+ }
+
+ public Headers build() {
+ return new Headers(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java b/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java
new file mode 100644
index 0000000..24cb0d4
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java
@@ -0,0 +1,1646 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.net.IDN;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import com.android.okhttp.okio.Buffer;
+
+/**
+ * A uniform resource locator (URL) with a scheme of either {@code http} or {@code https}. Use this
+ * class to compose and decompose Internet addresses. For example, this code will compose and print
+ * a URL for Google search: <pre> {@code
+ *
+ * HttpUrl url = new HttpUrl.Builder()
+ * .scheme("https")
+ * .host("www.google.com")
+ * .addPathSegment("search")
+ * .addQueryParameter("q", "polar bears")
+ * .build();
+ * System.out.println(url);
+ * }</pre>
+ *
+ * which prints: <pre> {@code
+ *
+ * https://www.google.com/search?q=polar%20bears
+ * }</pre>
+ *
+ * As another example, this code prints the human-readable query parameters of a Twitter search:
+ * <pre> {@code
+ *
+ * HttpUrl url = HttpUrl.parse("https://twitter.com/search?q=cute%20%23puppies&f=images");
+ * for (int i = 0, size = url.querySize(); i < size; i++) {
+ * System.out.println(url.queryParameterName(i) + ": " + url.queryParameterValue(i));
+ * }
+ * }</pre>
+ *
+ * which prints: <pre> {@code
+ *
+ * q: cute #puppies
+ * f: images
+ * }</pre>
+ *
+ * In addition to composing URLs from their component parts and decomposing URLs into their
+ * component parts, this class implements relative URL resolution: what address you'd reach by
+ * clicking a relative link on a specified page. For example: <pre> {@code
+ *
+ * HttpUrl base = HttpUrl.parse("https://www.youtube.com/user/WatchTheDaily/videos");
+ * HttpUrl link = base.resolve("../../watch?v=cbP2N1BQdYc");
+ * System.out.println(link);
+ * }</pre>
+ *
+ * which prints: <pre> {@code
+ *
+ * https://www.youtube.com/watch?v=cbP2N1BQdYc
+ * }</pre>
+ *
+ * <h3>What's in a URL?</h3>
+ *
+ * A URL has several components.
+ *
+ * <h4>Scheme</h4>
+ * Sometimes referred to as <i>protocol</i>, A URL's scheme describes what mechanism should be used
+ * to retrieve the resource. Although URLs have many schemes ({@code mailto}, {@code file}, {@code
+ * ftp}), this class only supports {@code http} and {@code https}. Use {@link URI java.net.URI} for
+ * URLs with arbitrary schemes.
+ *
+ * <h4>Username and Password</h4>
+ * Username and password are either present, or the empty string {@code ""} if absent. This class
+ * offers no mechanism to differentiate empty from absent. Neither of these components are popular
+ * in practice. Typically HTTP applications use other mechanisms for user identification and
+ * authentication.
+ *
+ * <h4>Host</h4>
+ * The host identifies the webserver that serves the URL's resource. It is either a hostname like
+ * {@code square.com} or {@code localhost}, an IPv4 address like {@code 192.168.0.1}, or an IPv6
+ * address like {@code ::1}.
+ *
+ * <p>Usually a webserver is reachable with multiple identifiers: its IP addresses, registered
+ * domain names, and even {@code localhost} when connecting from the server itself. Each of a
+ * webserver's names is a distinct URL and they are not interchangeable. For example, even if
+ * {@code http://square.github.io/dagger} and {@code http://google.github.io/dagger} are served by
+ * the same IP address, the two URLs identify different resources.
+ *
+ * <h4>Port</h4>
+ * The port used to connect to the webserver. By default this is 80 for HTTP and 443 for HTTPS. This
+ * class never returns -1 for the port: if no port is explicitly specified in the URL then the
+ * scheme's default is used.
+ *
+ * <h4>Path</h4>
+ * The path identifies a specific resource on the host. Paths have a hierarchical structure like
+ * "/square/okhttp/issues/1486". Each path segment is prefixed with "/". This class offers methods
+ * to compose and decompose paths by segment. If a path's last segment is the empty string, then the
+ * path ends with "/". This class always builds non-empty paths: if the path is omitted it defaults
+ * to "/", which is a path whose only segment is the empty string.
+ *
+ * <h4>Query</h4>
+ * The query is optional: it can be null, empty, or non-empty. For many HTTP URLs the query string
+ * is subdivided into a collection of name-value parameters. This class offers methods to set the
+ * query as the single string, or as individual name-value parameters. With name-value parameters
+ * the values are optional and names may be repeated.
+ *
+ * <h4>Fragment</h4>
+ * The fragment is optional: it can be null, empty, or non-empty. Unlike host, port, path, and query
+ * the fragment is not sent to the webserver: it's private to the client.
+ *
+ * <h3>Encoding</h3>
+ * Each component must be encoded before it is embedded in the complete URL. As we saw above, the
+ * string {@code cute #puppies} is encoded as {@code cute%20%23puppies} when used as a query
+ * parameter value.
+ *
+ * <h4>Percent encoding</h4>
+ * Percent encoding replaces a character (like {@code \ud83c\udf69}) with its UTF-8 hex bytes (like
+ * {@code %F0%9F%8D%A9}). This approach works for whitespace characters, control characters,
+ * non-ASCII characters, and characters that already have another meaning in a particular context.
+ *
+ * <p>Percent encoding is used in every URL component except for the hostname. But the set of
+ * characters that need to be encoded is different for each component. For example, the path
+ * component must escape all of its {@code ?} characters, otherwise it could be interpreted as the
+ * start of the URL's query. But within the query and fragment components, the {@code ?} character
+ * doesn't delimit anything and doesn't need to be escaped. <pre> {@code
+ *
+ * HttpUrl url = HttpUrl.parse("http://who-let-the-dogs.out").newBuilder()
+ * .addPathSegment("_Who?_")
+ * .query("_Who?_")
+ * .fragment("_Who?_")
+ * .build();
+ * System.out.println(url);
+ * }</pre>
+ *
+ * This prints: <pre> {@code
+ *
+ * http://who-let-the-dogs.out/_Who%3F_?_Who?_#_Who?_
+ * }</pre>
+ *
+ * When parsing URLs that lack percent encoding where it is required, this class will percent encode
+ * the offending characters.
+ *
+ * <h4>IDNA Mapping and Punycode encoding</h4>
+ * Hostnames have different requirements and use a different encoding scheme. It consists of IDNA
+ * mapping and Punycode encoding.
+ *
+ * <p>In order to avoid confusion and discourage phishing attacks,
+ * <a href="http://www.unicode.org/reports/tr46/#ToASCII">IDNA Mapping</a> transforms names to avoid
+ * confusing characters. This includes basic case folding: transforming shouting {@code SQUARE.COM}
+ * into cool and casual {@code square.com}. It also handles more exotic characters. For example, the
+ * Unicode trademark sign (™) could be confused for the letters "TM" in {@code http://ho™mail.com}.
+ * To mitigate this, the single character (™) maps to the string (tm). There is similar policy for
+ * all of the 1.1 million Unicode code points. Note that some code points such as "\ud83c\udf69" are
+ * not mapped and cannot be used in a hostname.
+ *
+ * <p><a href="http://ietf.org/rfc/rfc3492.txt">Punycode</a> converts a Unicode string to an ASCII
+ * string to make international domain names work everywhere. For example, "σ" encodes as
+ * "xn--4xa". The encoded string is not human readable, but can be used with classes like {@link
+ * InetAddress} to establish connections.
+ *
+ * <h3>Why another URL model?</h3>
+ * Java includes both {@link URL java.net.URL} and {@link URI java.net.URI}. We offer a new URL
+ * model to address problems that the others don't.
+ *
+ * <h4>Different URLs should be different</h4>
+ * Although they have different content, {@code java.net.URL} considers the following two URLs
+ * equal, and the {@link Object#equals equals()} method between them returns true:
+ * <ul>
+ * <li>http://square.github.io/
+ * <li>http://google.github.io/
+ * </ul>
+ * This is because those two hosts share the same IP address. This is an old, bad design decision
+ * that makes {@code java.net.URL} unusable for many things. It shouldn't be used as a {@link
+ * java.util.Map Map} key or in a {@link Set}. Doing so is both inefficient because equality may
+ * require a DNS lookup, and incorrect because unequal URLs may be equal because of how they are
+ * hosted.
+ *
+ * <h4>Equal URLs should be equal</h4>
+ * These two URLs are semantically identical, but {@code java.net.URI} disagrees:
+ * <ul>
+ * <li>http://host:80/
+ * <li>http://host
+ * </ul>
+ * Both the unnecessary port specification ({@code :80}) and the absent trailing slash ({@code /})
+ * cause URI to bucket the two URLs separately. This harms URI's usefulness in collections. Any
+ * application that stores information-per-URL will need to either canonicalize manually, or suffer
+ * unnecessary redundancy for such URLs.
+ *
+ * <p>Because they don't attempt canonical form, these classes are surprisingly difficult to use
+ * securely. Suppose you're building a webservice that checks that incoming paths are prefixed
+ * "/static/images/" before serving the corresponding assets from the filesystem. <pre> {@code
+ *
+ * String attack = "http://example.com/static/images/../../../../../etc/passwd";
+ * System.out.println(new URL(attack).getPath());
+ * System.out.println(new URI(attack).getPath());
+ * System.out.println(HttpUrl.parse(attack).path());
+ * }</pre>
+ *
+ * By canonicalizing the input paths, they are complicit in directory traversal attacks. Code that
+ * checks only the path prefix may suffer!
+ * <pre> {@code
+ *
+ * /static/images/../../../../../etc/passwd
+ * /static/images/../../../../../etc/passwd
+ * /etc/passwd
+ * }</pre>
+ *
+ * <h4>If it works on the web, it should work in your application</h4>
+ * The {@code java.net.URI} class is strict around what URLs it accepts. It rejects URLs like
+ * "http://example.com/abc|def" because the '|' character is unsupported. This class is more
+ * forgiving: it will automatically percent-encode the '|', yielding "http://example.com/abc%7Cdef".
+ * This kind behavior is consistent with web browsers. {@code HttpUrl} prefers consistency with
+ * major web browsers over consistency with obsolete specifications.
+ *
+ * <h4>Paths and Queries should decompose</h4>
+ * Neither of the built-in URL models offer direct access to path segments or query parameters.
+ * Manually using {@code StringBuilder} to assemble these components is cumbersome: do '+'
+ * characters get silently replaced with spaces? If a query parameter contains a '&', does that
+ * get escaped? By offering methods to read and write individual query parameters directly,
+ * application developers are saved from the hassles of encoding and decoding.
+ *
+ * <h4>Plus a modern API</h4>
+ * The URL (JDK1.0) and URI (Java 1.4) classes predate builders and instead use telescoping
+ * constructors. For example, there's no API to compose a URI with a custom port without also
+ * providing a query and fragment.
+ *
+ * <p>Instances of {@link HttpUrl} are well-formed and always have a scheme, host, and path. With
+ * {@code java.net.URL} it's possible to create an awkward URL like {@code http:/} with scheme and
+ * path but no hostname. Building APIs that consume such malformed values is difficult!
+ *
+ * <p>This class has a modern API. It avoids punitive checked exceptions: {@link #parse parse()}
+ * returns null if the input is an invalid URL. You can even be explicit about whether each
+ * component has been encoded already.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpUrl {
+ private static final char[] HEX_DIGITS =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ static final String USERNAME_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#";
+ static final String PASSWORD_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#";
+ static final String PATH_SEGMENT_ENCODE_SET = " \"<>^`{}|/\\?#";
+ static final String PATH_SEGMENT_ENCODE_SET_URI = "[]";
+ // ANDROID-CHANGED: http://b/30405333 - we do not encode single quote as %27 in query strings.
+ // static final String QUERY_ENCODE_SET = " \"'<>#";
+ // static final String QUERY_COMPONENT_ENCODE_SET = " \"'<>#&=";
+ static final String QUERY_ENCODE_SET = " \"<>#";
+ static final String QUERY_COMPONENT_ENCODE_SET = " \"<>#&=";
+ // ANDROID-CHANGED end.
+ static final String QUERY_COMPONENT_ENCODE_SET_URI = "\\^`{|}";
+ static final String FORM_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#&!$(),~";
+ static final String FRAGMENT_ENCODE_SET = "";
+ static final String FRAGMENT_ENCODE_SET_URI = " \"#<>\\^`{|}";
+
+ /** Either "http" or "https". */
+ private final String scheme;
+
+ /** Decoded username. */
+ private final String username;
+
+ /** Decoded password. */
+ private final String password;
+
+ /** Canonical hostname. */
+ private final String host;
+
+ /** Either 80, 443 or a user-specified port. In range [1..65535]. */
+ private final int port;
+
+ /**
+ * A list of canonical path segments. This list always contains at least one element, which may
+ * be the empty string. Each segment is formatted with a leading '/', so if path segments were
+ * ["a", "b", ""], then the encoded path would be "/a/b/".
+ */
+ private final List<String> pathSegments;
+
+ /**
+ * Alternating, decoded query names and values, or null for no query. Names may be empty or
+ * non-empty, but never null. Values are null if the name has no corresponding '=' separator, or
+ * empty, or non-empty.
+ */
+ private final List<String> queryNamesAndValues;
+
+ /** Decoded fragment. */
+ private final String fragment;
+
+ /** Canonical URL. */
+ private final String url;
+
+ private HttpUrl(Builder builder) {
+ this.scheme = builder.scheme;
+ this.username = percentDecode(builder.encodedUsername, false);
+ this.password = percentDecode(builder.encodedPassword, false);
+ this.host = builder.host;
+ this.port = builder.effectivePort();
+ this.pathSegments = percentDecode(builder.encodedPathSegments, false);
+ this.queryNamesAndValues = builder.encodedQueryNamesAndValues != null
+ ? percentDecode(builder.encodedQueryNamesAndValues, true)
+ : null;
+ this.fragment = builder.encodedFragment != null
+ ? percentDecode(builder.encodedFragment, false)
+ : null;
+ this.url = builder.toString();
+ }
+
+ /** Returns this URL as a {@link URL java.net.URL}. */
+ public URL url() {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e); // Unexpected!
+ }
+ }
+
+ /**
+ * Returns this URL as a {@link URI java.net.URI}. Because {@code URI} is more strict than this
+ * class, the returned URI may be semantically different from this URL:
+ * <ul>
+ * <li>Characters forbidden by URI like {@code [} and {@code |} will be escaped.
+ * <li>Invalid percent-encoded sequences like {@code %xx} will be encoded like {@code %25xx}.
+ * <li>Whitespace and control characters in the fragment will be stripped.
+ * </ul>
+ *
+ * <p>These differences may have a significant consequence when the URI is interpretted by a
+ * webserver. For this reason the {@linkplain URI URI class} and this method should be avoided.
+ */
+ public URI uri() {
+ String uri = newBuilder().reencodeForUri().toString();
+ try {
+ return new URI(uri);
+ } catch (URISyntaxException e) {
+ // Unlikely edge case: the URI has a forbidden character in the fragment. Strip it & retry.
+ try {
+ String stripped = uri.replaceAll("[\\u0000-\\u001F\\u007F-\\u009F\\p{javaWhitespace}]", "");
+ return URI.create(stripped);
+ } catch (Exception e1) {
+ throw new RuntimeException(e); // Unexpected!
+ }
+ }
+ }
+
+ /** Returns either "http" or "https". */
+ public String scheme() {
+ return scheme;
+ }
+
+ public boolean isHttps() {
+ return scheme.equals("https");
+ }
+
+ /** Returns the username, or an empty string if none is set. */
+ public String encodedUsername() {
+ if (username.isEmpty()) return "";
+ int usernameStart = scheme.length() + 3; // "://".length() == 3.
+ int usernameEnd = delimiterOffset(url, usernameStart, url.length(), ":@");
+ return url.substring(usernameStart, usernameEnd);
+ }
+
+ public String username() {
+ return username;
+ }
+
+ /** Returns the password, or an empty string if none is set. */
+ public String encodedPassword() {
+ if (password.isEmpty()) return "";
+ int passwordStart = url.indexOf(':', scheme.length() + 3) + 1;
+ int passwordEnd = url.indexOf('@');
+ return url.substring(passwordStart, passwordEnd);
+ }
+
+ /** Returns the decoded password, or an empty string if none is present. */
+ public String password() {
+ return password;
+ }
+
+ /**
+ * Returns the host address suitable for use with {@link InetAddress#getAllByName(String)}. May
+ * be:
+ * <ul>
+ * <li>A regular host name, like {@code android.com}.
+ * <li>An IPv4 address, like {@code 127.0.0.1}.
+ * <li>An IPv6 address, like {@code ::1}. Note that there are no square braces.
+ * <li>An encoded IDN, like {@code xn--n3h.net}.
+ * </ul>
+ */
+ public String host() {
+ return host;
+ }
+
+ /**
+ * Returns the explicitly-specified port if one was provided, or the default port for this URL's
+ * scheme. For example, this returns 8443 for {@code https://square.com:8443/} and 443 for {@code
+ * https://square.com/}. The result is in {@code [1..65535]}.
+ */
+ public int port() {
+ return port;
+ }
+
+ /**
+ * Returns 80 if {@code scheme.equals("http")}, 443 if {@code scheme.equals("https")} and -1
+ * otherwise.
+ */
+ public static int defaultPort(String scheme) {
+ if (scheme.equals("http")) {
+ return 80;
+ } else if (scheme.equals("https")) {
+ return 443;
+ } else {
+ return -1;
+ }
+ }
+
+ public int pathSize() {
+ return pathSegments.size();
+ }
+
+ /**
+ * Returns the entire path of this URL, encoded for use in HTTP resource resolution. The
+ * returned path is always nonempty and is prefixed with {@code /}.
+ */
+ public String encodedPath() {
+ int pathStart = url.indexOf('/', scheme.length() + 3); // "://".length() == 3.
+ int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");
+ return url.substring(pathStart, pathEnd);
+ }
+
+ static void pathSegmentsToString(StringBuilder out, List<String> pathSegments) {
+ for (int i = 0, size = pathSegments.size(); i < size; i++) {
+ out.append('/');
+ out.append(pathSegments.get(i));
+ }
+ }
+
+ public List<String> encodedPathSegments() {
+ int pathStart = url.indexOf('/', scheme.length() + 3);
+ int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");
+ List<String> result = new ArrayList<>();
+ for (int i = pathStart; i < pathEnd; ) {
+ i++; // Skip the '/'.
+ int segmentEnd = delimiterOffset(url, i, pathEnd, "/");
+ result.add(url.substring(i, segmentEnd));
+ i = segmentEnd;
+ }
+ return result;
+ }
+
+ public List<String> pathSegments() {
+ return pathSegments;
+ }
+
+ /**
+ * Returns the query of this URL, encoded for use in HTTP resource resolution. The returned string
+ * may be null (for URLs with no query), empty (for URLs with an empty query) or non-empty (all
+ * other URLs).
+ */
+ public String encodedQuery() {
+ if (queryNamesAndValues == null) return null; // No query.
+ int queryStart = url.indexOf('?') + 1;
+ int queryEnd = delimiterOffset(url, queryStart + 1, url.length(), "#");
+ return url.substring(queryStart, queryEnd);
+ }
+
+ static void namesAndValuesToQueryString(StringBuilder out, List<String> namesAndValues) {
+ for (int i = 0, size = namesAndValues.size(); i < size; i += 2) {
+ String name = namesAndValues.get(i);
+ String value = namesAndValues.get(i + 1);
+ if (i > 0) out.append('&');
+ out.append(name);
+ if (value != null) {
+ out.append('=');
+ out.append(value);
+ }
+ }
+ }
+
+ /**
+ * Cuts {@code encodedQuery} up into alternating parameter names and values. This divides a
+ * query string like {@code subject=math&easy&problem=5-2=3} into the list {@code ["subject",
+ * "math", "easy", null, "problem", "5-2=3"]}. Note that values may be null and may contain
+ * '=' characters.
+ */
+ static List<String> queryStringToNamesAndValues(String encodedQuery) {
+ List<String> result = new ArrayList<>();
+ for (int pos = 0; pos <= encodedQuery.length(); ) {
+ int ampersandOffset = encodedQuery.indexOf('&', pos);
+ if (ampersandOffset == -1) ampersandOffset = encodedQuery.length();
+
+ int equalsOffset = encodedQuery.indexOf('=', pos);
+ if (equalsOffset == -1 || equalsOffset > ampersandOffset) {
+ result.add(encodedQuery.substring(pos, ampersandOffset));
+ result.add(null); // No value for this name.
+ } else {
+ result.add(encodedQuery.substring(pos, equalsOffset));
+ result.add(encodedQuery.substring(equalsOffset + 1, ampersandOffset));
+ }
+ pos = ampersandOffset + 1;
+ }
+ return result;
+ }
+
+ public String query() {
+ if (queryNamesAndValues == null) return null; // No query.
+ StringBuilder result = new StringBuilder();
+ namesAndValuesToQueryString(result, queryNamesAndValues);
+ return result.toString();
+ }
+
+ public int querySize() {
+ return queryNamesAndValues != null ? queryNamesAndValues.size() / 2 : 0;
+ }
+
+ /**
+ * Returns the first query parameter named {@code name} decoded using UTF-8, or null if there is
+ * no such query parameter.
+ */
+ public String queryParameter(String name) {
+ if (queryNamesAndValues == null) return null;
+ for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+ if (name.equals(queryNamesAndValues.get(i))) {
+ return queryNamesAndValues.get(i + 1);
+ }
+ }
+ return null;
+ }
+
+ public Set<String> queryParameterNames() {
+ if (queryNamesAndValues == null) return Collections.emptySet();
+ Set<String> result = new LinkedHashSet<>();
+ for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+ result.add(queryNamesAndValues.get(i));
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ public List<String> queryParameterValues(String name) {
+ if (queryNamesAndValues == null) return Collections.emptyList();
+ List<String> result = new ArrayList<>();
+ for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+ if (name.equals(queryNamesAndValues.get(i))) {
+ result.add(queryNamesAndValues.get(i + 1));
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ public String queryParameterName(int index) {
+ return queryNamesAndValues.get(index * 2);
+ }
+
+ public String queryParameterValue(int index) {
+ return queryNamesAndValues.get(index * 2 + 1);
+ }
+
+ public String encodedFragment() {
+ if (fragment == null) return null;
+ int fragmentStart = url.indexOf('#') + 1;
+ return url.substring(fragmentStart);
+ }
+
+ public String fragment() {
+ return fragment;
+ }
+
+ /** Returns the URL that would be retrieved by following {@code link} from this URL. */
+ public HttpUrl resolve(String link) {
+ Builder builder = new Builder();
+ Builder.ParseResult result = builder.parse(this, link);
+ return result == Builder.ParseResult.SUCCESS ? builder.build() : null;
+ }
+
+ public Builder newBuilder() {
+ Builder result = new Builder();
+ result.scheme = scheme;
+ result.encodedUsername = encodedUsername();
+ result.encodedPassword = encodedPassword();
+ result.host = host;
+ // If we're set to a default port, unset it in case of a scheme change.
+ result.port = port != defaultPort(scheme) ? port : -1;
+ result.encodedPathSegments.clear();
+ result.encodedPathSegments.addAll(encodedPathSegments());
+ result.encodedQuery(encodedQuery());
+ result.encodedFragment = encodedFragment();
+ return result;
+ }
+
+ /**
+ * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS
+ * URL, or null if it isn't.
+ */
+ public static HttpUrl parse(String url) {
+ Builder builder = new Builder();
+ Builder.ParseResult result = builder.parse(null, url);
+ return result == Builder.ParseResult.SUCCESS ? builder.build() : null;
+ }
+
+ /**
+ * Returns an {@link HttpUrl} for {@code url} if its protocol is {@code http} or {@code https}, or
+ * null if it has any other protocol.
+ */
+ public static HttpUrl get(URL url) {
+ return parse(url.toString());
+ }
+
+ /**
+ * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS
+ * URL, or throws an exception if it isn't.
+ *
+ * @throws MalformedURLException if there was a non-host related URL issue
+ * @throws UnknownHostException if the host was invalid
+ */
+ static HttpUrl getChecked(String url) throws MalformedURLException, UnknownHostException {
+ Builder builder = new Builder();
+ Builder.ParseResult result = builder.parse(null, url);
+ switch (result) {
+ case SUCCESS:
+ return builder.build();
+ case INVALID_HOST:
+ throw new UnknownHostException("Invalid host: " + url);
+ case UNSUPPORTED_SCHEME:
+ case MISSING_SCHEME:
+ case INVALID_PORT:
+ default:
+ throw new MalformedURLException("Invalid URL: " + result + " for " + url);
+ }
+ }
+
+ public static HttpUrl get(URI uri) {
+ return parse(uri.toString());
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof HttpUrl && ((HttpUrl) o).url.equals(url);
+ }
+
+ @Override public int hashCode() {
+ return url.hashCode();
+ }
+
+ @Override public String toString() {
+ return url;
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static final class Builder {
+ String scheme;
+ String encodedUsername = "";
+ String encodedPassword = "";
+ String host;
+ int port = -1;
+ final List<String> encodedPathSegments = new ArrayList<>();
+ List<String> encodedQueryNamesAndValues;
+ String encodedFragment;
+
+ public Builder() {
+ encodedPathSegments.add(""); // The default path is '/' which needs a trailing space.
+ }
+
+ public Builder scheme(String scheme) {
+ if (scheme == null) {
+ throw new IllegalArgumentException("scheme == null");
+ } else if (scheme.equalsIgnoreCase("http")) {
+ this.scheme = "http";
+ } else if (scheme.equalsIgnoreCase("https")) {
+ this.scheme = "https";
+ } else {
+ throw new IllegalArgumentException("unexpected scheme: " + scheme);
+ }
+ return this;
+ }
+
+ public Builder username(String username) {
+ if (username == null) throw new IllegalArgumentException("username == null");
+ this.encodedUsername = canonicalize(username, USERNAME_ENCODE_SET, false, false, false, true);
+ return this;
+ }
+
+ public Builder encodedUsername(String encodedUsername) {
+ if (encodedUsername == null) throw new IllegalArgumentException("encodedUsername == null");
+ this.encodedUsername = canonicalize(
+ encodedUsername, USERNAME_ENCODE_SET, true, false, false, true);
+ return this;
+ }
+
+ public Builder password(String password) {
+ if (password == null) throw new IllegalArgumentException("password == null");
+ this.encodedPassword = canonicalize(password, PASSWORD_ENCODE_SET, false, false, false, true);
+ return this;
+ }
+
+ public Builder encodedPassword(String encodedPassword) {
+ if (encodedPassword == null) throw new IllegalArgumentException("encodedPassword == null");
+ this.encodedPassword = canonicalize(
+ encodedPassword, PASSWORD_ENCODE_SET, true, false, false, true);
+ return this;
+ }
+
+ /**
+ * @param host either a regular hostname, International Domain Name, IPv4 address, or IPv6
+ * address.
+ */
+ public Builder host(String host) {
+ if (host == null) throw new IllegalArgumentException("host == null");
+ String encoded = canonicalizeHost(host, 0, host.length());
+ if (encoded == null) throw new IllegalArgumentException("unexpected host: " + host);
+ this.host = encoded;
+ return this;
+ }
+
+ public Builder port(int port) {
+ if (port <= 0 || port > 65535) throw new IllegalArgumentException("unexpected port: " + port);
+ this.port = port;
+ return this;
+ }
+
+ int effectivePort() {
+ return port != -1 ? port : defaultPort(scheme);
+ }
+
+ public Builder addPathSegment(String pathSegment) {
+ if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null");
+ push(pathSegment, 0, pathSegment.length(), false, false);
+ return this;
+ }
+
+ public Builder addEncodedPathSegment(String encodedPathSegment) {
+ if (encodedPathSegment == null) {
+ throw new IllegalArgumentException("encodedPathSegment == null");
+ }
+ push(encodedPathSegment, 0, encodedPathSegment.length(), false, true);
+ return this;
+ }
+
+ public Builder setPathSegment(int index, String pathSegment) {
+ if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null");
+ String canonicalPathSegment = canonicalize(
+ pathSegment, 0, pathSegment.length(), PATH_SEGMENT_ENCODE_SET, false, false, false, true);
+ if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
+ throw new IllegalArgumentException("unexpected path segment: " + pathSegment);
+ }
+ encodedPathSegments.set(index, canonicalPathSegment);
+ return this;
+ }
+
+ public Builder setEncodedPathSegment(int index, String encodedPathSegment) {
+ if (encodedPathSegment == null) {
+ throw new IllegalArgumentException("encodedPathSegment == null");
+ }
+ String canonicalPathSegment = canonicalize(encodedPathSegment,
+ 0, encodedPathSegment.length(), PATH_SEGMENT_ENCODE_SET, true, false, false, true);
+ encodedPathSegments.set(index, canonicalPathSegment);
+ if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
+ throw new IllegalArgumentException("unexpected path segment: " + encodedPathSegment);
+ }
+ return this;
+ }
+
+ public Builder removePathSegment(int index) {
+ encodedPathSegments.remove(index);
+ if (encodedPathSegments.isEmpty()) {
+ encodedPathSegments.add(""); // Always leave at least one '/'.
+ }
+ return this;
+ }
+
+ public Builder encodedPath(String encodedPath) {
+ if (encodedPath == null) throw new IllegalArgumentException("encodedPath == null");
+ if (!encodedPath.startsWith("/")) {
+ throw new IllegalArgumentException("unexpected encodedPath: " + encodedPath);
+ }
+ resolvePath(encodedPath, 0, encodedPath.length());
+ return this;
+ }
+
+ public Builder query(String query) {
+ this.encodedQueryNamesAndValues = query != null
+ ? queryStringToNamesAndValues(canonicalize(
+ query, QUERY_ENCODE_SET, false, false, true, true))
+ : null;
+ return this;
+ }
+
+ public Builder encodedQuery(String encodedQuery) {
+ this.encodedQueryNamesAndValues = encodedQuery != null
+ ? queryStringToNamesAndValues(
+ canonicalize(encodedQuery, QUERY_ENCODE_SET, true, false, true, true))
+ : null;
+ return this;
+ }
+
+ /** Encodes the query parameter using UTF-8 and adds it to this URL's query string. */
+ public Builder addQueryParameter(String name, String value) {
+ if (name == null) throw new IllegalArgumentException("name == null");
+ if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
+ encodedQueryNamesAndValues.add(
+ canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true));
+ encodedQueryNamesAndValues.add(value != null
+ ? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)
+ : null);
+ return this;
+ }
+
+ /** Adds the pre-encoded query parameter to this URL's query string. */
+ public Builder addEncodedQueryParameter(String encodedName, String encodedValue) {
+ if (encodedName == null) throw new IllegalArgumentException("encodedName == null");
+ if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
+ encodedQueryNamesAndValues.add(
+ canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true));
+ encodedQueryNamesAndValues.add(encodedValue != null
+ ? canonicalize(encodedValue, QUERY_COMPONENT_ENCODE_SET, true, false, true, true)
+ : null);
+ return this;
+ }
+
+ public Builder setQueryParameter(String name, String value) {
+ removeAllQueryParameters(name);
+ addQueryParameter(name, value);
+ return this;
+ }
+
+ public Builder setEncodedQueryParameter(String encodedName, String encodedValue) {
+ removeAllEncodedQueryParameters(encodedName);
+ addEncodedQueryParameter(encodedName, encodedValue);
+ return this;
+ }
+
+ public Builder removeAllQueryParameters(String name) {
+ if (name == null) throw new IllegalArgumentException("name == null");
+ if (encodedQueryNamesAndValues == null) return this;
+ String nameToRemove = canonicalize(
+ name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true);
+ removeAllCanonicalQueryParameters(nameToRemove);
+ return this;
+ }
+
+ public Builder removeAllEncodedQueryParameters(String encodedName) {
+ if (encodedName == null) throw new IllegalArgumentException("encodedName == null");
+ if (encodedQueryNamesAndValues == null) return this;
+ removeAllCanonicalQueryParameters(
+ canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true));
+ return this;
+ }
+
+ private void removeAllCanonicalQueryParameters(String canonicalName) {
+ for (int i = encodedQueryNamesAndValues.size() - 2; i >= 0; i -= 2) {
+ if (canonicalName.equals(encodedQueryNamesAndValues.get(i))) {
+ encodedQueryNamesAndValues.remove(i + 1);
+ encodedQueryNamesAndValues.remove(i);
+ if (encodedQueryNamesAndValues.isEmpty()) {
+ encodedQueryNamesAndValues = null;
+ return;
+ }
+ }
+ }
+ }
+
+ public Builder fragment(String fragment) {
+ this.encodedFragment = fragment != null
+ ? canonicalize(fragment, FRAGMENT_ENCODE_SET, false, false, false, false)
+ : null;
+ return this;
+ }
+
+ public Builder encodedFragment(String encodedFragment) {
+ this.encodedFragment = encodedFragment != null
+ ? canonicalize(encodedFragment, FRAGMENT_ENCODE_SET, true, false, false, false)
+ : null;
+ return this;
+ }
+
+ /**
+ * Re-encodes the components of this URL so that it satisfies (obsolete) RFC 2396, which is
+ * particularly strict for certain components.
+ */
+ Builder reencodeForUri() {
+ for (int i = 0, size = encodedPathSegments.size(); i < size; i++) {
+ String pathSegment = encodedPathSegments.get(i);
+ encodedPathSegments.set(i,
+ canonicalize(pathSegment, PATH_SEGMENT_ENCODE_SET_URI, true, true, false, true));
+ }
+ if (encodedQueryNamesAndValues != null) {
+ for (int i = 0, size = encodedQueryNamesAndValues.size(); i < size; i++) {
+ String component = encodedQueryNamesAndValues.get(i);
+ if (component != null) {
+ encodedQueryNamesAndValues.set(i,
+ canonicalize(component, QUERY_COMPONENT_ENCODE_SET_URI, true, true, true, true));
+ }
+ }
+ }
+ if (encodedFragment != null) {
+ encodedFragment = canonicalize(
+ encodedFragment, FRAGMENT_ENCODE_SET_URI, true, true, false, false);
+ }
+ return this;
+ }
+
+ public HttpUrl build() {
+ if (scheme == null) throw new IllegalStateException("scheme == null");
+ if (host == null) throw new IllegalStateException("host == null");
+ return new HttpUrl(this);
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(scheme);
+ result.append("://");
+
+ if (!encodedUsername.isEmpty() || !encodedPassword.isEmpty()) {
+ result.append(encodedUsername);
+ if (!encodedPassword.isEmpty()) {
+ result.append(':');
+ result.append(encodedPassword);
+ }
+ result.append('@');
+ }
+
+ if (host.indexOf(':') != -1) {
+ // Host is an IPv6 address.
+ result.append('[');
+ result.append(host);
+ result.append(']');
+ } else {
+ result.append(host);
+ }
+
+ int effectivePort = effectivePort();
+ if (effectivePort != defaultPort(scheme)) {
+ result.append(':');
+ result.append(effectivePort);
+ }
+
+ pathSegmentsToString(result, encodedPathSegments);
+
+ if (encodedQueryNamesAndValues != null) {
+ result.append('?');
+ namesAndValuesToQueryString(result, encodedQueryNamesAndValues);
+ }
+
+ if (encodedFragment != null) {
+ result.append('#');
+ result.append(encodedFragment);
+ }
+
+ return result.toString();
+ }
+
+ enum ParseResult {
+ SUCCESS,
+ MISSING_SCHEME,
+ UNSUPPORTED_SCHEME,
+ INVALID_PORT,
+ INVALID_HOST,
+ }
+
+ ParseResult parse(HttpUrl base, String input) {
+ int pos = skipLeadingAsciiWhitespace(input, 0, input.length());
+ int limit = skipTrailingAsciiWhitespace(input, pos, input.length());
+
+ // Scheme.
+ int schemeDelimiterOffset = schemeDelimiterOffset(input, pos, limit);
+ if (schemeDelimiterOffset != -1) {
+ if (input.regionMatches(true, pos, "https:", 0, 6)) {
+ this.scheme = "https";
+ pos += "https:".length();
+ } else if (input.regionMatches(true, pos, "http:", 0, 5)) {
+ this.scheme = "http";
+ pos += "http:".length();
+ } else {
+ return ParseResult.UNSUPPORTED_SCHEME; // Not an HTTP scheme.
+ }
+ } else if (base != null) {
+ this.scheme = base.scheme;
+ } else {
+ return ParseResult.MISSING_SCHEME; // No scheme.
+ }
+
+ // Authority.
+ boolean hasUsername = false;
+ boolean hasPassword = false;
+ int slashCount = slashCount(input, pos, limit);
+ if (slashCount >= 2 || base == null || !base.scheme.equals(this.scheme)) {
+ // Read an authority if either:
+ // * The input starts with 2 or more slashes. These follow the scheme if it exists.
+ // * The input scheme exists and is different from the base URL's scheme.
+ //
+ // The structure of an authority is:
+ // username:password@host:port
+ //
+ // Username, password and port are optional.
+ // [username[:password]@]host[:port]
+ pos += slashCount;
+ authority:
+ while (true) {
+ int componentDelimiterOffset = delimiterOffset(input, pos, limit, "@/\\?#");
+ int c = componentDelimiterOffset != limit
+ ? input.charAt(componentDelimiterOffset)
+ : -1;
+ switch (c) {
+ case '@':
+ // User info precedes.
+ if (!hasPassword) {
+ int passwordColonOffset = delimiterOffset(
+ input, pos, componentDelimiterOffset, ":");
+ String canonicalUsername = canonicalize(
+ input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true);
+ this.encodedUsername = hasUsername
+ ? this.encodedUsername + "%40" + canonicalUsername
+ : canonicalUsername;
+ if (passwordColonOffset != componentDelimiterOffset) {
+ hasPassword = true;
+ this.encodedPassword = canonicalize(input, passwordColonOffset + 1,
+ componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true);
+ }
+ hasUsername = true;
+ } else {
+ this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos,
+ componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true);
+ }
+ pos = componentDelimiterOffset + 1;
+ break;
+
+ case -1:
+ case '/':
+ case '\\':
+ case '?':
+ case '#':
+ // Host info precedes.
+ int portColonOffset = portColonOffset(input, pos, componentDelimiterOffset);
+ if (portColonOffset + 1 < componentDelimiterOffset) {
+ this.host = canonicalizeHost(input, pos, portColonOffset);
+ this.port = parsePort(input, portColonOffset + 1, componentDelimiterOffset);
+ if (this.port == -1) return ParseResult.INVALID_PORT; // Invalid port.
+ } else {
+ this.host = canonicalizeHost(input, pos, portColonOffset);
+ this.port = defaultPort(this.scheme);
+ }
+ if (this.host == null) return ParseResult.INVALID_HOST; // Invalid host.
+ pos = componentDelimiterOffset;
+ break authority;
+ }
+ }
+ } else {
+ // This is a relative link. Copy over all authority components. Also maybe the path & query.
+ this.encodedUsername = base.encodedUsername();
+ this.encodedPassword = base.encodedPassword();
+ this.host = base.host;
+ this.port = base.port;
+ this.encodedPathSegments.clear();
+ this.encodedPathSegments.addAll(base.encodedPathSegments());
+ if (pos == limit || input.charAt(pos) == '#') {
+ encodedQuery(base.encodedQuery());
+ }
+ }
+
+ // Resolve the relative path.
+ int pathDelimiterOffset = delimiterOffset(input, pos, limit, "?#");
+ resolvePath(input, pos, pathDelimiterOffset);
+ pos = pathDelimiterOffset;
+
+ // Query.
+ if (pos < limit && input.charAt(pos) == '?') {
+ int queryDelimiterOffset = delimiterOffset(input, pos, limit, "#");
+ this.encodedQueryNamesAndValues = queryStringToNamesAndValues(canonicalize(
+ input, pos + 1, queryDelimiterOffset, QUERY_ENCODE_SET, true, false, true, true));
+ pos = queryDelimiterOffset;
+ }
+
+ // Fragment.
+ if (pos < limit && input.charAt(pos) == '#') {
+ this.encodedFragment = canonicalize(
+ input, pos + 1, limit, FRAGMENT_ENCODE_SET, true, false, false, false);
+ }
+
+ return ParseResult.SUCCESS;
+ }
+
+ private void resolvePath(String input, int pos, int limit) {
+ // Read a delimiter.
+ if (pos == limit) {
+ // Empty path: keep the base path as-is.
+ return;
+ }
+ char c = input.charAt(pos);
+ if (c == '/' || c == '\\') {
+ // Absolute path: reset to the default "/".
+ encodedPathSegments.clear();
+ encodedPathSegments.add("");
+ pos++;
+ } else {
+ // Relative path: clear everything after the last '/'.
+ encodedPathSegments.set(encodedPathSegments.size() - 1, "");
+ }
+
+ // Read path segments.
+ for (int i = pos; i < limit; ) {
+ int pathSegmentDelimiterOffset = delimiterOffset(input, i, limit, "/\\");
+ boolean segmentHasTrailingSlash = pathSegmentDelimiterOffset < limit;
+ push(input, i, pathSegmentDelimiterOffset, segmentHasTrailingSlash, true);
+ i = pathSegmentDelimiterOffset;
+ if (segmentHasTrailingSlash) i++;
+ }
+ }
+
+ /** Adds a path segment. If the input is ".." or equivalent, this pops a path segment. */
+ private void push(String input, int pos, int limit, boolean addTrailingSlash,
+ boolean alreadyEncoded) {
+ String segment = canonicalize(
+ input, pos, limit, PATH_SEGMENT_ENCODE_SET, alreadyEncoded, false, false, true);
+ if (isDot(segment)) {
+ return; // Skip '.' path segments.
+ }
+ if (isDotDot(segment)) {
+ pop();
+ return;
+ }
+ if (encodedPathSegments.get(encodedPathSegments.size() - 1).isEmpty()) {
+ encodedPathSegments.set(encodedPathSegments.size() - 1, segment);
+ } else {
+ encodedPathSegments.add(segment);
+ }
+ if (addTrailingSlash) {
+ encodedPathSegments.add("");
+ }
+ }
+
+ private boolean isDot(String input) {
+ return input.equals(".") || input.equalsIgnoreCase("%2e");
+ }
+
+ private boolean isDotDot(String input) {
+ return input.equals("..")
+ || input.equalsIgnoreCase("%2e.")
+ || input.equalsIgnoreCase(".%2e")
+ || input.equalsIgnoreCase("%2e%2e");
+ }
+
+ /**
+ * Removes a path segment. When this method returns the last segment is always "", which means
+ * the encoded path will have a trailing '/'.
+ *
+ * <p>Popping "/a/b/c/" yields "/a/b/". In this case the list of path segments goes from
+ * ["a", "b", "c", ""] to ["a", "b", ""].
+ *
+ * <p>Popping "/a/b/c" also yields "/a/b/". The list of path segments goes from ["a", "b", "c"]
+ * to ["a", "b", ""].
+ */
+ private void pop() {
+ String removed = encodedPathSegments.remove(encodedPathSegments.size() - 1);
+
+ // Make sure the path ends with a '/' by either adding an empty string or clearing a segment.
+ if (removed.isEmpty() && !encodedPathSegments.isEmpty()) {
+ encodedPathSegments.set(encodedPathSegments.size() - 1, "");
+ } else {
+ encodedPathSegments.add("");
+ }
+ }
+
+ /**
+ * Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code
+ * limit}.
+ */
+ private int skipLeadingAsciiWhitespace(String input, int pos, int limit) {
+ for (int i = pos; i < limit; i++) {
+ switch (input.charAt(i)) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ':
+ continue;
+ default:
+ return i;
+ }
+ }
+ return limit;
+ }
+
+ /**
+ * Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at
+ * {@code pos}.
+ */
+ private int skipTrailingAsciiWhitespace(String input, int pos, int limit) {
+ for (int i = limit - 1; i >= pos; i--) {
+ switch (input.charAt(i)) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ':
+ continue;
+ default:
+ return i + 1;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Returns the index of the ':' in {@code input} that is after scheme characters. Returns -1 if
+ * {@code input} does not have a scheme that starts at {@code pos}.
+ */
+ private static int schemeDelimiterOffset(String input, int pos, int limit) {
+ if (limit - pos < 2) return -1;
+
+ char c0 = input.charAt(pos);
+ if ((c0 < 'a' || c0 > 'z') && (c0 < 'A' || c0 > 'Z')) return -1; // Not a scheme start char.
+
+ for (int i = pos + 1; i < limit; i++) {
+ char c = input.charAt(i);
+
+ if ((c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9')
+ || c == '+'
+ || c == '-'
+ || c == '.') {
+ continue; // Scheme character. Keep going.
+ } else if (c == ':') {
+ return i; // Scheme prefix!
+ } else {
+ return -1; // Non-scheme character before the first ':'.
+ }
+ }
+
+ return -1; // No ':'; doesn't start with a scheme.
+ }
+
+ /** Returns the number of '/' and '\' slashes in {@code input}, starting at {@code pos}. */
+ private static int slashCount(String input, int pos, int limit) {
+ int slashCount = 0;
+ while (pos < limit) {
+ char c = input.charAt(pos);
+ if (c == '\\' || c == '/') {
+ slashCount++;
+ pos++;
+ } else {
+ break;
+ }
+ }
+ return slashCount;
+ }
+
+ /** Finds the first ':' in {@code input}, skipping characters between square braces "[...]". */
+ private static int portColonOffset(String input, int pos, int limit) {
+ for (int i = pos; i < limit; i++) {
+ switch (input.charAt(i)) {
+ case '[':
+ while (++i < limit) {
+ if (input.charAt(i) == ']') break;
+ }
+ break;
+ case ':':
+ return i;
+ }
+ }
+ return limit; // No colon.
+ }
+
+ private static String canonicalizeHost(String input, int pos, int limit) {
+ // Start by percent decoding the host. The WHATWG spec suggests doing this only after we've
+ // checked for IPv6 square braces. But Chrome does it first, and that's more lenient.
+ String percentDecoded = percentDecode(input, pos, limit, false);
+
+ // If the input contains a :, it’s an IPv6 address.
+ if (percentDecoded.contains(":")) {
+ // If the input is encased in square braces "[...]", drop 'em.
+ InetAddress inetAddress = percentDecoded.startsWith("[") && percentDecoded.endsWith("]")
+ ? decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1)
+ : decodeIpv6(percentDecoded, 0, percentDecoded.length());
+ if (inetAddress == null) return null;
+ byte[] address = inetAddress.getAddress();
+ if (address.length == 16) return inet6AddressToAscii(address);
+ throw new AssertionError();
+ }
+
+ return domainToAscii(percentDecoded);
+ }
+
+ /** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */
+ private static InetAddress decodeIpv6(String input, int pos, int limit) {
+ byte[] address = new byte[16];
+ int b = 0;
+ int compress = -1;
+ int groupOffset = -1;
+
+ for (int i = pos; i < limit; ) {
+ if (b == address.length) return null; // Too many groups.
+
+ // Read a delimiter.
+ if (i + 2 <= limit && input.regionMatches(i, "::", 0, 2)) {
+ // Compression "::" delimiter, which is anywhere in the input, including its prefix.
+ if (compress != -1) return null; // Multiple "::" delimiters.
+ i += 2;
+ b += 2;
+ compress = b;
+ if (i == limit) break;
+ } else if (b != 0) {
+ // Group separator ":" delimiter.
+ if (input.regionMatches(i, ":", 0, 1)) {
+ i++;
+ } else if (input.regionMatches(i, ".", 0, 1)) {
+ // If we see a '.', rewind to the beginning of the previous group and parse as IPv4.
+ if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null;
+ b += 2; // We rewound two bytes and then added four.
+ break;
+ } else {
+ return null; // Wrong delimiter.
+ }
+ }
+
+ // Read a group, one to four hex digits.
+ int value = 0;
+ groupOffset = i;
+ for (; i < limit; i++) {
+ char c = input.charAt(i);
+ int hexDigit = decodeHexDigit(c);
+ if (hexDigit == -1) break;
+ value = (value << 4) + hexDigit;
+ }
+ int groupLength = i - groupOffset;
+ if (groupLength == 0 || groupLength > 4) return null; // Group is the wrong size.
+
+ // We've successfully read a group. Assign its value to our byte array.
+ address[b++] = (byte) ((value >>> 8) & 0xff);
+ address[b++] = (byte) (value & 0xff);
+ }
+
+ // All done. If compression happened, we need to move bytes to the right place in the
+ // address. Here's a sample:
+ //
+ // input: "1111:2222:3333::7777:8888"
+ // before: { 11, 11, 22, 22, 33, 33, 00, 00, 77, 77, 88, 88, 00, 00, 00, 00 }
+ // compress: 6
+ // b: 10
+ // after: { 11, 11, 22, 22, 33, 33, 00, 00, 00, 00, 00, 00, 77, 77, 88, 88 }
+ //
+ if (b != address.length) {
+ if (compress == -1) return null; // Address didn't have compression or enough groups.
+ System.arraycopy(address, compress, address, address.length - (b - compress), b - compress);
+ Arrays.fill(address, compress, compress + (address.length - b), (byte) 0);
+ }
+
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */
+ private static boolean decodeIpv4Suffix(
+ String input, int pos, int limit, byte[] address, int addressOffset) {
+ int b = addressOffset;
+
+ for (int i = pos; i < limit; ) {
+ if (b == address.length) return false; // Too many groups.
+
+ // Read a delimiter.
+ if (b != addressOffset) {
+ if (input.charAt(i) != '.') return false; // Wrong delimiter.
+ i++;
+ }
+
+ // Read 1 or more decimal digits for a value in 0..255.
+ int value = 0;
+ int groupOffset = i;
+ for (; i < limit; i++) {
+ char c = input.charAt(i);
+ if (c < '0' || c > '9') break;
+ if (value == 0 && groupOffset != i) return false; // Reject unnecessary leading '0's.
+ value = (value * 10) + c - '0';
+ if (value > 255) return false; // Value out of range.
+ }
+ int groupLength = i - groupOffset;
+ if (groupLength == 0) return false; // No digits.
+
+ // We've successfully read a byte.
+ address[b++] = (byte) value;
+ }
+
+ if (b != addressOffset + 4) return false; // Too few groups. We wanted exactly four.
+ return true; // Success.
+ }
+
+ /**
+ * Performs IDN ToASCII encoding and canonicalize the result to lowercase. e.g. This converts
+ * {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to {@code www.google.com}.
+ * {@code null} will be returned if the input cannot be ToASCII encoded or if the result
+ * contains unsupported ASCII characters.
+ */
+ private static String domainToAscii(String input) {
+ try {
+ String result = IDN.toASCII(input).toLowerCase(Locale.US);
+ if (result.isEmpty()) return null;
+
+ // Confirm that the IDN ToASCII result doesn't contain any illegal characters.
+ if (containsInvalidHostnameAsciiCodes(result)) {
+ return null;
+ }
+ // TODO: implement all label limits.
+ return result;
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) {
+ for (int i = 0; i < hostnameAscii.length(); i++) {
+ char c = hostnameAscii.charAt(i);
+ // The WHATWG Host parsing rules accepts some character codes which are invalid by
+ // definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here
+ // we rule out characters that would cause problems in host headers.
+ if (c <= '\u001f' || c >= '\u007f') {
+ return true;
+ }
+ // Check for the characters mentioned in the WHATWG Host parsing spec:
+ // U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]"
+ // (excluding the characters covered above).
+ if (" #%/:?@[\\]".indexOf(c) != -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String inet6AddressToAscii(byte[] address) {
+ // Go through the address looking for the longest run of 0s. Each group is 2-bytes.
+ int longestRunOffset = -1;
+ int longestRunLength = 0;
+ for (int i = 0; i < address.length; i += 2) {
+ int currentRunOffset = i;
+ while (i < 16 && address[i] == 0 && address[i + 1] == 0) {
+ i += 2;
+ }
+ int currentRunLength = i - currentRunOffset;
+ if (currentRunLength > longestRunLength) {
+ longestRunOffset = currentRunOffset;
+ longestRunLength = currentRunLength;
+ }
+ }
+
+ // Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
+ Buffer result = new Buffer();
+ for (int i = 0; i < address.length; ) {
+ if (i == longestRunOffset) {
+ result.writeByte(':');
+ i += longestRunLength;
+ if (i == 16) result.writeByte(':');
+ } else {
+ if (i > 0) result.writeByte(':');
+ int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff;
+ result.writeHexadecimalUnsignedLong(group);
+ i += 2;
+ }
+ }
+ return result.readUtf8();
+ }
+
+ private static int parsePort(String input, int pos, int limit) {
+ try {
+ // Canonicalize the port string to skip '\n' etc.
+ String portString = canonicalize(input, pos, limit, "", false, false, false, true);
+ int i = Integer.parseInt(portString);
+ if (i > 0 && i <= 65535) return i;
+ return -1;
+ } catch (NumberFormatException e) {
+ return -1; // Invalid port.
+ }
+ }
+ }
+
+ /**
+ * Returns the index of the first character in {@code input} that contains a character in {@code
+ * delimiters}. Returns limit if there is no such character.
+ */
+ private static int delimiterOffset(String input, int pos, int limit, String delimiters) {
+ for (int i = pos; i < limit; i++) {
+ if (delimiters.indexOf(input.charAt(i)) != -1) return i;
+ }
+ return limit;
+ }
+
+ static String percentDecode(String encoded, boolean plusIsSpace) {
+ return percentDecode(encoded, 0, encoded.length(), plusIsSpace);
+ }
+
+ private List<String> percentDecode(List<String> list, boolean plusIsSpace) {
+ List<String> result = new ArrayList<>(list.size());
+ for (String s : list) {
+ result.add(s != null ? percentDecode(s, plusIsSpace) : null);
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ static String percentDecode(String encoded, int pos, int limit, boolean plusIsSpace) {
+ for (int i = pos; i < limit; i++) {
+ char c = encoded.charAt(i);
+ if (c == '%' || (c == '+' && plusIsSpace)) {
+ // Slow path: the character at i requires decoding!
+ Buffer out = new Buffer();
+ out.writeUtf8(encoded, pos, i);
+ percentDecode(out, encoded, i, limit, plusIsSpace);
+ return out.readUtf8();
+ }
+ }
+
+ // Fast path: no characters in [pos..limit) required decoding.
+ return encoded.substring(pos, limit);
+ }
+
+ static void percentDecode(Buffer out, String encoded, int pos, int limit, boolean plusIsSpace) {
+ int codePoint;
+ for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+ codePoint = encoded.codePointAt(i);
+ if (codePoint == '%' && i + 2 < limit) {
+ int d1 = decodeHexDigit(encoded.charAt(i + 1));
+ int d2 = decodeHexDigit(encoded.charAt(i + 2));
+ if (d1 != -1 && d2 != -1) {
+ out.writeByte((d1 << 4) + d2);
+ i += 2;
+ continue;
+ }
+ } else if (codePoint == '+' && plusIsSpace) {
+ out.writeByte(' ');
+ continue;
+ }
+ out.writeUtf8CodePoint(codePoint);
+ }
+ }
+
+ static boolean percentEncoded(String encoded, int pos, int limit) {
+ return pos + 2 < limit
+ && encoded.charAt(pos) == '%'
+ && decodeHexDigit(encoded.charAt(pos + 1)) != -1
+ && decodeHexDigit(encoded.charAt(pos + 2)) != -1;
+ }
+
+ static int decodeHexDigit(char c) {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ return -1;
+ }
+
+ /**
+ * Returns a substring of {@code input} on the range {@code [pos..limit)} with the following
+ * transformations:
+ * <ul>
+ * <li>Tabs, newlines, form feeds and carriage returns are skipped.
+ * <li>In queries, ' ' is encoded to '+' and '+' is encoded to "%2B".
+ * <li>Characters in {@code encodeSet} are percent-encoded.
+ * <li>Control characters and non-ASCII characters are percent-encoded.
+ * <li>All other characters are copied without transformation.
+ * </ul>
+ *
+ * @param alreadyEncoded true to leave '%' as-is; false to convert it to '%25'.
+ * @param strict true to encode '%' if it is not the prefix of a valid percent encoding.
+ * @param plusIsSpace true to encode '+' as "%2B" if it is not already encoded.
+ * @param asciiOnly true to encode all non-ASCII codepoints.
+ */
+ static String canonicalize(String input, int pos, int limit, String encodeSet,
+ boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+ int codePoint;
+ for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+ codePoint = input.codePointAt(i);
+ if (codePoint < 0x20
+ || codePoint == 0x7f
+ || codePoint >= 0x80 && asciiOnly
+ || encodeSet.indexOf(codePoint) != -1
+ || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))
+ || codePoint == '+' && plusIsSpace) {
+ // Slow path: the character at i requires encoding!
+ Buffer out = new Buffer();
+ out.writeUtf8(input, pos, i);
+ canonicalize(out, input, i, limit, encodeSet, alreadyEncoded, strict, plusIsSpace,
+ asciiOnly);
+ return out.readUtf8();
+ }
+ }
+
+ // Fast path: no characters in [pos..limit) required encoding.
+ return input.substring(pos, limit);
+ }
+
+ static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet,
+ boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+ Buffer utf8Buffer = null; // Lazily allocated.
+ int codePoint;
+ for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+ codePoint = input.codePointAt(i);
+ if (alreadyEncoded
+ && (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) {
+ // Skip this character.
+ } else if (codePoint == '+' && plusIsSpace) {
+ // Encode '+' as '%2B' since we permit ' ' to be encoded as either '+' or '%20'.
+ out.writeUtf8(alreadyEncoded ? "+" : "%2B");
+ } else if (codePoint < 0x20
+ || codePoint == 0x7f
+ || codePoint >= 0x80 && asciiOnly
+ || encodeSet.indexOf(codePoint) != -1
+ || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))) {
+ // Percent encode this character.
+ if (utf8Buffer == null) {
+ utf8Buffer = new Buffer();
+ }
+ utf8Buffer.writeUtf8CodePoint(codePoint);
+ while (!utf8Buffer.exhausted()) {
+ int b = utf8Buffer.readByte() & 0xff;
+ out.writeByte('%');
+ out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]);
+ out.writeByte(HEX_DIGITS[b & 0xf]);
+ }
+ } else {
+ // This character doesn't need encoding. Just copy it over.
+ out.writeUtf8CodePoint(codePoint);
+ }
+ }
+ }
+
+ static String canonicalize(String input, String encodeSet, boolean alreadyEncoded,
+ boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+ return canonicalize(input, 0, input.length(),
+ encodeSet, alreadyEncoded, strict, plusIsSpace, asciiOnly);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java
new file mode 100644
index 0000000..5dee765
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java
@@ -0,0 +1,35 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * Observes, modifies, and potentially short-circuits requests going out and the corresponding
+ * requests coming back in. Typically interceptors will be used to add, remove, or transform headers
+ * on the request or response.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Interceptor {
+ Response intercept(Chain chain) throws IOException;
+
+ interface Chain {
+ Request request();
+ Response proceed(Request request) throws IOException;
+ Connection connection();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java b/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java
new file mode 100644
index 0000000..15048e4
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java
@@ -0,0 +1,125 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type,
+ * appropriate to describe the content type of an HTTP request or response body.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class MediaType {
+ private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
+ private static final String QUOTED = "\"([^\"]*)\"";
+ private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
+ private static final Pattern PARAMETER = Pattern.compile(
+ ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?");
+
+ private final String mediaType;
+ private final String type;
+ private final String subtype;
+ private final String charset;
+
+ private MediaType(String mediaType, String type, String subtype, String charset) {
+ this.mediaType = mediaType;
+ this.type = type;
+ this.subtype = subtype;
+ this.charset = charset;
+ }
+
+ /**
+ * Returns a media type for {@code string}, or null if {@code string} is not a
+ * well-formed media type.
+ */
+ public static MediaType parse(String string) {
+ Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
+ if (!typeSubtype.lookingAt()) return null;
+ String type = typeSubtype.group(1).toLowerCase(Locale.US);
+ String subtype = typeSubtype.group(2).toLowerCase(Locale.US);
+
+ String charset = null;
+ Matcher parameter = PARAMETER.matcher(string);
+ for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
+ parameter.region(s, string.length());
+ if (!parameter.lookingAt()) return null; // This is not a well-formed media type.
+
+ String name = parameter.group(1);
+ if (name == null || !name.equalsIgnoreCase("charset")) continue;
+ String charsetParameter = parameter.group(2) != null
+ ? parameter.group(2) // Value is a token.
+ : parameter.group(3); // Value is a quoted string.
+ if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) {
+ throw new IllegalArgumentException("Multiple different charsets: " + string);
+ }
+ charset = charsetParameter;
+ }
+
+ return new MediaType(string, type, subtype, charset);
+ }
+
+ /**
+ * Returns the high-level media type, such as "text", "image", "audio",
+ * "video", or "application".
+ */
+ public String type() {
+ return type;
+ }
+
+ /**
+ * Returns a specific media subtype, such as "plain" or "png", "mpeg",
+ * "mp4" or "xml".
+ */
+ public String subtype() {
+ return subtype;
+ }
+
+ /**
+ * Returns the charset of this media type, or null if this media type doesn't
+ * specify a charset.
+ */
+ public Charset charset() {
+ return charset != null ? Charset.forName(charset) : null;
+ }
+
+ /**
+ * Returns the charset of this media type, or {@code defaultValue} if this
+ * media type doesn't specify a charset.
+ */
+ public Charset charset(Charset defaultValue) {
+ return charset != null ? Charset.forName(charset) : defaultValue;
+ }
+
+ /**
+ * Returns the encoded media type, like "text/plain; charset=utf-8",
+ * appropriate for use in a Content-Type header.
+ */
+ @Override public String toString() {
+ return mediaType;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
+ }
+
+ @Override public int hashCode() {
+ return mediaType.hashCode();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java b/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java
new file mode 100644
index 0000000..2bce7c5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java
@@ -0,0 +1,302 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.ByteString;
+
+/**
+ * Fluent API to build <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC
+ * 2387</a>-compliant request bodies.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class MultipartBuilder {
+ /**
+ * The "mixed" subtype of "multipart" is intended for use when the body
+ * parts are independent and need to be bundled in a particular order. Any
+ * "multipart" subtypes that an implementation does not recognize must be
+ * treated as being of subtype "mixed".
+ */
+ public static final MediaType MIXED = MediaType.parse("multipart/mixed");
+
+ /**
+ * The "multipart/alternative" type is syntactically identical to
+ * "multipart/mixed", but the semantics are different. In particular, each
+ * of the body parts is an "alternative" version of the same information.
+ */
+ public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
+
+ /**
+ * This type is syntactically identical to "multipart/mixed", but the
+ * semantics are different. In particular, in a digest, the default {@code
+ * Content-Type} value for a body part is changed from "text/plain" to
+ * "message/rfc822".
+ */
+ public static final MediaType DIGEST = MediaType.parse("multipart/digest");
+
+ /**
+ * This type is syntactically identical to "multipart/mixed", but the
+ * semantics are different. In particular, in a parallel entity, the order
+ * of body parts is not significant.
+ */
+ public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");
+
+ /**
+ * The media-type multipart/form-data follows the rules of all multipart
+ * MIME data streams as outlined in RFC 2046. In forms, there are a series
+ * of fields to be supplied by the user who fills out the form. Each field
+ * has a name. Within a given form, the names are unique.
+ */
+ public static final MediaType FORM = MediaType.parse("multipart/form-data");
+
+ private static final byte[] COLONSPACE = { ':', ' ' };
+ private static final byte[] CRLF = { '\r', '\n' };
+ private static final byte[] DASHDASH = { '-', '-' };
+
+ private final ByteString boundary;
+ private MediaType type = MIXED;
+
+ // Parallel lists of nullable headers and non-null bodies.
+ private final List<Headers> partHeaders = new ArrayList<>();
+ private final List<RequestBody> partBodies = new ArrayList<>();
+
+ /** Creates a new multipart builder that uses a random boundary token. */
+ public MultipartBuilder() {
+ this(UUID.randomUUID().toString());
+ }
+
+ /**
+ * Creates a new multipart builder that uses {@code boundary} to separate
+ * parts. Prefer the no-argument constructor to defend against injection
+ * attacks.
+ */
+ public MultipartBuilder(String boundary) {
+ this.boundary = ByteString.encodeUtf8(boundary);
+ }
+
+ /**
+ * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the
+ * default), {@link #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and
+ * {@link #FORM}.
+ */
+ public MultipartBuilder type(MediaType type) {
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+ if (!type.type().equals("multipart")) {
+ throw new IllegalArgumentException("multipart != " + type);
+ }
+ this.type = type;
+ return this;
+ }
+
+ /** Add a part to the body. */
+ public MultipartBuilder addPart(RequestBody body) {
+ return addPart(null, body);
+ }
+
+ /** Add a part to the body. */
+ public MultipartBuilder addPart(Headers headers, RequestBody body) {
+ if (body == null) {
+ throw new NullPointerException("body == null");
+ }
+ if (headers != null && headers.get("Content-Type") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Type");
+ }
+ if (headers != null && headers.get("Content-Length") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Length");
+ }
+
+ partHeaders.add(headers);
+ partBodies.add(body);
+ return this;
+ }
+
+ /**
+ * Appends a quoted-string to a StringBuilder.
+ *
+ * <p>RFC 2388 is rather vague about how one should escape special characters
+ * in form-data parameters, and as it turns out Firefox and Chrome actually
+ * do rather different things, and both say in their comments that they're
+ * not really sure what the right approach is. We go with Chrome's behavior
+ * (which also experimentally seems to match what IE does), but if you
+ * actually want to have a good chance of things working, please avoid
+ * double-quotes, newlines, percent signs, and the like in your field names.
+ */
+ private static StringBuilder appendQuotedString(StringBuilder target, String key) {
+ target.append('"');
+ for (int i = 0, len = key.length(); i < len; i++) {
+ char ch = key.charAt(i);
+ switch (ch) {
+ case '\n':
+ target.append("%0A");
+ break;
+ case '\r':
+ target.append("%0D");
+ break;
+ case '"':
+ target.append("%22");
+ break;
+ default:
+ target.append(ch);
+ break;
+ }
+ }
+ target.append('"');
+ return target;
+ }
+
+ /** Add a form data part to the body. */
+ public MultipartBuilder addFormDataPart(String name, String value) {
+ return addFormDataPart(name, null, RequestBody.create(null, value));
+ }
+
+ /** Add a form data part to the body. */
+ public MultipartBuilder addFormDataPart(String name, String filename, RequestBody value) {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ StringBuilder disposition = new StringBuilder("form-data; name=");
+ appendQuotedString(disposition, name);
+
+ if (filename != null) {
+ disposition.append("; filename=");
+ appendQuotedString(disposition, filename);
+ }
+
+ return addPart(Headers.of("Content-Disposition", disposition.toString()), value);
+ }
+
+ /** Assemble the specified parts into a request body. */
+ public RequestBody build() {
+ if (partHeaders.isEmpty()) {
+ throw new IllegalStateException("Multipart body must have at least one part.");
+ }
+ return new MultipartRequestBody(type, boundary, partHeaders, partBodies);
+ }
+
+ private static final class MultipartRequestBody extends RequestBody {
+ private final ByteString boundary;
+ private final MediaType contentType;
+ private final List<Headers> partHeaders;
+ private final List<RequestBody> partBodies;
+ private long contentLength = -1L;
+
+ public MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders,
+ List<RequestBody> partBodies) {
+ if (type == null) throw new NullPointerException("type == null");
+
+ this.boundary = boundary;
+ this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
+ this.partHeaders = Util.immutableList(partHeaders);
+ this.partBodies = Util.immutableList(partBodies);
+ }
+
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() throws IOException {
+ long result = contentLength;
+ if (result != -1L) return result;
+ return contentLength = writeOrCountBytes(null, true);
+ }
+
+ /**
+ * Either writes this request to {@code sink} or measures its content length. We have one method
+ * do double-duty to make sure the counting and content are consistent, particularly when it
+ * comes to awkward operations like measuring the encoded length of header strings, or the
+ * length-in-digits of an encoded integer.
+ */
+ private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
+ long byteCount = 0L;
+
+ Buffer byteCountBuffer = null;
+ if (countBytes) {
+ sink = byteCountBuffer = new Buffer();
+ }
+
+ for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
+ Headers headers = partHeaders.get(p);
+ RequestBody body = partBodies.get(p);
+
+ sink.write(DASHDASH);
+ sink.write(boundary);
+ sink.write(CRLF);
+
+ if (headers != null) {
+ for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
+ sink.writeUtf8(headers.name(h))
+ .write(COLONSPACE)
+ .writeUtf8(headers.value(h))
+ .write(CRLF);
+ }
+ }
+
+ MediaType contentType = body.contentType();
+ if (contentType != null) {
+ sink.writeUtf8("Content-Type: ")
+ .writeUtf8(contentType.toString())
+ .write(CRLF);
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength != -1) {
+ sink.writeUtf8("Content-Length: ")
+ .writeDecimalLong(contentLength)
+ .write(CRLF);
+ } else if (countBytes) {
+ // We can't measure the body's size without the sizes of its components.
+ byteCountBuffer.clear();
+ return -1L;
+ }
+
+ sink.write(CRLF);
+
+ if (countBytes) {
+ byteCount += contentLength;
+ } else {
+ partBodies.get(p).writeTo(sink);
+ }
+
+ sink.write(CRLF);
+ }
+
+ sink.write(DASHDASH);
+ sink.write(boundary);
+ sink.write(DASHDASH);
+ sink.write(CRLF);
+
+ if (countBytes) {
+ byteCount += byteCountBuffer.size();
+ byteCountBuffer.clear();
+ }
+
+ return byteCount;
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ writeOrCountBytes(sink, false);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java b/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java
new file mode 100644
index 0000000..b6698d3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java
@@ -0,0 +1,647 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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.android.okhttp;
+
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.AuthenticatorAdapter;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import com.android.okhttp.internal.tls.OkHostnameVerifier;
+import java.net.CookieHandler;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Configures and creates HTTP connections. Most applications can use a single
+ * OkHttpClient for all of their HTTP requests - benefiting from a shared
+ * response cache, thread pool, connection re-use, etc.
+ *
+ * <p>Instances of OkHttpClient are intended to be fully configured before they're
+ * shared - once shared they should be treated as immutable and can safely be used
+ * to concurrently open new connections. If required, threads can call
+ * {@link #clone()} to make a shallow copy of the OkHttpClient that can be
+ * safely modified with further configuration changes.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OkHttpClient implements Cloneable {
+ private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
+ Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
+
+ private static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
+ ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT);
+
+ static {
+ Internal.instance = new Internal() {
+ @Override public void addLenient(Headers.Builder builder, String line) {
+ builder.addLenient(line);
+ }
+
+ @Override public void addLenient(Headers.Builder builder, String name, String value) {
+ builder.addLenient(name, value);
+ }
+
+ @Override public void setCache(OkHttpClient client, InternalCache internalCache) {
+ client.setInternalCache(internalCache);
+ }
+
+ @Override public InternalCache internalCache(OkHttpClient client) {
+ return client.internalCache();
+ }
+
+ @Override public boolean connectionBecameIdle(
+ ConnectionPool pool, RealConnection connection) {
+ return pool.connectionBecameIdle(connection);
+ }
+
+ @Override public RealConnection get(
+ ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
+ return pool.get(address, streamAllocation);
+ }
+
+ @Override public void put(ConnectionPool pool, RealConnection connection) {
+ pool.put(connection);
+ }
+
+ @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
+ return connectionPool.routeDatabase;
+ }
+
+ @Override
+ public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
+ call.enqueue(responseCallback, forWebSocket);
+ }
+
+ @Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
+ return call.engine.streamAllocation;
+ }
+
+ @Override
+ public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
+ tlsConfiguration.apply(sslSocket, isFallback);
+ }
+
+ @Override public HttpUrl getHttpUrlChecked(String url)
+ throws MalformedURLException, UnknownHostException {
+ return HttpUrl.getChecked(url);
+ }
+ };
+ }
+
+ /** Lazily-initialized. */
+ private static SSLSocketFactory defaultSslSocketFactory;
+
+ private final RouteDatabase routeDatabase;
+ private Dispatcher dispatcher;
+ private Proxy proxy;
+ private List<Protocol> protocols;
+ private List<ConnectionSpec> connectionSpecs;
+ private final List<Interceptor> interceptors = new ArrayList<>();
+ private final List<Interceptor> networkInterceptors = new ArrayList<>();
+ private ProxySelector proxySelector;
+ private CookieHandler cookieHandler;
+
+ /** Non-null if this client is caching; possibly by {@code cache}. */
+ private InternalCache internalCache;
+ private Cache cache;
+
+ private SocketFactory socketFactory;
+ private SSLSocketFactory sslSocketFactory;
+ private HostnameVerifier hostnameVerifier;
+ private CertificatePinner certificatePinner;
+ private Authenticator authenticator;
+ private ConnectionPool connectionPool;
+ private Dns dns;
+ private boolean followSslRedirects = true;
+ private boolean followRedirects = true;
+ private boolean retryOnConnectionFailure = true;
+ private int connectTimeout = 10_000;
+ private int readTimeout = 10_000;
+ private int writeTimeout = 10_000;
+
+ public OkHttpClient() {
+ routeDatabase = new RouteDatabase();
+ dispatcher = new Dispatcher();
+ }
+
+ private OkHttpClient(OkHttpClient okHttpClient) {
+ this.routeDatabase = okHttpClient.routeDatabase;
+ this.dispatcher = okHttpClient.dispatcher;
+ this.proxy = okHttpClient.proxy;
+ this.protocols = okHttpClient.protocols;
+ this.connectionSpecs = okHttpClient.connectionSpecs;
+ this.interceptors.addAll(okHttpClient.interceptors);
+ this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
+ this.proxySelector = okHttpClient.proxySelector;
+ this.cookieHandler = okHttpClient.cookieHandler;
+ this.cache = okHttpClient.cache;
+ this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache;
+ this.socketFactory = okHttpClient.socketFactory;
+ this.sslSocketFactory = okHttpClient.sslSocketFactory;
+ this.hostnameVerifier = okHttpClient.hostnameVerifier;
+ this.certificatePinner = okHttpClient.certificatePinner;
+ this.authenticator = okHttpClient.authenticator;
+ this.connectionPool = okHttpClient.connectionPool;
+ this.dns = okHttpClient.dns;
+ this.followSslRedirects = okHttpClient.followSslRedirects;
+ this.followRedirects = okHttpClient.followRedirects;
+ this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
+ this.connectTimeout = okHttpClient.connectTimeout;
+ this.readTimeout = okHttpClient.readTimeout;
+ this.writeTimeout = okHttpClient.writeTimeout;
+ }
+
+ /**
+ * Sets the default connect timeout for new connections. A value of 0 means no timeout, otherwise
+ * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+ *
+ * @see URLConnection#setConnectTimeout(int)
+ */
+ public void setConnectTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+ connectTimeout = (int) millis;
+ }
+
+ /** Default connect timeout (in milliseconds). */
+ public int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /**
+ * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
+ * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+ *
+ * @see URLConnection#setReadTimeout(int)
+ */
+ public void setReadTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+ readTimeout = (int) millis;
+ }
+
+ /** Default read timeout (in milliseconds). */
+ public int getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
+ * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+ */
+ public void setWriteTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+ writeTimeout = (int) millis;
+ }
+
+ /** Default write timeout (in milliseconds). */
+ public int getWriteTimeout() {
+ return writeTimeout;
+ }
+
+ /**
+ * Sets the HTTP proxy that will be used by connections created by this
+ * client. This takes precedence over {@link #setProxySelector}, which is
+ * only honored when this proxy is null (which it is by default). To disable
+ * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+ */
+ public OkHttpClient setProxy(Proxy proxy) {
+ this.proxy = proxy;
+ return this;
+ }
+
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ /**
+ * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+ * is specified explicitly. The proxy selector may return multiple proxies;
+ * in that case they will be tried in sequence until a successful connection
+ * is established.
+ *
+ * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
+ * proxy selector will be used.
+ */
+ public OkHttpClient setProxySelector(ProxySelector proxySelector) {
+ this.proxySelector = proxySelector;
+ return this;
+ }
+
+ public ProxySelector getProxySelector() {
+ return proxySelector;
+ }
+
+ /**
+ * Sets the cookie handler to be used to read outgoing cookies and write
+ * incoming cookies.
+ *
+ * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
+ * cookie handler will be used.
+ */
+ public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+ this.cookieHandler = cookieHandler;
+ return this;
+ }
+
+ public CookieHandler getCookieHandler() {
+ return cookieHandler;
+ }
+
+ /** Sets the response cache to be used to read and write cached responses. */
+ void setInternalCache(InternalCache internalCache) {
+ this.internalCache = internalCache;
+ this.cache = null;
+ }
+
+ InternalCache internalCache() {
+ return internalCache;
+ }
+
+ public OkHttpClient setCache(Cache cache) {
+ this.cache = cache;
+ this.internalCache = null;
+ return this;
+ }
+
+ public Cache getCache() {
+ return cache;
+ }
+
+ /**
+ * Sets the DNS service used to lookup IP addresses for hostnames.
+ *
+ * <p>If unset, the {@link Dns#SYSTEM system-wide default} DNS will be used.
+ */
+ public OkHttpClient setDns(Dns dns) {
+ this.dns = dns;
+ return this;
+ }
+
+ public Dns getDns() {
+ return dns;
+ }
+
+ /**
+ * Sets the socket factory used to create connections. OkHttp only uses
+ * the parameterless {@link SocketFactory#createSocket() createSocket()}
+ * method to create unconnected sockets. Overriding this method,
+ * e. g., allows the socket to be bound to a specific local address.
+ *
+ * <p>If unset, the {@link SocketFactory#getDefault() system-wide default}
+ * socket factory will be used.
+ */
+ public OkHttpClient setSocketFactory(SocketFactory socketFactory) {
+ this.socketFactory = socketFactory;
+ return this;
+ }
+
+ public SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /**
+ * Sets the socket factory used to secure HTTPS connections.
+ *
+ * <p>If unset, a lazily created SSL socket factory will be used.
+ */
+ public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
+ this.sslSocketFactory = sslSocketFactory;
+ return this;
+ }
+
+ public SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /**
+ * Sets the verifier used to confirm that response certificates apply to
+ * requested hostnames for HTTPS connections.
+ *
+ * <p>If unset, a default hostname verifier will be used.
+ */
+ public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ return this;
+ }
+
+ public HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ /**
+ * Sets the certificate pinner that constrains which certificates are trusted.
+ * By default HTTPS connections rely on only the {@link #setSslSocketFactory
+ * SSL socket factory} to establish trust. Pinning certificates avoids the
+ * need to trust certificate authorities.
+ */
+ public OkHttpClient setCertificatePinner(CertificatePinner certificatePinner) {
+ this.certificatePinner = certificatePinner;
+ return this;
+ }
+
+ public CertificatePinner getCertificatePinner() {
+ return certificatePinner;
+ }
+
+ /**
+ * Sets the authenticator used to respond to challenges from the remote web
+ * server or proxy server.
+ *
+ * <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}
+ * authenticator will be used.
+ */
+ public OkHttpClient setAuthenticator(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+ /**
+ * Sets the connection pool used to recycle HTTP and HTTPS connections.
+ *
+ * <p>If unset, the {@link ConnectionPool#getDefault() system-wide
+ * default} connection pool will be used.
+ */
+ public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+ this.connectionPool = connectionPool;
+ return this;
+ }
+
+ public ConnectionPool getConnectionPool() {
+ return connectionPool;
+ }
+
+ /**
+ * Configure this client to follow redirects from HTTPS to HTTP and from HTTP
+ * to HTTPS.
+ *
+ * <p>If unset, protocol redirects will be followed. This is different than
+ * the built-in {@code HttpURLConnection}'s default.
+ */
+ public OkHttpClient setFollowSslRedirects(boolean followProtocolRedirects) {
+ this.followSslRedirects = followProtocolRedirects;
+ return this;
+ }
+
+ public boolean getFollowSslRedirects() {
+ return followSslRedirects;
+ }
+
+ /** Configure this client to follow redirects. If unset, redirects be followed. */
+ public void setFollowRedirects(boolean followRedirects) {
+ this.followRedirects = followRedirects;
+ }
+
+ public boolean getFollowRedirects() {
+ return followRedirects;
+ }
+
+ /**
+ * Configure this client to retry or not when a connectivity problem is encountered. By default,
+ * this client silently recovers from the following problems:
+ *
+ * <ul>
+ * <li><strong>Unreachable IP addresses.</strong> If the URL's host has multiple IP addresses,
+ * failure to reach any individual IP address doesn't fail the overall request. This can
+ * increase availability of multi-homed services.
+ * <li><strong>Stale pooled connections.</strong> The {@link ConnectionPool} reuses sockets
+ * to decrease request latency, but these connections will occasionally time out.
+ * <li><strong>Unreachable proxy servers.</strong> A {@link ProxySelector} can be used to
+ * attempt multiple proxy servers in sequence, eventually falling back to a direct
+ * connection.
+ * </ul>
+ *
+ * Set this to false to avoid retrying requests when doing so is destructive. In this case the
+ * calling application should do its own recovery of connectivity failures.
+ */
+ public void setRetryOnConnectionFailure(boolean retryOnConnectionFailure) {
+ this.retryOnConnectionFailure = retryOnConnectionFailure;
+ }
+
+ public boolean getRetryOnConnectionFailure() {
+ return retryOnConnectionFailure;
+ }
+
+ RouteDatabase routeDatabase() {
+ return routeDatabase;
+ }
+
+ /**
+ * Sets the dispatcher used to set policy and execute asynchronous requests.
+ * Must not be null.
+ */
+ public OkHttpClient setDispatcher(Dispatcher dispatcher) {
+ if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null");
+ this.dispatcher = dispatcher;
+ return this;
+ }
+
+ public Dispatcher getDispatcher() {
+ return dispatcher;
+ }
+
+ /**
+ * Configure the protocols used by this client to communicate with remote
+ * servers. By default this client will prefer the most efficient transport
+ * available, falling back to more ubiquitous protocols. Applications should
+ * only call this method to avoid specific compatibility problems, such as web
+ * servers that behave incorrectly when SPDY is enabled.
+ *
+ * <p>The following protocols are currently supported:
+ * <ul>
+ * <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
+ * <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">spdy/3.1</a>
+ * <li><a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-17">h2</a>
+ * </ul>
+ *
+ * <p><strong>This is an evolving set.</strong> Future releases include
+ * support for transitional protocols. The http/1.1 transport will never be
+ * dropped.
+ *
+ * <p>If multiple protocols are specified, <a
+ * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+ * will be used to negotiate a transport.
+ *
+ * <p>{@link Protocol#HTTP_1_0} is not supported in this set. Requests are
+ * initiated with {@code HTTP/1.1} only. If the server responds with {@code
+ * HTTP/1.0}, that will be exposed by {@link Response#protocol()}.
+ *
+ * @param protocols the protocols to use, in order of preference. The list
+ * must contain {@link Protocol#HTTP_1_1}. It must not contain null or
+ * {@link Protocol#HTTP_1_0}.
+ */
+ public OkHttpClient setProtocols(List<Protocol> protocols) {
+ protocols = Util.immutableList(protocols);
+ if (!protocols.contains(Protocol.HTTP_1_1)) {
+ throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
+ }
+ if (protocols.contains(Protocol.HTTP_1_0)) {
+ throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
+ }
+ if (protocols.contains(null)) {
+ throw new IllegalArgumentException("protocols must not contain null");
+ }
+ this.protocols = Util.immutableList(protocols);
+ return this;
+ }
+
+ public List<Protocol> getProtocols() {
+ return protocols;
+ }
+
+ public OkHttpClient setConnectionSpecs(List<ConnectionSpec> connectionSpecs) {
+ this.connectionSpecs = Util.immutableList(connectionSpecs);
+ return this;
+ }
+
+ public List<ConnectionSpec> getConnectionSpecs() {
+ return connectionSpecs;
+ }
+
+ /**
+ * Returns a modifiable list of interceptors that observe the full span of each call: from before
+ * the connection is established (if any) until after the response source is selected (either the
+ * origin server, cache, or both).
+ */
+ public List<Interceptor> interceptors() {
+ return interceptors;
+ }
+
+ /**
+ * Returns a modifiable list of interceptors that observe a single network request and response.
+ * These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error for
+ * a network interceptor to short-circuit or repeat a network request.
+ */
+ public List<Interceptor> networkInterceptors() {
+ return networkInterceptors;
+ }
+
+ /**
+ * Prepares the {@code request} to be executed at some point in the future.
+ */
+ public Call newCall(Request request) {
+ return new Call(this, request);
+ }
+
+ /**
+ * Cancels all scheduled or in-flight calls tagged with {@code tag}. Requests
+ * that are already complete cannot be canceled.
+ */
+ public OkHttpClient cancel(Object tag) {
+ getDispatcher().cancel(tag);
+ return this;
+ }
+
+ /**
+ * Returns a shallow copy of this OkHttpClient that uses the system-wide
+ * default for each field that hasn't been explicitly configured.
+ */
+ OkHttpClient copyWithDefaults() {
+ OkHttpClient result = new OkHttpClient(this);
+ if (result.proxySelector == null) {
+ result.proxySelector = ProxySelector.getDefault();
+ }
+ if (result.cookieHandler == null) {
+ result.cookieHandler = CookieHandler.getDefault();
+ }
+ if (result.socketFactory == null) {
+ result.socketFactory = SocketFactory.getDefault();
+ }
+ if (result.sslSocketFactory == null) {
+ result.sslSocketFactory = getDefaultSSLSocketFactory();
+ }
+ if (result.hostnameVerifier == null) {
+ result.hostnameVerifier = OkHostnameVerifier.INSTANCE;
+ }
+ if (result.certificatePinner == null) {
+ result.certificatePinner = CertificatePinner.DEFAULT;
+ }
+ if (result.authenticator == null) {
+ result.authenticator = AuthenticatorAdapter.INSTANCE;
+ }
+ if (result.connectionPool == null) {
+ result.connectionPool = ConnectionPool.getDefault();
+ }
+ if (result.protocols == null) {
+ result.protocols = DEFAULT_PROTOCOLS;
+ }
+ if (result.connectionSpecs == null) {
+ result.connectionSpecs = DEFAULT_CONNECTION_SPECS;
+ }
+ if (result.dns == null) {
+ result.dns = Dns.SYSTEM;
+ }
+ return result;
+ }
+
+ /**
+ * Java and Android programs default to using a single global SSL context,
+ * accessible to HTTP clients as {@link SSLSocketFactory#getDefault()}. If we
+ * used the shared SSL context, when OkHttp enables ALPN for its SPDY-related
+ * stuff, it would also enable ALPN for other usages, which might crash them
+ * because ALPN is enabled when it isn't expected to be.
+ *
+ * <p>This code avoids that by defaulting to an OkHttp-created SSL context.
+ * The drawback of this approach is that apps that customize the global SSL
+ * context will lose these customizations.
+ */
+ private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
+ if (defaultSslSocketFactory == null) {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, null, null);
+ defaultSslSocketFactory = sslContext.getSocketFactory();
+ } catch (GeneralSecurityException e) {
+ throw new AssertionError(); // The system has no TLS. Just give up.
+ }
+ }
+ return defaultSslSocketFactory;
+ }
+
+ /** Returns a shallow copy of this OkHttpClient. */
+ @Override public OkHttpClient clone() {
+ return new OkHttpClient(this);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java
new file mode 100644
index 0000000..3ddb8bf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java
@@ -0,0 +1,99 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * Protocols that OkHttp implements for <a
+ * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+ * selection.
+ *
+ * <h3>Protocol vs Scheme</h3>
+ * Despite its name, {@link java.net.URL#getProtocol()} returns the
+ * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
+ * the protocol (http/1.1, spdy/3.1, etc.). OkHttp uses the word <i>protocol</i>
+ * to identify how HTTP messages are framed.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum Protocol {
+ /**
+ * An obsolete plaintext framing that does not use persistent sockets by
+ * default.
+ */
+ HTTP_1_0("http/1.0"),
+
+ /**
+ * A plaintext framing that includes persistent connections.
+ *
+ * <p>This version of OkHttp implements <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>, and tracks
+ * revisions to that spec.
+ */
+ HTTP_1_1("http/1.1"),
+
+ /**
+ * Chromium's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on SPDY/3.
+ *
+ * <p>This version of OkHttp implements SPDY 3 <a
+ * href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">draft
+ * 3.1</a>. Future releases of OkHttp may use this identifier for a newer draft
+ * of the SPDY spec.
+ */
+ SPDY_3("spdy/3.1"),
+
+ /**
+ * The IETF's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on HTTP/2.
+ *
+ * <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support
+ * {@linkplain com.android.okhttp.CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
+ * , present in Java 8+ and Android 5+. Servers that enforce this may send an
+ * exception message including the string {@code INADEQUATE_SECURITY}.
+ */
+ HTTP_2("h2");
+
+ private final String protocol;
+
+ Protocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ /**
+ * Returns the protocol identified by {@code protocol}.
+ * @throws IOException if {@code protocol} is unknown.
+ */
+ public static Protocol get(String protocol) throws IOException {
+ // Unroll the loop over values() to save an allocation.
+ if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
+ if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
+ if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
+ if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
+ throw new IOException("Unexpected protocol: " + protocol);
+ }
+
+ /**
+ * Returns the string used to identify this protocol for ALPN, like
+ * "http/1.1", "spdy/3.1" or "h2".
+ */
+ @Override public String toString() {
+ return protocol;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java
new file mode 100644
index 0000000..e16496e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java
@@ -0,0 +1,286 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import com.android.okhttp.internal.http.HttpMethod;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * An HTTP request. Instances of this class are immutable if their {@link #body}
+ * is null or itself immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Request {
+ private final HttpUrl url;
+ private final String method;
+ private final Headers headers;
+ private final RequestBody body;
+ private final Object tag;
+
+ private volatile URL javaNetUrl; // Lazily initialized.
+ private volatile URI javaNetUri; // Lazily initialized.
+ private volatile CacheControl cacheControl; // Lazily initialized.
+
+ private Request(Builder builder) {
+ this.url = builder.url;
+ this.method = builder.method;
+ this.headers = builder.headers.build();
+ this.body = builder.body;
+ this.tag = builder.tag != null ? builder.tag : this;
+ }
+
+ public HttpUrl httpUrl() {
+ return url;
+ }
+
+ public URL url() {
+ URL result = javaNetUrl;
+ return result != null ? result : (javaNetUrl = url.url());
+ }
+
+ public URI uri() throws IOException {
+ try {
+ URI result = javaNetUri;
+ return result != null ? result : (javaNetUri = url.uri());
+ } catch (IllegalStateException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public String urlString() {
+ return url.toString();
+ }
+
+ public String method() {
+ return method;
+ }
+
+ public Headers headers() {
+ return headers;
+ }
+
+ public String header(String name) {
+ return headers.get(name);
+ }
+
+ public List<String> headers(String name) {
+ return headers.values(name);
+ }
+
+ public RequestBody body() {
+ return body;
+ }
+
+ public Object tag() {
+ return tag;
+ }
+
+ public Builder newBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
+ public boolean isHttps() {
+ return url.isHttps();
+ }
+
+ @Override public String toString() {
+ return "Request{method="
+ + method
+ + ", url="
+ + url
+ + ", tag="
+ + (tag != this ? tag : null)
+ + '}';
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static class Builder {
+ private HttpUrl url;
+ private String method;
+ private Headers.Builder headers;
+ private RequestBody body;
+ private Object tag;
+
+ public Builder() {
+ this.method = "GET";
+ this.headers = new Headers.Builder();
+ }
+
+ private Builder(Request request) {
+ this.url = request.url;
+ this.method = request.method;
+ this.body = request.body;
+ this.tag = request.tag;
+ this.headers = request.headers.newBuilder();
+ }
+
+ public Builder url(HttpUrl url) {
+ if (url == null) throw new IllegalArgumentException("url == null");
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Sets the URL target of this request.
+ *
+ * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
+ * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
+ */
+ public Builder url(String url) {
+ if (url == null) throw new IllegalArgumentException("url == null");
+
+ // Silently replace websocket URLs with HTTP URLs.
+ if (url.regionMatches(true, 0, "ws:", 0, 3)) {
+ url = "http:" + url.substring(3);
+ } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
+ url = "https:" + url.substring(4);
+ }
+
+ HttpUrl parsed = HttpUrl.parse(url);
+ if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
+ return url(parsed);
+ }
+
+ /**
+ * Sets the URL target of this request.
+ *
+ * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
+ * https}.
+ */
+ public Builder url(URL url) {
+ if (url == null) throw new IllegalArgumentException("url == null");
+ HttpUrl parsed = HttpUrl.get(url);
+ if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
+ return url(parsed);
+ }
+
+ /**
+ * Sets the header named {@code name} to {@code value}. If this request
+ * already has any headers with that name, they are all replaced.
+ */
+ public Builder header(String name, String value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ /**
+ * Adds a header with {@code name} and {@code value}. Prefer this method for
+ * multiply-valued headers like "Cookie".
+ *
+ * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
+ * OkHttp may replace {@code value} with a header derived from the request body.
+ */
+ public Builder addHeader(String name, String value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public Builder removeHeader(String name) {
+ headers.removeAll(name);
+ return this;
+ }
+
+ /** Removes all headers on this builder and adds {@code headers}. */
+ public Builder headers(Headers headers) {
+ this.headers = headers.newBuilder();
+ return this;
+ }
+
+ /**
+ * Sets this request's {@code Cache-Control} header, replacing any cache
+ * control headers already present. If {@code cacheControl} doesn't define
+ * any directives, this clears this request's cache-control headers.
+ */
+ public Builder cacheControl(CacheControl cacheControl) {
+ String value = cacheControl.toString();
+ if (value.isEmpty()) return removeHeader("Cache-Control");
+ return header("Cache-Control", value);
+ }
+
+ public Builder get() {
+ return method("GET", null);
+ }
+
+ public Builder head() {
+ return method("HEAD", null);
+ }
+
+ public Builder post(RequestBody body) {
+ return method("POST", body);
+ }
+
+ public Builder delete(RequestBody body) {
+ return method("DELETE", body);
+ }
+
+ public Builder delete() {
+ return delete(RequestBody.create(null, new byte[0]));
+ }
+
+ public Builder put(RequestBody body) {
+ return method("PUT", body);
+ }
+
+ public Builder patch(RequestBody body) {
+ return method("PATCH", body);
+ }
+
+ public Builder method(String method, RequestBody body) {
+ if (method == null || method.length() == 0) {
+ throw new IllegalArgumentException("method == null || method.length() == 0");
+ }
+ if (body != null && !HttpMethod.permitsRequestBody(method)) {
+ throw new IllegalArgumentException("method " + method + " must not have a request body.");
+ }
+ if (body == null && HttpMethod.requiresRequestBody(method)) {
+ throw new IllegalArgumentException("method " + method + " must have a request body.");
+ }
+ this.method = method;
+ this.body = body;
+ return this;
+ }
+
+ /**
+ * Attaches {@code tag} to the request. It can be used later to cancel the
+ * request. If the tag is unspecified or null, the request is canceled by
+ * using the request itself as the tag.
+ */
+ public Builder tag(Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ public Request build() {
+ if (url == null) throw new IllegalStateException("url == null");
+ return new Request(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java
new file mode 100644
index 0000000..a762d8a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java
@@ -0,0 +1,129 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class RequestBody {
+ /** Returns the Content-Type header for this body. */
+ public abstract MediaType contentType();
+
+ /**
+ * Returns the number of bytes that will be written to {@code out} in a call
+ * to {@link #writeTo}, or -1 if that count is unknown.
+ */
+ public long contentLength() throws IOException {
+ return -1;
+ }
+
+ /** Writes the content of this request to {@code out}. */
+ public abstract void writeTo(BufferedSink sink) throws IOException;
+
+ /**
+ * Returns a new request body that transmits {@code content}. If {@code
+ * contentType} is non-null and lacks a charset, this will use UTF-8.
+ */
+ public static RequestBody create(MediaType contentType, String content) {
+ Charset charset = Util.UTF_8;
+ if (contentType != null) {
+ charset = contentType.charset();
+ if (charset == null) {
+ charset = Util.UTF_8;
+ contentType = MediaType.parse(contentType + "; charset=utf-8");
+ }
+ }
+ byte[] bytes = content.getBytes(charset);
+ return create(contentType, bytes);
+ }
+
+ /** Returns a new request body that transmits {@code content}. */
+ public static RequestBody create(final MediaType contentType, final ByteString content) {
+ return new RequestBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() throws IOException {
+ return content.size();
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ sink.write(content);
+ }
+ };
+ }
+
+ /** Returns a new request body that transmits {@code content}. */
+ public static RequestBody create(final MediaType contentType, final byte[] content) {
+ return create(contentType, content, 0, content.length);
+ }
+
+ /** Returns a new request body that transmits {@code content}. */
+ public static RequestBody create(final MediaType contentType, final byte[] content,
+ final int offset, final int byteCount) {
+ if (content == null) throw new NullPointerException("content == null");
+ Util.checkOffsetAndCount(content.length, offset, byteCount);
+ return new RequestBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() {
+ return byteCount;
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ sink.write(content, offset, byteCount);
+ }
+ };
+ }
+
+ /** Returns a new request body that transmits the content of {@code file}. */
+ public static RequestBody create(final MediaType contentType, final File file) {
+ if (file == null) throw new NullPointerException("content == null");
+
+ return new RequestBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() {
+ return file.length();
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ Source source = null;
+ try {
+ source = Okio.source(file);
+ sink.writeAll(source);
+ } finally {
+ Util.closeQuietly(source);
+ }
+ }
+ };
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java
new file mode 100644
index 0000000..f4bba1b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java
@@ -0,0 +1,357 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import com.android.okhttp.internal.http.OkHeaders;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+/**
+ * An HTTP response. Instances of this class are not immutable: the response
+ * body is a one-shot value that may be consumed only once. All other properties
+ * are immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Response {
+ private final Request request;
+ private final Protocol protocol;
+ private final int code;
+ private final String message;
+ private final Handshake handshake;
+ private final Headers headers;
+ private final ResponseBody body;
+ private Response networkResponse;
+ private Response cacheResponse;
+ private final Response priorResponse;
+
+ private volatile CacheControl cacheControl; // Lazily initialized.
+
+ private Response(Builder builder) {
+ this.request = builder.request;
+ this.protocol = builder.protocol;
+ this.code = builder.code;
+ this.message = builder.message;
+ this.handshake = builder.handshake;
+ this.headers = builder.headers.build();
+ this.body = builder.body;
+ this.networkResponse = builder.networkResponse;
+ this.cacheResponse = builder.cacheResponse;
+ this.priorResponse = builder.priorResponse;
+ }
+
+ /**
+ * The wire-level request that initiated this HTTP response. This is not
+ * necessarily the same request issued by the application:
+ * <ul>
+ * <li>It may be transformed by the HTTP client. For example, the client
+ * may copy headers like {@code Content-Length} from the request body.
+ * <li>It may be the request generated in response to an HTTP redirect or
+ * authentication challenge. In this case the request URL may be
+ * different than the initial request URL.
+ * </ul>
+ */
+ public Request request() {
+ return request;
+ }
+
+ /**
+ * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link
+ * Protocol#HTTP_1_0}.
+ */
+ public Protocol protocol() {
+ return protocol;
+ }
+
+ /** Returns the HTTP status code. */
+ public int code() {
+ return code;
+ }
+
+ /**
+ * Returns true if the code is in [200..300), which means the request was
+ * successfully received, understood, and accepted.
+ */
+ public boolean isSuccessful() {
+ return code >= 200 && code < 300;
+ }
+
+ /** Returns the HTTP status message or null if it is unknown. */
+ public String message() {
+ return message;
+ }
+
+ /**
+ * Returns the TLS handshake of the connection that carried this response, or
+ * null if the response was received without TLS.
+ */
+ public Handshake handshake() {
+ return handshake;
+ }
+
+ public List<String> headers(String name) {
+ return headers.values(name);
+ }
+
+ public String header(String name) {
+ return header(name, null);
+ }
+
+ public String header(String name, String defaultValue) {
+ String result = headers.get(name);
+ return result != null ? result : defaultValue;
+ }
+
+ public Headers headers() {
+ return headers;
+ }
+
+ public ResponseBody body() {
+ return body;
+ }
+
+ public Builder newBuilder() {
+ return new Builder(this);
+ }
+
+ /** Returns true if this response redirects to another resource. */
+ public boolean isRedirect() {
+ switch (code) {
+ case HTTP_PERM_REDIRECT:
+ case HTTP_TEMP_REDIRECT:
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_MOVED_TEMP:
+ case HTTP_SEE_OTHER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns the raw response received from the network. Will be null if this
+ * response didn't use the network, such as when the response is fully cached.
+ * The body of the returned response should not be read.
+ */
+ public Response networkResponse() {
+ return networkResponse;
+ }
+
+ /**
+ * Returns the raw response received from the cache. Will be null if this
+ * response didn't use the cache. For conditional get requests the cache
+ * response and network response may both be non-null. The body of the
+ * returned response should not be read.
+ */
+ public Response cacheResponse() {
+ return cacheResponse;
+ }
+
+ /**
+ * Returns the response for the HTTP redirect or authorization challenge that
+ * triggered this response, or null if this response wasn't triggered by an
+ * automatic retry. The body of the returned response should not be read
+ * because it has already been consumed by the redirecting client.
+ */
+ public Response priorResponse() {
+ return priorResponse;
+ }
+
+ /**
+ * Returns the authorization challenges appropriate for this response's code.
+ * If the response code is 401 unauthorized, this returns the
+ * "WWW-Authenticate" challenges. If the response code is 407 proxy
+ * unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise
+ * this returns an empty list of challenges.
+ */
+ public List<Challenge> challenges() {
+ String responseField;
+ if (code == HTTP_UNAUTHORIZED) {
+ responseField = "WWW-Authenticate";
+ } else if (code == HTTP_PROXY_AUTH) {
+ responseField = "Proxy-Authenticate";
+ } else {
+ return Collections.emptyList();
+ }
+ return OkHeaders.parseChallenges(headers(), responseField);
+ }
+
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
+ @Override public String toString() {
+ return "Response{protocol="
+ + protocol
+ + ", code="
+ + code
+ + ", message="
+ + message
+ + ", url="
+ + request.urlString()
+ + '}';
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static class Builder {
+ private Request request;
+ private Protocol protocol;
+ private int code = -1;
+ private String message;
+ private Handshake handshake;
+ private Headers.Builder headers;
+ private ResponseBody body;
+ private Response networkResponse;
+ private Response cacheResponse;
+ private Response priorResponse;
+
+ public Builder() {
+ headers = new Headers.Builder();
+ }
+
+ private Builder(Response response) {
+ this.request = response.request;
+ this.protocol = response.protocol;
+ this.code = response.code;
+ this.message = response.message;
+ this.handshake = response.handshake;
+ this.headers = response.headers.newBuilder();
+ this.body = response.body;
+ this.networkResponse = response.networkResponse;
+ this.cacheResponse = response.cacheResponse;
+ this.priorResponse = response.priorResponse;
+ }
+
+ public Builder request(Request request) {
+ this.request = request;
+ return this;
+ }
+
+ public Builder protocol(Protocol protocol) {
+ this.protocol = protocol;
+ return this;
+ }
+
+ public Builder code(int code) {
+ this.code = code;
+ return this;
+ }
+
+ public Builder message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public Builder handshake(Handshake handshake) {
+ this.handshake = handshake;
+ return this;
+ }
+
+ /**
+ * Sets the header named {@code name} to {@code value}. If this request
+ * already has any headers with that name, they are all replaced.
+ */
+ public Builder header(String name, String value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ /**
+ * Adds a header with {@code name} and {@code value}. Prefer this method for
+ * multiply-valued headers like "Set-Cookie".
+ */
+ public Builder addHeader(String name, String value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public Builder removeHeader(String name) {
+ headers.removeAll(name);
+ return this;
+ }
+
+ /** Removes all headers on this builder and adds {@code headers}. */
+ public Builder headers(Headers headers) {
+ this.headers = headers.newBuilder();
+ return this;
+ }
+
+ public Builder body(ResponseBody body) {
+ this.body = body;
+ return this;
+ }
+
+ public Builder networkResponse(Response networkResponse) {
+ if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse);
+ this.networkResponse = networkResponse;
+ return this;
+ }
+
+ public Builder cacheResponse(Response cacheResponse) {
+ if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse);
+ this.cacheResponse = cacheResponse;
+ return this;
+ }
+
+ private void checkSupportResponse(String name, Response response) {
+ if (response.body != null) {
+ throw new IllegalArgumentException(name + ".body != null");
+ } else if (response.networkResponse != null) {
+ throw new IllegalArgumentException(name + ".networkResponse != null");
+ } else if (response.cacheResponse != null) {
+ throw new IllegalArgumentException(name + ".cacheResponse != null");
+ } else if (response.priorResponse != null) {
+ throw new IllegalArgumentException(name + ".priorResponse != null");
+ }
+ }
+
+ public Builder priorResponse(Response priorResponse) {
+ if (priorResponse != null) checkPriorResponse(priorResponse);
+ this.priorResponse = priorResponse;
+ return this;
+ }
+
+ private void checkPriorResponse(Response response) {
+ if (response.body != null) {
+ throw new IllegalArgumentException("priorResponse.body != null");
+ }
+ }
+
+ public Response build() {
+ if (request == null) throw new IllegalStateException("request == null");
+ if (protocol == null) throw new IllegalStateException("protocol == null");
+ if (code < 0) throw new IllegalStateException("code < 0: " + code);
+ return new Response(this);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java
new file mode 100644
index 0000000..2596a3a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java
@@ -0,0 +1,140 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+
+import static com.android.okhttp.internal.Util.UTF_8;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class ResponseBody implements Closeable {
+ /** Multiple calls to {@link #charStream()} must return the same instance. */
+ private Reader reader;
+
+ public abstract MediaType contentType();
+
+ /**
+ * Returns the number of bytes in that will returned by {@link #bytes}, or
+ * {@link #byteStream}, or -1 if unknown.
+ */
+ public abstract long contentLength() throws IOException;
+
+ public final InputStream byteStream() throws IOException {
+ return source().inputStream();
+ }
+
+ public abstract BufferedSource source() throws IOException;
+
+ public final byte[] bytes() throws IOException {
+ long contentLength = contentLength();
+ if (contentLength > Integer.MAX_VALUE) {
+ throw new IOException("Cannot buffer entire body for content length: " + contentLength);
+ }
+
+ BufferedSource source = source();
+ byte[] bytes;
+ try {
+ bytes = source.readByteArray();
+ } finally {
+ Util.closeQuietly(source);
+ }
+ if (contentLength != -1 && contentLength != bytes.length) {
+ throw new IOException("Content-Length and stream length disagree");
+ }
+ return bytes;
+ }
+
+ /**
+ * Returns the response as a character stream decoded with the charset
+ * of the Content-Type header. If that header is either absent or lacks a
+ * charset, this will attempt to decode the response body as UTF-8.
+ */
+ public final Reader charStream() throws IOException {
+ Reader r = reader;
+ return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
+ }
+
+ /**
+ * Returns the response as a string decoded with the charset of the
+ * Content-Type header. If that header is either absent or lacks a charset,
+ * this will attempt to decode the response body as UTF-8.
+ */
+ public final String string() throws IOException {
+ return new String(bytes(), charset().name());
+ }
+
+ private Charset charset() {
+ MediaType contentType = contentType();
+ return contentType != null ? contentType.charset(UTF_8) : UTF_8;
+ }
+
+ @Override public void close() throws IOException {
+ source().close();
+ }
+
+ /**
+ * Returns a new response body that transmits {@code content}. If {@code
+ * contentType} is non-null and lacks a charset, this will use UTF-8.
+ */
+ public static ResponseBody create(MediaType contentType, String content) {
+ Charset charset = Util.UTF_8;
+ if (contentType != null) {
+ charset = contentType.charset();
+ if (charset == null) {
+ charset = Util.UTF_8;
+ contentType = MediaType.parse(contentType + "; charset=utf-8");
+ }
+ }
+ Buffer buffer = new Buffer().writeString(content, charset);
+ return create(contentType, buffer.size(), buffer);
+ }
+
+ /** Returns a new response body that transmits {@code content}. */
+ public static ResponseBody create(final MediaType contentType, byte[] content) {
+ Buffer buffer = new Buffer().write(content);
+ return create(contentType, content.length, buffer);
+ }
+
+ /** Returns a new response body that transmits {@code content}. */
+ public static ResponseBody create(
+ final MediaType contentType, final long contentLength, final BufferedSource content) {
+ if (content == null) throw new NullPointerException("source == null");
+ return new ResponseBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() {
+ return contentLength;
+ }
+
+ @Override public BufferedSource source() {
+ return content;
+ }
+ };
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java
new file mode 100644
index 0000000..ba33f86
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/**
+ * The concrete route used by a connection to reach an abstract origin server.
+ * When creating a connection the client has many options:
+ * <ul>
+ * <li><strong>HTTP proxy:</strong> a proxy server may be explicitly
+ * configured for the client. Otherwise the {@linkplain java.net.ProxySelector
+ * proxy selector} is used. It may return multiple proxies to attempt.
+ * <li><strong>IP address:</strong> whether connecting directly to an origin
+ * server or a proxy, opening a socket requires an IP address. The DNS
+ * server may return multiple IP addresses to attempt.
+ * </ul>
+ * Each route is a specific selection of these options.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Route {
+ final Address address;
+ final Proxy proxy;
+ final InetSocketAddress inetSocketAddress;
+
+ public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) {
+ if (address == null) {
+ throw new NullPointerException("address == null");
+ }
+ if (proxy == null) {
+ throw new NullPointerException("proxy == null");
+ }
+ if (inetSocketAddress == null) {
+ throw new NullPointerException("inetSocketAddress == null");
+ }
+ this.address = address;
+ this.proxy = proxy;
+ this.inetSocketAddress = inetSocketAddress;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns the {@link Proxy} of this route.
+ *
+ * <strong>Warning:</strong> This may disagree with {@link Address#getProxy}
+ * when it is null. When the address's proxy is null, the proxy selector is
+ * used.
+ */
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ public InetSocketAddress getSocketAddress() {
+ return inetSocketAddress;
+ }
+
+ /**
+ * Returns true if this route tunnels HTTPS through an HTTP proxy. See <a
+ * href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.
+ */
+ public boolean requiresTunnel() {
+ return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
+ }
+
+ @Override public boolean equals(Object obj) {
+ if (obj instanceof Route) {
+ Route other = (Route) obj;
+ return address.equals(other.address)
+ && proxy.equals(other.proxy)
+ && inetSocketAddress.equals(other.inetSocketAddress);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + address.hashCode();
+ result = 31 * result + proxy.hashCode();
+ result = 31 * result + inetSocketAddress.hashCode();
+ return result;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java b/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java
new file mode 100644
index 0000000..c8cd1e3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp;
+
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Versions of TLS that can be offered when negotiating a secure socket. See
+ * {@link SSLSocket#setEnabledProtocols}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum TlsVersion {
+ TLS_1_2("TLSv1.2"), // 2008.
+ TLS_1_1("TLSv1.1"), // 2006.
+ TLS_1_0("TLSv1"), // 1999.
+ SSL_3_0("SSLv3"), // 1996.
+ ;
+
+ final String javaName;
+
+ TlsVersion(String javaName) {
+ this.javaName = javaName;
+ }
+
+ public static TlsVersion forJavaName(String javaName) {
+ switch (javaName) {
+ case "TLSv1.2": return TLS_1_2;
+ case "TLSv1.1": return TLS_1_1;
+ case "TLSv1": return TLS_1_0;
+ case "SSLv3": return SSL_3_0;
+ }
+ throw new IllegalArgumentException("Unexpected TLS version: " + javaName);
+ }
+
+ public String javaName() {
+ return javaName;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java
new file mode 100644
index 0000000..c91a00a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java
@@ -0,0 +1,145 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.ConnectionSpec;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ProtocolException;
+import java.net.UnknownServiceException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Handles the connection spec fallback strategy: When a secure socket connection fails
+ * due to a handshake / protocol problem the connection may be retried with different protocols.
+ * Instances are stateful and should be created and used for a single connection attempt.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionSpecSelector {
+
+ private final List<ConnectionSpec> connectionSpecs;
+ private int nextModeIndex;
+ private boolean isFallbackPossible;
+ private boolean isFallback;
+
+ public ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs) {
+ this.nextModeIndex = 0;
+ this.connectionSpecs = connectionSpecs;
+ }
+
+ /**
+ * Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate
+ * {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}.
+ *
+ * @throws IOException if the socket does not support any of the TLS modes available
+ */
+ public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
+ ConnectionSpec tlsConfiguration = null;
+ for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
+ ConnectionSpec connectionSpec = connectionSpecs.get(i);
+ if (connectionSpec.isCompatible(sslSocket)) {
+ tlsConfiguration = connectionSpec;
+ nextModeIndex = i + 1;
+ break;
+ }
+ }
+
+ if (tlsConfiguration == null) {
+ // This may be the first time a connection has been attempted and the socket does not support
+ // any the required protocols, or it may be a retry (but this socket supports fewer
+ // protocols than was suggested by a prior socket).
+ throw new UnknownServiceException(
+ "Unable to find acceptable protocols. isFallback=" + isFallback
+ + ", modes=" + connectionSpecs
+ + ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols()));
+ }
+
+ isFallbackPossible = isFallbackPossible(sslSocket);
+
+ Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
+
+ return tlsConfiguration;
+ }
+
+ /**
+ * Reports a failure to complete a connection. Determines the next {@link ConnectionSpec} to
+ * try, if any.
+ *
+ * @return {@code true} if the connection should be retried using
+ * {@link #configureSecureSocket(SSLSocket)} or {@code false} if not
+ */
+ public boolean connectionFailed(IOException e) {
+ // Any future attempt to connect using this strategy will be a fallback attempt.
+ isFallback = true;
+
+ if (!isFallbackPossible) {
+ return false;
+ }
+
+ // If there was a protocol problem, don't recover.
+ if (e instanceof ProtocolException) {
+ return false;
+ }
+
+ // If there was an interruption or timeout (SocketTimeoutException), don't recover.
+ // For the socket connect timeout case we do not try the same host with a different
+ // ConnectionSpec: we assume it is unreachable.
+ if (e instanceof InterruptedIOException) {
+ return false;
+ }
+
+ // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
+ // again with a different connection spec.
+ if (e instanceof SSLHandshakeException) {
+ // If the problem was a CertificateException from the X509TrustManager,
+ // do not retry.
+ if (e.getCause() instanceof CertificateException) {
+ return false;
+ }
+ }
+ if (e instanceof SSLPeerUnverifiedException) {
+ // e.g. a certificate pinning error.
+ return false;
+ }
+
+
+ // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we
+ // retry those when we probably should not.
+ return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException);
+ }
+
+ /**
+ * Returns {@code true} if any later {@link ConnectionSpec} in the fallback strategy looks
+ * possible based on the supplied {@link SSLSocket}. It assumes that a future socket will have the
+ * same capabilities as the supplied socket.
+ */
+ private boolean isFallbackPossible(SSLSocket socket) {
+ for (int i = nextModeIndex; i < connectionSpecs.size(); i++) {
+ if (connectionSpecs.get(i).isCompatible(socket)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java
new file mode 100644
index 0000000..2169de9
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java
@@ -0,0 +1,1048 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal;
+
+import com.android.okhttp.internal.io.FileSystem;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ * <li>When an entry is being <strong>created</strong> it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ * <li>When an entry is being <strong>edited</strong>, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TEMP = "journal.tmp";
+ static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,120}");
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+ * DIRTY 335c4c6028171cfddfbaae1a9c313c52
+ * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+ * REMOVE 335c4c6028171cfddfbaae1a9c313c52
+ * DIRTY 1ab96a171faeeee38496d8b330771a7a
+ * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+ * READ 335c4c6028171cfddfbaae1a9c313c52
+ * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final FileSystem fileSystem;
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final File journalFileBackup;
+ private final int appVersion;
+ private long maxSize;
+ private final int valueCount;
+ private long size = 0;
+ private BufferedSink journalWriter;
+ private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
+ private int redundantOpCount;
+ private boolean hasJournalErrors;
+
+ // Must be read and written when synchronized on 'this'.
+ private boolean initialized;
+ private boolean closed;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /** Used to run 'cleanupRunnable' for journal rebuilds. */
+ private final Executor executor;
+ private final Runnable cleanupRunnable = new Runnable() {
+ public void run() {
+ synchronized (DiskLruCache.this) {
+ if (!initialized | closed) {
+ return; // Nothing to do
+ }
+ try {
+ trimToSize();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ };
+
+ DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize,
+ Executor executor) {
+ this.fileSystem = fileSystem;
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+ this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ this.executor = executor;
+ }
+
+ public synchronized void initialize() throws IOException {
+ assert Thread.holdsLock(this);
+
+ if (initialized) {
+ return; // Already initialized.
+ }
+
+ // If a bkp file exists, use it instead.
+ if (fileSystem.exists(journalFileBackup)) {
+ // If journal file also exists just delete backup file.
+ if (fileSystem.exists(journalFile)) {
+ fileSystem.delete(journalFileBackup);
+ } else {
+ fileSystem.rename(journalFileBackup, journalFile);
+ }
+ }
+
+ // Prefer to pick up where we left off.
+ if (fileSystem.exists(journalFile)) {
+ try {
+ readJournal();
+ processJournal();
+ initialized = true;
+ return;
+ } catch (IOException journalIsCorrupt) {
+ Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
+ + journalIsCorrupt.getMessage() + ", removing");
+ delete();
+ closed = false;
+ }
+ }
+
+ rebuildJournal();
+
+ initialized = true;
+ }
+
+ /**
+ * Create a cache which will reside in {@code directory}. This cache is lazily initialized on
+ * first access and will be created if it does not exist.
+ *
+ * @param directory a writable directory
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ */
+ public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion,
+ int valueCount, long maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // Use a single background thread to evict entries.
+ Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));
+
+ return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
+ }
+
+ private void readJournal() throws IOException {
+ BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
+ try {
+ String magic = source.readUtf8LineStrict();
+ String version = source.readUtf8LineStrict();
+ String appVersionString = source.readUtf8LineStrict();
+ String valueCountString = source.readUtf8LineStrict();
+ String blank = source.readUtf8LineStrict();
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ + valueCountString + ", " + blank + "]");
+ }
+
+ int lineCount = 0;
+ while (true) {
+ try {
+ readJournalLine(source.readUtf8LineStrict());
+ lineCount++;
+ } catch (EOFException endOfJournal) {
+ break;
+ }
+ }
+ redundantOpCount = lineCount - lruEntries.size();
+
+ // If we ended on a truncated line, rebuild the journal before appending to it.
+ if (!source.exhausted()) {
+ rebuildJournal();
+ } else {
+ journalWriter = newJournalWriter();
+ }
+ } finally {
+ Util.closeQuietly(source);
+ }
+ }
+
+ private BufferedSink newJournalWriter() throws FileNotFoundException {
+ Sink fileSink = fileSystem.appendingSink(journalFile);
+ Sink faultHidingSink = new FaultHidingSink(fileSink) {
+ @Override protected void onException(IOException e) {
+ assert (Thread.holdsLock(DiskLruCache.this));
+ hasJournalErrors = true;
+ }
+ };
+ return Okio.buffer(faultHidingSink);
+ }
+
+ private void readJournalLine(String line) throws IOException {
+ int firstSpace = line.indexOf(' ');
+ if (firstSpace == -1) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ int keyBegin = firstSpace + 1;
+ int secondSpace = line.indexOf(' ', keyBegin);
+ final String key;
+ if (secondSpace == -1) {
+ key = line.substring(keyBegin);
+ if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+ lruEntries.remove(key);
+ return;
+ }
+ } else {
+ key = line.substring(keyBegin, secondSpace);
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+ String[] parts = line.substring(secondSpace + 1).split(" ");
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(parts);
+ } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+ entry.currentEditor = new Editor(entry);
+ } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+ // This work was already done by calling lruEntries.get().
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ fileSystem.delete(journalFileTmp);
+ for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+ Entry entry = i.next();
+ if (entry.currentEditor == null) {
+ for (int t = 0; t < valueCount; t++) {
+ size += entry.lengths[t];
+ }
+ } else {
+ entry.currentEditor = null;
+ for (int t = 0; t < valueCount; t++) {
+ fileSystem.delete(entry.cleanFiles[t]);
+ fileSystem.delete(entry.dirtyFiles[t]);
+ }
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Creates a new journal that omits redundant information. This replaces the
+ * current journal if it exists.
+ */
+ private synchronized void rebuildJournal() throws IOException {
+ if (journalWriter != null) {
+ journalWriter.close();
+ }
+
+ BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
+ try {
+ writer.writeUtf8(MAGIC).writeByte('\n');
+ writer.writeUtf8(VERSION_1).writeByte('\n');
+ writer.writeDecimalLong(appVersion).writeByte('\n');
+ writer.writeDecimalLong(valueCount).writeByte('\n');
+ writer.writeByte('\n');
+
+ for (Entry entry : lruEntries.values()) {
+ if (entry.currentEditor != null) {
+ writer.writeUtf8(DIRTY).writeByte(' ');
+ writer.writeUtf8(entry.key);
+ writer.writeByte('\n');
+ } else {
+ writer.writeUtf8(CLEAN).writeByte(' ');
+ writer.writeUtf8(entry.key);
+ entry.writeLengths(writer);
+ writer.writeByte('\n');
+ }
+ }
+ } finally {
+ writer.close();
+ }
+
+ if (fileSystem.exists(journalFile)) {
+ fileSystem.rename(journalFile, journalFileBackup);
+ }
+ fileSystem.rename(journalFileTmp, journalFile);
+ fileSystem.delete(journalFileBackup);
+
+ journalWriter = newJournalWriter();
+ hasJournalErrors = false;
+ }
+
+ /**
+ * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+ * exist is not currently readable. If a value is returned, it is moved to
+ * the head of the LRU queue.
+ */
+ public synchronized Snapshot get(String key) throws IOException {
+ initialize();
+
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null || !entry.readable) return null;
+
+ Snapshot snapshot = entry.snapshot();
+ if (snapshot == null) return null;
+
+ redundantOpCount++;
+ journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
+ if (journalRebuildRequired()) {
+ executor.execute(cleanupRunnable);
+ }
+
+ return snapshot;
+ }
+
+ /**
+ * Returns an editor for the entry named {@code key}, or null if another
+ * edit is in progress.
+ */
+ public Editor edit(String key) throws IOException {
+ return edit(key, ANY_SEQUENCE_NUMBER);
+ }
+
+ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+ initialize();
+
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+ || entry.sequenceNumber != expectedSequenceNumber)) {
+ return null; // Snapshot is stale.
+ }
+ if (entry != null && entry.currentEditor != null) {
+ return null; // Another edit is in progress.
+ }
+
+ // Flush the journal before creating files to prevent file leaks.
+ journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
+ journalWriter.flush();
+
+ if (hasJournalErrors) {
+ return null; // Don't edit; the journal can't be written.
+ }
+
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+ Editor editor = new Editor(entry);
+ entry.currentEditor = editor;
+ return editor;
+ }
+
+ /** Returns the directory where this cache stores its data. */
+ public File getDirectory() {
+ return directory;
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public synchronized long getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Changes the maximum number of bytes the cache can store and queues a job
+ * to trim the existing store, if necessary.
+ */
+ public synchronized void setMaxSize(long maxSize) {
+ this.maxSize = maxSize;
+ if (initialized) {
+ executor.execute(cleanupRunnable);
+ }
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the max size if a background
+ * deletion is pending.
+ */
+ public synchronized long size() throws IOException {
+ initialize();
+ return size;
+ }
+
+ private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+ Entry entry = editor.entry;
+ if (entry.currentEditor != editor) {
+ throw new IllegalStateException();
+ }
+
+ // If this edit is creating the entry for the first time, every index must have a value.
+ if (success && !entry.readable) {
+ for (int i = 0; i < valueCount; i++) {
+ if (!editor.written[i]) {
+ editor.abort();
+ throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+ }
+ if (!fileSystem.exists(entry.dirtyFiles[i])) {
+ editor.abort();
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File dirty = entry.dirtyFiles[i];
+ if (success) {
+ if (fileSystem.exists(dirty)) {
+ File clean = entry.cleanFiles[i];
+ fileSystem.rename(dirty, clean);
+ long oldLength = entry.lengths[i];
+ long newLength = fileSystem.size(clean);
+ entry.lengths[i] = newLength;
+ size = size - oldLength + newLength;
+ }
+ } else {
+ fileSystem.delete(dirty);
+ }
+ }
+
+ redundantOpCount++;
+ entry.currentEditor = null;
+ if (entry.readable | success) {
+ entry.readable = true;
+ journalWriter.writeUtf8(CLEAN).writeByte(' ');
+ journalWriter.writeUtf8(entry.key);
+ entry.writeLengths(journalWriter);
+ journalWriter.writeByte('\n');
+ if (success) {
+ entry.sequenceNumber = nextSequenceNumber++;
+ }
+ } else {
+ lruEntries.remove(entry.key);
+ journalWriter.writeUtf8(REMOVE).writeByte(' ');
+ journalWriter.writeUtf8(entry.key);
+ journalWriter.writeByte('\n');
+ }
+ journalWriter.flush();
+
+ if (size > maxSize || journalRebuildRequired()) {
+ executor.execute(cleanupRunnable);
+ }
+ }
+
+ /**
+ * We only rebuild the journal when it will halve the size of the journal
+ * and eliminate at least 2000 ops.
+ */
+ private boolean journalRebuildRequired() {
+ final int redundantOpCompactThreshold = 2000;
+ return redundantOpCount >= redundantOpCompactThreshold
+ && redundantOpCount >= lruEntries.size();
+ }
+
+ /**
+ * Drops the entry for {@code key} if it exists and can be removed. If the
+ * entry for {@code key} is currently being edited, that edit will complete
+ * normally but its value will not be stored.
+ *
+ * @return true if an entry was removed.
+ */
+ public synchronized boolean remove(String key) throws IOException {
+ initialize();
+
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null) return false;
+ return removeEntry(entry);
+ }
+
+ private boolean removeEntry(Entry entry) throws IOException {
+ if (entry.currentEditor != null) {
+ entry.currentEditor.detach(); // Prevent the edit from completing normally.
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ fileSystem.delete(entry.cleanFiles[i]);
+ size -= entry.lengths[i];
+ entry.lengths[i] = 0;
+ }
+
+ redundantOpCount++;
+ journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');
+ lruEntries.remove(entry.key);
+
+ if (journalRebuildRequired()) {
+ executor.execute(cleanupRunnable);
+ }
+
+ return true;
+ }
+
+ /** Returns true if this cache has been closed. */
+ public synchronized boolean isClosed() {
+ return closed;
+ }
+
+ private synchronized void checkNotClosed() {
+ if (isClosed()) {
+ throw new IllegalStateException("cache is closed");
+ }
+ }
+
+ /** Force buffered operations to the filesystem. */
+ public synchronized void flush() throws IOException {
+ if (!initialized) return;
+
+ checkNotClosed();
+ trimToSize();
+ journalWriter.flush();
+ }
+
+ /** Closes this cache. Stored values will remain on the filesystem. */
+ public synchronized void close() throws IOException {
+ if (!initialized || closed) {
+ closed = true;
+ return;
+ }
+ // Copying for safe iteration.
+ for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) {
+ if (entry.currentEditor != null) {
+ entry.currentEditor.abort();
+ }
+ }
+ trimToSize();
+ journalWriter.close();
+ journalWriter = null;
+ closed = true;
+ }
+
+ private void trimToSize() throws IOException {
+ while (size > maxSize) {
+ Entry toEvict = lruEntries.values().iterator().next();
+ removeEntry(toEvict);
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ close();
+ fileSystem.deleteContents(directory);
+ }
+
+ /**
+ * Deletes all stored values from the cache. In-flight edits will complete
+ * normally but their values will not be stored.
+ */
+ public synchronized void evictAll() throws IOException {
+ initialize();
+ // Copying for safe iteration.
+ for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) {
+ removeEntry(entry);
+ }
+ }
+
+ private void validateKey(String key) {
+ Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"");
+ }
+ }
+
+ /**
+ * Returns an iterator over the cache's current entries. This iterator doesn't throw {@code
+ * ConcurrentModificationException}, but if new entries are added while iterating, those new
+ * entries will not be returned by the iterator. If existing entries are removed during iteration,
+ * they will be absent (unless they were already returned).
+ *
+ * <p>If there are I/O problems during iteration, this iterator fails silently. For example, if
+ * the hosting filesystem becomes unreachable, the iterator will omit elements rather than
+ * throwing exceptions.
+ *
+ * <p><strong>The caller must {@link Snapshot#close close}</strong> each snapshot returned by
+ * {@link Iterator#next}. Failing to do so leaks open files!
+ *
+ * <p>The returned iterator supports {@link Iterator#remove}.
+ */
+ public synchronized Iterator<Snapshot> snapshots() throws IOException {
+ initialize();
+ return new Iterator<Snapshot>() {
+ /** Iterate a copy of the entries to defend against concurrent modification errors. */
+ final Iterator<Entry> delegate = new ArrayList<>(lruEntries.values()).iterator();
+
+ /** The snapshot to return from {@link #next}. Null if we haven't computed that yet. */
+ Snapshot nextSnapshot;
+
+ /** The snapshot to remove with {@link #remove}. Null if removal is illegal. */
+ Snapshot removeSnapshot;
+
+ @Override public boolean hasNext() {
+ if (nextSnapshot != null) return true;
+
+ synchronized (DiskLruCache.this) {
+ // If the cache is closed, truncate the iterator.
+ if (closed) return false;
+
+ while (delegate.hasNext()) {
+ Entry entry = delegate.next();
+ Snapshot snapshot = entry.snapshot();
+ if (snapshot == null) continue; // Evicted since we copied the entries.
+ nextSnapshot = snapshot;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override public Snapshot next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ removeSnapshot = nextSnapshot;
+ nextSnapshot = null;
+ return removeSnapshot;
+ }
+
+ @Override public void remove() {
+ if (removeSnapshot == null) throw new IllegalStateException("remove() before next()");
+ try {
+ DiskLruCache.this.remove(removeSnapshot.key);
+ } catch (IOException ignored) {
+ // Nothing useful to do here. We failed to remove from the cache. Most likely that's
+ // because we couldn't update the journal, but the cached entry will still be gone.
+ } finally {
+ removeSnapshot = null;
+ }
+ }
+ };
+ }
+
+ /** A snapshot of the values for an entry.
+ * @hide This class is not part of the Android public SDK API*/
+ public final class Snapshot implements Closeable {
+ private final String key;
+ private final long sequenceNumber;
+ private final Source[] sources;
+ private final long[] lengths;
+
+ private Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) {
+ this.key = key;
+ this.sequenceNumber = sequenceNumber;
+ this.sources = sources;
+ this.lengths = lengths;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns an editor for this snapshot's entry, or null if either the
+ * entry has changed since this snapshot was created or if another edit
+ * is in progress.
+ */
+ public Editor edit() throws IOException {
+ return DiskLruCache.this.edit(key, sequenceNumber);
+ }
+
+ /** Returns the unbuffered stream with the value for {@code index}. */
+ public Source getSource(int index) {
+ return sources[index];
+ }
+
+ /** Returns the byte length of the value for {@code index}. */
+ public long getLength(int index) {
+ return lengths[index];
+ }
+
+ public void close() {
+ for (Source in : sources) {
+ Util.closeQuietly(in);
+ }
+ }
+ }
+
+ private static final Sink NULL_SINK = new Sink() {
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ source.skip(byteCount);
+ }
+
+ @Override public void flush() throws IOException {
+ }
+
+ @Override public Timeout timeout() {
+ return Timeout.NONE;
+ }
+
+ @Override public void close() throws IOException {
+ }
+ };
+
+ /** Edits the values for an entry.
+ * @hide This class is not part of the Android public SDK API*/
+ public final class Editor {
+ private final Entry entry;
+ private final boolean[] written;
+ private boolean done;
+
+ private Editor(Entry entry) {
+ this.entry = entry;
+ this.written = (entry.readable) ? null : new boolean[valueCount];
+ }
+
+ /**
+ * Prevents this editor from completing normally. This is necessary either when the edit causes
+ * an I/O error, or if the target entry is evicted while this editor is active. In either case
+ * we delete the editor's created files and prevent new files from being created. Note that once
+ * an editor has been detached it is possible for another editor to edit the entry.
+ */
+ void detach() {
+ if (entry.currentEditor == this) {
+ for (int i = 0; i < valueCount; i++) {
+ try {
+ fileSystem.delete(entry.dirtyFiles[i]);
+ } catch (IOException e) {
+ // This file is potentially leaked. Not much we can do about that.
+ }
+ }
+ entry.currentEditor = null;
+ }
+ }
+
+ /**
+ * Returns an unbuffered input stream to read the last committed value,
+ * or null if no value has been committed.
+ */
+ public Source newSource(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (done) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable || entry.currentEditor != this) {
+ return null;
+ }
+ try {
+ return fileSystem.source(entry.cleanFiles[index]);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns a new unbuffered output stream to write the value at
+ * {@code index}. If the underlying output stream encounters errors
+ * when writing to the filesystem, this edit will be aborted when
+ * {@link #commit} is called. The returned output stream does not throw
+ * IOExceptions.
+ */
+ public Sink newSink(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (done) {
+ throw new IllegalStateException();
+ }
+ if (entry.currentEditor != this) {
+ return NULL_SINK;
+ }
+ if (!entry.readable) {
+ written[index] = true;
+ }
+ File dirtyFile = entry.dirtyFiles[index];
+ Sink sink;
+ try {
+ sink = fileSystem.sink(dirtyFile);
+ } catch (FileNotFoundException e) {
+ return NULL_SINK;
+ }
+ return new FaultHidingSink(sink) {
+ @Override protected void onException(IOException e) {
+ synchronized (DiskLruCache.this) {
+ detach();
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * Commits this edit so it is visible to readers. This releases the
+ * edit lock so another edit may be started on the same key.
+ */
+ public void commit() throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (done) {
+ throw new IllegalStateException();
+ }
+ if (entry.currentEditor == this) {
+ completeEdit(this, true);
+ }
+ done = true;
+ }
+ }
+
+ /**
+ * Aborts this edit. This releases the edit lock so another edit may be
+ * started on the same key.
+ */
+ public void abort() throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (done) {
+ throw new IllegalStateException();
+ }
+ if (entry.currentEditor == this) {
+ completeEdit(this, false);
+ }
+ done = true;
+ }
+ }
+
+ public void abortUnlessCommitted() {
+ synchronized (DiskLruCache.this) {
+ if (!done && entry.currentEditor == this) {
+ try {
+ completeEdit(this, false);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+ }
+
+ private final class Entry {
+ private final String key;
+
+ /** Lengths of this entry's files. */
+ private final long[] lengths;
+ private final File[] cleanFiles;
+ private final File[] dirtyFiles;
+
+ /** True if this entry has ever been published. */
+ private boolean readable;
+
+ /** The ongoing edit or null if this entry is not being edited. */
+ private Editor currentEditor;
+
+ /** The sequence number of the most recently committed edit to this entry. */
+ private long sequenceNumber;
+
+ private Entry(String key) {
+ this.key = key;
+
+ lengths = new long[valueCount];
+ cleanFiles = new File[valueCount];
+ dirtyFiles = new File[valueCount];
+
+ // The names are repetitive so re-use the same builder to avoid allocations.
+ StringBuilder fileBuilder = new StringBuilder(key).append('.');
+ int truncateTo = fileBuilder.length();
+ for (int i = 0; i < valueCount; i++) {
+ fileBuilder.append(i);
+ cleanFiles[i] = new File(directory, fileBuilder.toString());
+ fileBuilder.append(".tmp");
+ dirtyFiles[i] = new File(directory, fileBuilder.toString());
+ fileBuilder.setLength(truncateTo);
+ }
+ }
+
+ /** Set lengths using decimal numbers like "10123". */
+ private void setLengths(String[] strings) throws IOException {
+ if (strings.length != valueCount) {
+ throw invalidLengths(strings);
+ }
+
+ try {
+ for (int i = 0; i < strings.length; i++) {
+ lengths[i] = Long.parseLong(strings[i]);
+ }
+ } catch (NumberFormatException e) {
+ throw invalidLengths(strings);
+ }
+ }
+
+ /** Append space-prefixed lengths to {@code writer}. */
+ void writeLengths(BufferedSink writer) throws IOException {
+ for (long length : lengths) {
+ writer.writeByte(' ').writeDecimalLong(length);
+ }
+ }
+
+ private IOException invalidLengths(String[] strings) throws IOException {
+ throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+ }
+
+ /**
+ * Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a
+ * single published snapshot. If we opened streams lazily then the streams could come from
+ * different edits.
+ */
+ Snapshot snapshot() {
+ if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();
+
+ Source[] sources = new Source[valueCount];
+ long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out.
+ try {
+ for (int i = 0; i < valueCount; i++) {
+ sources[i] = fileSystem.source(cleanFiles[i]);
+ }
+ return new Snapshot(key, sequenceNumber, sources, lengths);
+ } catch (FileNotFoundException e) {
+ // A file must have been deleted manually!
+ for (int i = 0; i < valueCount; i++) {
+ if (sources[i] != null) {
+ Util.closeQuietly(sources[i]);
+ } else {
+ break;
+ }
+ }
+ return null;
+ }
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java
new file mode 100644
index 0000000..d76152d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal;
+
+import java.io.IOException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.ForwardingSink;
+import com.android.okhttp.okio.Sink;
+
+/** A sink that never throws IOExceptions, even if the underlying sink does. */
+class FaultHidingSink extends ForwardingSink {
+ private boolean hasErrors;
+
+ public FaultHidingSink(Sink delegate) {
+ super(delegate);
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (hasErrors) {
+ source.skip(byteCount);
+ return;
+ }
+ try {
+ super.write(source, byteCount);
+ } catch (IOException e) {
+ hasErrors = true;
+ onException(e);
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ if (hasErrors) return;
+ try {
+ super.flush();
+ } catch (IOException e) {
+ hasErrors = true;
+ onException(e);
+ }
+ }
+
+ @Override public void close() throws IOException {
+ if (hasErrors) return;
+ try {
+ super.close();
+ } catch (IOException e) {
+ hasErrors = true;
+ onException(e);
+ }
+ }
+
+ protected void onException(IOException e) {
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java
new file mode 100644
index 0000000..20f937d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java
@@ -0,0 +1,76 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.Call;
+import com.android.okhttp.Callback;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.ConnectionSpec;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import java.net.MalformedURLException;
+import java.net.UnknownHostException;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Escalate internal APIs in {@code com.squareup.okhttp} so they can be used
+ * from OkHttp's implementation packages. The only implementation of this
+ * interface is in {@link com.android.okhttp.OkHttpClient}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class Internal {
+ public static final Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+
+ public static void initializeInstanceForTests() {
+ // Needed in tests to ensure that the instance is actually pointing to something.
+ new OkHttpClient();
+ }
+
+ public static Internal instance;
+
+ public abstract void addLenient(Headers.Builder builder, String line);
+
+ public abstract void addLenient(Headers.Builder builder, String name, String value);
+
+ public abstract void setCache(OkHttpClient client, InternalCache internalCache);
+
+ public abstract InternalCache internalCache(OkHttpClient client);
+
+ public abstract RealConnection get(
+ ConnectionPool pool, Address address, StreamAllocation streamAllocation);
+
+ public abstract void put(ConnectionPool pool, RealConnection connection);
+
+ public abstract boolean connectionBecameIdle(ConnectionPool pool, RealConnection connection);
+
+ public abstract RouteDatabase routeDatabase(ConnectionPool connectionPool);
+
+ public abstract void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket,
+ boolean isFallback);
+
+ public abstract HttpUrl getHttpUrlChecked(String url)
+ throws MalformedURLException, UnknownHostException;
+
+ // TODO delete the following when web sockets move into the main package.
+ public abstract void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket);
+ public abstract StreamAllocation callEngineGetStreamAllocation(Call call);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java
new file mode 100644
index 0000000..772e13e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java
@@ -0,0 +1,54 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import java.io.IOException;
+
+/**
+ * OkHttp's internal cache interface. Applications shouldn't implement this:
+ * instead use {@link com.android.okhttp.Cache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface InternalCache {
+ Response get(Request request) throws IOException;
+
+ CacheRequest put(Response response) throws IOException;
+
+ /**
+ * Remove any cache entries for the supplied {@code request}. This is invoked
+ * when the client invalidates the cache, such as when making POST requests.
+ */
+ void remove(Request request) throws IOException;
+
+ /**
+ * Handles a conditional request hit by updating the stored cache response
+ * with the headers from {@code network}. The cached response body is not
+ * updated. If the stored response has changed since {@code cached} was
+ * returned, this does nothing.
+ */
+ void update(Response cached, Response network) throws IOException;
+
+ /** Track an conditional GET that was satisfied by this cache. */
+ void trackConditionalCacheHit();
+
+ /** Track an HTTP response being satisfied with {@code cacheStrategy}. */
+ void trackResponse(CacheStrategy cacheStrategy);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java
new file mode 100644
index 0000000..dcaeb91
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java
@@ -0,0 +1,42 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal;
+
+/**
+ * Runnable implementation which always sets its thread name.
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class NamedRunnable implements Runnable {
+ protected final String name;
+
+ public NamedRunnable(String format, Object... args) {
+ this.name = String.format(format, args);
+ }
+
+ @Override public final void run() {
+ String oldName = Thread.currentThread().getName();
+ Thread.currentThread().setName(name);
+ try {
+ execute();
+ } finally {
+ Thread.currentThread().setName(oldName);
+ }
+ }
+
+ protected abstract void execute();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java
new file mode 100644
index 0000000..ee2425f
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java
@@ -0,0 +1,177 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Duck-typing for methods: Represents a method that may or may not be present on an object.
+ *
+ * @param <T> the type of the object the method might be on, typically an interface or base class
+ */
+class OptionalMethod<T> {
+
+ /** The return type of the method. null means "don't care". */
+ private final Class<?> returnType;
+
+ private final String methodName;
+
+ private final Class[] methodParams;
+
+ /**
+ * Creates an optional method.
+ *
+ * @param returnType the return type to required, null if it does not matter
+ * @param methodName the name of the method
+ * @param methodParams the method parameter types
+ */
+ public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
+ this.returnType = returnType;
+ this.methodName = methodName;
+ this.methodParams = methodParams;
+ }
+
+ /**
+ * Returns true if the method exists on the supplied {@code target}.
+ */
+ public boolean isSupported(T target) {
+ return getMethod(target.getClass()) != null;
+ }
+
+ /**
+ * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
+ * public then {@code null} is returned. See also
+ * {@link #invokeOptionalWithoutCheckedException(Object, Object...)}.
+ *
+ * @throws IllegalArgumentException if the arguments are invalid
+ * @throws InvocationTargetException if the invocation throws an exception
+ */
+ public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
+ Method m = getMethod(target.getClass());
+ if (m == null) {
+ return null;
+ }
+ try {
+ return m.invoke(target, args);
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Invokes the method on {@code target}. If the method does not exist or is not
+ * public then {@code null} is returned. Any RuntimeException thrown by the method is thrown,
+ * checked exceptions are wrapped in an {@link AssertionError}.
+ *
+ * @throws IllegalArgumentException if the arguments are invalid
+ */
+ public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
+ try {
+ return invokeOptional(target, args);
+ } catch (InvocationTargetException e) {
+ Throwable targetException = e.getTargetException();
+ if (targetException instanceof RuntimeException) {
+ throw (RuntimeException) targetException;
+ }
+ AssertionError error = new AssertionError("Unexpected exception");
+ error.initCause(targetException);
+ throw error;
+ }
+ }
+
+ /**
+ * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
+ * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
+ *
+ * @throws IllegalArgumentException if the arguments are invalid
+ * @throws InvocationTargetException if the invocation throws an exception
+ */
+ public Object invoke(T target, Object... args) throws InvocationTargetException {
+ Method m = getMethod(target.getClass());
+ if (m == null) {
+ throw new AssertionError("Method " + methodName + " not supported for object " + target);
+ }
+ try {
+ return m.invoke(target, args);
+ } catch (IllegalAccessException e) {
+ // Method should be public: we checked.
+ AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
+ error.initCause(e);
+ throw error;
+ }
+ }
+
+ /**
+ * Invokes the method on {@code target}. Throws an error if the method is not supported. Any
+ * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in
+ * an {@link AssertionError}.
+ *
+ * @throws IllegalArgumentException if the arguments are invalid
+ */
+ public Object invokeWithoutCheckedException(T target, Object... args) {
+ try {
+ return invoke(target, args);
+ } catch (InvocationTargetException e) {
+ Throwable targetException = e.getTargetException();
+ if (targetException instanceof RuntimeException) {
+ throw (RuntimeException) targetException;
+ }
+ AssertionError error = new AssertionError("Unexpected exception");
+ error.initCause(targetException);
+ throw error;
+ }
+ }
+
+ /**
+ * Perform a lookup for the method. No caching.
+ * In order to return a method the method name and arguments must match those specified when
+ * the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it
+ * must also be compatible. The method must also be public.
+ */
+ private Method getMethod(Class<?> clazz) {
+ Method method = null;
+ if (methodName != null) {
+ method = getPublicMethod(clazz, methodName, methodParams);
+ if (method != null
+ && returnType != null
+ && !returnType.isAssignableFrom(method.getReturnType())) {
+
+ // If the return type is non-null it must be compatible.
+ method = null;
+ }
+ }
+ return method;
+ }
+
+ private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
+ Method method = null;
+ try {
+ method = clazz.getMethod(methodName, parameterTypes);
+ if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
+ method = null;
+ }
+ } catch (NoSuchMethodException e) {
+ // None.
+ }
+ return method;
+ }
+}
+
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java
new file mode 100644
index 0000000..afe2317
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java
@@ -0,0 +1,51 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal;
+
+import com.android.okhttp.Route;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A blacklist of failed routes to avoid when creating a new connection to a
+ * target address. This is used so that OkHttp can learn from its mistakes: if
+ * there was a failure attempting to connect to a specific IP address or proxy
+ * server, that failure is remembered and alternate routes are preferred.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteDatabase {
+ private final Set<Route> failedRoutes = new LinkedHashSet<>();
+
+ /** Records a failure connecting to {@code failedRoute}. */
+ public synchronized void failed(Route failedRoute) {
+ failedRoutes.add(failedRoute);
+ }
+
+ /** Records success connecting to {@code failedRoute}. */
+ public synchronized void connected(Route route) {
+ failedRoutes.remove(route);
+ }
+
+ /** Returns true if {@code route} has failed recently and should be avoided. */
+ public synchronized boolean shouldPostpone(Route route) {
+ return failedRoutes.contains(route);
+ }
+
+ public synchronized int failedRoutesCount() {
+ return failedRoutes.size();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java
new file mode 100644
index 0000000..b611109
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java
@@ -0,0 +1,306 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal;
+
+import com.android.okhttp.HttpUrl;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Source;
+
+/** Junk drawer of utility methods.
+ * @hide This class is not part of the Android public SDK API*/
+public final class Util {
+ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ /** A cheap and type-safe constant for the UTF-8 Charset. */
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private Util() {
+ }
+
+ public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
+ if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ /** Returns true if two possibly-null objects are equal. */
+ public static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ /**
+ * Closes {@code closeable}, ignoring any checked exceptions. Does nothing
+ * if {@code closeable} is null.
+ */
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Closes {@code socket}, ignoring any checked exceptions. Does nothing if
+ * {@code socket} is null.
+ */
+ public static void closeQuietly(Socket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (AssertionError e) {
+ if (!isAndroidGetsocknameError(e)) throw e;
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if
+ * {@code serverSocket} is null.
+ */
+ public static void closeQuietly(ServerSocket serverSocket) {
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Closes {@code a} and {@code b}. If either close fails, this completes
+ * the other close and rethrows the first encountered exception.
+ */
+ public static void closeAll(Closeable a, Closeable b) throws IOException {
+ Throwable thrown = null;
+ try {
+ a.close();
+ } catch (Throwable e) {
+ thrown = e;
+ }
+ try {
+ b.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ if (thrown == null) return;
+ if (thrown instanceof IOException) throw (IOException) thrown;
+ if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
+ if (thrown instanceof Error) throw (Error) thrown;
+ throw new AssertionError(thrown);
+ }
+
+ /**
+ * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading
+ * a complete source is helpful, such as when doing so completes a cache body or frees a socket
+ * connection for reuse.
+ */
+ public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
+ try {
+ return skipAll(source, timeout, timeUnit);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
+ * extend the deadline if one exists already.
+ */
+ public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
+ long now = System.nanoTime();
+ long originalDuration = source.timeout().hasDeadline()
+ ? source.timeout().deadlineNanoTime() - now
+ : Long.MAX_VALUE;
+ source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
+ try {
+ Buffer skipBuffer = new Buffer();
+ while (source.read(skipBuffer, 2048) != -1) {
+ skipBuffer.clear();
+ }
+ return true; // Success! The source has been exhausted.
+ } catch (InterruptedIOException e) {
+ return false; // We ran out of time before exhausting the source.
+ } finally {
+ if (originalDuration == Long.MAX_VALUE) {
+ source.timeout().clearDeadline();
+ } else {
+ source.timeout().deadlineNanoTime(now + originalDuration);
+ }
+ }
+ }
+
+ /** Returns a 32 character string containing an MD5 hash of {@code s}. */
+ public static String md5Hex(String s) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
+ return ByteString.of(md5bytes).hex();
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */
+ public static String shaBase64(String s) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+ byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8"));
+ return ByteString.of(sha1Bytes).base64();
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Returns a SHA-1 hash of {@code s}. */
+ public static ByteString sha1(ByteString s) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+ byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
+ return ByteString.of(sha1Bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Returns an immutable copy of {@code list}. */
+ public static <T> List<T> immutableList(List<T> list) {
+ return Collections.unmodifiableList(new ArrayList<>(list));
+ }
+
+ /** Returns an immutable list containing {@code elements}. */
+ public static <T> List<T> immutableList(T... elements) {
+ return Collections.unmodifiableList(Arrays.asList(elements.clone()));
+ }
+
+ /** Returns an immutable copy of {@code map}. */
+ public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
+ return Collections.unmodifiableMap(new LinkedHashMap<>(map));
+ }
+
+ public static ThreadFactory threadFactory(final String name, final boolean daemon) {
+ return new ThreadFactory() {
+ @Override public Thread newThread(Runnable runnable) {
+ Thread result = new Thread(runnable, name);
+ result.setDaemon(daemon);
+ return result;
+ }
+ };
+ }
+
+ /**
+ * Returns an array containing containing only elements found in {@code first} and also in
+ * {@code second}. The returned elements are in the same order as in {@code first}.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T[] intersect(Class<T> arrayType, T[] first, T[] second) {
+ List<T> result = intersect(first, second);
+ return result.toArray((T[]) Array.newInstance(arrayType, result.size()));
+ }
+
+ /**
+ * Returns a list containing containing only elements found in {@code first} and also in
+ * {@code second}. The returned elements are in the same order as in {@code first}.
+ */
+ private static <T> List<T> intersect(T[] first, T[] second) {
+ List<T> result = new ArrayList<>();
+ for (T a : first) {
+ for (T b : second) {
+ if (a.equals(b)) {
+ result.add(b);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
+ String host = url.host().contains(":")
+ ? "[" + url.host() + "]"
+ : url.host();
+ return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
+ ? host + ":" + url.port()
+ : host;
+ }
+
+ /** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */
+ public static String toHumanReadableAscii(String s) {
+ for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {
+ c = s.codePointAt(i);
+ if (c > '\u001f' && c < '\u007f') continue;
+
+ Buffer buffer = new Buffer();
+ buffer.writeUtf8(s, 0, i);
+ for (int j = i; j < length; j += Character.charCount(c)) {
+ c = s.codePointAt(j);
+ buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
+ }
+ return buffer.readUtf8();
+ }
+ return s;
+ }
+
+ /**
+ * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
+ * https://code.google.com/p/android/issues/detail?id=54072
+ */
+ public static boolean isAndroidGetsocknameError(AssertionError e) {
+ return e.getCause() != null && e.getMessage() != null
+ && e.getMessage().contains("getsockname failed");
+ }
+
+ public static boolean contains(String[] array, String value) {
+ return Arrays.asList(array).contains(value);
+ }
+
+ public static String[] concat(String[] array, String value) {
+ String[] result = new String[array.length + 1];
+ System.arraycopy(array, 0, result, 0, array.length);
+ result[result.length - 1] = value;
+ return result;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java
new file mode 100644
index 0000000..d5d71ff
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java
@@ -0,0 +1,95 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-7
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum ErrorCode {
+ /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
+ NO_ERROR(0, -1, 0),
+
+ PROTOCOL_ERROR(1, 1, 1),
+
+ /** A subtype of PROTOCOL_ERROR used by SPDY. */
+ INVALID_STREAM(1, 2, -1),
+
+ /** A subtype of PROTOCOL_ERROR used by SPDY. */
+ UNSUPPORTED_VERSION(1, 4, -1),
+
+ /** A subtype of PROTOCOL_ERROR used by SPDY. */
+ STREAM_IN_USE(1, 8, -1),
+
+ /** A subtype of PROTOCOL_ERROR used by SPDY. */
+ STREAM_ALREADY_CLOSED(1, 9, -1),
+
+ INTERNAL_ERROR(2, 6, 2),
+
+ FLOW_CONTROL_ERROR(3, 7, -1),
+
+ STREAM_CLOSED(5, -1, -1),
+
+ FRAME_TOO_LARGE(6, 11, -1),
+
+ REFUSED_STREAM(7, 3, -1),
+
+ CANCEL(8, 5, -1),
+
+ COMPRESSION_ERROR(9, -1, -1),
+
+ CONNECT_ERROR(10, -1, -1),
+
+ ENHANCE_YOUR_CALM(11, -1, -1),
+
+ INADEQUATE_SECURITY(12, -1, -1),
+
+ HTTP_1_1_REQUIRED(13, -1, -1),
+
+ INVALID_CREDENTIALS(-1, 10, -1);
+
+ public final int httpCode;
+ public final int spdyRstCode;
+ public final int spdyGoAwayCode;
+
+ private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) {
+ this.httpCode = httpCode;
+ this.spdyRstCode = spdyRstCode;
+ this.spdyGoAwayCode = spdyGoAwayCode;
+ }
+
+ public static ErrorCode fromSpdy3Rst(int code) {
+ for (ErrorCode errorCode : ErrorCode.values()) {
+ if (errorCode.spdyRstCode == code) return errorCode;
+ }
+ return null;
+ }
+
+ public static ErrorCode fromHttp2(int code) {
+ for (ErrorCode errorCode : ErrorCode.values()) {
+ if (errorCode.httpCode == code) return errorCode;
+ }
+ return null;
+ }
+
+ public static ErrorCode fromSpdyGoAway(int code) {
+ for (ErrorCode errorCode : ErrorCode.values()) {
+ if (errorCode.spdyGoAwayCode == code) return errorCode;
+ }
+ return null;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java
new file mode 100644
index 0000000..f17ed42
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+
+/** Reads transport frames for SPDY/3 or HTTP/2.
+ * @hide This class is not part of the Android public SDK API*/
+public interface FrameReader extends Closeable {
+ void readConnectionPreface() throws IOException;
+ boolean nextFrame(Handler handler) throws IOException;
+
+ interface Handler {
+ void data(boolean inFinished, int streamId, BufferedSource source, int length)
+ throws IOException;
+
+ /**
+ * Create or update incoming headers, creating the corresponding streams
+ * if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
+ * SYN_REPLY, and HTTP/2 HEADERS and PUSH_PROMISE.
+ *
+ * @param outFinished true if the receiver should not send further frames.
+ * @param inFinished true if the sender will not send further frames.
+ * @param streamId the stream owning these headers.
+ * @param associatedStreamId the stream that triggered the sender to create
+ * this stream.
+ */
+ void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+ List<Header> headerBlock, HeadersMode headersMode);
+ void rstStream(int streamId, ErrorCode errorCode);
+ void settings(boolean clearPrevious, Settings settings);
+
+ /** HTTP/2 only. */
+ void ackSettings();
+
+ /**
+ * Read a connection-level ping from the peer. {@code ack} indicates this
+ * is a reply. Payload parameters are different between SPDY/3 and HTTP/2.
+ * <p>
+ * In SPDY/3, only the first {@code payload1} parameter is set. If the
+ * reader is a client, it is an unsigned even number. Likewise, a server
+ * will receive an odd number.
+ * <p>
+ * In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+ * set. The data is opaque binary, and there are no rules on the content.
+ */
+ void ping(boolean ack, int payload1, int payload2);
+
+ /**
+ * The peer tells us to stop creating streams. It is safe to replay
+ * streams with {@code ID > lastGoodStreamId} on a new connection. In-
+ * flight streams with {@code ID <= lastGoodStreamId} can only be replayed
+ * on a new connection if they are idempotent.
+ *
+ * @param lastGoodStreamId the last stream ID the peer processed before
+ * sending this message. If {@code lastGoodStreamId} is zero, the peer
+ * processed no frames.
+ * @param errorCode reason for closing the connection.
+ * @param debugData only valid for HTTP/2; opaque debug data to send.
+ */
+ void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
+
+ /**
+ * Notifies that an additional {@code windowSizeIncrement} bytes can be
+ * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+ */
+ void windowUpdate(int streamId, long windowSizeIncrement);
+
+ /**
+ * Called when reading a headers or priority frame. This may be used to
+ * change the stream's weight from the default (16) to a new value.
+ *
+ * @param streamId stream which has a priority change.
+ * @param streamDependency the stream ID this stream is dependent on.
+ * @param weight relative proportion of priority in [1..256].
+ * @param exclusive inserts this stream ID as the sole child of
+ * {@code streamDependency}.
+ */
+ void priority(int streamId, int streamDependency, int weight, boolean exclusive);
+
+ /**
+ * HTTP/2 only. Receive a push promise header block.
+ * <p>
+ * A push promise contains all the headers that pertain to a server-initiated
+ * request, and a {@code promisedStreamId} to which response frames will be
+ * delivered. Push promise frames are sent as a part of the response to
+ * {@code streamId}.
+ *
+ * @param streamId client-initiated stream ID. Must be an odd number.
+ * @param promisedStreamId server-initiated stream ID. Must be an even
+ * number.
+ * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+ * {@code :authority}, and (@code :path}.
+ */
+ void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+ throws IOException;
+
+ /**
+ * HTTP/2 only. Expresses that resources for the connection or a client-
+ * initiated stream are available from a different network location or
+ * protocol configuration.
+ *
+ * <p>See <a href="http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-01">alt-svc</a>
+ *
+ * @param streamId when a client-initiated stream ID (odd number), the
+ * origin of this alternate service is the origin of the stream. When
+ * zero, the origin is specified in the {@code origin} parameter.
+ * @param origin when present, the
+ * <a href="http://tools.ietf.org/html/rfc6454">origin</a> is typically
+ * represented as a combination of scheme, host and port. When empty,
+ * the origin is that of the {@code streamId}.
+ * @param protocol an ALPN protocol, such as {@code h2}.
+ * @param host an IP address or hostname.
+ * @param port the IP port associated with the service.
+ * @param maxAge time in seconds that this alternative is considered fresh.
+ */
+ void alternateService(int streamId, String origin, ByteString protocol, String host, int port,
+ long maxAge);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java
new file mode 100644
index 0000000..75b049d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java
@@ -0,0 +1,105 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.Buffer;
+
+/** Writes transport frames for SPDY/3 or HTTP/2.
+ * @hide This class is not part of the Android public SDK API*/
+public interface FrameWriter extends Closeable {
+ /** HTTP/2 only. */
+ void connectionPreface() throws IOException;
+ /** Informs the peer that we've applied its latest settings. */
+ void ackSettings(Settings peerSettings) throws IOException;
+
+ /**
+ * HTTP/2 only. Send a push promise header block.
+ * <p>
+ * A push promise contains all the headers that pertain to a server-initiated
+ * request, and a {@code promisedStreamId} to which response frames will be
+ * delivered. Push promise frames are sent as a part of the response to
+ * {@code streamId}. The {@code promisedStreamId} has a priority of one
+ * greater than {@code streamId}.
+ *
+ * @param streamId client-initiated stream ID. Must be an odd number.
+ * @param promisedStreamId server-initiated stream ID. Must be an even
+ * number.
+ * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+ * {@code :authority}, and (@code :path}.
+ */
+ void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+ throws IOException;
+
+ /** SPDY/3 only. */
+ void flush() throws IOException;
+ void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+ List<Header> headerBlock) throws IOException;
+ void synReply(boolean outFinished, int streamId, List<Header> headerBlock)
+ throws IOException;
+ void headers(int streamId, List<Header> headerBlock) throws IOException;
+ void rstStream(int streamId, ErrorCode errorCode) throws IOException;
+
+ /** The maximum size of bytes that may be sent in a single call to {@link #data}. */
+ int maxDataLength();
+
+ /**
+ * {@code source.length} may be longer than the max length of the variant's data frame.
+ * Implementations must send multiple frames as necessary.
+ *
+ * @param source the buffer to draw bytes from. May be null if byteCount is 0.
+ * @param byteCount must be between 0 and the minimum of {code source.length}
+ * and {@link #maxDataLength}.
+ */
+ void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException;
+
+ /** Write okhttp's settings to the peer. */
+ void settings(Settings okHttpSettings) throws IOException;
+
+ /**
+ * Send a connection-level ping to the peer. {@code ack} indicates this is
+ * a reply. Payload parameters are different between SPDY/3 and HTTP/2.
+ * <p>
+ * In SPDY/3, only the first {@code payload1} parameter is sent. If the
+ * sender is a client, it is an unsigned odd number. Likewise, a server
+ * will send an even number.
+ * <p>
+ * In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+ * sent. The data is opaque binary, and there are no rules on the content.
+ */
+ void ping(boolean ack, int payload1, int payload2) throws IOException;
+
+ /**
+ * Tell the peer to stop creating streams and that we last processed
+ * {@code lastGoodStreamId}, or zero if no streams were processed.
+ *
+ * @param lastGoodStreamId the last stream ID processed, or zero if no
+ * streams were processed.
+ * @param errorCode reason for closing the connection.
+ * @param debugData only valid for HTTP/2; opaque debug data to send.
+ */
+ void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
+
+ /**
+ * Inform peer that an additional {@code windowSizeIncrement} bytes can be
+ * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+ */
+ void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java
new file mode 100644
index 0000000..2945e65
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java
@@ -0,0 +1,949 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.NamedRunnable;
+import com.android.okhttp.internal.Util;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+
+import static com.android.okhttp.internal.Internal.logger;
+import static com.android.okhttp.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
+/**
+ * A socket connection to a remote peer. A connection hosts streams which can
+ * send and receive data.
+ *
+ * <p>Many methods in this API are <strong>synchronous:</strong> the call is
+ * completed before the method returns. This is typical for Java but atypical
+ * for SPDY. This is motivated by exception transparency: an IOException that
+ * was triggered by a certain caller can be caught and handled by that caller.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class FramedConnection implements Closeable {
+
+ // Internal state of this connection is guarded by 'this'. No blocking
+ // operations may be performed while holding this lock!
+ //
+ // Socket writes are guarded by frameWriter.
+ //
+ // Socket reads are unguarded but are only made by the reader thread.
+ //
+ // Certain operations (like SYN_STREAM) need to synchronize on both the
+ // frameWriter (to do blocking I/O) and this (to create streams). Such
+ // operations must synchronize on 'this' last. This ensures that we never
+ // wait for a blocking operation while holding 'this'.
+
+ private static final ExecutorService executor = new ThreadPoolExecutor(0,
+ Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
+ Util.threadFactory("OkHttp FramedConnection", true));
+
+ /** The protocol variant, like {@link com.android.okhttp.internal.framed.Spdy3}. */
+ final Protocol protocol;
+
+ /** True if this peer initiated the connection. */
+ final boolean client;
+
+ /**
+ * User code to run in response to incoming streams or settings. Calls to this are always invoked
+ * on {@link #executor}.
+ */
+ private final Listener listener;
+ private final Map<Integer, FramedStream> streams = new HashMap<>();
+ private final String hostName;
+ private int lastGoodStreamId;
+ private int nextStreamId;
+ private boolean shutdown;
+ private long idleStartTimeNs = System.nanoTime();
+
+ /** Ensures push promise callbacks events are sent in order per stream. */
+ private final ExecutorService pushExecutor;
+
+ /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */
+ private Map<Integer, Ping> pings;
+ /** User code to run in response to push promise events. */
+ private final PushObserver pushObserver;
+ private int nextPingId;
+
+ /**
+ * The total number of bytes consumed by the application, but not yet
+ * acknowledged by sending a {@code WINDOW_UPDATE} frame on this connection.
+ */
+ // Visible for testing
+ long unacknowledgedBytesRead = 0;
+
+ /**
+ * Count of bytes that can be written on the connection before receiving a
+ * window update.
+ */
+ // Visible for testing
+ long bytesLeftInWriteWindow;
+
+ /** Settings we communicate to the peer. */
+ Settings okHttpSettings = new Settings();
+
+ private static final int OKHTTP_CLIENT_WINDOW_SIZE = 16 * 1024 * 1024;
+
+ /** Settings we receive from the peer. */
+ // TODO: MWS will need to guard on this setting before attempting to push.
+ final Settings peerSettings = new Settings();
+
+ private boolean receivedInitialPeerSettings = false;
+ final Variant variant;
+ final Socket socket;
+ final FrameWriter frameWriter;
+
+ // Visible for testing
+ final Reader readerRunnable;
+
+ private FramedConnection(Builder builder) throws IOException {
+ protocol = builder.protocol;
+ pushObserver = builder.pushObserver;
+ client = builder.client;
+ listener = builder.listener;
+ // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-5.1.1
+ nextStreamId = builder.client ? 1 : 2;
+ if (builder.client && protocol == Protocol.HTTP_2) {
+ nextStreamId += 2; // In HTTP/2, 1 on client is reserved for Upgrade.
+ }
+
+ nextPingId = builder.client ? 1 : 2;
+
+ // Flow control was designed more for servers, or proxies than edge clients.
+ // If we are a client, set the flow control window to 16MiB. This avoids
+ // thrashing window updates every 64KiB, yet small enough to avoid blowing
+ // up the heap.
+ if (builder.client) {
+ okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, OKHTTP_CLIENT_WINDOW_SIZE);
+ }
+
+ 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));
+ // 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);
+ } else if (protocol == Protocol.SPDY_3) {
+ variant = new Spdy3();
+ pushExecutor = null;
+ } else {
+ throw new AssertionError(protocol);
+ }
+ bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ socket = builder.socket;
+ frameWriter = variant.newWriter(builder.sink, client);
+
+ readerRunnable = new Reader(variant.newReader(builder.source, client));
+ new Thread(readerRunnable).start(); // Not a daemon thread.
+ }
+
+ /** The protocol as selected using ALPN. */
+ public Protocol getProtocol() {
+ return protocol;
+ }
+
+ /**
+ * Returns the number of {@link FramedStream#isOpen() open streams} on this
+ * connection.
+ */
+ public synchronized int openStreamCount() {
+ return streams.size();
+ }
+
+ synchronized FramedStream getStream(int id) {
+ return streams.get(id);
+ }
+
+ synchronized FramedStream removeStream(int streamId) {
+ FramedStream stream = streams.remove(streamId);
+ if (stream != null && streams.isEmpty()) {
+ setIdle(true);
+ }
+ notifyAll(); // The removed stream may be blocked on a connection-wide window update.
+ return stream;
+ }
+
+ private synchronized void setIdle(boolean value) {
+ idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE;
+ }
+
+ /** Returns true if this connection is idle. */
+ public synchronized boolean isIdle() {
+ return idleStartTimeNs != Long.MAX_VALUE;
+ }
+
+ public synchronized int maxConcurrentStreams() {
+ return peerSettings.getMaxConcurrentStreams(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the time in ns when this connection became idle or Long.MAX_VALUE
+ * if connection is not idle.
+ */
+ public synchronized long getIdleStartTimeNs() {
+ return idleStartTimeNs;
+ }
+
+ /**
+ * Returns a new server-initiated stream.
+ *
+ * @param associatedStreamId the stream that triggered the sender to create
+ * this stream.
+ * @param out true to create an output stream that we can use to send data
+ * to the remote peer. Corresponds to {@code FLAG_FIN}.
+ */
+ public FramedStream pushStream(int associatedStreamId, List<Header> requestHeaders, boolean out)
+ throws IOException {
+ if (client) throw new IllegalStateException("Client cannot push requests.");
+ if (protocol != Protocol.HTTP_2) throw new IllegalStateException("protocol != HTTP_2");
+ return newStream(associatedStreamId, requestHeaders, out, false);
+ }
+
+ /**
+ * Returns a new locally-initiated stream.
+ *
+ * @param out true to create an output stream that we can use to send data to the remote peer.
+ * Corresponds to {@code FLAG_FIN}.
+ * @param in true to create an input stream that the remote peer can use to send data to us.
+ * Corresponds to {@code FLAG_UNIDIRECTIONAL}.
+ */
+ public FramedStream newStream(List<Header> requestHeaders, boolean out, boolean in)
+ throws IOException {
+ return newStream(0, requestHeaders, out, in);
+ }
+
+ private FramedStream newStream(int associatedStreamId, List<Header> requestHeaders, boolean out,
+ boolean in) throws IOException {
+ boolean outFinished = !out;
+ boolean inFinished = !in;
+ FramedStream stream;
+ int streamId;
+
+ synchronized (frameWriter) {
+ synchronized (this) {
+ if (shutdown) {
+ throw new IOException("shutdown");
+ }
+ streamId = nextStreamId;
+ nextStreamId += 2;
+ stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
+ if (stream.isOpen()) {
+ streams.put(streamId, stream);
+ setIdle(false);
+ }
+ }
+ if (associatedStreamId == 0) {
+ frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
+ requestHeaders);
+ } else if (client) {
+ throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
+ } else { // HTTP/2 has a PUSH_PROMISE frame.
+ frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
+ }
+ }
+
+ if (!out) {
+ frameWriter.flush();
+ }
+
+ return stream;
+ }
+
+ void writeSynReply(int streamId, boolean outFinished, List<Header> alternating)
+ throws IOException {
+ frameWriter.synReply(outFinished, streamId, alternating);
+ }
+
+ /**
+ * Callers of this method are not thread safe, and sometimes on application threads. Most often,
+ * this method will be called to send a buffer worth of data to the peer.
+ *
+ * <p>Writes are subject to the write window of the stream and the connection. Until there is a
+ * window sufficient to send {@code byteCount}, the caller will block. For example, a user of
+ * {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's
+ * write window will block.
+ *
+ * <p>Zero {@code byteCount} writes are not subject to flow control and will not block. The only
+ * use case for zero {@code byteCount} is closing a flushed output stream.
+ */
+ public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount)
+ throws IOException {
+ if (byteCount == 0) { // Empty data frames are not flow-controlled.
+ frameWriter.data(outFinished, streamId, buffer, 0);
+ return;
+ }
+
+ while (byteCount > 0) {
+ int toWrite;
+ synchronized (FramedConnection.this) {
+ try {
+ while (bytesLeftInWriteWindow <= 0) {
+ // Before blocking, confirm that the stream we're writing is still open. It's possible
+ // that the stream has since been closed (such as if this write timed out.)
+ if (!streams.containsKey(streamId)) {
+ throw new IOException("stream closed");
+ }
+ FramedConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE.
+ }
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException();
+ }
+
+ toWrite = (int) Math.min(byteCount, bytesLeftInWriteWindow);
+ toWrite = Math.min(toWrite, frameWriter.maxDataLength());
+ bytesLeftInWriteWindow -= toWrite;
+ }
+
+ byteCount -= toWrite;
+ frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite);
+ }
+ }
+
+ /**
+ * {@code delta} will be negative if a settings frame initial window is
+ * smaller than the last.
+ */
+ void addBytesToWriteWindow(long delta) {
+ bytesLeftInWriteWindow += delta;
+ if (delta > 0) FramedConnection.this.notifyAll();
+ }
+
+ void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
+ executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
+ @Override public void execute() {
+ try {
+ writeSynReset(streamId, errorCode);
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ void writeSynReset(int streamId, ErrorCode statusCode) throws IOException {
+ frameWriter.rstStream(streamId, statusCode);
+ }
+
+ void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
+ executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostName, streamId) {
+ @Override public void execute() {
+ try {
+ frameWriter.windowUpdate(streamId, unacknowledgedBytesRead);
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Sends a ping frame to the peer. Use the returned object to await the
+ * ping's response and observe its round trip time.
+ */
+ public Ping ping() throws IOException {
+ Ping ping = new Ping();
+ int pingId;
+ synchronized (this) {
+ if (shutdown) {
+ throw new IOException("shutdown");
+ }
+ pingId = nextPingId;
+ nextPingId += 2;
+ if (pings == null) pings = new HashMap<>();
+ pings.put(pingId, ping);
+ }
+ writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping);
+ return ping;
+ }
+
+ 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) {
+ @Override public void execute() {
+ try {
+ writePing(reply, payload1, payload2, ping);
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException {
+ synchronized (frameWriter) {
+ // Observe the sent time immediately before performing I/O.
+ if (ping != null) ping.send();
+ frameWriter.ping(reply, payload1, payload2);
+ }
+ }
+
+ private synchronized Ping removePing(int id) {
+ return pings != null ? pings.remove(id) : null;
+ }
+
+ public void flush() throws IOException {
+ frameWriter.flush();
+ }
+
+ /**
+ * Degrades this connection such that new streams can neither be created
+ * locally, nor accepted from the remote peer. Existing streams are not
+ * impacted. This is intended to permit an endpoint to gracefully stop
+ * accepting new requests without harming previously established streams.
+ */
+ public void shutdown(ErrorCode statusCode) throws IOException {
+ synchronized (frameWriter) {
+ int lastGoodStreamId;
+ synchronized (this) {
+ if (shutdown) {
+ return;
+ }
+ shutdown = true;
+ lastGoodStreamId = this.lastGoodStreamId;
+ }
+ // TODO: propagate exception message into debugData
+ frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY);
+ }
+ }
+
+ /**
+ * Closes this connection. This cancels all open streams and unanswered
+ * pings. It closes the underlying input and output streams and shuts down
+ * internal executor services.
+ */
+ @Override public void close() throws IOException {
+ close(ErrorCode.NO_ERROR, ErrorCode.CANCEL);
+ }
+
+ private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException {
+ assert (!Thread.holdsLock(this));
+ IOException thrown = null;
+ try {
+ shutdown(connectionCode);
+ } catch (IOException e) {
+ thrown = e;
+ }
+
+ FramedStream[] streamsToClose = null;
+ Ping[] pingsToCancel = null;
+ synchronized (this) {
+ if (!streams.isEmpty()) {
+ streamsToClose = streams.values().toArray(new FramedStream[streams.size()]);
+ streams.clear();
+ setIdle(false);
+ }
+ if (pings != null) {
+ pingsToCancel = pings.values().toArray(new Ping[pings.size()]);
+ pings = null;
+ }
+ }
+
+ if (streamsToClose != null) {
+ for (FramedStream stream : streamsToClose) {
+ try {
+ stream.close(streamCode);
+ } catch (IOException e) {
+ if (thrown != null) thrown = e;
+ }
+ }
+ }
+
+ if (pingsToCancel != null) {
+ for (Ping ping : pingsToCancel) {
+ ping.cancel();
+ }
+ }
+
+ // Close the writer to release its resources (such as deflaters).
+ try {
+ frameWriter.close();
+ } catch (IOException e) {
+ if (thrown == null) thrown = e;
+ }
+
+ // Close the socket to break out the reader thread, which will clean up after itself.
+ try {
+ socket.close();
+ } catch (IOException e) {
+ thrown = e;
+ }
+
+ if (thrown != null) throw thrown;
+ }
+
+ /**
+ * Sends a connection header if the current variant requires it. This should
+ * be called after {@link Builder#build} for all new connections.
+ */
+ public void sendConnectionPreface() throws IOException {
+ frameWriter.connectionPreface();
+ frameWriter.settings(okHttpSettings);
+ int windowSize = okHttpSettings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE);
+ if (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) {
+ frameWriter.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE);
+ }
+ }
+
+ /** Merges {@code settings} into this peer's settings and sends them to the remote peer. */
+ public void setSettings(Settings settings) throws IOException {
+ synchronized (frameWriter) {
+ synchronized (this) {
+ if (shutdown) {
+ throw new IOException("shutdown");
+ }
+ okHttpSettings.merge(settings);
+ frameWriter.settings(settings);
+ }
+ }
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static class Builder {
+ private Socket socket;
+ private String hostName;
+ private BufferedSource source;
+ private BufferedSink sink;
+ private Listener listener = Listener.REFUSE_INCOMING_STREAMS;
+ private Protocol protocol = Protocol.SPDY_3;
+ private PushObserver pushObserver = PushObserver.CANCEL;
+ private boolean client;
+
+ /**
+ * @param client true if this peer initiated the connection; false if this
+ * peer accepted the connection.
+ */
+ public Builder(boolean client) throws IOException {
+ this.client = client;
+ }
+
+ public Builder socket(Socket socket) throws IOException {
+ return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(),
+ Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket)));
+ }
+
+ public Builder socket(
+ Socket socket, String hostName, BufferedSource source, BufferedSink sink) {
+ this.socket = socket;
+ this.hostName = hostName;
+ this.source = source;
+ this.sink = sink;
+ return this;
+ }
+
+ public Builder listener(Listener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ public Builder protocol(Protocol protocol) {
+ this.protocol = protocol;
+ return this;
+ }
+
+ public Builder pushObserver(PushObserver pushObserver) {
+ this.pushObserver = pushObserver;
+ return this;
+ }
+
+ public FramedConnection build() throws IOException {
+ return new FramedConnection(this);
+ }
+ }
+
+ /**
+ * Methods in this class must not lock FrameWriter. If a method needs to
+ * write a frame, create an async task to do so.
+ */
+ class Reader extends NamedRunnable implements FrameReader.Handler {
+ final FrameReader frameReader;
+
+ private Reader(FrameReader frameReader) {
+ super("OkHttp %s", hostName);
+ this.frameReader = frameReader;
+ }
+
+ @Override protected void execute() {
+ ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
+ ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
+ try {
+ if (!client) {
+ frameReader.readConnectionPreface();
+ }
+ while (frameReader.nextFrame(this)) {
+ }
+ connectionErrorCode = ErrorCode.NO_ERROR;
+ streamErrorCode = ErrorCode.CANCEL;
+ } catch (IOException e) {
+ connectionErrorCode = ErrorCode.PROTOCOL_ERROR;
+ streamErrorCode = ErrorCode.PROTOCOL_ERROR;
+ } finally {
+ try {
+ close(connectionErrorCode, streamErrorCode);
+ } catch (IOException ignored) {
+ }
+ Util.closeQuietly(frameReader);
+ }
+ }
+
+ @Override public void data(boolean inFinished, int streamId, BufferedSource source, int length)
+ throws IOException {
+ if (pushedStream(streamId)) {
+ pushDataLater(streamId, source, length, inFinished);
+ return;
+ }
+ FramedStream dataStream = getStream(streamId);
+ if (dataStream == null) {
+ writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
+ source.skip(length);
+ return;
+ }
+ dataStream.receiveData(source, length);
+ if (inFinished) {
+ dataStream.receiveFin();
+ }
+ }
+
+ @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
+ int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
+ if (pushedStream(streamId)) {
+ pushHeadersLater(streamId, headerBlock, inFinished);
+ return;
+ }
+ FramedStream stream;
+ synchronized (FramedConnection.this) {
+ // If we're shutdown, don't bother with this stream.
+ if (shutdown) return;
+
+ stream = getStream(streamId);
+
+ if (stream == null) {
+ // The headers claim to be for an existing stream, but we don't have one.
+ if (headersMode.failIfStreamAbsent()) {
+ writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
+ return;
+ }
+
+ // If the stream ID is less than the last created ID, assume it's already closed.
+ if (streamId <= lastGoodStreamId) return;
+
+ // If the stream ID is in the client's namespace, assume it's already closed.
+ if (streamId % 2 == nextStreamId % 2) return;
+
+ // Create a stream.
+ final FramedStream
+ newStream = new FramedStream(streamId, FramedConnection.this, outFinished,
+ inFinished, headerBlock);
+ lastGoodStreamId = streamId;
+ streams.put(streamId, newStream);
+ 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);
+ try {
+ newStream.close(ErrorCode.PROTOCOL_ERROR);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ });
+ return;
+ }
+ }
+
+ // The headers claim to be for a new stream, but we already have one.
+ if (headersMode.failIfStreamPresent()) {
+ stream.closeLater(ErrorCode.PROTOCOL_ERROR);
+ removeStream(streamId);
+ return;
+ }
+
+ // Update an existing stream.
+ stream.receiveHeaders(headerBlock, headersMode);
+ if (inFinished) stream.receiveFin();
+ }
+
+ @Override public void rstStream(int streamId, ErrorCode errorCode) {
+ if (pushedStream(streamId)) {
+ pushResetLater(streamId, errorCode);
+ return;
+ }
+ FramedStream rstStream = removeStream(streamId);
+ if (rstStream != null) {
+ rstStream.receiveRstStream(errorCode);
+ }
+ }
+
+ @Override public void settings(boolean clearPrevious, Settings newSettings) {
+ long delta = 0;
+ FramedStream[] streamsToNotify = null;
+ synchronized (FramedConnection.this) {
+ int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ if (clearPrevious) peerSettings.clear();
+ peerSettings.merge(newSettings);
+ if (getProtocol() == Protocol.HTTP_2) {
+ ackSettingsLater(newSettings);
+ }
+ int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
+ delta = peerInitialWindowSize - priorWriteWindowSize;
+ if (!receivedInitialPeerSettings) {
+ addBytesToWriteWindow(delta);
+ receivedInitialPeerSettings = true;
+ }
+ if (!streams.isEmpty()) {
+ streamsToNotify = streams.values().toArray(new FramedStream[streams.size()]);
+ }
+ }
+ executor.execute(new NamedRunnable("OkHttp %s settings", hostName) {
+ @Override public void execute() {
+ listener.onSettings(FramedConnection.this);
+ }
+ });
+ }
+ if (streamsToNotify != null && delta != 0) {
+ for (FramedStream stream : streamsToNotify) {
+ synchronized (stream) {
+ stream.addBytesToWriteWindow(delta);
+ }
+ }
+ }
+ }
+
+ private void ackSettingsLater(final Settings peerSettings) {
+ executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostName) {
+ @Override public void execute() {
+ try {
+ frameWriter.ackSettings(peerSettings);
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ @Override public void ackSettings() {
+ // TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT.
+ }
+
+ @Override public void ping(boolean reply, int payload1, int payload2) {
+ if (reply) {
+ Ping ping = removePing(payload1);
+ if (ping != null) {
+ ping.receive();
+ }
+ } else {
+ // Send a reply to a client ping if this is a server and vice versa.
+ writePingLater(true, payload1, payload2, null);
+ }
+ }
+
+ @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
+ if (debugData.size() > 0) { // TODO: log the debugData
+ }
+
+ // Copy the streams first. We don't want to hold a lock when we call receiveRstStream().
+ FramedStream[] streamsCopy;
+ synchronized (FramedConnection.this) {
+ streamsCopy = streams.values().toArray(new FramedStream[streams.size()]);
+ shutdown = true;
+ }
+
+ // Fail all streams created after the last good stream ID.
+ for (FramedStream framedStream : streamsCopy) {
+ if (framedStream.getId() > lastGoodStreamId && framedStream.isLocallyInitiated()) {
+ framedStream.receiveRstStream(ErrorCode.REFUSED_STREAM);
+ removeStream(framedStream.getId());
+ }
+ }
+ }
+
+ @Override public void windowUpdate(int streamId, long windowSizeIncrement) {
+ if (streamId == 0) {
+ synchronized (FramedConnection.this) {
+ bytesLeftInWriteWindow += windowSizeIncrement;
+ FramedConnection.this.notifyAll();
+ }
+ } else {
+ FramedStream stream = getStream(streamId);
+ if (stream != null) {
+ synchronized (stream) {
+ stream.addBytesToWriteWindow(windowSizeIncrement);
+ }
+ }
+ }
+ }
+
+ @Override public void priority(int streamId, int streamDependency, int weight,
+ boolean exclusive) {
+ // TODO: honor priority.
+ }
+
+ @Override
+ public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {
+ pushRequestLater(promisedStreamId, requestHeaders);
+ }
+
+ @Override public void alternateService(int streamId, String origin, ByteString protocol,
+ String host, int port, long maxAge) {
+ // TODO: register alternate service.
+ }
+ }
+
+ /** Even, positive numbered streams are pushed streams in HTTP/2. */
+ private boolean pushedStream(int streamId) {
+ return protocol == Protocol.HTTP_2 && streamId != 0 && (streamId & 1) == 0;
+ }
+
+ // Guarded by this.
+ private final Set<Integer> currentPushRequests = new LinkedHashSet<>();
+
+ private void pushRequestLater(final int streamId, final List<Header> requestHeaders) {
+ synchronized (this) {
+ if (currentPushRequests.contains(streamId)) {
+ writeSynResetLater(streamId, ErrorCode.PROTOCOL_ERROR);
+ return;
+ }
+ currentPushRequests.add(streamId);
+ }
+ pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostName, streamId) {
+ @Override public void execute() {
+ boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
+ try {
+ if (cancel) {
+ frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+ synchronized (FramedConnection.this) {
+ currentPushRequests.remove(streamId);
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ private void pushHeadersLater(final int streamId, final List<Header> requestHeaders,
+ final boolean inFinished) {
+ pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostName, streamId) {
+ @Override public void execute() {
+ boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
+ try {
+ if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+ if (cancel || inFinished) {
+ synchronized (FramedConnection.this) {
+ currentPushRequests.remove(streamId);
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Eagerly reads {@code byteCount} bytes from the source before launching a background task to
+ * process the data. This avoids corrupting the stream.
+ */
+ private void pushDataLater(final int streamId, final BufferedSource source, final int byteCount,
+ final boolean inFinished) throws IOException {
+ final Buffer buffer = new Buffer();
+ 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) {
+ @Override public void execute() {
+ try {
+ boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
+ if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+ if (cancel || inFinished) {
+ synchronized (FramedConnection.this) {
+ currentPushRequests.remove(streamId);
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ }
+
+ private void pushResetLater(final int streamId, final ErrorCode errorCode) {
+ pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostName, streamId) {
+ @Override public void execute() {
+ pushObserver.onReset(streamId, errorCode);
+ synchronized (FramedConnection.this) {
+ currentPushRequests.remove(streamId);
+ }
+ }
+ });
+ }
+
+ /** Listener of streams and settings initiated by the peer.
+ * @hide This class is not part of the Android public SDK API*/
+ public abstract static class Listener {
+ public static final Listener REFUSE_INCOMING_STREAMS = new Listener() {
+ @Override public void onStream(FramedStream stream) throws IOException {
+ stream.close(ErrorCode.REFUSED_STREAM);
+ }
+ };
+
+ /**
+ * Handle a new stream from this connection's peer. Implementations should
+ * respond by either {@linkplain FramedStream#reply replying to the stream}
+ * or {@linkplain FramedStream#close closing it}. This response does not
+ * need to be synchronous.
+ */
+ public abstract void onStream(FramedStream stream) throws IOException;
+
+ /**
+ * Notification that the connection's peer's settings may have changed.
+ * Implementations should take appropriate action to handle the updated
+ * settings.
+ *
+ * <p>It is the implementation's responsibility to handle concurrent calls
+ * to this method. A remote peer that sends multiple settings frames will
+ * trigger multiple calls to this method, and those calls are not
+ * necessarily serialized.
+ */
+ public void onSettings(FramedConnection connection) {
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java
new file mode 100644
index 0000000..9339acf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java
@@ -0,0 +1,618 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.framed;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.List;
+import com.android.okhttp.okio.AsyncTimeout;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
+/** A logical bidirectional stream.
+ * @hide This class is not part of the Android public SDK API*/
+public final class FramedStream {
+ // Internal state is guarded by this. No long-running or potentially
+ // blocking operations are performed while the lock is held.
+
+ /**
+ * The total number of bytes consumed by the application (with {@link
+ * FramedDataSource#read}), but not yet acknowledged by sending a {@code
+ * WINDOW_UPDATE} frame on this stream.
+ */
+ // Visible for testing
+ long unacknowledgedBytesRead = 0;
+
+ /**
+ * Count of bytes that can be written on the stream before receiving a
+ * window update. Even if this is positive, writes will block until there
+ * available bytes in {@code connection.bytesLeftInWriteWindow}.
+ */
+ // guarded by this
+ long bytesLeftInWriteWindow;
+
+ private final int id;
+ private final FramedConnection connection;
+
+ /** Headers sent by the stream initiator. Immutable and non null. */
+ private final List<Header> requestHeaders;
+
+ /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
+ private List<Header> responseHeaders;
+
+ private final FramedDataSource source;
+ final FramedDataSink sink;
+ private final StreamTimeout readTimeout = new StreamTimeout();
+ private final StreamTimeout writeTimeout = new StreamTimeout();
+
+ /**
+ * The reason why this stream was abnormally closed. If there are multiple
+ * reasons to abnormally close this stream (such as both peers closing it
+ * near-simultaneously) then this is the first reason known to this peer.
+ */
+ private ErrorCode errorCode = null;
+
+ FramedStream(int id, FramedConnection connection, boolean outFinished, boolean inFinished,
+ List<Header> requestHeaders) {
+ if (connection == null) throw new NullPointerException("connection == null");
+ if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
+ this.id = id;
+ this.connection = connection;
+ this.bytesLeftInWriteWindow =
+ connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ this.source = new FramedDataSource(
+ connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
+ this.sink = new FramedDataSink();
+ this.source.finished = inFinished;
+ this.sink.finished = outFinished;
+ this.requestHeaders = requestHeaders;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Returns true if this stream is open. A stream is open until either:
+ * <ul>
+ * <li>A {@code SYN_RESET} frame abnormally terminates the stream.
+ * <li>Both input and output streams have transmitted all data and
+ * headers.
+ * </ul>
+ * Note that the input stream may continue to yield data even after a stream
+ * reports itself as not open. This is because input data is buffered.
+ */
+ public synchronized boolean isOpen() {
+ if (errorCode != null) {
+ return false;
+ }
+ if ((source.finished || source.closed)
+ && (sink.finished || sink.closed)
+ && responseHeaders != null) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Returns true if this stream was created by this peer. */
+ public boolean isLocallyInitiated() {
+ boolean streamIsClient = ((id & 1) == 1);
+ return connection.client == streamIsClient;
+ }
+
+ public FramedConnection getConnection() {
+ return connection;
+ }
+
+ public List<Header> getRequestHeaders() {
+ return requestHeaders;
+ }
+
+ /**
+ * Returns the stream's response headers, blocking if necessary if they
+ * have not been received yet.
+ */
+ public synchronized List<Header> getResponseHeaders() throws IOException {
+ readTimeout.enter();
+ try {
+ while (responseHeaders == null && errorCode == null) {
+ waitForIo();
+ }
+ } finally {
+ readTimeout.exitAndThrowIfTimedOut();
+ }
+ if (responseHeaders != null) return responseHeaders;
+ throw new IOException("stream was reset: " + errorCode);
+ }
+
+ /**
+ * Returns the reason why this stream was closed, or null if it closed
+ * normally or has not yet been closed.
+ */
+ public synchronized ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ /**
+ * Sends a reply to an incoming stream.
+ *
+ * @param out true to create an output stream that we can use to send data
+ * to the remote peer. Corresponds to {@code FLAG_FIN}.
+ */
+ public void reply(List<Header> responseHeaders, boolean out) throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ boolean outFinished = false;
+ synchronized (this) {
+ if (responseHeaders == null) {
+ throw new NullPointerException("responseHeaders == null");
+ }
+ if (this.responseHeaders != null) {
+ throw new IllegalStateException("reply already sent");
+ }
+ this.responseHeaders = responseHeaders;
+ if (!out) {
+ this.sink.finished = true;
+ outFinished = true;
+ }
+ }
+ connection.writeSynReply(id, outFinished, responseHeaders);
+
+ if (outFinished) {
+ connection.flush();
+ }
+ }
+
+ public Timeout readTimeout() {
+ return readTimeout;
+ }
+
+ public Timeout writeTimeout() {
+ return writeTimeout;
+ }
+
+ /** Returns a source that reads data from the peer. */
+ public Source getSource() {
+ return source;
+ }
+
+ /**
+ * Returns a sink that can be used to write data to the peer.
+ *
+ * @throws IllegalStateException if this stream was initiated by the peer
+ * and a {@link #reply} has not yet been sent.
+ */
+ public Sink getSink() {
+ synchronized (this) {
+ if (responseHeaders == null && !isLocallyInitiated()) {
+ throw new IllegalStateException("reply before requesting the sink");
+ }
+ }
+ return sink;
+ }
+
+ /**
+ * Abnormally terminate this stream. This blocks until the {@code RST_STREAM}
+ * frame has been transmitted.
+ */
+ public void close(ErrorCode rstStatusCode) throws IOException {
+ if (!closeInternal(rstStatusCode)) {
+ return; // Already closed.
+ }
+ connection.writeSynReset(id, rstStatusCode);
+ }
+
+ /**
+ * Abnormally terminate this stream. This enqueues a {@code RST_STREAM}
+ * frame and returns immediately.
+ */
+ public void closeLater(ErrorCode errorCode) {
+ if (!closeInternal(errorCode)) {
+ return; // Already closed.
+ }
+ connection.writeSynResetLater(id, errorCode);
+ }
+
+ /** Returns true if this stream was closed. */
+ private boolean closeInternal(ErrorCode errorCode) {
+ assert (!Thread.holdsLock(this));
+ synchronized (this) {
+ if (this.errorCode != null) {
+ return false;
+ }
+ if (source.finished && sink.finished) {
+ return false;
+ }
+ this.errorCode = errorCode;
+ notifyAll();
+ }
+ connection.removeStream(id);
+ return true;
+ }
+
+ void receiveHeaders(List<Header> headers, HeadersMode headersMode) {
+ assert (!Thread.holdsLock(FramedStream.this));
+ ErrorCode errorCode = null;
+ boolean open = true;
+ synchronized (this) {
+ if (responseHeaders == null) {
+ if (headersMode.failIfHeadersAbsent()) {
+ errorCode = ErrorCode.PROTOCOL_ERROR;
+ } else {
+ responseHeaders = headers;
+ open = isOpen();
+ notifyAll();
+ }
+ } else {
+ if (headersMode.failIfHeadersPresent()) {
+ errorCode = ErrorCode.STREAM_IN_USE;
+ } else {
+ List<Header> newHeaders = new ArrayList<>();
+ newHeaders.addAll(responseHeaders);
+ newHeaders.addAll(headers);
+ this.responseHeaders = newHeaders;
+ }
+ }
+ }
+ if (errorCode != null) {
+ closeLater(errorCode);
+ } else if (!open) {
+ connection.removeStream(id);
+ }
+ }
+
+ void receiveData(BufferedSource in, int length) throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ this.source.receive(in, length);
+ }
+
+ void receiveFin() {
+ assert (!Thread.holdsLock(FramedStream.this));
+ boolean open;
+ synchronized (this) {
+ this.source.finished = true;
+ open = isOpen();
+ notifyAll();
+ }
+ if (!open) {
+ connection.removeStream(id);
+ }
+ }
+
+ synchronized void receiveRstStream(ErrorCode errorCode) {
+ if (this.errorCode == null) {
+ this.errorCode = errorCode;
+ notifyAll();
+ }
+ }
+
+ /**
+ * A source that reads the incoming data frames of a stream. Although this
+ * class uses synchronization to safely receive incoming data frames, it is
+ * not intended for use by multiple readers.
+ */
+ private final class FramedDataSource implements Source {
+ /** Buffer to receive data from the network into. Only accessed by the reader thread. */
+ private final Buffer receiveBuffer = new Buffer();
+
+ /** Buffer with readable data. Guarded by FramedStream.this. */
+ private final Buffer readBuffer = new Buffer();
+
+ /** Maximum number of bytes to buffer before reporting a flow control error. */
+ private final long maxByteCount;
+
+ /** True if the caller has closed this stream. */
+ private boolean closed;
+
+ /**
+ * True if either side has cleanly shut down this stream. We will
+ * receive no more bytes beyond those already in the buffer.
+ */
+ private boolean finished;
+
+ private FramedDataSource(long maxByteCount) {
+ this.maxByteCount = maxByteCount;
+ }
+
+ @Override public long read(Buffer sink, long byteCount)
+ throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+
+ long read;
+ synchronized (FramedStream.this) {
+ waitUntilReadable();
+ checkNotClosed();
+ if (readBuffer.size() == 0) return -1; // This source is exhausted.
+
+ // Move bytes from the read buffer into the caller's buffer.
+ read = readBuffer.read(sink, Math.min(byteCount, readBuffer.size()));
+
+ // Flow control: notify the peer that we're ready for more data!
+ unacknowledgedBytesRead += read;
+ if (unacknowledgedBytesRead
+ >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
+ connection.writeWindowUpdateLater(id, unacknowledgedBytesRead);
+ unacknowledgedBytesRead = 0;
+ }
+ }
+
+ // Update connection.unacknowledgedBytesRead outside the stream lock.
+ synchronized (connection) { // Multiple application threads may hit this section.
+ connection.unacknowledgedBytesRead += read;
+ if (connection.unacknowledgedBytesRead
+ >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
+ connection.writeWindowUpdateLater(0, connection.unacknowledgedBytesRead);
+ connection.unacknowledgedBytesRead = 0;
+ }
+ }
+
+ return read;
+ }
+
+ /** Returns once the source is either readable or finished. */
+ private void waitUntilReadable() throws IOException {
+ readTimeout.enter();
+ try {
+ while (readBuffer.size() == 0 && !finished && !closed && errorCode == null) {
+ waitForIo();
+ }
+ } finally {
+ readTimeout.exitAndThrowIfTimedOut();
+ }
+ }
+
+ void receive(BufferedSource in, long byteCount) throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+
+ while (byteCount > 0) {
+ boolean finished;
+ boolean flowControlError;
+ synchronized (FramedStream.this) {
+ finished = this.finished;
+ flowControlError = byteCount + readBuffer.size() > maxByteCount;
+ }
+
+ // If the peer sends more data than we can handle, discard it and close the connection.
+ if (flowControlError) {
+ in.skip(byteCount);
+ closeLater(ErrorCode.FLOW_CONTROL_ERROR);
+ return;
+ }
+
+ // Discard data received after the stream is finished. It's probably a benign race.
+ if (finished) {
+ in.skip(byteCount);
+ return;
+ }
+
+ // Fill the receive buffer without holding any locks.
+ long read = in.read(receiveBuffer, byteCount);
+ if (read == -1) throw new EOFException();
+ byteCount -= read;
+
+ // Move the received data to the read buffer to the reader can read it.
+ synchronized (FramedStream.this) {
+ boolean wasEmpty = readBuffer.size() == 0;
+ readBuffer.writeAll(receiveBuffer);
+ if (wasEmpty) {
+ FramedStream.this.notifyAll();
+ }
+ }
+ }
+ }
+
+ @Override public Timeout timeout() {
+ return readTimeout;
+ }
+
+ @Override public void close() throws IOException {
+ synchronized (FramedStream.this) {
+ closed = true;
+ readBuffer.clear();
+ FramedStream.this.notifyAll();
+ }
+ cancelStreamIfNecessary();
+ }
+
+ private void checkNotClosed() throws IOException {
+ if (closed) {
+ throw new IOException("stream closed");
+ }
+ if (errorCode != null) {
+ throw new IOException("stream was reset: " + errorCode);
+ }
+ }
+ }
+
+ private void cancelStreamIfNecessary() throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ boolean open;
+ boolean cancel;
+ synchronized (this) {
+ cancel = !source.finished && source.closed && (sink.finished || sink.closed);
+ open = isOpen();
+ }
+ if (cancel) {
+ // RST this stream to prevent additional data from being sent. This
+ // is safe because the input stream is closed (we won't use any
+ // further bytes) and the output stream is either finished or closed
+ // (so RSTing both streams doesn't cause harm).
+ FramedStream.this.close(ErrorCode.CANCEL);
+ } else if (!open) {
+ connection.removeStream(id);
+ }
+ }
+
+ /**
+ * A sink that writes outgoing data frames of a stream. This class is not
+ * thread safe.
+ */
+ final class FramedDataSink implements Sink {
+ private static final long EMIT_BUFFER_SIZE = 16384;
+
+ /**
+ * Buffer of outgoing data. This batches writes of small writes into this sink as larges
+ * frames written to the outgoing connection. Batching saves the (small) framing overhead.
+ */
+ private final Buffer sendBuffer = new Buffer();
+
+ private boolean closed;
+
+ /**
+ * True if either side has cleanly shut down this stream. We shall send
+ * no more bytes.
+ */
+ private boolean finished;
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ sendBuffer.write(source, byteCount);
+ while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {
+ emitDataFrame(false);
+ }
+ }
+
+ /**
+ * Emit a single data frame to the connection. The frame's size be limited by this stream's
+ * write window. This method will block until the write window is nonempty.
+ */
+ private void emitDataFrame(boolean outFinished) throws IOException {
+ long toWrite;
+ synchronized (FramedStream.this) {
+ writeTimeout.enter();
+ try {
+ while (bytesLeftInWriteWindow <= 0 && !finished && !closed && errorCode == null) {
+ waitForIo(); // Wait until we receive a WINDOW_UPDATE for this stream.
+ }
+ } finally {
+ writeTimeout.exitAndThrowIfTimedOut();
+ }
+
+ checkOutNotClosed(); // Kick out if the stream was reset or closed while waiting.
+ toWrite = Math.min(bytesLeftInWriteWindow, sendBuffer.size());
+ bytesLeftInWriteWindow -= toWrite;
+ }
+
+ writeTimeout.enter();
+ try {
+ connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);
+ } finally {
+ writeTimeout.exitAndThrowIfTimedOut();
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ synchronized (FramedStream.this) {
+ checkOutNotClosed();
+ }
+ while (sendBuffer.size() > 0) {
+ emitDataFrame(false);
+ connection.flush();
+ }
+ }
+
+ @Override public Timeout timeout() {
+ return writeTimeout;
+ }
+
+ @Override public void close() throws IOException {
+ assert (!Thread.holdsLock(FramedStream.this));
+ synchronized (FramedStream.this) {
+ if (closed) return;
+ }
+ if (!sink.finished) {
+ // Emit the remaining data, setting the END_STREAM flag on the last frame.
+ if (sendBuffer.size() > 0) {
+ while (sendBuffer.size() > 0) {
+ emitDataFrame(true);
+ }
+ } else {
+ // Send an empty frame just so we can set the END_STREAM flag.
+ connection.writeData(id, true, null, 0);
+ }
+ }
+ synchronized (FramedStream.this) {
+ closed = true;
+ }
+ connection.flush();
+ cancelStreamIfNecessary();
+ }
+ }
+
+ /**
+ * {@code delta} will be negative if a settings frame initial window is
+ * smaller than the last.
+ */
+ void addBytesToWriteWindow(long delta) {
+ bytesLeftInWriteWindow += delta;
+ if (delta > 0) FramedStream.this.notifyAll();
+ }
+
+ private void checkOutNotClosed() throws IOException {
+ if (sink.closed) {
+ throw new IOException("stream closed");
+ } else if (sink.finished) {
+ throw new IOException("stream finished");
+ } else if (errorCode != null) {
+ throw new IOException("stream was reset: " + errorCode);
+ }
+ }
+
+ /**
+ * Like {@link #wait}, but throws an {@code InterruptedIOException} when
+ * interrupted instead of the more awkward {@link InterruptedException}.
+ */
+ private void waitForIo() throws InterruptedIOException {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException();
+ }
+ }
+
+ /**
+ * The Okio timeout watchdog will call {@link #timedOut} if the timeout is
+ * reached. In that case we close the stream (asynchronously) which will
+ * notify the waiting thread.
+ */
+ class StreamTimeout extends AsyncTimeout {
+ @Override protected void timedOut() {
+ closeLater(ErrorCode.CANCEL);
+ }
+
+ @Override protected IOException newTimeoutException(IOException cause) {
+ SocketTimeoutException socketTimeoutException = new SocketTimeoutException("timeout");
+ if (cause != null) {
+ socketTimeoutException.initCause(cause);
+ }
+ return socketTimeoutException;
+ }
+
+ public void exitAndThrowIfTimedOut() throws IOException {
+ if (exit()) throw newTimeoutException(null /* cause */);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java
new file mode 100644
index 0000000..4e2c67f
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.framed;
+
+import com.android.okhttp.okio.ByteString;
+
+/** HTTP header: the name is an ASCII string, but the value can be UTF-8.
+ * @hide This class is not part of the Android public SDK API*/
+public final class Header {
+ // Special header names defined in the SPDY and HTTP/2 specs.
+ public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status");
+ public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
+ public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
+ public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
+ public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
+ public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
+ public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
+
+ /** Name in case-insensitive ASCII encoding. */
+ public final ByteString name;
+ /** Value in UTF-8 encoding. */
+ public final ByteString value;
+ final int hpackSize;
+
+ // TODO: search for toLowerCase and consider moving logic here.
+ public Header(String name, String value) {
+ this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
+ }
+
+ public Header(ByteString name, String value) {
+ this(name, ByteString.encodeUtf8(value));
+ }
+
+ public Header(ByteString name, ByteString value) {
+ this.name = name;
+ this.value = value;
+ this.hpackSize = 32 + name.size() + value.size();
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Header) {
+ Header that = (Header) other;
+ return this.name.equals(that.name)
+ && this.value.equals(that.value);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + name.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+
+ @Override public String toString() {
+ return String.format("%s: %s", name.utf8(), value.utf8());
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java
new file mode 100644
index 0000000..bcbb492
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java
@@ -0,0 +1,53 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum HeadersMode {
+ SPDY_SYN_STREAM,
+ SPDY_REPLY,
+ SPDY_HEADERS,
+ HTTP_20_HEADERS;
+
+ /** Returns true if it is an error these headers to create a new stream. */
+ public boolean failIfStreamAbsent() {
+ return this == SPDY_REPLY || this == SPDY_HEADERS;
+ }
+
+ /** Returns true if it is an error these headers to update an existing stream. */
+ public boolean failIfStreamPresent() {
+ return this == SPDY_SYN_STREAM;
+ }
+
+ /**
+ * Returns true if it is an error these headers to be the initial headers of a
+ * response.
+ */
+ public boolean failIfHeadersAbsent() {
+ return this == SPDY_HEADERS;
+ }
+
+ /**
+ * Returns true if it is an error these headers to be update existing headers
+ * of a response.
+ */
+ public boolean failIfHeadersPresent() {
+ return this == SPDY_REPLY;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java
new file mode 100644
index 0000000..538e498
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java
@@ -0,0 +1,436 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Read and write HPACK v10.
+ *
+ * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
+ *
+ * This implementation uses an array for the dynamic table and a list for
+ * indexed entries. Dynamic entries are added to the array, starting in the
+ * last position moving forward. When the array fills, it is doubled.
+ */
+final class Hpack {
+ private static final int PREFIX_4_BITS = 0x0f;
+ private static final int PREFIX_5_BITS = 0x1f;
+ private static final int PREFIX_6_BITS = 0x3f;
+ private static final int PREFIX_7_BITS = 0x7f;
+
+ private static final Header[] STATIC_HEADER_TABLE = new Header[] {
+ new Header(Header.TARGET_AUTHORITY, ""),
+ new Header(Header.TARGET_METHOD, "GET"),
+ new Header(Header.TARGET_METHOD, "POST"),
+ new Header(Header.TARGET_PATH, "/"),
+ new Header(Header.TARGET_PATH, "/index.html"),
+ new Header(Header.TARGET_SCHEME, "http"),
+ new Header(Header.TARGET_SCHEME, "https"),
+ new Header(Header.RESPONSE_STATUS, "200"),
+ new Header(Header.RESPONSE_STATUS, "204"),
+ new Header(Header.RESPONSE_STATUS, "206"),
+ new Header(Header.RESPONSE_STATUS, "304"),
+ new Header(Header.RESPONSE_STATUS, "400"),
+ new Header(Header.RESPONSE_STATUS, "404"),
+ new Header(Header.RESPONSE_STATUS, "500"),
+ new Header("accept-charset", ""),
+ new Header("accept-encoding", "gzip, deflate"),
+ new Header("accept-language", ""),
+ new Header("accept-ranges", ""),
+ new Header("accept", ""),
+ new Header("access-control-allow-origin", ""),
+ new Header("age", ""),
+ new Header("allow", ""),
+ new Header("authorization", ""),
+ new Header("cache-control", ""),
+ new Header("content-disposition", ""),
+ new Header("content-encoding", ""),
+ new Header("content-language", ""),
+ new Header("content-length", ""),
+ new Header("content-location", ""),
+ new Header("content-range", ""),
+ new Header("content-type", ""),
+ new Header("cookie", ""),
+ new Header("date", ""),
+ new Header("etag", ""),
+ new Header("expect", ""),
+ new Header("expires", ""),
+ new Header("from", ""),
+ new Header("host", ""),
+ new Header("if-match", ""),
+ new Header("if-modified-since", ""),
+ new Header("if-none-match", ""),
+ new Header("if-range", ""),
+ new Header("if-unmodified-since", ""),
+ new Header("last-modified", ""),
+ new Header("link", ""),
+ new Header("location", ""),
+ new Header("max-forwards", ""),
+ new Header("proxy-authenticate", ""),
+ new Header("proxy-authorization", ""),
+ new Header("range", ""),
+ new Header("referer", ""),
+ new Header("refresh", ""),
+ new Header("retry-after", ""),
+ new Header("server", ""),
+ new Header("set-cookie", ""),
+ new Header("strict-transport-security", ""),
+ new Header("transfer-encoding", ""),
+ new Header("user-agent", ""),
+ new Header("vary", ""),
+ new Header("via", ""),
+ new Header("www-authenticate", "")
+ };
+
+ private Hpack() {
+ }
+
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1
+ static final class Reader {
+
+ private final List<Header> headerList = new ArrayList<>();
+ private final BufferedSource source;
+
+ private int headerTableSizeSetting;
+ private int maxDynamicTableByteCount;
+ // Visible for testing.
+ Header[] dynamicTable = new Header[8];
+ // Array is populated back to front, so new entries always have lowest index.
+ int nextHeaderIndex = dynamicTable.length - 1;
+ int headerCount = 0;
+ int dynamicTableByteCount = 0;
+
+ Reader(int headerTableSizeSetting, Source source) {
+ this.headerTableSizeSetting = headerTableSizeSetting;
+ this.maxDynamicTableByteCount = headerTableSizeSetting;
+ this.source = Okio.buffer(source);
+ }
+
+ int maxDynamicTableByteCount() {
+ return maxDynamicTableByteCount;
+ }
+
+ /**
+ * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}.
+ * While this establishes the maximum dynamic table size, the
+ * {@link #maxDynamicTableByteCount} set during processing may limit the
+ * table size to a smaller amount.
+ * <p> Evicts entries or clears the table as needed.
+ */
+ void headerTableSizeSetting(int headerTableSizeSetting) {
+ this.headerTableSizeSetting = headerTableSizeSetting;
+ this.maxDynamicTableByteCount = headerTableSizeSetting;
+ adjustDynamicTableByteCount();
+ }
+
+ private void adjustDynamicTableByteCount() {
+ if (maxDynamicTableByteCount < dynamicTableByteCount) {
+ if (maxDynamicTableByteCount == 0) {
+ clearDynamicTable();
+ } else {
+ evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount);
+ }
+ }
+ }
+
+ private void clearDynamicTable() {
+ headerList.clear();
+ Arrays.fill(dynamicTable, null);
+ nextHeaderIndex = dynamicTable.length - 1;
+ headerCount = 0;
+ dynamicTableByteCount = 0;
+ }
+
+ /** Returns the count of entries evicted. */
+ private int evictToRecoverBytes(int bytesToRecover) {
+ int entriesToEvict = 0;
+ if (bytesToRecover > 0) {
+ // determine how many headers need to be evicted.
+ for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
+ bytesToRecover -= dynamicTable[j].hpackSize;
+ dynamicTableByteCount -= dynamicTable[j].hpackSize;
+ headerCount--;
+ entriesToEvict++;
+ }
+ System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable,
+ nextHeaderIndex + 1 + entriesToEvict, headerCount);
+ nextHeaderIndex += entriesToEvict;
+ }
+ return entriesToEvict;
+ }
+
+ /**
+ * Read {@code byteCount} bytes of headers from the source stream. This
+ * implementation does not propagate the never indexed flag of a header.
+ */
+ void readHeaders() throws IOException {
+ while (!source.exhausted()) {
+ int b = source.readByte() & 0xff;
+ if (b == 0x80) { // 10000000
+ throw new IOException("index == 0");
+ } else if ((b & 0x80) == 0x80) { // 1NNNNNNN
+ int index = readInt(b, PREFIX_7_BITS);
+ readIndexedHeader(index - 1);
+ } else if (b == 0x40) { // 01000000
+ readLiteralHeaderWithIncrementalIndexingNewName();
+ } else if ((b & 0x40) == 0x40) { // 01NNNNNN
+ int index = readInt(b, PREFIX_6_BITS);
+ readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
+ } else if ((b & 0x20) == 0x20) { // 001NNNNN
+ maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS);
+ if (maxDynamicTableByteCount < 0
+ || maxDynamicTableByteCount > headerTableSizeSetting) {
+ throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount);
+ }
+ adjustDynamicTableByteCount();
+ } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.
+ readLiteralHeaderWithoutIndexingNewName();
+ } else { // 000?NNNN - Ignore never indexed bit.
+ int index = readInt(b, PREFIX_4_BITS);
+ readLiteralHeaderWithoutIndexingIndexedName(index - 1);
+ }
+ }
+ }
+
+ public List<Header> getAndResetHeaderList() {
+ List<Header> result = new ArrayList<>(headerList);
+ headerList.clear();
+ return result;
+ }
+
+ private void readIndexedHeader(int index) throws IOException {
+ if (isStaticHeader(index)) {
+ Header staticEntry = STATIC_HEADER_TABLE[index];
+ headerList.add(staticEntry);
+ } else {
+ int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length);
+ if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) {
+ throw new IOException("Header index too large " + (index + 1));
+ }
+ headerList.add(dynamicTable[dynamicTableIndex]);
+ }
+ }
+
+ // referencedHeaders is relative to nextHeaderIndex + 1.
+ private int dynamicTableIndex(int index) {
+ return nextHeaderIndex + 1 + index;
+ }
+
+ private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
+ ByteString name = getName(index);
+ ByteString value = readByteString();
+ headerList.add(new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
+ ByteString name = checkLowercase(readByteString());
+ ByteString value = readByteString();
+ headerList.add(new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
+ throws IOException {
+ ByteString name = getName(nameIndex);
+ ByteString value = readByteString();
+ insertIntoDynamicTable(-1, new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
+ ByteString name = checkLowercase(readByteString());
+ ByteString value = readByteString();
+ insertIntoDynamicTable(-1, new Header(name, value));
+ }
+
+ private ByteString getName(int index) {
+ if (isStaticHeader(index)) {
+ return STATIC_HEADER_TABLE[index].name;
+ } else {
+ return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name;
+ }
+ }
+
+ private boolean isStaticHeader(int index) {
+ return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1;
+ }
+
+ /** index == -1 when new. */
+ private void insertIntoDynamicTable(int index, Header entry) {
+ headerList.add(entry);
+
+ int delta = entry.hpackSize;
+ if (index != -1) { // Index -1 == new header.
+ delta -= dynamicTable[dynamicTableIndex(index)].hpackSize;
+ }
+
+ // if the new or replacement header is too big, drop all entries.
+ if (delta > maxDynamicTableByteCount) {
+ clearDynamicTable();
+ return;
+ }
+
+ // Evict headers to the required length.
+ int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;
+ int entriesEvicted = evictToRecoverBytes(bytesToRecover);
+
+ if (index == -1) { // Adding a value to the dynamic table.
+ if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.
+ Header[] doubled = new Header[dynamicTable.length * 2];
+ System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);
+ nextHeaderIndex = dynamicTable.length - 1;
+ dynamicTable = doubled;
+ }
+ index = nextHeaderIndex--;
+ dynamicTable[index] = entry;
+ headerCount++;
+ } else { // Replace value at same position.
+ index += dynamicTableIndex(index) + entriesEvicted;
+ dynamicTable[index] = entry;
+ }
+ dynamicTableByteCount += delta;
+ }
+
+ private int readByte() throws IOException {
+ return source.readByte() & 0xff;
+ }
+
+ int readInt(int firstByte, int prefixMask) throws IOException {
+ int prefix = firstByte & prefixMask;
+ if (prefix < prefixMask) {
+ return prefix; // This was a single byte value.
+ }
+
+ // This is a multibyte value. Read 7 bits at a time.
+ int result = prefixMask;
+ int shift = 0;
+ while (true) {
+ int b = readByte();
+ if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
+ result += (b & 0x7f) << shift;
+ shift += 7;
+ } else {
+ result += b << shift; // Last byte.
+ break;
+ }
+ }
+ return result;
+ }
+
+ /** Reads a potentially Huffman encoded byte string. */
+ ByteString readByteString() throws IOException {
+ int firstByte = readByte();
+ boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
+ int length = readInt(firstByte, PREFIX_7_BITS);
+
+ if (huffmanDecode) {
+ return ByteString.of(Huffman.get().decode(source.readByteArray(length)));
+ } else {
+ return source.readByteString(length);
+ }
+ }
+ }
+
+ private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex();
+
+ private static Map<ByteString, Integer> nameToFirstIndex() {
+ Map<ByteString, Integer> result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length);
+ for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
+ if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
+ result.put(STATIC_HEADER_TABLE[i].name, i);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ static final class Writer {
+ private final Buffer out;
+
+ Writer(Buffer out) {
+ this.out = out;
+ }
+
+ /** This does not use "never indexed" semantics for sensitive headers. */
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3
+ void writeHeaders(List<Header> headerBlock) throws IOException {
+ // TODO: implement index tracking
+ for (int i = 0, size = headerBlock.size(); i < size; i++) {
+ ByteString name = headerBlock.get(i).name.toAsciiLowercase();
+ Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
+ if (staticIndex != null) {
+ // Literal Header Field without Indexing - Indexed Name.
+ writeInt(staticIndex + 1, PREFIX_4_BITS, 0);
+ writeByteString(headerBlock.get(i).value);
+ } else {
+ out.writeByte(0x00); // Literal Header without Indexing - New Name.
+ writeByteString(name);
+ writeByteString(headerBlock.get(i).value);
+ }
+ }
+ }
+
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1
+ void writeInt(int value, int prefixMask, int bits) throws IOException {
+ // Write the raw value for a single byte value.
+ if (value < prefixMask) {
+ out.writeByte(bits | value);
+ return;
+ }
+
+ // Write the mask to start a multibyte value.
+ out.writeByte(bits | prefixMask);
+ value -= prefixMask;
+
+ // Write 7 bits at a time 'til we're done.
+ while (value >= 0x80) {
+ int b = value & 0x7f;
+ out.writeByte(b | 0x80);
+ value >>>= 7;
+ }
+ out.writeByte(value);
+ }
+
+ void writeByteString(ByteString data) throws IOException {
+ writeInt(data.size(), PREFIX_7_BITS, 0);
+ out.write(data);
+ }
+ }
+
+ /**
+ * An HTTP/2 response cannot contain uppercase header characters and must
+ * be treated as malformed.
+ */
+ private static ByteString checkLowercase(ByteString name) throws IOException {
+ for (int i = 0, length = name.size(); i < length; i++) {
+ byte c = name.getByte(i);
+ if (c >= 'A' && c <= 'Z') {
+ throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8());
+ }
+ }
+ return name;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java
new file mode 100644
index 0000000..882d7e8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java
@@ -0,0 +1,773 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.framed.Http2.FrameLogger.formatHeader;
+import static java.lang.String.format;
+import static java.util.logging.Level.FINE;
+import static com.android.okhttp.okio.ByteString.EMPTY;
+
+/**
+ * Read and write HTTP/2 frames.
+ * <p>
+ * This implementation assumes we do not send an increased
+ * {@link Settings#getMaxFrameSize frame size setting} to the peer. Hence, we
+ * expect all frames to have a max length of {@link #INITIAL_MAX_FRAME_SIZE}.
+ * <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-17
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Http2 implements Variant {
+ private static final Logger logger = Logger.getLogger(FrameLogger.class.getName());
+
+ @Override public Protocol getProtocol() {
+ return Protocol.HTTP_2;
+ }
+
+ private static final ByteString CONNECTION_PREFACE
+ = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
+
+ /** The initial max frame size, applied independently writing to, or reading from the peer. */
+ static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384
+
+ static final byte TYPE_DATA = 0x0;
+ static final byte TYPE_HEADERS = 0x1;
+ static final byte TYPE_PRIORITY = 0x2;
+ static final byte TYPE_RST_STREAM = 0x3;
+ static final byte TYPE_SETTINGS = 0x4;
+ static final byte TYPE_PUSH_PROMISE = 0x5;
+ static final byte TYPE_PING = 0x6;
+ static final byte TYPE_GOAWAY = 0x7;
+ static final byte TYPE_WINDOW_UPDATE = 0x8;
+ static final byte TYPE_CONTINUATION = 0x9;
+
+ static final byte FLAG_NONE = 0x0;
+ static final byte FLAG_ACK = 0x1; // Used for settings and ping.
+ static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
+ static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
+ static final byte FLAG_END_PUSH_PROMISE = 0x4;
+ static final byte FLAG_PADDED = 0x8; // Used for headers and data.
+ static final byte FLAG_PRIORITY = 0x20; // Used for headers.
+ static final byte FLAG_COMPRESSED = 0x20; // Used for data.
+
+ /**
+ * Creates a frame reader with max header table size of 4096 and data frame
+ * compression disabled.
+ */
+ @Override public FrameReader newReader(BufferedSource source, boolean client) {
+ return new Reader(source, 4096, client);
+ }
+
+ @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
+ return new Writer(sink, client);
+ }
+
+ static final class Reader implements FrameReader {
+ private final BufferedSource source;
+ private final ContinuationSource continuation;
+ private final boolean client;
+
+ // Visible for testing.
+ final Hpack.Reader hpackReader;
+
+ Reader(BufferedSource source, int headerTableSize, boolean client) {
+ this.source = source;
+ this.client = client;
+ this.continuation = new ContinuationSource(this.source);
+ this.hpackReader = new Hpack.Reader(headerTableSize, continuation);
+ }
+
+ @Override public void readConnectionPreface() throws IOException {
+ if (client) return; // Nothing to read; servers doesn't send a connection preface!
+ ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
+ if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
+ if (!CONNECTION_PREFACE.equals(connectionPreface)) {
+ throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
+ }
+ }
+
+ @Override public boolean nextFrame(Handler handler) throws IOException {
+ try {
+ source.require(9); // Frame header size
+ } catch (IOException e) {
+ return false; // This might be a normal socket close.
+ }
+
+ /* 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Length (24) |
+ * +---------------+---------------+---------------+
+ * | Type (8) | Flags (8) |
+ * +-+-+-----------+---------------+-------------------------------+
+ * |R| Stream Identifier (31) |
+ * +=+=============================================================+
+ * | Frame Payload (0...) ...
+ * +---------------------------------------------------------------+
+ */
+ int length = readMedium(source);
+ if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
+ throw ioException("FRAME_SIZE_ERROR: %s", length);
+ }
+ byte type = (byte) (source.readByte() & 0xff);
+ byte flags = (byte) (source.readByte() & 0xff);
+ int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
+ if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+
+ switch (type) {
+ case TYPE_DATA:
+ readData(handler, length, flags, streamId);
+ break;
+
+ case TYPE_HEADERS:
+ readHeaders(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PRIORITY:
+ readPriority(handler, length, flags, streamId);
+ break;
+
+ case TYPE_RST_STREAM:
+ readRstStream(handler, length, flags, streamId);
+ break;
+
+ case TYPE_SETTINGS:
+ readSettings(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PUSH_PROMISE:
+ readPushPromise(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PING:
+ readPing(handler, length, flags, streamId);
+ break;
+
+ case TYPE_GOAWAY:
+ readGoAway(handler, length, flags, streamId);
+ break;
+
+ case TYPE_WINDOW_UPDATE:
+ readWindowUpdate(handler, length, flags, streamId);
+ break;
+
+ default:
+ // Implementations MUST discard frames that have unknown or unsupported types.
+ source.skip(length);
+ }
+ return true;
+ }
+
+ private void readHeaders(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
+
+ boolean endStream = (flags & FLAG_END_STREAM) != 0;
+
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+
+ if ((flags & FLAG_PRIORITY) != 0) {
+ readPriority(handler, streamId);
+ length -= 5; // account for above read.
+ }
+
+ length = lengthWithoutPadding(length, flags, padding);
+
+ List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+
+ handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
+ }
+
+ private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)
+ throws IOException {
+ continuation.length = continuation.left = length;
+ continuation.padding = padding;
+ continuation.flags = flags;
+ continuation.streamId = streamId;
+
+ // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
+ // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5
+ hpackReader.readHeaders();
+ return hpackReader.getAndResetHeaderList();
+ }
+
+ private void readData(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
+ boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+ boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
+ if (gzipped) {
+ throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
+ }
+
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+ length = lengthWithoutPadding(length, flags, padding);
+
+ handler.data(inFinished, streamId, source, length);
+ source.skip(padding);
+ }
+
+ private void readPriority(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
+ if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
+ readPriority(handler, streamId);
+ }
+
+ private void readPriority(Handler handler, int streamId) throws IOException {
+ int w1 = source.readInt();
+ boolean exclusive = (w1 & 0x80000000) != 0;
+ int streamDependency = (w1 & 0x7fffffff);
+ int weight = (source.readByte() & 0xff) + 1;
+ handler.priority(streamId, streamDependency, weight, exclusive);
+ }
+
+ private void readRstStream(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
+ if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
+ int errorCodeInt = source.readInt();
+ ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+ if (errorCode == null) {
+ throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+ }
+ handler.rstStream(streamId, errorCode);
+ }
+
+ private void readSettings(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
+ if ((flags & FLAG_ACK) != 0) {
+ if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
+ handler.ackSettings();
+ return;
+ }
+
+ if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
+ Settings settings = new Settings();
+ for (int i = 0; i < length; i += 6) {
+ short id = source.readShort();
+ int value = source.readInt();
+
+ switch (id) {
+ case 1: // SETTINGS_HEADER_TABLE_SIZE
+ break;
+ case 2: // SETTINGS_ENABLE_PUSH
+ if (value != 0 && value != 1) {
+ throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
+ }
+ break;
+ case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
+ id = 4; // Renumbered in draft 10.
+ break;
+ case 4: // SETTINGS_INITIAL_WINDOW_SIZE
+ id = 7; // Renumbered in draft 10.
+ if (value < 0) {
+ throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
+ }
+ break;
+ case 5: // SETTINGS_MAX_FRAME_SIZE
+ if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
+ throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value);
+ }
+ break;
+ case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
+ break; // Advisory only, so ignored.
+ default:
+ throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
+ }
+ settings.set(id, 0, value);
+ }
+ handler.settings(false, settings);
+ if (settings.getHeaderTableSize() >= 0) {
+ hpackReader.headerTableSizeSetting(settings.getHeaderTableSize());
+ }
+ }
+
+ private void readPushPromise(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (streamId == 0) {
+ throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
+ }
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+ int promisedStreamId = source.readInt() & 0x7fffffff;
+ length -= 4; // account for above read.
+ length = lengthWithoutPadding(length, flags, padding);
+ List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+ handler.pushPromise(streamId, promisedStreamId, headerBlock);
+ }
+
+ private void readPing(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
+ if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
+ int payload1 = source.readInt();
+ int payload2 = source.readInt();
+ boolean ack = (flags & FLAG_ACK) != 0;
+ handler.ping(ack, payload1, payload2);
+ }
+
+ private void readGoAway(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
+ if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
+ int lastStreamId = source.readInt();
+ int errorCodeInt = source.readInt();
+ int opaqueDataLength = length - 8;
+ ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+ if (errorCode == null) {
+ throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+ }
+ ByteString debugData = EMPTY;
+ if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
+ debugData = source.readByteString(opaqueDataLength);
+ }
+ handler.goAway(lastStreamId, errorCode, debugData);
+ }
+
+ private void readWindowUpdate(Handler handler, int length, byte flags, int streamId)
+ throws IOException {
+ if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
+ long increment = (source.readInt() & 0x7fffffffL);
+ if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+ handler.windowUpdate(streamId, increment);
+ }
+
+ @Override public void close() throws IOException {
+ source.close();
+ }
+ }
+
+ static final class Writer implements FrameWriter {
+ private final BufferedSink sink;
+ private final boolean client;
+ private final Buffer hpackBuffer;
+ private final Hpack.Writer hpackWriter;
+ private int maxFrameSize;
+ private boolean closed;
+
+ Writer(BufferedSink sink, boolean client) {
+ this.sink = sink;
+ this.client = client;
+ this.hpackBuffer = new Buffer();
+ this.hpackWriter = new Hpack.Writer(hpackBuffer);
+ this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
+ }
+
+ @Override public synchronized void flush() throws IOException {
+ if (closed) throw new IOException("closed");
+ sink.flush();
+ }
+
+ @Override public synchronized void ackSettings(Settings peerSettings) throws IOException {
+ if (closed) throw new IOException("closed");
+ this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize);
+ int length = 0;
+ byte type = TYPE_SETTINGS;
+ byte flags = FLAG_ACK;
+ int streamId = 0;
+ frameHeader(streamId, length, type, flags);
+ sink.flush();
+ }
+
+ @Override public synchronized void connectionPreface() throws IOException {
+ if (closed) throw new IOException("closed");
+ if (!client) return; // Nothing to write; servers don't send connection headers!
+ if (logger.isLoggable(FINE)) {
+ logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
+ }
+ sink.write(CONNECTION_PREFACE.toByteArray());
+ sink.flush();
+ }
+
+ @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+ int streamId, int associatedStreamId, List<Header> headerBlock)
+ throws IOException {
+ if (inFinished) throw new UnsupportedOperationException();
+ if (closed) throw new IOException("closed");
+ headers(outFinished, streamId, headerBlock);
+ }
+
+ @Override public synchronized void synReply(boolean outFinished, int streamId,
+ List<Header> headerBlock) throws IOException {
+ if (closed) throw new IOException("closed");
+ headers(outFinished, streamId, headerBlock);
+ }
+
+ @Override public synchronized void headers(int streamId, List<Header> headerBlock)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ headers(false, streamId, headerBlock);
+ }
+
+ @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
+ List<Header> requestHeaders) throws IOException {
+ if (closed) throw new IOException("closed");
+ hpackWriter.writeHeaders(requestHeaders);
+
+ long byteCount = hpackBuffer.size();
+ int length = (int) Math.min(maxFrameSize - 4, byteCount);
+ byte type = TYPE_PUSH_PROMISE;
+ byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+ frameHeader(streamId, length + 4, type, flags);
+ sink.writeInt(promisedStreamId & 0x7fffffff);
+ sink.write(hpackBuffer, length);
+
+ if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+ }
+
+ void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
+ if (closed) throw new IOException("closed");
+ hpackWriter.writeHeaders(headerBlock);
+
+ long byteCount = hpackBuffer.size();
+ int length = (int) Math.min(maxFrameSize, byteCount);
+ byte type = TYPE_HEADERS;
+ byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+ if (outFinished) flags |= FLAG_END_STREAM;
+ frameHeader(streamId, length, type, flags);
+ sink.write(hpackBuffer, length);
+
+ if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+ }
+
+ private void writeContinuationFrames(int streamId, long byteCount) throws IOException {
+ while (byteCount > 0) {
+ int length = (int) Math.min(maxFrameSize, byteCount);
+ byteCount -= length;
+ frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0);
+ sink.write(hpackBuffer, length);
+ }
+ }
+
+ @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ if (errorCode.httpCode == -1) throw new IllegalArgumentException();
+
+ int length = 4;
+ byte type = TYPE_RST_STREAM;
+ byte flags = FLAG_NONE;
+ frameHeader(streamId, length, type, flags);
+ sink.writeInt(errorCode.httpCode);
+ sink.flush();
+ }
+
+ @Override public int maxDataLength() {
+ return maxFrameSize;
+ }
+
+ @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
+ int byteCount) throws IOException {
+ if (closed) throw new IOException("closed");
+ byte flags = FLAG_NONE;
+ if (outFinished) flags |= FLAG_END_STREAM;
+ dataFrame(streamId, flags, source, byteCount);
+ }
+
+ void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
+ byte type = TYPE_DATA;
+ frameHeader(streamId, byteCount, type, flags);
+ if (byteCount > 0) {
+ sink.write(buffer, byteCount);
+ }
+ }
+
+ @Override public synchronized void settings(Settings settings) throws IOException {
+ if (closed) throw new IOException("closed");
+ int length = settings.size() * 6;
+ byte type = TYPE_SETTINGS;
+ byte flags = FLAG_NONE;
+ int streamId = 0;
+ frameHeader(streamId, length, type, flags);
+ for (int i = 0; i < Settings.COUNT; i++) {
+ if (!settings.isSet(i)) continue;
+ int id = i;
+ if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
+ else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
+ sink.writeShort(id);
+ sink.writeInt(settings.get(i));
+ }
+ sink.flush();
+ }
+
+ @Override public synchronized void ping(boolean ack, int payload1, int payload2)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ int length = 8;
+ byte type = TYPE_PING;
+ byte flags = ack ? FLAG_ACK : FLAG_NONE;
+ int streamId = 0;
+ frameHeader(streamId, length, type, flags);
+ sink.writeInt(payload1);
+ sink.writeInt(payload2);
+ sink.flush();
+ }
+
+ @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
+ byte[] debugData) throws IOException {
+ if (closed) throw new IOException("closed");
+ if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
+ int length = 8 + debugData.length;
+ byte type = TYPE_GOAWAY;
+ byte flags = FLAG_NONE;
+ int streamId = 0;
+ frameHeader(streamId, length, type, flags);
+ sink.writeInt(lastGoodStreamId);
+ sink.writeInt(errorCode.httpCode);
+ if (debugData.length > 0) {
+ sink.write(debugData);
+ }
+ sink.flush();
+ }
+
+ @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
+ throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
+ windowSizeIncrement);
+ }
+ int length = 4;
+ byte type = TYPE_WINDOW_UPDATE;
+ byte flags = FLAG_NONE;
+ frameHeader(streamId, length, type, flags);
+ sink.writeInt((int) windowSizeIncrement);
+ sink.flush();
+ }
+
+ @Override public synchronized void close() throws IOException {
+ closed = true;
+ sink.close();
+ }
+
+ void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {
+ if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags));
+ if (length > maxFrameSize) {
+ throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length);
+ }
+ if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
+ writeMedium(sink, length);
+ sink.writeByte(type & 0xff);
+ sink.writeByte(flags & 0xff);
+ sink.writeInt(streamId & 0x7fffffff);
+ }
+ }
+
+ private static IllegalArgumentException illegalArgument(String message, Object... args) {
+ throw new IllegalArgumentException(format(message, args));
+ }
+
+ private static IOException ioException(String message, Object... args) throws IOException {
+ throw new IOException(format(message, args));
+ }
+
+ /**
+ * Decompression of the header block occurs above the framing layer. This
+ * class lazily reads continuation frames as they are needed by {@link
+ * Hpack.Reader#readHeaders()}.
+ */
+ static final class ContinuationSource implements Source {
+ private final BufferedSource source;
+
+ int length;
+ byte flags;
+ int streamId;
+
+ int left;
+ short padding;
+
+ public ContinuationSource(BufferedSource source) {
+ this.source = source;
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ while (left == 0) {
+ source.skip(padding);
+ padding = 0;
+ if ((flags & FLAG_END_HEADERS) != 0) return -1;
+ readContinuationHeader();
+ // TODO: test case for empty continuation header?
+ }
+
+ long read = source.read(sink, Math.min(byteCount, left));
+ if (read == -1) return -1;
+ left -= read;
+ return read;
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ }
+
+ private void readContinuationHeader() throws IOException {
+ int previousStreamId = streamId;
+
+ length = left = readMedium(source);
+ byte type = (byte) (source.readByte() & 0xff);
+ flags = (byte) (source.readByte() & 0xff);
+ if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+ streamId = (source.readInt() & 0x7fffffff);
+ if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
+ if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
+ }
+ }
+
+ private static int lengthWithoutPadding(int length, byte flags, short padding)
+ throws IOException {
+ if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length.
+ if (padding > length) {
+ throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
+ }
+ return (short) (length - padding);
+ }
+
+ /**
+ * Logs a human-readable representation of HTTP/2 frame headers.
+ *
+ * <p>The format is:
+ *
+ * <pre>
+ * direction streamID length type flags
+ * </pre>
+ * Where direction is {@code <<} for inbound and {@code >>} for outbound.
+ *
+ * <p> For example, the following would indicate a HEAD request sent from
+ * the client.
+ * <pre>
+ * {@code
+ * << 0x0000000f 12 HEADERS END_HEADERS|END_STREAM
+ * }
+ * </pre>
+ */
+ static final class FrameLogger {
+
+ static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) {
+ String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type);
+ String formattedFlags = formatFlags(type, flags);
+ return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length,
+ formattedType, formattedFlags);
+ }
+
+ /**
+ * Looks up valid string representing flags from the table. Invalid
+ * combinations are represented in binary.
+ */
+ // Visible for testing.
+ static String formatFlags(byte type, byte flags) {
+ if (flags == 0) return "";
+ switch (type) { // Special case types that have 0 or 1 flag.
+ case TYPE_SETTINGS:
+ case TYPE_PING:
+ return flags == FLAG_ACK ? "ACK" : BINARY[flags];
+ case TYPE_PRIORITY:
+ case TYPE_RST_STREAM:
+ case TYPE_GOAWAY:
+ case TYPE_WINDOW_UPDATE:
+ return BINARY[flags];
+ }
+ String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags];
+ // Special case types that have overlap flag values.
+ if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) {
+ return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation.
+ } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) {
+ return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation.
+ }
+ return result;
+ }
+
+ /** Lookup table for valid frame types. */
+ private static final String[] TYPES = new String[] {
+ "DATA",
+ "HEADERS",
+ "PRIORITY",
+ "RST_STREAM",
+ "SETTINGS",
+ "PUSH_PROMISE",
+ "PING",
+ "GOAWAY",
+ "WINDOW_UPDATE",
+ "CONTINUATION"
+ };
+
+ /**
+ * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid
+ * combinations are represented in binary.
+ */
+ private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20.
+ private static final String[] BINARY = new String[256];
+
+ static {
+ for (int i = 0; i < BINARY.length; i++) {
+ BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0');
+ }
+
+ FLAGS[FLAG_NONE] = "";
+ FLAGS[FLAG_END_STREAM] = "END_STREAM";
+
+ int[] prefixFlags = new int[] {FLAG_END_STREAM};
+
+ FLAGS[FLAG_PADDED] = "PADDED";
+ for (int prefixFlag : prefixFlags) {
+ FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED";
+ }
+
+ FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE.
+ FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED.
+ FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS.
+ int[] frameFlags =
+ new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY};
+
+ for (int frameFlag : frameFlags) {
+ for (int prefixFlag : prefixFlags) {
+ FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag];
+ FLAGS[prefixFlag | frameFlag | FLAG_PADDED] =
+ FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED";
+ }
+ }
+
+ for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation.
+ if (FLAGS[i] == null) FLAGS[i] = BINARY[i];
+ }
+ }
+ }
+
+ private static int readMedium(BufferedSource source) throws IOException {
+ return (source.readByte() & 0xff) << 16
+ | (source.readByte() & 0xff) << 8
+ | (source.readByte() & 0xff);
+ }
+
+ private static void writeMedium(BufferedSink sink, int i) throws IOException {
+ sink.writeByte((i >>> 16) & 0xff);
+ sink.writeByte((i >>> 8) & 0xff);
+ sink.writeByte(i & 0xff);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java
new file mode 100644
index 0000000..311e5fc
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java
@@ -0,0 +1,226 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 2013 Twitter, 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.android.okhttp.internal.framed;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class was originally composed from the following classes in
+ * <a href="https://github.com/twitter/hpack">Twitter Hpack</a>.
+ * <ul>
+ * <li>{@code com.twitter.hpack.HuffmanEncoder}</li>
+ * <li>{@code com.twitter.hpack.HuffmanDecoder}</li>
+ * <li>{@code com.twitter.hpack.HpackUtil}</li>
+ * </ul>
+ */
+class Huffman {
+
+ // Appendix C: Huffman Codes
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-B
+ private static final int[] CODES = {
+ 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8,
+ 0xffffea, 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed,
+ 0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4,
+ 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8,
+ 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, 0x17, 0x18,
+ 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb,
+ 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0,
+ 0x1ffc, 0x3ffc, 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75,
+ 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe,
+ 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4,
+ 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, 0xffffeb,
+ 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3,
+ 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda,
+ 0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd,
+ 0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0,
+ 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4,
+ 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7,
+ 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf,
+ 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7,
+ 0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3,
+ 0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8,
+ 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4,
+ 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea,
+ 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee
+ };
+
+ private static final byte[] CODE_LENGTHS = {
+ 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5,
+ 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6,
+ 6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, 22, 23,
+ 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23,
+ 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22,
+ 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27,
+ 26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21,
+ 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27,
+ 27, 27, 27, 27, 26
+ };
+
+ private static final Huffman INSTANCE = new Huffman();
+
+ public static Huffman get() {
+ return INSTANCE;
+ }
+
+ private final Node root = new Node();
+
+ private Huffman() {
+ buildTree();
+ }
+
+ void encode(byte[] data, OutputStream out) throws IOException {
+ long current = 0;
+ int n = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ int b = data[i] & 0xFF;
+ int code = CODES[b];
+ int nbits = CODE_LENGTHS[b];
+
+ current <<= nbits;
+ current |= code;
+ n += nbits;
+
+ while (n >= 8) {
+ n -= 8;
+ out.write(((int) (current >> n)));
+ }
+ }
+
+ if (n > 0) {
+ current <<= (8 - n);
+ current |= (0xFF >>> n);
+ out.write((int) current);
+ }
+ }
+
+ int encodedLength(byte[] bytes) {
+ long len = 0;
+
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xFF;
+ len += CODE_LENGTHS[b];
+ }
+
+ return (int) ((len + 7) >> 3);
+ }
+
+ byte[] decode(byte[] buf) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Node node = root;
+ int current = 0;
+ int nbits = 0;
+ for (int i = 0; i < buf.length; i++) {
+ int b = buf[i] & 0xFF;
+ current = (current << 8) | b;
+ nbits += 8;
+ while (nbits >= 8) {
+ int c = (current >>> (nbits - 8)) & 0xFF;
+ node = node.children[c];
+ if (node.children == null) {
+ // terminal node
+ baos.write(node.symbol);
+ nbits -= node.terminalBits;
+ node = root;
+ } else {
+ // non-terminal node
+ nbits -= 8;
+ }
+ }
+ }
+
+ while (nbits > 0) {
+ int c = (current << (8 - nbits)) & 0xFF;
+ node = node.children[c];
+ if (node.children != null || node.terminalBits > nbits) {
+ break;
+ }
+ baos.write(node.symbol);
+ nbits -= node.terminalBits;
+ node = root;
+ }
+
+ return baos.toByteArray();
+ }
+
+ private void buildTree() {
+ for (int i = 0; i < CODE_LENGTHS.length; i++) {
+ addCode(i, CODES[i], CODE_LENGTHS[i]);
+ }
+ }
+
+ private void addCode(int sym, int code, byte len) {
+ Node terminal = new Node(sym, len);
+
+ Node current = root;
+ while (len > 8) {
+ len -= 8;
+ int i = ((code >>> len) & 0xFF);
+ if (current.children == null) {
+ throw new IllegalStateException("invalid dictionary: prefix not unique");
+ }
+ if (current.children[i] == null) {
+ current.children[i] = new Node();
+ }
+ current = current.children[i];
+ }
+
+ int shift = 8 - len;
+ int start = (code << shift) & 0xFF;
+ int end = 1 << shift;
+ for (int i = start; i < start + end; i++) {
+ current.children[i] = terminal;
+ }
+ }
+
+ private static final class Node {
+
+ // Null if terminal.
+ private final Node[] children;
+
+ // Terminal nodes have a symbol.
+ private final int symbol;
+
+ // Number of bits represented in the terminal node.
+ private final int terminalBits;
+
+ /** Construct an internal node. */
+ Node() {
+ this.children = new Node[256];
+ this.symbol = 0; // Not read.
+ this.terminalBits = 0; // Not read.
+ }
+
+ /**
+ * Construct a terminal node.
+ *
+ * @param symbol symbol the node represents
+ * @param bits length of Huffman code in bits
+ */
+ Node(int symbol, int bits) {
+ this.children = null;
+ this.symbol = symbol;
+ int b = bits & 0x07;
+ this.terminalBits = b == 0 ? 8 : b;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java
new file mode 100644
index 0000000..8c84927
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.InflaterSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Reads a SPDY/3 Name/Value header block. This class is made complicated by the
+ * requirement that we're strict with which bytes we put in the compressed bytes
+ * buffer. We need to put all compressed bytes into that buffer -- but no other
+ * bytes.
+ */
+class NameValueBlockReader {
+ /** This source transforms compressed bytes into uncompressed bytes. */
+ private final InflaterSource inflaterSource;
+
+ /**
+ * How many compressed bytes must be read into inflaterSource before
+ * {@link #readNameValueBlock} returns.
+ */
+ private int compressedLimit;
+
+ /** This source holds inflated bytes. */
+ private final BufferedSource source;
+
+ public NameValueBlockReader(BufferedSource source) {
+ // Limit the inflater input stream to only those bytes in the Name/Value
+ // block. We cut the inflater off at its source because we can't predict the
+ // ratio of compressed bytes to uncompressed bytes.
+ Source throttleSource = new ForwardingSource(source) {
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (compressedLimit == 0) return -1; // Out of data for the current block.
+ long read = super.read(sink, Math.min(byteCount, compressedLimit));
+ if (read == -1) return -1;
+ compressedLimit -= read;
+ return read;
+ }
+ };
+
+ // Subclass inflater to install a dictionary when it's needed.
+ Inflater inflater = new Inflater() {
+ @Override public int inflate(byte[] buffer, int offset, int count)
+ throws DataFormatException {
+ int result = super.inflate(buffer, offset, count);
+ if (result == 0 && needsDictionary()) {
+ setDictionary(Spdy3.DICTIONARY);
+ result = super.inflate(buffer, offset, count);
+ }
+ return result;
+ }
+ };
+
+ this.inflaterSource = new InflaterSource(throttleSource, inflater);
+ this.source = Okio.buffer(inflaterSource);
+ }
+
+ public List<Header> readNameValueBlock(int length) throws IOException {
+ this.compressedLimit += length;
+
+ int numberOfPairs = source.readInt();
+ if (numberOfPairs < 0) throw new IOException("numberOfPairs < 0: " + numberOfPairs);
+ if (numberOfPairs > 1024) throw new IOException("numberOfPairs > 1024: " + numberOfPairs);
+
+ List<Header> entries = new ArrayList<>(numberOfPairs);
+ for (int i = 0; i < numberOfPairs; i++) {
+ ByteString name = readByteString().toAsciiLowercase();
+ ByteString values = readByteString();
+ if (name.size() == 0) throw new IOException("name.size == 0");
+ entries.add(new Header(name, values));
+ }
+
+ doneReading();
+ return entries;
+ }
+
+ private ByteString readByteString() throws IOException {
+ int length = source.readInt();
+ return source.readByteString(length);
+ }
+
+ private void doneReading() throws IOException {
+ // Move any outstanding unread bytes into the inflater. One side-effect of
+ // deflate compression is that sometimes there are bytes remaining in the
+ // stream after we've consumed all of the content.
+ if (compressedLimit > 0) {
+ inflaterSource.refill();
+ if (compressedLimit != 0) throw new IOException("compressedLimit > 0: " + compressedLimit);
+ }
+ }
+
+ public void close() throws IOException {
+ source.close();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java
new file mode 100644
index 0000000..42eb248
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java
@@ -0,0 +1,73 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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.android.okhttp.internal.framed;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A locally-originated ping.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Ping {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private long sent = -1;
+ private long received = -1;
+
+ Ping() {
+ }
+
+ void send() {
+ if (sent != -1) throw new IllegalStateException();
+ sent = System.nanoTime();
+ }
+
+ void receive() {
+ if (received != -1 || sent == -1) throw new IllegalStateException();
+ received = System.nanoTime();
+ latch.countDown();
+ }
+
+ void cancel() {
+ if (received != -1 || sent == -1) throw new IllegalStateException();
+ received = sent - 1;
+ latch.countDown();
+ }
+
+ /**
+ * Returns the round trip time for this ping in nanoseconds, waiting for the
+ * response to arrive if necessary. Returns -1 if the response was
+ * canceled.
+ */
+ public long roundTripTime() throws InterruptedException {
+ latch.await();
+ return received - sent;
+ }
+
+ /**
+ * Returns the round trip time for this ping in nanoseconds, or -1 if the
+ * response was canceled, or -2 if the timeout elapsed before the round
+ * trip completed.
+ */
+ public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException {
+ if (latch.await(timeout, unit)) {
+ return received - sent;
+ } else {
+ return -2;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java
new file mode 100644
index 0000000..8c71514
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java
@@ -0,0 +1,97 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.BufferedSource;
+
+/**
+ * {@link com.android.okhttp.Protocol#HTTP_2 HTTP/2} only.
+ * Processes server-initiated HTTP requests on the client. Implementations must
+ * quickly dispatch callbacks to avoid creating a bottleneck.
+ *
+ * <p>While {@link #onReset} may occur at any time, the following callbacks are
+ * expected in order, correlated by stream ID.
+ * <ul>
+ * <li>{@link #onRequest}</li>
+ * <li>{@link #onHeaders} (unless canceled)</li>
+ * <li>{@link #onData} (optional sequence of data frames)</li>
+ * </ul>
+ *
+ * <p>As a stream ID is scoped to a single HTTP/2 connection, implementations
+ * which target multiple connections should expect repetition of stream IDs.
+ *
+ * <p>Return true to request cancellation of a pushed stream. Note that this
+ * does not guarantee future frames won't arrive on the stream ID.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface PushObserver {
+ /**
+ * Describes the request that the server intends to push a response for.
+ *
+ * @param streamId server-initiated stream ID: an even number.
+ * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+ * {@code :authority}, and (@code :path}.
+ */
+ boolean onRequest(int streamId, List<Header> requestHeaders);
+
+ /**
+ * The response headers corresponding to a pushed request. When {@code last}
+ * is true, there are no data frames to follow.
+ *
+ * @param streamId server-initiated stream ID: an even number.
+ * @param responseHeaders minimally includes {@code :status}.
+ * @param last when true, there is no response data.
+ */
+ boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last);
+
+ /**
+ * A chunk of response data corresponding to a pushed request. This data
+ * must either be read or skipped.
+ *
+ * @param streamId server-initiated stream ID: an even number.
+ * @param source location of data corresponding with this stream ID.
+ * @param byteCount number of bytes to read or skip from the source.
+ * @param last when true, there are no data frames to follow.
+ */
+ boolean onData(int streamId, BufferedSource source, int byteCount, boolean last)
+ throws IOException;
+
+ /** Indicates the reason why this stream was canceled. */
+ void onReset(int streamId, ErrorCode errorCode);
+
+ PushObserver CANCEL = new PushObserver() {
+
+ @Override public boolean onRequest(int streamId, List<Header> requestHeaders) {
+ return true;
+ }
+
+ @Override public boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last) {
+ return true;
+ }
+
+ @Override public boolean onData(int streamId, BufferedSource source, int byteCount,
+ boolean last) throws IOException {
+ source.skip(byteCount);
+ return true;
+ }
+
+ @Override public void onReset(int streamId, ErrorCode errorCode) {
+ }
+ };
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java
new file mode 100644
index 0000000..223de47
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java
@@ -0,0 +1,241 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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.android.okhttp.internal.framed;
+
+import java.util.Arrays;
+
+/**
+ * Settings describe characteristics of the sending peer, which are used by the receiving peer.
+ * Settings are {@link FramedConnection connection} scoped.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Settings {
+ /**
+ * From the SPDY/3 and HTTP/2 specs, the default initial window size for all
+ * streams is 64 KiB. (Chrome 25 uses 10 MiB).
+ */
+ static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
+
+ /** Peer request to clear durable settings. */
+ static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
+
+ /** Sent by servers only. The peer requests this setting persisted for future connections. */
+ static final int PERSIST_VALUE = 0x1;
+ /** Sent by clients only. The client is reminding the server of a persisted value. */
+ static final int PERSISTED = 0x2;
+
+ /** spdy/3: Sender's estimate of max incoming kbps. */
+ static final int UPLOAD_BANDWIDTH = 1;
+ /** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */
+ static final int HEADER_TABLE_SIZE = 1;
+ /** spdy/3: Sender's estimate of max outgoing kbps. */
+ static final int DOWNLOAD_BANDWIDTH = 2;
+ /** HTTP/2: The peer must not send a PUSH_PROMISE frame when this is 0. */
+ static final int ENABLE_PUSH = 2;
+ /** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
+ static final int ROUND_TRIP_TIME = 3;
+ /** Sender's maximum number of concurrent streams. */
+ static final int MAX_CONCURRENT_STREAMS = 4;
+ /** spdy/3: Current CWND in Packets. */
+ static final int CURRENT_CWND = 5;
+ /** HTTP/2: Size in bytes of the largest frame payload the sender will accept. */
+ static final int MAX_FRAME_SIZE = 5;
+ /** spdy/3: Retransmission rate. Percentage */
+ static final int DOWNLOAD_RETRANS_RATE = 6;
+ /** HTTP/2: Advisory only. Size in bytes of the largest header list the sender will accept. */
+ static final int MAX_HEADER_LIST_SIZE = 6;
+ /** Window size in bytes. */
+ static final int INITIAL_WINDOW_SIZE = 7;
+ /** spdy/3: Size of the client certificate vector. Unsupported. */
+ static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
+ /** Flow control options. */
+ static final int FLOW_CONTROL_OPTIONS = 10;
+
+ /** Total number of settings. */
+ static final int COUNT = 10;
+
+ /** If set, flow control is disabled for streams directed to the sender of these settings. */
+ static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1;
+
+ /** Bitfield of which flags that values. */
+ private int set;
+
+ /** Bitfield of flags that have {@link #PERSIST_VALUE}. */
+ private int persistValue;
+
+ /** Bitfield of flags that have {@link #PERSISTED}. */
+ private int persisted;
+
+ /** Flag values. */
+ private final int[] values = new int[COUNT];
+
+ void clear() {
+ set = persistValue = persisted = 0;
+ Arrays.fill(values, 0);
+ }
+
+ Settings set(int id, int idFlags, int value) {
+ if (id >= values.length) {
+ return this; // Discard unknown settings.
+ }
+
+ int bit = 1 << id;
+ set |= bit;
+ if ((idFlags & PERSIST_VALUE) != 0) {
+ persistValue |= bit;
+ } else {
+ persistValue &= ~bit;
+ }
+ if ((idFlags & PERSISTED) != 0) {
+ persisted |= bit;
+ } else {
+ persisted &= ~bit;
+ }
+
+ values[id] = value;
+ return this;
+ }
+
+ /** Returns true if a value has been assigned for the setting {@code id}. */
+ boolean isSet(int id) {
+ int bit = 1 << id;
+ return (set & bit) != 0;
+ }
+
+ /** Returns the value for the setting {@code id}, or 0 if unset. */
+ int get(int id) {
+ return values[id];
+ }
+
+ /** Returns the flags for the setting {@code id}, or 0 if unset. */
+ int flags(int id) {
+ int result = 0;
+ if (isPersisted(id)) result |= Settings.PERSISTED;
+ if (persistValue(id)) result |= Settings.PERSIST_VALUE;
+ return result;
+ }
+
+ /** Returns the number of settings that have values assigned. */
+ int size() {
+ return Integer.bitCount(set);
+ }
+
+ /** spdy/3 only. */
+ int getUploadBandwidth(int defaultValue) {
+ int bit = 1 << UPLOAD_BANDWIDTH;
+ return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
+ }
+
+ /** HTTP/2 only. Returns -1 if unset. */
+ int getHeaderTableSize() {
+ int bit = 1 << HEADER_TABLE_SIZE;
+ return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1;
+ }
+
+ /** spdy/3 only. */
+ int getDownloadBandwidth(int defaultValue) {
+ int bit = 1 << DOWNLOAD_BANDWIDTH;
+ return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
+ }
+
+ /** HTTP/2 only. */
+ // TODO: honor this setting in HTTP/2.
+ boolean getEnablePush(boolean defaultValue) {
+ int bit = 1 << ENABLE_PUSH;
+ return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1;
+ }
+
+ /** spdy/3 only. */
+ int getRoundTripTime(int defaultValue) {
+ int bit = 1 << ROUND_TRIP_TIME;
+ return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
+ }
+
+ // TODO: honor this setting in spdy/3 and HTTP/2.
+ int getMaxConcurrentStreams(int defaultValue) {
+ int bit = 1 << MAX_CONCURRENT_STREAMS;
+ return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
+ }
+
+ /** spdy/3 only. */
+ int getCurrentCwnd(int defaultValue) {
+ int bit = 1 << CURRENT_CWND;
+ return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
+ }
+
+ /** HTTP/2 only. */
+ int getMaxFrameSize(int defaultValue) {
+ int bit = 1 << MAX_FRAME_SIZE;
+ return (bit & set) != 0 ? values[MAX_FRAME_SIZE] : defaultValue;
+ }
+
+ /** spdy/3 only. */
+ int getDownloadRetransRate(int defaultValue) {
+ int bit = 1 << DOWNLOAD_RETRANS_RATE;
+ return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
+ }
+
+ /** HTTP/2 only. */
+ int getMaxHeaderListSize(int defaultValue) {
+ int bit = 1 << MAX_HEADER_LIST_SIZE;
+ return (bit & set) != 0 ? values[MAX_HEADER_LIST_SIZE] : defaultValue;
+ }
+
+ int getInitialWindowSize(int defaultValue) {
+ int bit = 1 << INITIAL_WINDOW_SIZE;
+ return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
+ }
+
+ /** spdy/3 only. */
+ int getClientCertificateVectorSize(int defaultValue) {
+ int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
+ return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
+ }
+
+ // TODO: honor this setting in spdy/3 and HTTP/2.
+ boolean isFlowControlDisabled() {
+ int bit = 1 << FLOW_CONTROL_OPTIONS;
+ int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
+ return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0;
+ }
+
+ /**
+ * Returns true if this user agent should use this setting in future spdy/3
+ * connections to the same host.
+ */
+ boolean persistValue(int id) {
+ int bit = 1 << id;
+ return (persistValue & bit) != 0;
+ }
+
+ /** Returns true if this setting was persisted. */
+ boolean isPersisted(int id) {
+ int bit = 1 << id;
+ return (persisted & bit) != 0;
+ }
+
+ /**
+ * Writes {@code other} into this. If any setting is populated by this and
+ * {@code other}, the value and flags from {@code other} will be kept.
+ */
+ void merge(Settings other) {
+ for (int i = 0; i < COUNT; i++) {
+ if (!other.isSet(i)) continue;
+ set(i, other.flags(i), other.get(i));
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java
new file mode 100644
index 0000000..d792cd8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java
@@ -0,0 +1,493 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.Util;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
+import java.util.List;
+import java.util.zip.Deflater;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.DeflaterSink;
+import com.android.okhttp.okio.Okio;
+
+/**
+ * Read and write spdy/3.1 frames.
+ * http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Spdy3 implements Variant {
+
+ @Override public Protocol getProtocol() {
+ return Protocol.SPDY_3;
+ }
+
+ static final int TYPE_DATA = 0x0;
+ static final int TYPE_SYN_STREAM = 0x1;
+ static final int TYPE_SYN_REPLY = 0x2;
+ static final int TYPE_RST_STREAM = 0x3;
+ static final int TYPE_SETTINGS = 0x4;
+ static final int TYPE_PING = 0x6;
+ static final int TYPE_GOAWAY = 0x7;
+ static final int TYPE_HEADERS = 0x8;
+ static final int TYPE_WINDOW_UPDATE = 0x9;
+
+ static final int FLAG_FIN = 0x1;
+ static final int FLAG_UNIDIRECTIONAL = 0x2;
+
+ static final int VERSION = 3;
+
+ static final byte[] DICTIONARY;
+ static {
+ try {
+ DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
+ + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
+ + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
+ + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
+ + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
+ + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
+ + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
+ + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
+ + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
+ + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
+ + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
+ + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
+ + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
+ + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
+ + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
+ + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
+ + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
+ + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
+ + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
+ + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
+ + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
+ + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
+ + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
+ + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
+ + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
+ + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
+ + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
+ + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
+ + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
+ + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
+ + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
+ + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
+ + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override public FrameReader newReader(BufferedSource source, boolean client) {
+ return new Reader(source, client);
+ }
+
+ @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
+ return new Writer(sink, client);
+ }
+
+ /** Read spdy/3 frames. */
+ static final class Reader implements FrameReader {
+ private final BufferedSource source;
+ private final boolean client;
+ private final NameValueBlockReader headerBlockReader;
+
+ Reader(BufferedSource source, boolean client) {
+ this.source = source;
+ this.headerBlockReader = new NameValueBlockReader(this.source);
+ this.client = client;
+ }
+
+ @Override public void readConnectionPreface() {
+ }
+
+ /**
+ * Send the next frame to {@code handler}. Returns true unless there are no
+ * more frames on the stream.
+ */
+ @Override public boolean nextFrame(Handler handler) throws IOException {
+ int w1;
+ int w2;
+ try {
+ w1 = source.readInt();
+ w2 = source.readInt();
+ } catch (IOException e) {
+ return false; // This might be a normal socket close.
+ }
+
+ boolean control = (w1 & 0x80000000) != 0;
+ int flags = (w2 & 0xff000000) >>> 24;
+ int length = (w2 & 0xffffff);
+
+ if (control) {
+ int version = (w1 & 0x7fff0000) >>> 16;
+ int type = (w1 & 0xffff);
+
+ if (version != 3) {
+ throw new ProtocolException("version != 3: " + version);
+ }
+
+ switch (type) {
+ case TYPE_SYN_STREAM:
+ readSynStream(handler, flags, length);
+ return true;
+
+ case TYPE_SYN_REPLY:
+ readSynReply(handler, flags, length);
+ return true;
+
+ case TYPE_RST_STREAM:
+ readRstStream(handler, flags, length);
+ return true;
+
+ case TYPE_SETTINGS:
+ readSettings(handler, flags, length);
+ return true;
+
+ case TYPE_PING:
+ readPing(handler, flags, length);
+ return true;
+
+ case TYPE_GOAWAY:
+ readGoAway(handler, flags, length);
+ return true;
+
+ case TYPE_HEADERS:
+ readHeaders(handler, flags, length);
+ return true;
+
+ case TYPE_WINDOW_UPDATE:
+ readWindowUpdate(handler, flags, length);
+ return true;
+
+ default:
+ source.skip(length);
+ return true;
+ }
+ } else {
+ int streamId = w1 & 0x7fffffff;
+ boolean inFinished = (flags & FLAG_FIN) != 0;
+ handler.data(inFinished, streamId, source, length);
+ return true;
+ }
+ }
+
+ private void readSynStream(Handler handler, int flags, int length) throws IOException {
+ int w1 = source.readInt();
+ int w2 = source.readInt();
+ int streamId = w1 & 0x7fffffff;
+ int associatedStreamId = w2 & 0x7fffffff;
+ source.readShort(); // int priority = (s3 & 0xe000) >>> 13; int slot = s3 & 0xff;
+ List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 10);
+
+ boolean inFinished = (flags & FLAG_FIN) != 0;
+ boolean outFinished = (flags & FLAG_UNIDIRECTIONAL) != 0;
+ handler.headers(outFinished, inFinished, streamId, associatedStreamId, headerBlock,
+ HeadersMode.SPDY_SYN_STREAM);
+ }
+
+ private void readSynReply(Handler handler, int flags, int length) throws IOException {
+ int w1 = source.readInt();
+ int streamId = w1 & 0x7fffffff;
+ List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
+ boolean inFinished = (flags & FLAG_FIN) != 0;
+ handler.headers(false, inFinished, streamId, -1, headerBlock, HeadersMode.SPDY_REPLY);
+ }
+
+ private void readRstStream(Handler handler, int flags, int length) throws IOException {
+ if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
+ int streamId = source.readInt() & 0x7fffffff;
+ int errorCodeInt = source.readInt();
+ ErrorCode errorCode = ErrorCode.fromSpdy3Rst(errorCodeInt);
+ if (errorCode == null) {
+ throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+ }
+ handler.rstStream(streamId, errorCode);
+ }
+
+ private void readHeaders(Handler handler, int flags, int length) throws IOException {
+ int w1 = source.readInt();
+ int streamId = w1 & 0x7fffffff;
+ List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
+ handler.headers(false, false, streamId, -1, headerBlock, HeadersMode.SPDY_HEADERS);
+ }
+
+ private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
+ if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
+ int w1 = source.readInt();
+ int w2 = source.readInt();
+ int streamId = w1 & 0x7fffffff;
+ long increment = w2 & 0x7fffffff;
+ if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+ handler.windowUpdate(streamId, increment);
+ }
+
+ private void readPing(Handler handler, int flags, int length) throws IOException {
+ if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
+ int id = source.readInt();
+ boolean ack = client == ((id & 1) == 1);
+ handler.ping(ack, id, 0);
+ }
+
+ private void readGoAway(Handler handler, int flags, int length) throws IOException {
+ if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
+ int lastGoodStreamId = source.readInt() & 0x7fffffff;
+ int errorCodeInt = source.readInt();
+ ErrorCode errorCode = ErrorCode.fromSpdyGoAway(errorCodeInt);
+ if (errorCode == null) {
+ throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+ }
+ handler.goAway(lastGoodStreamId, errorCode, ByteString.EMPTY);
+ }
+
+ private void readSettings(Handler handler, int flags, int length) throws IOException {
+ int numberOfEntries = source.readInt();
+ if (length != 4 + 8 * numberOfEntries) {
+ throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
+ }
+ Settings settings = new Settings();
+ for (int i = 0; i < numberOfEntries; i++) {
+ int w1 = source.readInt();
+ int value = source.readInt();
+ int idFlags = (w1 & 0xff000000) >>> 24;
+ int id = w1 & 0xffffff;
+ settings.set(id, idFlags, value);
+ }
+ boolean clearPrevious = (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0;
+ handler.settings(clearPrevious, settings);
+ }
+
+ private static IOException ioException(String message, Object... args) throws IOException {
+ throw new IOException(String.format(message, args));
+ }
+
+ @Override public void close() throws IOException {
+ headerBlockReader.close();
+ }
+ }
+
+ /** Write spdy/3 frames. */
+ static final class Writer implements FrameWriter {
+ private final BufferedSink sink;
+ private final Buffer headerBlockBuffer;
+ private final BufferedSink headerBlockOut;
+ private final boolean client;
+ private boolean closed;
+
+ Writer(BufferedSink sink, boolean client) {
+ this.sink = sink;
+ this.client = client;
+
+ Deflater deflater = new Deflater();
+ deflater.setDictionary(DICTIONARY);
+ headerBlockBuffer = new Buffer();
+ headerBlockOut = Okio.buffer(new DeflaterSink(headerBlockBuffer, deflater));
+ }
+
+ @Override public void ackSettings(Settings peerSettings) {
+ // Do nothing: no ACK for SPDY/3 settings.
+ }
+
+ @Override
+ public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+ throws IOException {
+ // Do nothing: no push promise for SPDY/3.
+ }
+
+ @Override public synchronized void connectionPreface() {
+ // Do nothing: no connection preface for SPDY/3.
+ }
+
+ @Override public synchronized void flush() throws IOException {
+ if (closed) throw new IOException("closed");
+ sink.flush();
+ }
+
+ @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+ int streamId, int associatedStreamId, List<Header> headerBlock)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ writeNameValueBlockToBuffer(headerBlock);
+ int length = (int) (10 + headerBlockBuffer.size());
+ int type = TYPE_SYN_STREAM;
+ int flags = (outFinished ? FLAG_FIN : 0) | (inFinished ? FLAG_UNIDIRECTIONAL : 0);
+
+ int unused = 0;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(streamId & 0x7fffffff);
+ sink.writeInt(associatedStreamId & 0x7fffffff);
+ sink.writeShort((unused & 0x7) << 13 | (unused & 0x1f) << 8 | (unused & 0xff));
+ sink.writeAll(headerBlockBuffer);
+ sink.flush();
+ }
+
+ @Override public synchronized void synReply(boolean outFinished, int streamId,
+ List<Header> headerBlock) throws IOException {
+ if (closed) throw new IOException("closed");
+ writeNameValueBlockToBuffer(headerBlock);
+ int type = TYPE_SYN_REPLY;
+ int flags = (outFinished ? FLAG_FIN : 0);
+ int length = (int) (headerBlockBuffer.size() + 4);
+
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(streamId & 0x7fffffff);
+ sink.writeAll(headerBlockBuffer);
+ sink.flush();
+ }
+
+ @Override public synchronized void headers(int streamId, List<Header> headerBlock)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ writeNameValueBlockToBuffer(headerBlock);
+ int flags = 0;
+ int type = TYPE_HEADERS;
+ int length = (int) (headerBlockBuffer.size() + 4);
+
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(streamId & 0x7fffffff);
+ sink.writeAll(headerBlockBuffer);
+ }
+
+ @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
+ int flags = 0;
+ int type = TYPE_RST_STREAM;
+ int length = 8;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(streamId & 0x7fffffff);
+ sink.writeInt(errorCode.spdyRstCode);
+ sink.flush();
+ }
+
+ @Override public int maxDataLength() {
+ return 16383;
+ }
+
+ @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
+ int byteCount) throws IOException {
+ int flags = (outFinished ? FLAG_FIN : 0);
+ sendDataFrame(streamId, flags, source, byteCount);
+ }
+
+ void sendDataFrame(int streamId, int flags, Buffer buffer, int byteCount)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ if (byteCount > 0xffffffL) {
+ throw new IllegalArgumentException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount);
+ }
+ sink.writeInt(streamId & 0x7fffffff);
+ sink.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff);
+ if (byteCount > 0) {
+ sink.write(buffer, byteCount);
+ }
+ }
+
+ private void writeNameValueBlockToBuffer(List<Header> headerBlock) throws IOException {
+ headerBlockOut.writeInt(headerBlock.size());
+ for (int i = 0, size = headerBlock.size(); i < size; i++) {
+ ByteString name = headerBlock.get(i).name;
+ headerBlockOut.writeInt(name.size());
+ headerBlockOut.write(name);
+ ByteString value = headerBlock.get(i).value;
+ headerBlockOut.writeInt(value.size());
+ headerBlockOut.write(value);
+ }
+ headerBlockOut.flush();
+ }
+
+ @Override public synchronized void settings(Settings settings) throws IOException {
+ if (closed) throw new IOException("closed");
+ int type = TYPE_SETTINGS;
+ int flags = 0;
+ int size = settings.size();
+ int length = 4 + size * 8;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(size);
+ for (int i = 0; i <= Settings.COUNT; i++) {
+ if (!settings.isSet(i)) continue;
+ int settingsFlags = settings.flags(i);
+ sink.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
+ sink.writeInt(settings.get(i));
+ }
+ sink.flush();
+ }
+
+ @Override public synchronized void ping(boolean reply, int payload1, int payload2)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ boolean payloadIsReply = client != ((payload1 & 1) == 1);
+ if (reply != payloadIsReply) throw new IllegalArgumentException("payload != reply");
+ int type = TYPE_PING;
+ int flags = 0;
+ int length = 4;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(payload1);
+ sink.flush();
+ }
+
+ @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
+ byte[] ignored) throws IOException {
+ if (closed) throw new IOException("closed");
+ if (errorCode.spdyGoAwayCode == -1) {
+ throw new IllegalArgumentException("errorCode.spdyGoAwayCode == -1");
+ }
+ int type = TYPE_GOAWAY;
+ int flags = 0;
+ int length = 8;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(lastGoodStreamId);
+ sink.writeInt(errorCode.spdyGoAwayCode);
+ sink.flush();
+ }
+
+ @Override public synchronized void windowUpdate(int streamId, long increment)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ if (increment == 0 || increment > 0x7fffffffL) {
+ throw new IllegalArgumentException(
+ "windowSizeIncrement must be between 1 and 0x7fffffff: " + increment);
+ }
+ int type = TYPE_WINDOW_UPDATE;
+ int flags = 0;
+ int length = 8;
+ sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+ sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+ sink.writeInt(streamId);
+ sink.writeInt((int) increment);
+ sink.flush();
+ }
+
+ @Override public synchronized void close() throws IOException {
+ closed = true;
+ Util.closeAll(sink, headerBlockOut);
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java
new file mode 100644
index 0000000..92f319c
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java
@@ -0,0 +1,39 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+
+/** A version and dialect of the framed socket protocol.
+ * @hide This class is not part of the Android public SDK API*/
+public interface Variant {
+
+ /** The protocol as selected using ALPN. */
+ Protocol getProtocol();
+
+ /**
+ * @param client true if this is the HTTP client's reader, reading frames from a server.
+ */
+ FrameReader newReader(BufferedSource source, boolean client);
+
+ /**
+ * @param client true if this is the HTTP client's writer, writing frames to a server.
+ */
+ FrameWriter newWriter(BufferedSink sink, boolean client);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java
new file mode 100644
index 0000000..48257f3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java
@@ -0,0 +1,89 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.http;
+
+import com.android.okhttp.Authenticator;
+import com.android.okhttp.Challenge;
+import com.android.okhttp.Credentials;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import java.io.IOException;
+import java.net.Authenticator.RequestorType;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.util.List;
+
+/** Adapts {@link java.net.Authenticator} to {@link com.android.okhttp.Authenticator}.
+ * @hide This class is not part of the Android public SDK API*/
+public final class AuthenticatorAdapter implements Authenticator {
+ /** Uses the global authenticator to get the password. */
+ public static final Authenticator INSTANCE = new AuthenticatorAdapter();
+
+ @Override public Request authenticate(Proxy proxy, Response response) throws IOException {
+ List<Challenge> challenges = response.challenges();
+ Request request = response.request();
+ HttpUrl url = request.httpUrl();
+ for (int i = 0, size = challenges.size(); i < size; i++) {
+ Challenge challenge = challenges.get(i);
+ if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+ PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+ url.host(), getConnectToInetAddress(proxy, url), url.port(), url.scheme(),
+ challenge.getRealm(), challenge.getScheme(), url.url(), RequestorType.SERVER);
+ if (auth == null) continue;
+
+ String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+ return request.newBuilder()
+ .header("Authorization", credential)
+ .build();
+ }
+ return null;
+
+ }
+
+ @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
+ List<Challenge> challenges = response.challenges();
+ Request request = response.request();
+ HttpUrl url = request.httpUrl();
+ for (int i = 0, size = challenges.size(); i < size; i++) {
+ Challenge challenge = challenges.get(i);
+ if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+ InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
+ PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+ proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
+ url.scheme(), challenge.getRealm(), challenge.getScheme(), url.url(),
+ RequestorType.PROXY);
+ if (auth == null) continue;
+
+ String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+ return request.newBuilder()
+ .header("Proxy-Authorization", credential)
+ .build();
+ }
+ return null;
+ }
+
+ private InetAddress getConnectToInetAddress(Proxy proxy, HttpUrl url) throws IOException {
+ return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
+ ? ((InetSocketAddress) proxy.address()).getAddress()
+ : InetAddress.getByName(url.host());
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java
new file mode 100644
index 0000000..f419884
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java
@@ -0,0 +1,28 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface CacheRequest {
+ Sink body() throws IOException;
+ void abort();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java
new file mode 100644
index 0000000..de544db
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java
@@ -0,0 +1,309 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.CacheControl;
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import java.util.Date;
+
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
+import static java.net.HttpURLConnection.HTTP_GONE;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_REQ_TOO_LONG;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * Given a request and cached response, this figures out whether to use the
+ * network, the cache, or both.
+ *
+ * <p>Selecting a cache strategy may add conditions to the request (like the
+ * "If-Modified-Since" header for conditional GETs) or warnings to the cached
+ * response (if the cached data is potentially stale).
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CacheStrategy {
+ /** The request to send on the network, or null if this call doesn't use the network. */
+ public final Request networkRequest;
+
+ /** The cached response to return or validate; or null if this call doesn't use a cache. */
+ public final Response cacheResponse;
+
+ private CacheStrategy(Request networkRequest, Response cacheResponse) {
+ this.networkRequest = networkRequest;
+ this.cacheResponse = cacheResponse;
+ }
+
+ /**
+ * Returns true if {@code response} can be stored to later serve another
+ * request.
+ */
+ public static boolean isCacheable(Response response, Request request) {
+ // Always go to network for uncacheable response codes (RFC 7231 section 6.1),
+ // This implementation doesn't support caching partial content.
+ switch (response.code()) {
+ case HTTP_OK:
+ case HTTP_NOT_AUTHORITATIVE:
+ case HTTP_NO_CONTENT:
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_NOT_FOUND:
+ case HTTP_BAD_METHOD:
+ case HTTP_GONE:
+ case HTTP_REQ_TOO_LONG:
+ case HTTP_NOT_IMPLEMENTED:
+ case HTTP_PERM_REDIRECT:
+ // These codes can be cached unless headers forbid it.
+ break;
+
+ case HTTP_MOVED_TEMP:
+ case HTTP_TEMP_REDIRECT:
+ // These codes can only be cached with the right response headers.
+ // http://tools.ietf.org/html/rfc7234#section-3
+ // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
+ if (response.header("Expires") != null
+ || response.cacheControl().maxAgeSeconds() != -1
+ || response.cacheControl().isPublic()
+ || response.cacheControl().isPrivate()) {
+ break;
+ }
+ // Fall-through.
+
+ default:
+ // All other codes cannot be cached.
+ return false;
+ }
+
+ // A 'no-store' directive on request or response prevents the response from being cached.
+ return !response.cacheControl().noStore() && !request.cacheControl().noStore();
+ }
+
+ /**
+ * @hide This class is not part of the Android public SDK API
+ */
+ public static class Factory {
+ final long nowMillis;
+ final Request request;
+ final Response cacheResponse;
+
+ /** The server's time when the cached response was served, if known. */
+ private Date servedDate;
+ private String servedDateString;
+
+ /** The last modified date of the cached response, if known. */
+ private Date lastModified;
+ private String lastModifiedString;
+
+ /**
+ * The expiration date of the cached response, if known. If both this field
+ * and the max age are set, the max age is preferred.
+ */
+ private Date expires;
+
+ /**
+ * Extension header set by OkHttp specifying the timestamp when the cached
+ * HTTP request was first initiated.
+ */
+ private long sentRequestMillis;
+
+ /**
+ * Extension header set by OkHttp specifying the timestamp when the cached
+ * HTTP response was first received.
+ */
+ private long receivedResponseMillis;
+
+ /** Etag of the cached response. */
+ private String etag;
+
+ /** Age of the cached response. */
+ private int ageSeconds = -1;
+
+ public Factory(long nowMillis, Request request, Response cacheResponse) {
+ this.nowMillis = nowMillis;
+ this.request = request;
+ this.cacheResponse = cacheResponse;
+
+ if (cacheResponse != null) {
+ Headers headers = cacheResponse.headers();
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ String fieldName = headers.name(i);
+ String value = headers.value(i);
+ if ("Date".equalsIgnoreCase(fieldName)) {
+ servedDate = HttpDate.parse(value);
+ servedDateString = value;
+ } else if ("Expires".equalsIgnoreCase(fieldName)) {
+ expires = HttpDate.parse(value);
+ } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
+ lastModified = HttpDate.parse(value);
+ lastModifiedString = value;
+ } else if ("ETag".equalsIgnoreCase(fieldName)) {
+ etag = value;
+ } else if ("Age".equalsIgnoreCase(fieldName)) {
+ ageSeconds = HeaderParser.parseSeconds(value, -1);
+ } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+ sentRequestMillis = Long.parseLong(value);
+ } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+ receivedResponseMillis = Long.parseLong(value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a strategy to satisfy {@code request} using the a cached response
+ * {@code response}.
+ */
+ public CacheStrategy get() {
+ CacheStrategy candidate = getCandidate();
+
+ if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
+ // We're forbidden from using the network and the cache is insufficient.
+ return new CacheStrategy(null, null);
+ }
+
+ return candidate;
+ }
+
+ /** Returns a strategy to use assuming the request can use the network. */
+ private CacheStrategy getCandidate() {
+ // No cached response.
+ if (cacheResponse == null) {
+ return new CacheStrategy(request, null);
+ }
+
+ // Drop the cached response if it's missing a required handshake.
+ if (request.isHttps() && cacheResponse.handshake() == null) {
+ return new CacheStrategy(request, null);
+ }
+
+ // If this response shouldn't have been stored, it should never be used
+ // as a response source. This check should be redundant as long as the
+ // persistence store is well-behaved and the rules are constant.
+ if (!isCacheable(cacheResponse, request)) {
+ return new CacheStrategy(request, null);
+ }
+
+ CacheControl requestCaching = request.cacheControl();
+ if (requestCaching.noCache() || hasConditions(request)) {
+ return new CacheStrategy(request, null);
+ }
+
+ long ageMillis = cacheResponseAge();
+ long freshMillis = computeFreshnessLifetime();
+
+ if (requestCaching.maxAgeSeconds() != -1) {
+ freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
+ }
+
+ long minFreshMillis = 0;
+ if (requestCaching.minFreshSeconds() != -1) {
+ minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
+ }
+
+ long maxStaleMillis = 0;
+ CacheControl responseCaching = cacheResponse.cacheControl();
+ if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
+ maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
+ }
+
+ if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+ Response.Builder builder = cacheResponse.newBuilder();
+ if (ageMillis + minFreshMillis >= freshMillis) {
+ builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
+ }
+ long oneDayMillis = 24 * 60 * 60 * 1000L;
+ if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
+ builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+ }
+ return new CacheStrategy(null, builder.build());
+ }
+
+ Request.Builder conditionalRequestBuilder = request.newBuilder();
+
+ if (etag != null) {
+ conditionalRequestBuilder.header("If-None-Match", etag);
+ } else if (lastModified != null) {
+ conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
+ } else if (servedDate != null) {
+ conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
+ }
+
+ Request conditionalRequest = conditionalRequestBuilder.build();
+ return hasConditions(conditionalRequest)
+ ? new CacheStrategy(conditionalRequest, cacheResponse)
+ : new CacheStrategy(conditionalRequest, null);
+ }
+
+ /**
+ * Returns the number of milliseconds that the response was fresh for,
+ * starting from the served date.
+ */
+ private long computeFreshnessLifetime() {
+ CacheControl responseCaching = cacheResponse.cacheControl();
+ if (responseCaching.maxAgeSeconds() != -1) {
+ return SECONDS.toMillis(responseCaching.maxAgeSeconds());
+ } else if (expires != null) {
+ long servedMillis = servedDate != null
+ ? servedDate.getTime()
+ : receivedResponseMillis;
+ long delta = expires.getTime() - servedMillis;
+ return delta > 0 ? delta : 0;
+ } else if (lastModified != null
+ && cacheResponse.request().httpUrl().query() == null) {
+ // As recommended by the HTTP RFC and implemented in Firefox, the
+ // max age of a document should be defaulted to 10% of the
+ // document's age at the time it was served. Default expiration
+ // dates aren't used for URIs containing a query.
+ long servedMillis = servedDate != null
+ ? servedDate.getTime()
+ : sentRequestMillis;
+ long delta = servedMillis - lastModified.getTime();
+ return delta > 0 ? (delta / 10) : 0;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the current age of the response, in milliseconds. The calculation
+ * is specified by RFC 2616, 13.2.3 Age Calculations.
+ */
+ private long cacheResponseAge() {
+ long apparentReceivedAge = servedDate != null
+ ? Math.max(0, receivedResponseMillis - servedDate.getTime())
+ : 0;
+ long receivedAge = ageSeconds != -1
+ ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
+ : apparentReceivedAge;
+ long responseDuration = receivedResponseMillis - sentRequestMillis;
+ long residentDuration = nowMillis - receivedResponseMillis;
+ return receivedAge + responseDuration + residentDuration;
+ }
+
+ /**
+ * Returns true if computeFreshnessLifetime used a heuristic. If we used a
+ * heuristic to serve a cached response older than 24 hours, we are required
+ * to attach a warning.
+ */
+ private boolean isFreshnessLifetimeHeuristic() {
+ return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
+ }
+
+ /**
+ * Returns true if the request contains conditions that save the server from
+ * sending a response that the client has locally. When a request is enqueued
+ * with its own conditions, the built-in response cache won't be used.
+ */
+ private static boolean hasConditions(Request request) {
+ return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java
new file mode 100644
index 0000000..7d398ea
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java
@@ -0,0 +1,73 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.http;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HeaderParser {
+ /**
+ * Returns the next index in {@code input} at or after {@code pos} that
+ * contains a character from {@code characters}. Returns the input length if
+ * none of the requested characters can be found.
+ */
+ public static int skipUntil(String input, int pos, String characters) {
+ for (; pos < input.length(); pos++) {
+ if (characters.indexOf(input.charAt(pos)) != -1) {
+ break;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Returns the next non-whitespace character in {@code input} that is white
+ * space. Result is undefined if input contains newline characters.
+ */
+ public static int skipWhitespace(String input, int pos) {
+ for (; pos < input.length(); pos++) {
+ char c = input.charAt(pos);
+ if (c != ' ' && c != '\t') {
+ break;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Returns {@code value} as a positive integer, or 0 if it is negative, or
+ * {@code defaultValue} if it cannot be parsed.
+ */
+ public static int parseSeconds(String value, int defaultValue) {
+ try {
+ long seconds = Long.parseLong(value);
+ if (seconds > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ } else if (seconds < 0) {
+ return 0;
+ } else {
+ return (int) seconds;
+ }
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private HeaderParser() {
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java
new file mode 100644
index 0000000..a925746
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java
@@ -0,0 +1,508 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.io.RealConnection;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ForwardingTimeout;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.checkOffsetAndCount;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * A socket connection that can be used to send HTTP/1.1 messages. This class
+ * strictly enforces the following lifecycle:
+ * <ol>
+ * <li>{@link #writeRequest Send request headers}.
+ * <li>Open a sink to write the request body. Either {@link
+ * #newFixedLengthSink fixed-length} or {@link #newChunkedSink chunked}.
+ * <li>Write to and then close that sink.
+ * <li>{@link #readResponse Read response headers}.
+ * <li>Open a source to read the response body. Either {@link
+ * #newFixedLengthSource fixed-length}, {@link #newChunkedSource chunked}
+ * or {@link #newUnknownLengthSource unknown length}.
+ * <li>Read from and close that source.
+ * </ol>
+ * <p>Exchanges that do not have a request body may skip creating and closing
+ * the request body. Exchanges that do not have a response body can call {@link
+ * #newFixedLengthSource(long) newFixedLengthSource(0)} and may skip reading and
+ * closing that source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Http1xStream implements HttpStream {
+ private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers.
+ private static final int STATE_OPEN_REQUEST_BODY = 1;
+ private static final int STATE_WRITING_REQUEST_BODY = 2;
+ private static final int STATE_READ_RESPONSE_HEADERS = 3;
+ private static final int STATE_OPEN_RESPONSE_BODY = 4;
+ private static final int STATE_READING_RESPONSE_BODY = 5;
+ private static final int STATE_CLOSED = 6;
+
+ /** The stream allocation that owns this stream. May be null for HTTPS proxy tunnels. */
+ private final StreamAllocation streamAllocation;
+ private final BufferedSource source;
+ private final BufferedSink sink;
+ private HttpEngine httpEngine;
+ private int state = STATE_IDLE;
+
+ public Http1xStream(StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) {
+ this.streamAllocation = streamAllocation;
+ this.source = source;
+ this.sink = sink;
+ }
+
+ @Override public void setHttpEngine(HttpEngine httpEngine) {
+ this.httpEngine = httpEngine;
+ }
+
+ @Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
+ if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
+ // Stream a request body of unknown length.
+ return newChunkedSink();
+ }
+
+ if (contentLength != -1) {
+ // Stream a request body of a known length.
+ return newFixedLengthSink(contentLength);
+ }
+
+ throw new IllegalStateException(
+ "Cannot stream a request body without chunked encoding or a known content length!");
+ }
+
+ @Override public void cancel() {
+ RealConnection connection = streamAllocation.connection();
+ if (connection != null) connection.cancel();
+ }
+
+ /**
+ * Prepares the HTTP headers and sends them to the server.
+ *
+ * <p>For streaming requests with a body, headers must be prepared
+ * <strong>before</strong> the output stream has been written to. Otherwise
+ * the body would need to be buffered!
+ *
+ * <p>For non-streaming requests with a body, headers must be prepared
+ * <strong>after</strong> the output stream has been written to and closed.
+ * This ensures that the {@code Content-Length} header field receives the
+ * proper value.
+ */
+ @Override public void writeRequestHeaders(Request request) throws IOException {
+ httpEngine.writingRequestHeaders();
+ String requestLine = RequestLine.get(
+ request, httpEngine.getConnection().getRoute().getProxy().type());
+ writeRequest(request.headers(), requestLine);
+ }
+
+ @Override public Response.Builder readResponseHeaders() throws IOException {
+ return readResponse();
+ }
+
+ @Override public ResponseBody openResponseBody(Response response) throws IOException {
+ Source source = getTransferStream(response);
+ return new RealResponseBody(response.headers(), Okio.buffer(source));
+ }
+
+ private Source getTransferStream(Response response) throws IOException {
+ if (!HttpEngine.hasBody(response)) {
+ return newFixedLengthSource(0);
+ }
+
+ if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
+ return newChunkedSource(httpEngine);
+ }
+
+ long contentLength = OkHeaders.contentLength(response);
+ if (contentLength != -1) {
+ return newFixedLengthSource(contentLength);
+ }
+
+ // Wrap the input stream from the connection (rather than just returning
+ // "socketIn" directly here), so that we can control its use after the
+ // reference escapes.
+ return newUnknownLengthSource();
+ }
+
+ /** Returns true if this connection is closed. */
+ public boolean isClosed() {
+ return state == STATE_CLOSED;
+ }
+
+ @Override public void finishRequest() throws IOException {
+ sink.flush();
+ }
+
+ /** Returns bytes of a request header for sending on an HTTP transport. */
+ public void writeRequest(Headers headers, String requestLine) throws IOException {
+ if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
+ sink.writeUtf8(requestLine).writeUtf8("\r\n");
+ for (int i = 0, size = headers.size(); i < size; i ++) {
+ sink.writeUtf8(headers.name(i))
+ .writeUtf8(": ")
+ .writeUtf8(headers.value(i))
+ .writeUtf8("\r\n");
+ }
+ sink.writeUtf8("\r\n");
+ state = STATE_OPEN_REQUEST_BODY;
+ }
+
+ /** Parses bytes of a response header from an HTTP transport. */
+ public Response.Builder readResponse() throws IOException {
+ if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
+ throw new IllegalStateException("state: " + state);
+ }
+
+ try {
+ while (true) {
+ StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+
+ Response.Builder responseBuilder = new Response.Builder()
+ .protocol(statusLine.protocol)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(readHeaders());
+
+ if (statusLine.code != HTTP_CONTINUE) {
+ state = STATE_OPEN_RESPONSE_BODY;
+ return responseBuilder;
+ }
+ }
+ } catch (EOFException e) {
+ // Provide more context if the server ends the stream before sending a response.
+ IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ /** Reads headers or trailers. */
+ public Headers readHeaders() throws IOException {
+ Headers.Builder headers = new Headers.Builder();
+ // parse the result headers until the first blank line
+ for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
+ Internal.instance.addLenient(headers, line);
+ }
+ return headers.build();
+ }
+
+ public Sink newChunkedSink() {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_WRITING_REQUEST_BODY;
+ return new ChunkedSink();
+ }
+
+ public Sink newFixedLengthSink(long contentLength) {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_WRITING_REQUEST_BODY;
+ return new FixedLengthSink(contentLength);
+ }
+
+ @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READ_RESPONSE_HEADERS;
+ requestBody.writeToSocket(sink);
+ }
+
+ public Source newFixedLengthSource(long length) throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READING_RESPONSE_BODY;
+ return new FixedLengthSource(length);
+ }
+
+ public Source newChunkedSource(HttpEngine httpEngine) throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READING_RESPONSE_BODY;
+ return new ChunkedSource(httpEngine);
+ }
+
+ public Source newUnknownLengthSource() throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ if (streamAllocation == null) throw new IllegalStateException("streamAllocation == null");
+ state = STATE_READING_RESPONSE_BODY;
+ streamAllocation.noNewStreams();
+ return new UnknownLengthSource();
+ }
+
+ /**
+ * Sets the delegate of {@code timeout} to {@link Timeout#NONE} and resets its underlying timeout
+ * to the default configuration. Use this to avoid unexpected sharing of timeouts between pooled
+ * connections.
+ */
+ private void detachTimeout(ForwardingTimeout timeout) {
+ Timeout oldDelegate = timeout.delegate();
+ timeout.setDelegate(Timeout.NONE);
+ oldDelegate.clearDeadline();
+ oldDelegate.clearTimeout();
+ }
+
+ /** An HTTP body with a fixed length known in advance. */
+ private final class FixedLengthSink implements Sink {
+ private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
+ private boolean closed;
+ private long bytesRemaining;
+
+ private FixedLengthSink(long bytesRemaining) {
+ this.bytesRemaining = bytesRemaining;
+ }
+
+ @Override public Timeout timeout() {
+ return timeout;
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ checkOffsetAndCount(source.size(), 0, byteCount);
+ if (byteCount > bytesRemaining) {
+ throw new ProtocolException("expected " + bytesRemaining
+ + " bytes but received " + byteCount);
+ }
+ sink.write(source, byteCount);
+ bytesRemaining -= byteCount;
+ }
+
+ @Override public void flush() throws IOException {
+ if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ if (bytesRemaining > 0) throw new ProtocolException("unexpected end of stream");
+ detachTimeout(timeout);
+ state = STATE_READ_RESPONSE_HEADERS;
+ }
+ }
+
+ /**
+ * An HTTP body with alternating chunk sizes and chunk bodies. It is the
+ * caller's responsibility to buffer chunks; typically by using a buffered
+ * sink with this sink.
+ */
+ private final class ChunkedSink implements Sink {
+ private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
+ private boolean closed;
+
+ @Override public Timeout timeout() {
+ return timeout;
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ if (byteCount == 0) return;
+
+ sink.writeHexadecimalUnsignedLong(byteCount);
+ sink.writeUtf8("\r\n");
+ sink.write(source, byteCount);
+ sink.writeUtf8("\r\n");
+ }
+
+ @Override public synchronized void flush() throws IOException {
+ if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush();
+ }
+
+ @Override public synchronized void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ sink.writeUtf8("0\r\n\r\n");
+ detachTimeout(timeout);
+ state = STATE_READ_RESPONSE_HEADERS;
+ }
+ }
+
+ private abstract class AbstractSource implements Source {
+ protected final ForwardingTimeout timeout = new ForwardingTimeout(source.timeout());
+ protected boolean closed;
+
+ @Override public Timeout timeout() {
+ return timeout;
+ }
+
+ /**
+ * Closes the cache entry and makes the socket available for reuse. This
+ * should be invoked when the end of the body has been reached.
+ */
+ protected final void endOfInput() throws IOException {
+ if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+
+ detachTimeout(timeout);
+
+ state = STATE_CLOSED;
+ if (streamAllocation != null) {
+ streamAllocation.streamFinished(Http1xStream.this);
+ }
+ }
+
+ protected final void unexpectedEndOfInput() {
+ if (state == STATE_CLOSED) return;
+
+ state = STATE_CLOSED;
+ if (streamAllocation != null) {
+ streamAllocation.noNewStreams();
+ streamAllocation.streamFinished(Http1xStream.this);
+ }
+ }
+ }
+
+ /** An HTTP body with a fixed length specified in advance. */
+ private class FixedLengthSource extends AbstractSource {
+ private long bytesRemaining;
+
+ public FixedLengthSource(long length) throws IOException {
+ bytesRemaining = length;
+ if (bytesRemaining == 0) {
+ endOfInput();
+ }
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (bytesRemaining == 0) return -1;
+
+ long read = source.read(sink, Math.min(bytesRemaining, byteCount));
+ if (read == -1) {
+ unexpectedEndOfInput(); // The server didn't supply the promised content length.
+ throw new ProtocolException("unexpected end of stream");
+ }
+
+ bytesRemaining -= read;
+ if (bytesRemaining == 0) {
+ endOfInput();
+ }
+ return read;
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ if (bytesRemaining != 0
+ && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+ unexpectedEndOfInput();
+ }
+
+ closed = true;
+ }
+ }
+
+ /** An HTTP body with alternating chunk sizes and chunk bodies. */
+ private class ChunkedSource extends AbstractSource {
+ private static final long NO_CHUNK_YET = -1L;
+ private long bytesRemainingInChunk = NO_CHUNK_YET;
+ private boolean hasMoreChunks = true;
+ private final HttpEngine httpEngine;
+
+ ChunkedSource(HttpEngine httpEngine) throws IOException {
+ this.httpEngine = httpEngine;
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (!hasMoreChunks) return -1;
+
+ if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
+ readChunkSize();
+ if (!hasMoreChunks) return -1;
+ }
+
+ long read = source.read(sink, Math.min(byteCount, bytesRemainingInChunk));
+ if (read == -1) {
+ unexpectedEndOfInput(); // The server didn't supply the promised chunk length.
+ throw new ProtocolException("unexpected end of stream");
+ }
+ bytesRemainingInChunk -= read;
+ return read;
+ }
+
+ private void readChunkSize() throws IOException {
+ // Read the suffix of the previous chunk.
+ if (bytesRemainingInChunk != NO_CHUNK_YET) {
+ source.readUtf8LineStrict();
+ }
+ try {
+ bytesRemainingInChunk = source.readHexadecimalUnsignedLong();
+ String extensions = source.readUtf8LineStrict().trim();
+ if (bytesRemainingInChunk < 0 || (!extensions.isEmpty() && !extensions.startsWith(";"))) {
+ throw new ProtocolException("expected chunk size and optional extensions but was \""
+ + bytesRemainingInChunk + extensions + "\"");
+ }
+ } catch (NumberFormatException e) {
+ throw new ProtocolException(e.getMessage());
+ }
+ if (bytesRemainingInChunk == 0L) {
+ hasMoreChunks = false;
+ httpEngine.receiveHeaders(readHeaders());
+ endOfInput();
+ }
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ if (hasMoreChunks && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+ unexpectedEndOfInput();
+ }
+ closed = true;
+ }
+ }
+
+ /** An HTTP message body terminated by the end of the underlying stream. */
+ private class UnknownLengthSource extends AbstractSource {
+ private boolean inputExhausted;
+
+ @Override public long read(Buffer sink, long byteCount)
+ throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (inputExhausted) return -1;
+
+ long read = source.read(sink, byteCount);
+ if (read == -1) {
+ inputExhausted = true;
+ endOfInput();
+ return -1;
+ }
+ return read;
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ if (!inputExhausted) {
+ unexpectedEndOfInput();
+ }
+ closed = true;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java
new file mode 100644
index 0000000..64908e0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java
@@ -0,0 +1,298 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.framed.ErrorCode;
+import com.android.okhttp.internal.framed.FramedConnection;
+import com.android.okhttp.internal.framed.FramedStream;
+import com.android.okhttp.internal.framed.Header;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+import static com.android.okhttp.internal.framed.Header.RESPONSE_STATUS;
+import static com.android.okhttp.internal.framed.Header.TARGET_AUTHORITY;
+import static com.android.okhttp.internal.framed.Header.TARGET_HOST;
+import static com.android.okhttp.internal.framed.Header.TARGET_METHOD;
+import static com.android.okhttp.internal.framed.Header.TARGET_PATH;
+import static com.android.okhttp.internal.framed.Header.TARGET_SCHEME;
+import static com.android.okhttp.internal.framed.Header.VERSION;
+
+/** An HTTP stream for HTTP/2 and SPDY.
+ * @hide This class is not part of the Android public SDK API*/
+public final class Http2xStream implements HttpStream {
+ private static final ByteString CONNECTION = ByteString.encodeUtf8("connection");
+ private static final ByteString HOST = ByteString.encodeUtf8("host");
+ private static final ByteString KEEP_ALIVE = ByteString.encodeUtf8("keep-alive");
+ private static final ByteString PROXY_CONNECTION = ByteString.encodeUtf8("proxy-connection");
+ private static final ByteString TRANSFER_ENCODING = ByteString.encodeUtf8("transfer-encoding");
+ private static final ByteString TE = ByteString.encodeUtf8("te");
+ private static final ByteString ENCODING = ByteString.encodeUtf8("encoding");
+ private static final ByteString UPGRADE = ByteString.encodeUtf8("upgrade");
+
+ /** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */
+ private static final List<ByteString> SPDY_3_SKIPPED_REQUEST_HEADERS = Util.immutableList(
+ CONNECTION,
+ HOST,
+ KEEP_ALIVE,
+ PROXY_CONNECTION,
+ TRANSFER_ENCODING,
+ TARGET_METHOD,
+ TARGET_PATH,
+ TARGET_SCHEME,
+ TARGET_AUTHORITY,
+ TARGET_HOST,
+ VERSION);
+ private static final List<ByteString> SPDY_3_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
+ CONNECTION,
+ HOST,
+ KEEP_ALIVE,
+ PROXY_CONNECTION,
+ TRANSFER_ENCODING);
+
+ /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
+ private static final List<ByteString> HTTP_2_SKIPPED_REQUEST_HEADERS = Util.immutableList(
+ CONNECTION,
+ HOST,
+ KEEP_ALIVE,
+ PROXY_CONNECTION,
+ TE,
+ TRANSFER_ENCODING,
+ ENCODING,
+ UPGRADE,
+ TARGET_METHOD,
+ TARGET_PATH,
+ TARGET_SCHEME,
+ TARGET_AUTHORITY,
+ TARGET_HOST,
+ VERSION);
+ private static final List<ByteString> HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
+ CONNECTION,
+ HOST,
+ KEEP_ALIVE,
+ PROXY_CONNECTION,
+ TE,
+ TRANSFER_ENCODING,
+ ENCODING,
+ UPGRADE);
+
+ private final StreamAllocation streamAllocation;
+ private final FramedConnection framedConnection;
+ private HttpEngine httpEngine;
+ private FramedStream stream;
+
+ public Http2xStream(StreamAllocation streamAllocation, FramedConnection framedConnection) {
+ this.streamAllocation = streamAllocation;
+ this.framedConnection = framedConnection;
+ }
+
+ @Override public void setHttpEngine(HttpEngine httpEngine) {
+ this.httpEngine = httpEngine;
+ }
+
+ @Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
+ return stream.getSink();
+ }
+
+ @Override public void writeRequestHeaders(Request request) throws IOException {
+ if (stream != null) return;
+
+ httpEngine.writingRequestHeaders();
+ boolean permitsRequestBody = httpEngine.permitsRequestBody(request);
+ List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
+ ? http2HeadersList(request)
+ : spdy3HeadersList(request);
+ boolean hasResponseBody = true;
+ stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
+ stream.readTimeout().timeout(httpEngine.client.getReadTimeout(), TimeUnit.MILLISECONDS);
+ stream.writeTimeout().timeout(httpEngine.client.getWriteTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+ requestBody.writeToSocket(stream.getSink());
+ }
+
+ @Override public void finishRequest() throws IOException {
+ stream.getSink().close();
+ }
+
+ @Override public Response.Builder readResponseHeaders() throws IOException {
+ return framedConnection.getProtocol() == Protocol.HTTP_2
+ ? readHttp2HeadersList(stream.getResponseHeaders())
+ : readSpdy3HeadersList(stream.getResponseHeaders());
+ }
+
+ /**
+ * Returns a list of alternating names and values containing a SPDY request.
+ * Names are all lowercase. No names are repeated. If any name has multiple
+ * values, they are concatenated using "\0" as a delimiter.
+ */
+ public static List<Header> spdy3HeadersList(Request request) {
+ Headers headers = request.headers();
+ List<Header> result = new ArrayList<>(headers.size() + 5);
+ result.add(new Header(TARGET_METHOD, request.method()));
+ result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
+ result.add(new Header(VERSION, "HTTP/1.1"));
+ result.add(new Header(TARGET_HOST, Util.hostHeader(request.httpUrl(), false)));
+ result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
+
+ Set<ByteString> names = new LinkedHashSet<>();
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ // header names must be lowercase.
+ ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
+
+ // Drop headers that are forbidden when layering HTTP over SPDY.
+ if (SPDY_3_SKIPPED_REQUEST_HEADERS.contains(name)) continue;
+
+ // If we haven't seen this name before, add the pair to the end of the list...
+ String value = headers.value(i);
+ if (names.add(name)) {
+ result.add(new Header(name, value));
+ continue;
+ }
+
+ // ...otherwise concatenate the existing values and this value.
+ for (int j = 0; j < result.size(); j++) {
+ if (result.get(j).name.equals(name)) {
+ String concatenated = joinOnNull(result.get(j).value.utf8(), value);
+ result.set(j, new Header(name, concatenated));
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String joinOnNull(String first, String second) {
+ return new StringBuilder(first).append('\0').append(second).toString();
+ }
+
+ public static List<Header> http2HeadersList(Request request) {
+ Headers headers = request.headers();
+ List<Header> result = new ArrayList<>(headers.size() + 4);
+ result.add(new Header(TARGET_METHOD, request.method()));
+ result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
+ result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.httpUrl(), false))); // Optional
+ result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
+
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ // header names must be lowercase.
+ ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
+ if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)) {
+ result.add(new Header(name, headers.value(i)));
+ }
+ }
+ return result;
+ }
+
+ /** Returns headers for a name value block containing a SPDY response. */
+ public static Response.Builder readSpdy3HeadersList(List<Header> headerBlock) throws IOException {
+ String status = null;
+ String version = "HTTP/1.1";
+ Headers.Builder headersBuilder = new Headers.Builder();
+ for (int i = 0, size = headerBlock.size(); i < size; i++) {
+ ByteString name = headerBlock.get(i).name;
+
+ String values = headerBlock.get(i).value.utf8();
+ for (int start = 0; start < values.length(); ) {
+ int end = values.indexOf('\0', start);
+ if (end == -1) {
+ end = values.length();
+ }
+ String value = values.substring(start, end);
+ if (name.equals(RESPONSE_STATUS)) {
+ status = value;
+ } else if (name.equals(VERSION)) {
+ version = value;
+ } else if (!SPDY_3_SKIPPED_RESPONSE_HEADERS.contains(name)) {
+ headersBuilder.add(name.utf8(), value);
+ }
+ start = end + 1;
+ }
+ }
+ if (status == null) throw new ProtocolException("Expected ':status' header not present");
+
+ StatusLine statusLine = StatusLine.parse(version + " " + status);
+ return new Response.Builder()
+ .protocol(Protocol.SPDY_3)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(headersBuilder.build());
+ }
+
+ /** Returns headers for a name value block containing an HTTP/2 response. */
+ public static Response.Builder readHttp2HeadersList(List<Header> headerBlock) throws IOException {
+ String status = null;
+
+ Headers.Builder headersBuilder = new Headers.Builder();
+ for (int i = 0, size = headerBlock.size(); i < size; i++) {
+ ByteString name = headerBlock.get(i).name;
+
+ String value = headerBlock.get(i).value.utf8();
+ if (name.equals(RESPONSE_STATUS)) {
+ status = value;
+ } else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {
+ headersBuilder.add(name.utf8(), value);
+ }
+ }
+ if (status == null) throw new ProtocolException("Expected ':status' header not present");
+
+ StatusLine statusLine = StatusLine.parse("HTTP/1.1 " + status);
+ return new Response.Builder()
+ .protocol(Protocol.HTTP_2)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(headersBuilder.build());
+ }
+
+ @Override public ResponseBody openResponseBody(Response response) throws IOException {
+ Source source = new StreamFinishingSource(stream.getSource());
+ return new RealResponseBody(response.headers(), Okio.buffer(source));
+ }
+
+ @Override public void cancel() {
+ if (stream != null) stream.closeLater(ErrorCode.CANCEL);
+ }
+
+ class StreamFinishingSource extends ForwardingSource {
+ public StreamFinishingSource(Source delegate) {
+ super(delegate);
+ }
+
+ @Override public void close() throws IOException {
+ streamAllocation.streamFinished(Http2xStream.this);
+ super.close();
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java
new file mode 100644
index 0000000..52265c2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java
@@ -0,0 +1,121 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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.android.okhttp.internal.http;
+
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpDate {
+
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+ /**
+ * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
+ * cookies are on the fast path.
+ */
+ private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT =
+ new ThreadLocal<DateFormat>() {
+ @Override protected DateFormat initialValue() {
+ // RFC 2616 specified: RFC 822, updated by RFC 1123 format with fixed GMT.
+ DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+ rfc1123.setLenient(false);
+ rfc1123.setTimeZone(GMT);
+ return rfc1123;
+ }
+ };
+
+ /** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */
+ private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] {
+ // HTTP formats required by RFC2616 but with any timezone.
+ "EEE, dd MMM yyyy HH:mm:ss zzz", // RFC 822, updated by RFC 1123 with any TZ
+ "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 850, obsoleted by RFC 1036 with any TZ.
+ "EEE MMM d HH:mm:ss yyyy", // ANSI C's asctime() format
+ // Alternative formats.
+ "EEE, dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MMM-yyyy HH-mm-ss z",
+ "EEE, dd MMM yy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH:mm:ss z",
+ "EEE dd MMM yyyy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH-mm-ss z",
+ "EEE dd-MMM-yy HH:mm:ss z",
+ "EEE dd MMM yy HH:mm:ss z",
+ "EEE,dd-MMM-yy HH:mm:ss z",
+ "EEE,dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MM-yyyy HH:mm:ss z",
+
+ /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
+ "EEE MMM d yyyy HH:mm:ss z",
+ };
+
+ private static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS =
+ new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length];
+
+ /** Returns the date for {@code value}. Returns null if the value couldn't be parsed. */
+ public static Date parse(String value) {
+ if (value.length() == 0) {
+ return null;
+ }
+
+ ParsePosition position = new ParsePosition(0);
+ Date result = STANDARD_DATE_FORMAT.get().parse(value, position);
+ if (position.getIndex() == value.length()) {
+ // STANDARD_DATE_FORMAT must match exactly; all text must be consumed, e.g. no ignored
+ // non-standard trailing "+01:00". Those cases are covered below.
+ return result;
+ }
+ synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) {
+ for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) {
+ DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i];
+ if (format == null) {
+ format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US);
+ // Set the timezone to use when interpreting formats that don't have a timezone. GMT is
+ // specified by RFC 2616.
+ format.setTimeZone(GMT);
+ BROWSER_COMPATIBLE_DATE_FORMATS[i] = format;
+ }
+ position.setIndex(0);
+ result = format.parse(value, position);
+ if (position.getIndex() != 0) {
+ // Something was parsed. It's possible the entire string was not consumed but we ignore
+ // that. If any of the BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS ended in "'GMT'" we'd have
+ // to also check that position.getIndex() == value.length() otherwise parsing might have
+ // terminated early, ignoring things like "+01:00". Leaving this as != 0 means that any
+ // trailing junk is ignored.
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Returns the string for {@code value}. */
+ public static String format(Date value) {
+ return STANDARD_DATE_FORMAT.get().format(value);
+ }
+
+ private HttpDate() {
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java
new file mode 100644
index 0000000..0ef78ed
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java
@@ -0,0 +1,989 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.CertificatePinner;
+import com.android.okhttp.Connection;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Interceptor;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.GzipSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.closeQuietly;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * Handles a single HTTP request/response pair. Each HTTP engine follows this
+ * lifecycle:
+ * <ol>
+ * <li>It is created.
+ * <li>The HTTP request message is sent with sendRequest(). Once the request
+ * is sent it is an error to modify the request headers. After
+ * sendRequest() has been called the request body can be written to if
+ * it exists.
+ * <li>The HTTP response message is read with readResponse(). After the
+ * response has been read the response headers and body can be read.
+ * All responses have a response body input stream, though in some
+ * instances this stream is empty.
+ * </ol>
+ *
+ * <p>The request and response may be served by the HTTP response cache, by the
+ * network, or by both in the event of a conditional GET.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpEngine {
+ /**
+ * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
+ * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
+ */
+ public static final int MAX_FOLLOW_UPS = 20;
+
+ private static final ResponseBody EMPTY_BODY = new ResponseBody() {
+ @Override public MediaType contentType() {
+ return null;
+ }
+ @Override public long contentLength() {
+ return 0;
+ }
+ @Override public BufferedSource source() {
+ return new Buffer();
+ }
+ };
+
+ final OkHttpClient client;
+
+ public final StreamAllocation streamAllocation;
+ private final Response priorResponse;
+ private HttpStream httpStream;
+
+ /** The time when the request headers were written, or -1 if they haven't been written yet. */
+ long sentRequestMillis = -1;
+
+ /**
+ * True if this client added an "Accept-Encoding: gzip" header field and is
+ * therefore responsible for also decompressing the transfer stream.
+ */
+ private boolean transparentGzip;
+
+ /**
+ * True if the request body must be completely buffered before transmission;
+ * false if it can be streamed. Buffering has two advantages: we don't need
+ * the content-length in advance and we can retransmit if necessary. The
+ * upside of streaming is that we can save memory.
+ */
+ public final boolean bufferRequestBody;
+
+ /**
+ * The original application-provided request. Never modified by OkHttp. When
+ * follow-up requests are necessary, they are derived from this request.
+ */
+ private final Request userRequest;
+
+ /**
+ * The request to send on the network, or null for no network request. This is
+ * derived from the user request, and customized to support OkHttp features
+ * like compression and caching.
+ */
+ private Request networkRequest;
+
+ /**
+ * The cached response, or null if the cache doesn't exist or cannot be used
+ * for this request. Conditional caching means this may be non-null even when
+ * the network request is non-null. Never modified by OkHttp.
+ */
+ private Response cacheResponse;
+
+ /**
+ * The user-visible response. This is derived from either the network
+ * response, cache response, or both. It is customized to support OkHttp
+ * features like compression and caching.
+ */
+ private Response userResponse;
+
+ private Sink requestBodyOut;
+ private BufferedSink bufferedRequestBody;
+ private final boolean callerWritesRequestBody;
+ private final boolean forWebSocket;
+
+ /** The cache request currently being populated from a network response. */
+ private CacheRequest storeRequest;
+ private CacheStrategy cacheStrategy;
+
+ /**
+ * @param request the HTTP request without a body. The body must be written via the engine's
+ * request body stream.
+ * @param callerWritesRequestBody true for the {@code HttpURLConnection}-style interaction
+ * model where control flow is returned to the calling application to write the request body
+ * before the response body is readable.
+ */
+ public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
+ boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
+ RetryableSink requestBodyOut, Response priorResponse) {
+ this.client = client;
+ this.userRequest = request;
+ this.bufferRequestBody = bufferRequestBody;
+ this.callerWritesRequestBody = callerWritesRequestBody;
+ this.forWebSocket = forWebSocket;
+ this.streamAllocation = streamAllocation != null
+ ? streamAllocation
+ : new StreamAllocation(client.getConnectionPool(), createAddress(client, request));
+ this.requestBodyOut = requestBodyOut;
+ this.priorResponse = priorResponse;
+ }
+
+ /**
+ * Figures out what the response source will be, and opens a socket to that
+ * source if necessary. Prepares the request headers and gets ready to start
+ * writing the request body if it exists.
+ *
+ * @throws RequestException if there was a problem with request setup. Unrecoverable.
+ * @throws RouteException if the was a problem during connection via a specific route. Sometimes
+ * recoverable. See {@link #recover(RouteException)}.
+ * @throws IOException if there was a problem while making a request. Sometimes recoverable. See
+ * {@link #recover(IOException)}.
+ *
+ */
+ public void sendRequest() throws RequestException, RouteException, IOException {
+ if (cacheStrategy != null) return; // Already sent.
+ if (httpStream != null) throw new IllegalStateException();
+
+ Request request = networkRequest(userRequest);
+
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ Response cacheCandidate = responseCache != null
+ ? responseCache.get(request)
+ : null;
+
+ long now = System.currentTimeMillis();
+ cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
+ networkRequest = cacheStrategy.networkRequest;
+ cacheResponse = cacheStrategy.cacheResponse;
+
+ if (responseCache != null) {
+ responseCache.trackResponse(cacheStrategy);
+ }
+
+ if (cacheCandidate != null && cacheResponse == null) {
+ closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
+ }
+
+ if (networkRequest != null) {
+ httpStream = connect();
+ httpStream.setHttpEngine(this);
+
+ // If the caller's control flow writes the request body, we need to create that stream
+ // immediately. And that means we need to immediately write the request headers, so we can
+ // start streaming the request body. (We may already have a request body if we're retrying a
+ // failed POST.)
+ if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
+ long contentLength = OkHeaders.contentLength(request);
+ if (bufferRequestBody) {
+ if (contentLength > Integer.MAX_VALUE) {
+ throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ + "setChunkedStreamingMode() for requests larger than 2 GiB.");
+ }
+
+ if (contentLength != -1) {
+ // Buffer a request body of a known length.
+ httpStream.writeRequestHeaders(networkRequest);
+ requestBodyOut = new RetryableSink((int) contentLength);
+ } else {
+ // Buffer a request body of an unknown length. Don't write request
+ // headers until the entire body is ready; otherwise we can't set the
+ // Content-Length header correctly.
+ requestBodyOut = new RetryableSink();
+ }
+ } else {
+ httpStream.writeRequestHeaders(networkRequest);
+ requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
+ }
+ }
+
+ } else {
+ if (cacheResponse != null) {
+ // We have a valid cached response. Promote it to the user response immediately.
+ this.userResponse = cacheResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .cacheResponse(stripBody(cacheResponse))
+ .build();
+ } else {
+ // We're forbidden from using the network, and the cache is insufficient.
+ this.userResponse = new Response.Builder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .protocol(Protocol.HTTP_1_1)
+ .code(504)
+ .message("Unsatisfiable Request (only-if-cached)")
+ .body(EMPTY_BODY)
+ .build();
+ }
+
+ userResponse = unzip(userResponse);
+ }
+ }
+
+ private HttpStream connect() throws RouteException, RequestException, IOException {
+ boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
+ return streamAllocation.newStream(client.getConnectTimeout(),
+ client.getReadTimeout(), client.getWriteTimeout(),
+ client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
+ }
+
+ private static Response stripBody(Response response) {
+ return response != null && response.body() != null
+ ? response.newBuilder().body(null).build()
+ : response;
+ }
+
+
+ /**
+ * Called immediately before the transport transmits HTTP request headers.
+ * This is used to observe the sent time should the request be cached.
+ */
+ public void writingRequestHeaders() {
+ if (sentRequestMillis != -1) throw new IllegalStateException();
+ sentRequestMillis = System.currentTimeMillis();
+ }
+
+ boolean permitsRequestBody(Request request) {
+ return HttpMethod.permitsRequestBody(request.method());
+ }
+
+ /** Returns the request body or null if this request doesn't have a body. */
+ public Sink getRequestBody() {
+ if (cacheStrategy == null) throw new IllegalStateException();
+ return requestBodyOut;
+ }
+
+ public BufferedSink getBufferedRequestBody() {
+ BufferedSink result = bufferedRequestBody;
+ if (result != null) return result;
+ Sink requestBody = getRequestBody();
+ return requestBody != null
+ ? (bufferedRequestBody = Okio.buffer(requestBody))
+ : null;
+ }
+
+ public boolean hasResponse() {
+ return userResponse != null;
+ }
+
+ public Request getRequest() {
+ return userRequest;
+ }
+
+ /** Returns the engine's response. */
+ // TODO: the returned body will always be null.
+ public Response getResponse() {
+ if (userResponse == null) throw new IllegalStateException();
+ return userResponse;
+ }
+
+ public Connection getConnection() {
+ return streamAllocation.connection();
+ }
+
+ /**
+ * Attempt to recover from failure to connect via a route. Returns a new HTTP engine
+ * that should be used for the retry if there are other routes to try, or null if
+ * there are no more routes to try.
+ */
+ public HttpEngine recover(RouteException e) {
+ if (!streamAllocation.recover(e)) {
+ return null;
+ }
+
+ if (!client.getRetryOnConnectionFailure()) {
+ return null;
+ }
+
+ StreamAllocation streamAllocation = close();
+
+ // For failure recovery, use the same route selector with a new connection.
+ return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
+ forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
+ }
+
+ /**
+ * Report and attempt to recover from a failure to communicate with a server. Returns a new
+ * HTTP engine that should be used for the retry if {@code e} is recoverable, or null if
+ * the failure is permanent. Requests with a body can only be recovered if the
+ * body is buffered.
+ */
+ public HttpEngine recover(IOException e, Sink requestBodyOut) {
+ if (!streamAllocation.recover(e, requestBodyOut)) {
+ return null;
+ }
+
+ if (!client.getRetryOnConnectionFailure()) {
+ return null;
+ }
+
+ StreamAllocation streamAllocation = close();
+
+ // For failure recovery, use the same route selector with a new connection.
+ return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
+ forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
+ }
+
+ public HttpEngine recover(IOException e) {
+ return recover(e, requestBodyOut);
+ }
+
+ private void maybeCache() throws IOException {
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ if (responseCache == null) return;
+
+ // Should we cache this response for this request?
+ if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
+ if (HttpMethod.invalidatesCache(networkRequest.method())) {
+ try {
+ responseCache.remove(networkRequest);
+ } catch (IOException ignored) {
+ // The cache cannot be written.
+ }
+ }
+ return;
+ }
+
+ // Offer this request to the cache.
+ storeRequest = responseCache.put(stripBody(userResponse));
+ }
+
+ /**
+ * Configure the socket connection to be either pooled or closed when it is
+ * either exhausted or closed. If it is unneeded when this is called, it will
+ * be released immediately.
+ */
+ public void releaseStreamAllocation() throws IOException {
+ streamAllocation.release();
+ }
+
+ /**
+ * Immediately closes the socket connection if it's currently held by this engine. Use this to
+ * interrupt an in-flight request from any thread. It's the caller's responsibility to close the
+ * request body and response body streams; otherwise resources may be leaked.
+ *
+ * <p>This method is safe to be called concurrently, but provides limited guarantees. If a
+ * transport layer connection has been established (such as a HTTP/2 stream) that is terminated.
+ * Otherwise if a socket connection is being established, that is terminated.
+ */
+ public void cancel() {
+ streamAllocation.cancel();
+ }
+
+ /**
+ * Release any resources held by this engine. Returns the stream allocation held by this engine,
+ * which itself must be used or released.
+ */
+ public StreamAllocation close() {
+ if (bufferedRequestBody != null) {
+ // This also closes the wrapped requestBodyOut.
+ closeQuietly(bufferedRequestBody);
+ } else if (requestBodyOut != null) {
+ closeQuietly(requestBodyOut);
+ }
+
+ if (userResponse != null) {
+ closeQuietly(userResponse.body());
+ } else {
+ // If this engine never achieved a response body, its stream allocation is dead.
+ streamAllocation.connectionFailed();
+ }
+
+ return streamAllocation;
+ }
+
+ /**
+ * Returns a new response that does gzip decompression on {@code response}, if transparent gzip
+ * was both offered by OkHttp and used by the origin server.
+ *
+ * <p>In addition to decompression, this will also strip the corresponding headers. We strip the
+ * Content-Encoding header to prevent the application from attempting to double decompress. We
+ * strip the Content-Length header because it is the length of the compressed content, but the
+ * application is only interested in the length of the uncompressed content.
+ *
+ * <p>This method should only be used for non-empty response bodies. Response codes like "304 Not
+ * Modified" can include "Content-Encoding: gzip" without a response body and we will crash if we
+ * attempt to decompress the zero-byte source.
+ */
+ private Response unzip(final Response response) throws IOException {
+ if (!transparentGzip || !"gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) {
+ return response;
+ }
+
+ if (response.body() == null) {
+ return response;
+ }
+
+ GzipSource responseBody = new GzipSource(response.body().source());
+ Headers strippedHeaders = response.headers().newBuilder()
+ .removeAll("Content-Encoding")
+ .removeAll("Content-Length")
+ .build();
+ return response.newBuilder()
+ .headers(strippedHeaders)
+ .body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)))
+ .build();
+ }
+
+ /**
+ * Returns true if the response must have a (possibly 0-length) body.
+ * See RFC 2616 section 4.3.
+ */
+ public static boolean hasBody(Response response) {
+ // HEAD requests never yield a body regardless of the response headers.
+ if (response.request().method().equals("HEAD")) {
+ return false;
+ }
+
+ int responseCode = response.code();
+ if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
+ && responseCode != HTTP_NO_CONTENT
+ && responseCode != HTTP_NOT_MODIFIED) {
+ return true;
+ }
+
+ // If the Content-Length or Transfer-Encoding headers disagree with the
+ // response code, the response is malformed. For best compatibility, we
+ // honor the headers.
+ if (OkHeaders.contentLength(response) != -1
+ || "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Populates request with defaults and cookies.
+ *
+ * <p>This client doesn't specify a default {@code Accept} header because it
+ * doesn't know what content types the application is interested in.
+ */
+ private Request networkRequest(Request request) throws IOException {
+ Request.Builder result = request.newBuilder();
+
+ if (request.header("Host") == null) {
+ result.header("Host", Util.hostHeader(request.httpUrl(), false));
+ }
+
+ if (request.header("Connection") == null) {
+ result.header("Connection", "Keep-Alive");
+ }
+
+ if (request.header("Accept-Encoding") == null) {
+ transparentGzip = true;
+ result.header("Accept-Encoding", "gzip");
+ }
+
+ CookieHandler cookieHandler = client.getCookieHandler();
+ if (cookieHandler != null) {
+ // Capture the request headers added so far so that they can be offered to the CookieHandler.
+ // This is mostly to stay close to the RI; it is unlikely any of the headers above would
+ // affect cookie choice besides "Host".
+ Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);
+
+ Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);
+
+ // Add any new cookies to the request.
+ OkHeaders.addCookies(result, cookies);
+ }
+
+ if (request.header("User-Agent") == null) {
+ result.header("User-Agent", Version.userAgent());
+ }
+
+ return result.build();
+ }
+
+ /**
+ * Flushes the remaining request header and body, parses the HTTP response
+ * headers and starts reading the HTTP response body if it exists.
+ */
+ public void readResponse() throws IOException {
+ if (userResponse != null) {
+ return; // Already ready.
+ }
+ if (networkRequest == null && cacheResponse == null) {
+ throw new IllegalStateException("call sendRequest() first!");
+ }
+ if (networkRequest == null) {
+ return; // No network response to read.
+ }
+
+ Response networkResponse;
+
+ if (forWebSocket) {
+ httpStream.writeRequestHeaders(networkRequest);
+ networkResponse = readNetworkResponse();
+
+ } else if (!callerWritesRequestBody) {
+ networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
+
+ } else {
+ // Emit the request body's buffer so that everything is in requestBodyOut.
+ if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
+ bufferedRequestBody.emit();
+ }
+
+ // Emit the request headers if we haven't yet. We might have just learned the Content-Length.
+ if (sentRequestMillis == -1) {
+ if (OkHeaders.contentLength(networkRequest) == -1
+ && requestBodyOut instanceof RetryableSink) {
+ long contentLength = ((RetryableSink) requestBodyOut).contentLength();
+ networkRequest = networkRequest.newBuilder()
+ .header("Content-Length", Long.toString(contentLength))
+ .build();
+ }
+ httpStream.writeRequestHeaders(networkRequest);
+ }
+
+ // Write the request body to the socket.
+ if (requestBodyOut != null) {
+ if (bufferedRequestBody != null) {
+ // This also closes the wrapped requestBodyOut.
+ bufferedRequestBody.close();
+ } else {
+ requestBodyOut.close();
+ }
+ if (requestBodyOut instanceof RetryableSink) {
+ httpStream.writeRequestBody((RetryableSink) requestBodyOut);
+ }
+ }
+
+ networkResponse = readNetworkResponse();
+ }
+
+ receiveHeaders(networkResponse.headers());
+
+ // If we have a cache response too, then we're doing a conditional get.
+ if (cacheResponse != null) {
+ if (validate(cacheResponse, networkResponse)) {
+ userResponse = cacheResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .headers(combine(cacheResponse.headers(), networkResponse.headers()))
+ .cacheResponse(stripBody(cacheResponse))
+ .networkResponse(stripBody(networkResponse))
+ .build();
+ networkResponse.body().close();
+ releaseStreamAllocation();
+
+ // Update the cache after combining headers but before stripping the
+ // Content-Encoding header (as performed by initContentStream()).
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ responseCache.trackConditionalCacheHit();
+ responseCache.update(cacheResponse, stripBody(userResponse));
+ userResponse = unzip(userResponse);
+ return;
+ } else {
+ closeQuietly(cacheResponse.body());
+ }
+ }
+
+ userResponse = networkResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .cacheResponse(stripBody(cacheResponse))
+ .networkResponse(stripBody(networkResponse))
+ .build();
+
+ if (hasBody(userResponse)) {
+ maybeCache();
+ userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
+ }
+ }
+
+ class NetworkInterceptorChain implements Interceptor.Chain {
+ private final int index;
+ private final Request request;
+ private int calls;
+
+ NetworkInterceptorChain(int index, Request request) {
+ this.index = index;
+ this.request = request;
+ }
+
+ @Override public Connection connection() {
+ return streamAllocation.connection();
+ }
+
+ @Override public Request request() {
+ return request;
+ }
+
+ @Override public Response proceed(Request request) throws IOException {
+ calls++;
+
+ if (index > 0) {
+ Interceptor caller = client.networkInterceptors().get(index - 1);
+ Address address = connection().getRoute().getAddress();
+
+ // Confirm that the interceptor uses the connection we've already prepared.
+ if (!request.httpUrl().host().equals(address.getUriHost())
+ || request.httpUrl().port() != address.getUriPort()) {
+ throw new IllegalStateException("network interceptor " + caller
+ + " must retain the same host and port");
+ }
+
+ // Confirm that this is the interceptor's first call to chain.proceed().
+ if (calls > 1) {
+ throw new IllegalStateException("network interceptor " + caller
+ + " must call proceed() exactly once");
+ }
+ }
+
+ if (index < client.networkInterceptors().size()) {
+ // There's another interceptor in the chain. Call that.
+ NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
+ Interceptor interceptor = client.networkInterceptors().get(index);
+ Response interceptedResponse = interceptor.intercept(chain);
+
+ // Confirm that the interceptor made the required call to chain.proceed().
+ if (chain.calls != 1) {
+ throw new IllegalStateException("network interceptor " + interceptor
+ + " must call proceed() exactly once");
+ }
+ if (interceptedResponse == null) {
+ throw new NullPointerException("network interceptor " + interceptor
+ + " returned null");
+ }
+
+ return interceptedResponse;
+ }
+
+ httpStream.writeRequestHeaders(request);
+
+ //Update the networkRequest with the possibly updated interceptor request.
+ networkRequest = request;
+
+ if (permitsRequestBody(request) && request.body() != null) {
+ Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
+ BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
+ request.body().writeTo(bufferedRequestBody);
+ bufferedRequestBody.close();
+ }
+
+ Response response = readNetworkResponse();
+
+ int code = response.code();
+ if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
+ throw new ProtocolException(
+ "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
+ }
+
+ return response;
+ }
+ }
+
+ private Response readNetworkResponse() throws IOException {
+ httpStream.finishRequest();
+
+ Response networkResponse = httpStream.readResponseHeaders()
+ .request(networkRequest)
+ .handshake(streamAllocation.connection().getHandshake())
+ .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
+ .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
+ .build();
+
+ if (!forWebSocket) {
+ networkResponse = networkResponse.newBuilder()
+ .body(httpStream.openResponseBody(networkResponse))
+ .build();
+ }
+
+ if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
+ || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
+ streamAllocation.noNewStreams();
+ }
+
+ return networkResponse;
+ }
+
+ /**
+ * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source
+ * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
+ * may never exhaust the source stream and therefore not complete the cached response.
+ */
+ private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
+ throws IOException {
+ // Some apps return a null body; for compatibility we treat that like a null cache request.
+ if (cacheRequest == null) return response;
+ Sink cacheBodyUnbuffered = cacheRequest.body();
+ if (cacheBodyUnbuffered == null) return response;
+
+ final BufferedSource source = response.body().source();
+ final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
+
+ Source cacheWritingSource = new Source() {
+ boolean cacheRequestClosed;
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ long bytesRead;
+ try {
+ bytesRead = source.read(sink, byteCount);
+ } catch (IOException e) {
+ if (!cacheRequestClosed) {
+ cacheRequestClosed = true;
+ cacheRequest.abort(); // Failed to write a complete cache response.
+ }
+ throw e;
+ }
+
+ if (bytesRead == -1) {
+ if (!cacheRequestClosed) {
+ cacheRequestClosed = true;
+ cacheBody.close(); // The cache response is complete!
+ }
+ return -1;
+ }
+
+ sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
+ cacheBody.emitCompleteSegments();
+ return bytesRead;
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (!cacheRequestClosed
+ && !Util.discard(this, HttpStream.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+ cacheRequestClosed = true;
+ cacheRequest.abort();
+ }
+ source.close();
+ }
+ };
+
+ return response.newBuilder()
+ .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
+ .build();
+ }
+
+ /**
+ * Returns true if {@code cached} should be used; false if {@code network}
+ * response should be used.
+ */
+ private static boolean validate(Response cached, Response network) {
+ if (network.code() == HTTP_NOT_MODIFIED) {
+ return true;
+ }
+
+ // The HTTP spec says that if the network's response is older than our
+ // cached response, we may return the cache's response. Like Chrome (but
+ // unlike Firefox), this client prefers to return the newer response.
+ Date lastModified = cached.headers().getDate("Last-Modified");
+ if (lastModified != null) {
+ Date networkLastModified = network.headers().getDate("Last-Modified");
+ if (networkLastModified != null
+ && networkLastModified.getTime() < lastModified.getTime()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Combines cached headers with a network headers as defined by RFC 2616,
+ * 13.5.3.
+ */
+ private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException {
+ Headers.Builder result = new Headers.Builder();
+
+ for (int i = 0, size = cachedHeaders.size(); i < size; i++) {
+ String fieldName = cachedHeaders.name(i);
+ String value = cachedHeaders.value(i);
+ if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
+ continue; // Drop 100-level freshness warnings.
+ }
+ if (!OkHeaders.isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
+ result.add(fieldName, value);
+ }
+ }
+
+ for (int i = 0, size = networkHeaders.size(); i < size; i++) {
+ String fieldName = networkHeaders.name(i);
+ if ("Content-Length".equalsIgnoreCase(fieldName)) {
+ continue; // Ignore content-length headers of validating responses.
+ }
+ if (OkHeaders.isEndToEnd(fieldName)) {
+ result.add(fieldName, networkHeaders.value(i));
+ }
+ }
+
+ return result.build();
+ }
+
+ public void receiveHeaders(Headers headers) throws IOException {
+ CookieHandler cookieHandler = client.getCookieHandler();
+ if (cookieHandler != null) {
+ cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public Request followUpRequest() throws IOException {
+ if (userResponse == null) throw new IllegalStateException();
+ Connection connection = streamAllocation.connection();
+ Route route = connection != null
+ ? connection.getRoute()
+ : null;
+ Proxy selectedProxy = route != null
+ ? route.getProxy()
+ : client.getProxy();
+ int responseCode = userResponse.code();
+
+ final String method = userRequest.method();
+ switch (responseCode) {
+ case HTTP_PROXY_AUTH:
+ if (selectedProxy.type() != Proxy.Type.HTTP) {
+ throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
+ }
+ // fall-through
+ case HTTP_UNAUTHORIZED:
+ return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy);
+
+ case HTTP_PERM_REDIRECT:
+ case HTTP_TEMP_REDIRECT:
+ // "If the 307 or 308 status code is received in response to a request other than GET
+ // or HEAD, the user agent MUST NOT automatically redirect the request"
+ if (!method.equals("GET") && !method.equals("HEAD")) {
+ return null;
+ }
+ // fall-through
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_MOVED_TEMP:
+ case HTTP_SEE_OTHER:
+ // Does the client allow redirects?
+ if (!client.getFollowRedirects()) return null;
+
+ String location = userResponse.header("Location");
+ if (location == null) return null;
+ HttpUrl url = userRequest.httpUrl().resolve(location);
+
+ // Don't follow redirects to unsupported protocols.
+ if (url == null) return null;
+
+ // If configured, don't follow redirects between SSL and non-SSL.
+ boolean sameScheme = url.scheme().equals(userRequest.httpUrl().scheme());
+ if (!sameScheme && !client.getFollowSslRedirects()) return null;
+
+ // Redirects don't include a request body.
+ Request.Builder requestBuilder = userRequest.newBuilder();
+ if (HttpMethod.permitsRequestBody(method)) {
+ if (HttpMethod.redirectsToGet(method)) {
+ requestBuilder.method("GET", null);
+ } else {
+ requestBuilder.method(method, null);
+ }
+ requestBuilder.removeHeader("Transfer-Encoding");
+ requestBuilder.removeHeader("Content-Length");
+ requestBuilder.removeHeader("Content-Type");
+ }
+
+ // When redirecting across hosts, drop all authentication headers. This
+ // is potentially annoying to the application layer since they have no
+ // way to retain them.
+ if (!sameConnection(url)) {
+ requestBuilder.removeHeader("Authorization");
+ }
+
+ return requestBuilder.url(url).build();
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if an HTTP request for {@code followUp} can reuse the
+ * connection used by this engine.
+ */
+ public boolean sameConnection(HttpUrl followUp) {
+ HttpUrl url = userRequest.httpUrl();
+ return url.host().equals(followUp.host())
+ && url.port() == followUp.port()
+ && url.scheme().equals(followUp.scheme());
+ }
+
+ private static Address createAddress(OkHttpClient client, Request request) {
+ SSLSocketFactory sslSocketFactory = null;
+ HostnameVerifier hostnameVerifier = null;
+ CertificatePinner certificatePinner = null;
+ if (request.isHttps()) {
+ sslSocketFactory = client.getSslSocketFactory();
+ hostnameVerifier = client.getHostnameVerifier();
+ certificatePinner = client.getCertificatePinner();
+ }
+
+ return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
+ client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
+ client.getAuthenticator(), client.getProxy(), client.getProtocols(),
+ client.getConnectionSpecs(), client.getProxySelector());
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java
new file mode 100644
index 0000000..5bd7b48
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java
@@ -0,0 +1,55 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpMethod {
+ public static boolean invalidatesCache(String method) {
+ return method.equals("POST")
+ || method.equals("PATCH")
+ || method.equals("PUT")
+ || method.equals("DELETE")
+ || method.equals("MOVE"); // WebDAV
+ }
+
+ public static boolean requiresRequestBody(String method) {
+ return method.equals("POST")
+ || method.equals("PUT")
+ || method.equals("PATCH")
+ || method.equals("PROPPATCH") // WebDAV
+ || method.equals("REPORT"); // CalDAV/CardDAV (defined in WebDAV Versioning)
+ }
+
+ public static boolean permitsRequestBody(String method) {
+ return requiresRequestBody(method)
+ || method.equals("OPTIONS")
+ || method.equals("DELETE") // Permitted as spec is ambiguous.
+ || method.equals("PROPFIND") // (WebDAV) without body: request <allprop/>
+ || method.equals("MKCOL") // (WebDAV) may contain a body, but behaviour is unspecified
+ || method.equals("LOCK"); // (WebDAV) body: create lock, without body: refresh lock
+ }
+
+ public static boolean redirectsToGet(String method) {
+ // All requests but PROPFIND should redirect to a GET request.
+ return !method.equals("PROPFIND");
+ }
+
+ private HttpMethod() {
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java
new file mode 100644
index 0000000..ccc026d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java
@@ -0,0 +1,65 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.android.okhttp.internal.http;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import java.io.IOException;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface HttpStream {
+ /**
+ * The timeout to use while discarding a stream of input data. Since this is
+ * used for connection reuse, this timeout should be significantly less than
+ * the time it takes to establish a new connection.
+ */
+ int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
+
+ /** Returns an output stream where the request body can be streamed. */
+ Sink createRequestBody(Request request, long contentLength) throws IOException;
+
+ /** This should update the HTTP engine's sentRequestMillis field. */
+ void writeRequestHeaders(Request request) throws IOException;
+
+ /**
+ * Sends the request body returned by {@link #createRequestBody} to the
+ * remote peer.
+ */
+ void writeRequestBody(RetryableSink requestBody) throws IOException;
+
+ /** Flush the request to the underlying socket. */
+ void finishRequest() throws IOException;
+
+ /** Read and return response headers. */
+ Response.Builder readResponseHeaders() throws IOException;
+
+ /** Returns a stream that reads the response body. */
+ ResponseBody openResponseBody(Response response) throws IOException;
+
+ void setHttpEngine(HttpEngine httpEngine);
+
+ /**
+ * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
+ * That may happen later by the connection pool thread.
+ */
+ void cancel();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java
new file mode 100644
index 0000000..f192dcb
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java
@@ -0,0 +1,292 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.Authenticator;
+import com.android.okhttp.Challenge;
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.Platform;
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import static com.android.okhttp.internal.Util.equal;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+
+/** Headers and utilities for internal use by OkHttp.
+ * @hide This class is not part of the Android public SDK API*/
+public final class OkHeaders {
+ private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+ // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
+ @Override public int compare(String a, String b) {
+ if (a == b) {
+ return 0;
+ } else if (a == null) {
+ return -1;
+ } else if (b == null) {
+ return 1;
+ } else {
+ return String.CASE_INSENSITIVE_ORDER.compare(a, b);
+ }
+ }
+ };
+
+ static final String PREFIX = Platform.get().getPrefix();
+
+ /**
+ * Synthetic response header: the local time when the request was sent.
+ */
+ public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
+
+ /**
+ * Synthetic response header: the local time when the response was received.
+ */
+ public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
+
+ /**
+ * Synthetic response header: the selected
+ * {@link com.android.okhttp.Protocol protocol} ("spdy/3.1", "http/1.1", etc).
+ */
+ public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol";
+
+ /** Synthetic response header: the location from which the response was loaded. */
+ public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source";
+
+ private OkHeaders() {
+ }
+
+ public static long contentLength(Request request) {
+ return contentLength(request.headers());
+ }
+
+ public static long contentLength(Response response) {
+ return contentLength(response.headers());
+ }
+
+ public static long contentLength(Headers headers) {
+ return stringToLong(headers.get("Content-Length"));
+ }
+
+ private static long stringToLong(String s) {
+ if (s == null) return -1;
+ try {
+ return Long.parseLong(s);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns an immutable map containing each field to its list of values.
+ *
+ * @param valueForNullKey the request line for requests, or the status line
+ * for responses. If non-null, this value is mapped to the null key.
+ */
+ public static Map<String, List<String>> toMultimap(Headers headers, String valueForNullKey) {
+ Map<String, List<String>> result = new TreeMap<>(FIELD_NAME_COMPARATOR);
+ for (int i = 0, size = headers.size(); i < size; i++) {
+ String fieldName = headers.name(i);
+ String value = headers.value(i);
+
+ List<String> allValues = new ArrayList<>();
+ List<String> otherValues = result.get(fieldName);
+ if (otherValues != null) {
+ allValues.addAll(otherValues);
+ }
+ allValues.add(value);
+ result.put(fieldName, Collections.unmodifiableList(allValues));
+ }
+ if (valueForNullKey != null) {
+ result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ public static void addCookies(Request.Builder builder, Map<String, List<String>> cookieHeaders) {
+ for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
+ String key = entry.getKey();
+ if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
+ && !entry.getValue().isEmpty()) {
+ builder.addHeader(key, buildCookieHeader(entry.getValue()));
+ }
+ }
+ }
+
+ /**
+ * Send all cookies in one big header, as recommended by
+ * <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
+ */
+ private static String buildCookieHeader(List<String> cookies) {
+ if (cookies.size() == 1) return cookies.get(0);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, size = cookies.size(); i < size; i++) {
+ if (i > 0) sb.append("; ");
+ sb.append(cookies.get(i));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns true if none of the Vary headers have changed between {@code
+ * cachedRequest} and {@code newRequest}.
+ */
+ public static boolean varyMatches(
+ Response cachedResponse, Headers cachedRequest, Request newRequest) {
+ for (String field : varyFields(cachedResponse)) {
+ if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if a Vary header contains an asterisk. Such responses cannot
+ * be cached.
+ */
+ public static boolean hasVaryAll(Response response) {
+ return hasVaryAll(response.headers());
+ }
+
+ /**
+ * Returns true if a Vary header contains an asterisk. Such responses cannot
+ * be cached.
+ */
+ public static boolean hasVaryAll(Headers responseHeaders) {
+ return varyFields(responseHeaders).contains("*");
+ }
+
+ private static Set<String> varyFields(Response response) {
+ return varyFields(response.headers());
+ }
+
+ /**
+ * Returns the names of the request headers that need to be checked for
+ * equality when caching.
+ */
+ public static Set<String> varyFields(Headers responseHeaders) {
+ Set<String> result = Collections.emptySet();
+ for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+ if (!"Vary".equalsIgnoreCase(responseHeaders.name(i))) continue;
+
+ String value = responseHeaders.value(i);
+ if (result.isEmpty()) {
+ result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ for (String varyField : value.split(",")) {
+ result.add(varyField.trim());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the subset of the headers in {@code response}'s request that
+ * impact the content of response's body.
+ */
+ public static Headers varyHeaders(Response response) {
+ // Use the request headers sent over the network, since that's what the
+ // response varies on. Otherwise OkHttp-supplied headers like
+ // "Accept-Encoding: gzip" may be lost.
+ Headers requestHeaders = response.networkResponse().request().headers();
+ Headers responseHeaders = response.headers();
+ return varyHeaders(requestHeaders, responseHeaders);
+ }
+
+ /**
+ * Returns the subset of the headers in {@code requestHeaders} that
+ * impact the content of response's body.
+ */
+ public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) {
+ Set<String> varyFields = varyFields(responseHeaders);
+ if (varyFields.isEmpty()) return new Headers.Builder().build();
+
+ Headers.Builder result = new Headers.Builder();
+ for (int i = 0, size = requestHeaders.size(); i < size; i++) {
+ String fieldName = requestHeaders.name(i);
+ if (varyFields.contains(fieldName)) {
+ result.add(fieldName, requestHeaders.value(i));
+ }
+ }
+ return result.build();
+ }
+
+ /**
+ * Returns true if {@code fieldName} is an end-to-end HTTP header, as
+ * defined by RFC 2616, 13.5.1.
+ */
+ static boolean isEndToEnd(String fieldName) {
+ return !"Connection".equalsIgnoreCase(fieldName)
+ && !"Keep-Alive".equalsIgnoreCase(fieldName)
+ && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
+ && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
+ && !"TE".equalsIgnoreCase(fieldName)
+ && !"Trailers".equalsIgnoreCase(fieldName)
+ && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
+ && !"Upgrade".equalsIgnoreCase(fieldName);
+ }
+
+ /**
+ * Parse RFC 2617 challenges. This API is only interested in the scheme
+ * name and realm.
+ */
+ public static List<Challenge> parseChallenges(Headers responseHeaders, String challengeHeader) {
+ // auth-scheme = token
+ // auth-param = token "=" ( token | quoted-string )
+ // challenge = auth-scheme 1*SP 1#auth-param
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ List<Challenge> result = new ArrayList<>();
+ for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+ if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(i))) {
+ continue;
+ }
+ String value = responseHeaders.value(i);
+ int pos = 0;
+ while (pos < value.length()) {
+ int tokenStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, " ");
+
+ String scheme = value.substring(tokenStart, pos).trim();
+ pos = HeaderParser.skipWhitespace(value, pos);
+
+ // TODO: This currently only handles schemes with a 'realm' parameter;
+ // It needs to be fixed to handle any scheme and any parameters
+ // http://code.google.com/p/android/issues/detail?id=11140
+
+ if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
+ break; // Unexpected challenge parameter; give up!
+ }
+
+ pos += "realm=\"".length();
+ int realmStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, "\"");
+ String realm = value.substring(realmStart, pos);
+ pos++; // Consume '"' close quote.
+ pos = HeaderParser.skipUntil(value, pos, ",");
+ pos++; // Consume ',' comma.
+ pos = HeaderParser.skipWhitespace(value, pos);
+ result.add(new Challenge(scheme, realm));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * React to a failed authorization response by looking up new credentials.
+ * Returns a request for a subsequent attempt, or null if no further attempts
+ * should be made.
+ */
+ public static Request processAuthHeader(Authenticator authenticator, Response response,
+ Proxy proxy) throws IOException {
+ return response.code() == HTTP_PROXY_AUTH
+ ? authenticator.authenticateProxy(proxy, response)
+ : authenticator.authenticate(proxy, response);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java
new file mode 100644
index 0000000..5bd4840
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java
@@ -0,0 +1,48 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.okio.BufferedSource;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RealResponseBody extends ResponseBody {
+ private final Headers headers;
+ private final BufferedSource source;
+
+ public RealResponseBody(Headers headers, BufferedSource source) {
+ this.headers = headers;
+ this.source = source;
+ }
+
+ @Override public MediaType contentType() {
+ String contentType = headers.get("Content-Type");
+ return contentType != null ? MediaType.parse(contentType) : null;
+ }
+
+ @Override public long contentLength() {
+ return OkHeaders.contentLength(headers);
+ }
+
+ @Override public BufferedSource source() {
+ return source;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java
new file mode 100644
index 0000000..729b8d5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java
@@ -0,0 +1,36 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+
+/**
+ * Indicates a problem with interpreting a request. It may indicate there was a problem with the
+ * request itself, or the environment being used to interpret the request (network failure, etc.).
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RequestException extends Exception {
+
+ public RequestException(IOException cause) {
+ super(cause);
+ }
+
+ @Override
+ public IOException getCause() {
+ return (IOException) super.getCause();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java
new file mode 100644
index 0000000..fcfda0b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java
@@ -0,0 +1,54 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Request;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RequestLine {
+ private RequestLine() {
+ }
+
+ /**
+ * Returns the request status line, like "GET / HTTP/1.1". This is exposed
+ * to the application by {@link HttpURLConnection#getHeaderFields}, so it
+ * needs to be set even if the transport is SPDY.
+ */
+ static String get(Request request, Proxy.Type proxyType) {
+ StringBuilder result = new StringBuilder();
+ result.append(request.method());
+ result.append(' ');
+
+ if (includeAuthorityInRequestLine(request, proxyType)) {
+ result.append(request.httpUrl());
+ } else {
+ result.append(requestPath(request.httpUrl()));
+ }
+
+ result.append(" HTTP/1.1");
+ return result.toString();
+ }
+
+ /**
+ * Returns true if the request line should contain the full URL with host
+ * and port (like "GET http://android.com/foo HTTP/1.1") or only the path
+ * (like "GET /foo HTTP/1.1").
+ */
+ private static boolean includeAuthorityInRequestLine(Request request, Proxy.Type proxyType) {
+ return !request.isHttps() && proxyType == Proxy.Type.HTTP;
+ }
+
+ /**
+ * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never empty,
+ * even if the request URL is. Includes the query component if it exists.
+ */
+ public static String requestPath(HttpUrl url) {
+ String path = url.encodedPath();
+ String query = url.encodedQuery();
+ return query != null ? (path + '?' + query) : path;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java
new file mode 100644
index 0000000..2891079
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java
@@ -0,0 +1,82 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2010 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.android.okhttp.internal.http;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.checkOffsetAndCount;
+
+/**
+ * An HTTP request body that's completely buffered in memory. This allows
+ * the post body to be transparently re-sent if the HTTP request must be
+ * sent multiple times.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RetryableSink implements Sink {
+ private boolean closed;
+ private final int limit;
+ private final Buffer content = new Buffer();
+
+ public RetryableSink(int limit) {
+ this.limit = limit;
+ }
+
+ public RetryableSink() {
+ this(-1);
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ if (content.size() < limit) {
+ throw new ProtocolException(
+ "content-length promised " + limit + " bytes, but received " + content.size());
+ }
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ checkOffsetAndCount(source.size(), 0, byteCount);
+ if (limit != -1 && content.size() > limit - byteCount) {
+ throw new ProtocolException("exceeded content-length limit of " + limit + " bytes");
+ }
+ content.write(source, byteCount);
+ }
+
+ @Override public void flush() throws IOException {
+ }
+
+ @Override public Timeout timeout() {
+ return Timeout.NONE;
+ }
+
+ public long contentLength() throws IOException {
+ return content.size();
+ }
+
+ public void writeToSocket(Sink socketOut) throws IOException {
+ // Copy the content; otherwise we won't have data to retry.
+ Buffer buffer = new Buffer();
+ content.copyTo(buffer, 0, content.size());
+ socketOut.write(buffer, buffer.size());
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java
new file mode 100644
index 0000000..72ed5de
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java
@@ -0,0 +1,63 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * An exception thrown to indicate a problem connecting via a single Route. Multiple attempts may
+ * have been made with alternative protocols, none of which were successful.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteException extends Exception {
+ private static final Method addSuppressedExceptionMethod;
+ static {
+ Method m;
+ try {
+ m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+ } catch (Exception e) {
+ m = null;
+ }
+ addSuppressedExceptionMethod = m;
+ }
+ private IOException lastException;
+
+ public RouteException(IOException cause) {
+ super(cause);
+ lastException = cause;
+ }
+
+ public IOException getLastConnectException() {
+ return lastException;
+ }
+
+ public void addConnectException(IOException e) {
+ addSuppressedIfPossible(e, lastException);
+ lastException = e;
+ }
+
+ private void addSuppressedIfPossible(IOException e, IOException suppressed) {
+ if (addSuppressedExceptionMethod != null) {
+ try {
+ addSuppressedExceptionMethod.invoke(e, suppressed);
+ } catch (InvocationTargetException | IllegalAccessException ignored) {
+ }
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java
new file mode 100644
index 0000000..75ecf64
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java
@@ -0,0 +1,228 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.RouteDatabase;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Selects routes to connect to an origin server. Each connection requires a
+ * choice of proxy server, IP address, and TLS mode. Connections may also be
+ * recycled.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteSelector {
+ private final Address address;
+ private final RouteDatabase routeDatabase;
+
+ /* The most recently attempted route. */
+ private Proxy lastProxy;
+ private InetSocketAddress lastInetSocketAddress;
+
+ /* State for negotiating the next proxy to use. */
+ private List<Proxy> proxies = Collections.emptyList();
+ private int nextProxyIndex;
+
+ /* State for negotiating the next socket address to use. */
+ private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
+ private int nextInetSocketAddressIndex;
+
+ /* State for negotiating failed routes */
+ private final List<Route> postponedRoutes = new ArrayList<>();
+
+ public RouteSelector(Address address, RouteDatabase routeDatabase) {
+ this.address = address;
+ this.routeDatabase = routeDatabase;
+
+ resetNextProxy(address.url(), address.getProxy());
+ }
+
+ /**
+ * Returns true if there's another route to attempt. Every address has at
+ * least one route.
+ */
+ public boolean hasNext() {
+ return hasNextInetSocketAddress()
+ || hasNextProxy()
+ || hasNextPostponed();
+ }
+
+ public Route next() throws IOException {
+ // Compute the next route to attempt.
+ if (!hasNextInetSocketAddress()) {
+ if (!hasNextProxy()) {
+ if (!hasNextPostponed()) {
+ throw new NoSuchElementException();
+ }
+ return nextPostponed();
+ }
+ lastProxy = nextProxy();
+ }
+ lastInetSocketAddress = nextInetSocketAddress();
+
+ Route route = new Route(address, lastProxy, lastInetSocketAddress);
+ if (routeDatabase.shouldPostpone(route)) {
+ postponedRoutes.add(route);
+ // We will only recurse in order to skip previously failed routes. They will be tried last.
+ return next();
+ }
+
+ return route;
+ }
+
+ /**
+ * Clients should invoke this method when they encounter a connectivity
+ * failure on a connection returned by this route selector.
+ */
+ public void connectFailed(Route failedRoute, IOException failure) {
+ if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && address.getProxySelector() != null) {
+ // Tell the proxy selector when we fail to connect on a fresh connection.
+ address.getProxySelector().connectFailed(
+ address.url().uri(), failedRoute.getProxy().address(), failure);
+ }
+
+ routeDatabase.failed(failedRoute);
+ }
+
+ /** Prepares the proxy servers to try. */
+ private void resetNextProxy(HttpUrl url, Proxy proxy) {
+ if (proxy != null) {
+ // If the user specifies a proxy, try that and only that.
+ proxies = Collections.singletonList(proxy);
+ } else {
+ // Try each of the ProxySelector choices until one connection succeeds. If none succeed
+ // then we'll try a direct connection below.
+ proxies = new ArrayList<>();
+ List<Proxy> selectedProxies = address.getProxySelector().select(url.uri());
+ if (selectedProxies != null) proxies.addAll(selectedProxies);
+ // Finally try a direct connection. We only try it once!
+ proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
+ proxies.add(Proxy.NO_PROXY);
+ }
+ nextProxyIndex = 0;
+ }
+
+ /** Returns true if there's another proxy to try. */
+ private boolean hasNextProxy() {
+ return nextProxyIndex < proxies.size();
+ }
+
+ /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
+ private Proxy nextProxy() throws IOException {
+ if (!hasNextProxy()) {
+ throw new SocketException("No route to " + address.getUriHost()
+ + "; exhausted proxy configurations: " + proxies);
+ }
+ Proxy result = proxies.get(nextProxyIndex++);
+ resetNextInetSocketAddress(result);
+ return result;
+ }
+
+ /** Prepares the socket addresses to attempt for the current proxy or host. */
+ private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
+ // Clear the addresses. Necessary if getAllByName() below throws!
+ inetSocketAddresses = new ArrayList<>();
+
+ String socketHost;
+ int socketPort;
+ if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
+ socketHost = address.getUriHost();
+ socketPort = address.getUriPort();
+ } else {
+ SocketAddress proxyAddress = proxy.address();
+ if (!(proxyAddress instanceof InetSocketAddress)) {
+ throw new IllegalArgumentException(
+ "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
+ }
+ InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
+ socketHost = getHostString(proxySocketAddress);
+ socketPort = proxySocketAddress.getPort();
+ }
+
+ if (socketPort < 1 || socketPort > 65535) {
+ throw new SocketException("No route to " + socketHost + ":" + socketPort
+ + "; port is out of range");
+ }
+
+ if (proxy.type() == Proxy.Type.SOCKS) {
+ inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
+ } else {
+ // Try each address for best behavior in mixed IPv4/IPv6 environments.
+ List<InetAddress> addresses = address.getDns().lookup(socketHost);
+ for (int i = 0, size = addresses.size(); i < size; i++) {
+ InetAddress inetAddress = addresses.get(i);
+ inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
+ }
+ }
+
+ nextInetSocketAddressIndex = 0;
+ }
+
+ /**
+ * Obtain a "host" from an {@link InetSocketAddress}. This returns a string containing either an
+ * actual host name or a numeric IP address.
+ */
+ // Visible for testing
+ static String getHostString(InetSocketAddress socketAddress) {
+ InetAddress address = socketAddress.getAddress();
+ if (address == null) {
+ // The InetSocketAddress was specified with a string (either a numeric IP or a host name). If
+ // it is a name, all IPs for that name should be tried. If it is an IP address, only that IP
+ // address should be tried.
+ return socketAddress.getHostName();
+ }
+ // The InetSocketAddress has a specific address: we should only try that address. Therefore we
+ // return the address and ignore any host name that may be available.
+ return address.getHostAddress();
+ }
+
+ /** Returns true if there's another socket address to try. */
+ private boolean hasNextInetSocketAddress() {
+ return nextInetSocketAddressIndex < inetSocketAddresses.size();
+ }
+
+ /** Returns the next socket address to try. */
+ private InetSocketAddress nextInetSocketAddress() throws IOException {
+ if (!hasNextInetSocketAddress()) {
+ throw new SocketException("No route to " + address.getUriHost()
+ + "; exhausted inet socket addresses: " + inetSocketAddresses);
+ }
+ return inetSocketAddresses.get(nextInetSocketAddressIndex++);
+ }
+
+ /** Returns true if there is another postponed route to try. */
+ private boolean hasNextPostponed() {
+ return !postponedRoutes.isEmpty();
+ }
+
+ /** Returns the next postponed route to try. */
+ private Route nextPostponed() {
+ return postponedRoutes.remove(0);
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java
new file mode 100644
index 0000000..45b52c3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java
@@ -0,0 +1,92 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Response;
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/** An HTTP response status line like "HTTP/1.1 200 OK".
+ * @hide This class is not part of the Android public SDK API*/
+public final class StatusLine {
+ /** Numeric status code, 307: Temporary Redirect. */
+ public static final int HTTP_TEMP_REDIRECT = 307;
+ public static final int HTTP_PERM_REDIRECT = 308;
+ public static final int HTTP_CONTINUE = 100;
+
+ public final Protocol protocol;
+ public final int code;
+ public final String message;
+
+ public StatusLine(Protocol protocol, int code, String message) {
+ this.protocol = protocol;
+ this.code = code;
+ this.message = message;
+ }
+
+ public static StatusLine get(Response response) {
+ return new StatusLine(response.protocol(), response.code(), response.message());
+ }
+
+ public static StatusLine parse(String statusLine) throws IOException {
+ // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
+
+ // Parse protocol like "HTTP/1.1" followed by a space.
+ int codeStart;
+ Protocol protocol;
+ if (statusLine.startsWith("HTTP/1.")) {
+ if (statusLine.length() < 9 || statusLine.charAt(8) != ' ') {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ int httpMinorVersion = statusLine.charAt(7) - '0';
+ codeStart = 9;
+ if (httpMinorVersion == 0) {
+ protocol = Protocol.HTTP_1_0;
+ } else if (httpMinorVersion == 1) {
+ protocol = Protocol.HTTP_1_1;
+ } else {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ } else if (statusLine.startsWith("ICY ")) {
+ // Shoutcast uses ICY instead of "HTTP/1.0".
+ protocol = Protocol.HTTP_1_0;
+ codeStart = 4;
+ } else {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+
+ // Parse response code like "200". Always 3 digits.
+ if (statusLine.length() < codeStart + 3) {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ int code;
+ try {
+ code = Integer.parseInt(statusLine.substring(codeStart, codeStart + 3));
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+
+ // Parse an optional response message like "OK" or "Not Modified". If it
+ // exists, it is separated from the response code by a space.
+ String message = "";
+ if (statusLine.length() > codeStart + 3) {
+ if (statusLine.charAt(codeStart + 3) != ' ') {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ message = statusLine.substring(codeStart + 4);
+ }
+
+ return new StatusLine(protocol, code, message);
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1");
+ result.append(' ').append(code);
+ if (message != null) {
+ result.append(' ').append(message);
+ }
+ return result.toString();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java
new file mode 100644
index 0000000..4199e90
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java
@@ -0,0 +1,412 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.io.RealConnection;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.ProtocolException;
+import java.net.SocketTimeoutException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import com.android.okhttp.okio.Sink;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * This class coordinates the relationship between three entities:
+ *
+ * <ul>
+ * <li><strong>Connections:</strong> physical socket connections to remote servers. These are
+ * potentially slow to establish so it is necessary to be able to cancel a connection
+ * currently being connected.
+ * <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
+ * connections. Each connection has its own allocation limit, which defines how many
+ * concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
+ * at a time, SPDY and HTTP/2 typically carry multiple.
+ * <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
+ * its follow up requests. We prefer to keep all streams of a single call on the same
+ * connection for better behavior and locality.
+ * </ul>
+ *
+ * <p>Instances of this class act on behalf of the call, using one or more streams over one or
+ * more connections. This class has APIs to release each of the above resources:
+ *
+ * <ul>
+ * <li>{@link #noNewStreams()} prevents the connection from being used for new streams in the
+ * future. Use this after a {@code Connection: close} header, or when the connection may be
+ * inconsistent.
+ * <li>{@link #streamFinished streamFinished()} releases the active stream from this allocation.
+ * Note that only one stream may be active at a given time, so it is necessary to call {@link
+ * #streamFinished streamFinished()} before creating a subsequent stream with {@link
+ * #newStream newStream()}.
+ * <li>{@link #release()} removes the call's hold on the connection. Note that this won't
+ * immediately free the connection if there is a stream still lingering. That happens when a
+ * call is complete but its response body has yet to be fully consumed.
+ * </ul>
+ *
+ * <p>This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have
+ * the smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that
+ * stream but not the other streams sharing its connection. But if the TLS handshake is still in
+ * progress then canceling may break the entire connection.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class StreamAllocation {
+ public final Address address;
+ private final ConnectionPool connectionPool;
+
+ // State guarded by connectionPool.
+ private RouteSelector routeSelector;
+ private RealConnection connection;
+ private boolean released;
+ private boolean canceled;
+ private HttpStream stream;
+
+ public StreamAllocation(ConnectionPool connectionPool, Address address) {
+ this.connectionPool = connectionPool;
+ this.address = address;
+ }
+
+ public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
+ boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
+ throws RouteException, IOException {
+ try {
+ RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
+ writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
+
+ HttpStream resultStream;
+ if (resultConnection.framedConnection != null) {
+ resultStream = new Http2xStream(this, resultConnection.framedConnection);
+ } else {
+ resultConnection.getSocket().setSoTimeout(readTimeout);
+ resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
+ resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
+ resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
+ }
+
+ synchronized (connectionPool) {
+ resultConnection.streamCount++;
+ stream = resultStream;
+ return resultStream;
+ }
+ } catch (IOException e) {
+ throw new RouteException(e);
+ }
+ }
+
+ /**
+ * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
+ * until a healthy connection is found.
+ */
+ private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
+ int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
+ throws IOException, RouteException {
+ while (true) {
+ RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
+ connectionRetryEnabled);
+
+ // If this is a brand new connection, we can skip the extensive health checks.
+ synchronized (connectionPool) {
+ if (candidate.streamCount == 0) {
+ return candidate;
+ }
+ }
+
+ // Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
+ if (candidate.isHealthy(doExtensiveHealthChecks)) {
+ return candidate;
+ }
+
+ connectionFailed();
+ }
+ }
+
+ /**
+ * Returns a connection to host a new stream. This prefers the existing connection if it exists,
+ * then the pool, finally building a new connection.
+ */
+ private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
+ boolean connectionRetryEnabled) throws IOException, RouteException {
+ synchronized (connectionPool) {
+ if (released) throw new IllegalStateException("released");
+ if (stream != null) throw new IllegalStateException("stream != null");
+ if (canceled) throw new IOException("Canceled");
+
+ RealConnection allocatedConnection = this.connection;
+ if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
+ return allocatedConnection;
+ }
+
+ // Attempt to get a connection from the pool.
+ RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
+ if (pooledConnection != null) {
+ this.connection = pooledConnection;
+ return pooledConnection;
+ }
+
+ // Attempt to create a connection.
+ if (routeSelector == null) {
+ routeSelector = new RouteSelector(address, routeDatabase());
+ }
+ }
+
+ Route route = routeSelector.next();
+ RealConnection newConnection = new RealConnection(route);
+ acquire(newConnection);
+
+ synchronized (connectionPool) {
+ Internal.instance.put(connectionPool, newConnection);
+ this.connection = newConnection;
+ if (canceled) throw new IOException("Canceled");
+ }
+
+ newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
+ connectionRetryEnabled);
+ routeDatabase().connected(newConnection.getRoute());
+
+ return newConnection;
+ }
+
+ public void streamFinished(HttpStream stream) {
+ synchronized (connectionPool) {
+ if (stream == null || stream != this.stream) {
+ throw new IllegalStateException("expected " + this.stream + " but was " + stream);
+ }
+ }
+ deallocate(false, false, true);
+ }
+
+ public HttpStream stream() {
+ synchronized (connectionPool) {
+ return stream;
+ }
+ }
+
+ private RouteDatabase routeDatabase() {
+ return Internal.instance.routeDatabase(connectionPool);
+ }
+
+ public synchronized RealConnection connection() {
+ return connection;
+ }
+
+ public void release() {
+ deallocate(false, true, false);
+ }
+
+ /** Forbid new streams from being created on the connection that hosts this allocation. */
+ public void noNewStreams() {
+ deallocate(true, false, false);
+ }
+
+ /**
+ * Releases resources held by this allocation. If sufficient resources are allocated, the
+ * connection will be detached or closed.
+ */
+ private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
+ RealConnection connectionToClose = null;
+ synchronized (connectionPool) {
+ if (streamFinished) {
+ this.stream = null;
+ }
+ if (released) {
+ this.released = true;
+ }
+ if (connection != null) {
+ if (noNewStreams) {
+ connection.noNewStreams = true;
+ }
+ if (this.stream == null && (this.released || connection.noNewStreams)) {
+ release(connection);
+ if (connection.streamCount > 0) {
+ routeSelector = null;
+ }
+ if (connection.allocations.isEmpty()) {
+ connection.idleAtNanos = System.nanoTime();
+ if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
+ connectionToClose = connection;
+ }
+ }
+ connection = null;
+ }
+ }
+ }
+ if (connectionToClose != null) {
+ Util.closeQuietly(connectionToClose.getSocket());
+ }
+ }
+
+ public void cancel() {
+ HttpStream streamToCancel;
+ RealConnection connectionToCancel;
+ synchronized (connectionPool) {
+ canceled = true;
+ streamToCancel = stream;
+ connectionToCancel = connection;
+ }
+ if (streamToCancel != null) {
+ streamToCancel.cancel();
+ } else if (connectionToCancel != null) {
+ connectionToCancel.cancel();
+ }
+ }
+
+ private void connectionFailed(IOException e) {
+ synchronized (connectionPool) {
+ if (routeSelector != null) {
+ if (connection.streamCount == 0) {
+ // Record the failure on a fresh route.
+ Route failedRoute = connection.getRoute();
+ routeSelector.connectFailed(failedRoute, e);
+ } else {
+ // We saw a failure on a recycled connection, reset this allocation with a fresh route.
+ routeSelector = null;
+ }
+ }
+ }
+ connectionFailed();
+ }
+
+ /** Finish the current stream and prevent new streams from being created. */
+ public void connectionFailed() {
+ deallocate(true, false, true);
+ }
+
+ /**
+ * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
+ * {@link #release} on the same connection.
+ */
+ public void acquire(RealConnection connection) {
+ connection.allocations.add(new WeakReference<>(this));
+ }
+
+ /** Remove this allocation from the connection's list of allocations. */
+ private void release(RealConnection connection) {
+ for (int i = 0, size = connection.allocations.size(); i < size; i++) {
+ Reference<StreamAllocation> reference = connection.allocations.get(i);
+ if (reference.get() == this) {
+ connection.allocations.remove(i);
+ return;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ public boolean recover(RouteException e) {
+ // Android-changed: Canceled StreamAllocations can never recover http://b/33763156
+ if (canceled) {
+ return false;
+ }
+ if (connection != null) {
+ connectionFailed(e.getLastConnectException());
+ }
+
+ if ((routeSelector != null && !routeSelector.hasNext()) // No more routes to attempt.
+ || !isRecoverable(e)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean recover(IOException e, Sink requestBodyOut) {
+ if (connection != null) {
+ int streamCount = connection.streamCount;
+ connectionFailed(e);
+
+ if (streamCount == 1) {
+ // This isn't a recycled connection.
+ // TODO(jwilson): find a better way for this.
+ return false;
+ }
+ }
+
+ boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
+ if ((routeSelector != null && !routeSelector.hasNext()) // No more routes to attempt.
+ || !isRecoverable(e)
+ || !canRetryRequestBody) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isRecoverable(IOException e) {
+ // If there was a protocol problem, don't recover.
+ if (e instanceof ProtocolException) {
+ return false;
+ }
+
+ // If there was an interruption or timeout, don't recover.
+ if (e instanceof InterruptedIOException) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isRecoverable(RouteException e) {
+ // Problems with a route may mean the connection can be retried with a new route, or may
+ // indicate a client-side or server-side issue that should not be retried. To tell, we must look
+ // at the cause.
+
+ IOException ioe = e.getLastConnectException();
+
+ // If there was a protocol problem, don't recover.
+ if (ioe instanceof ProtocolException) {
+ return false;
+ }
+
+ // If there was an interruption don't recover, but if there was a timeout
+ // we should try the next route (if there is one).
+ if (ioe instanceof InterruptedIOException) {
+ return ioe instanceof SocketTimeoutException;
+ }
+
+ // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
+ // again with a different route.
+ if (ioe instanceof SSLHandshakeException) {
+ // If the problem was a CertificateException from the X509TrustManager,
+ // do not retry.
+ if (ioe.getCause() instanceof CertificateException) {
+ return false;
+ }
+ }
+ if (ioe instanceof SSLPeerUnverifiedException) {
+ // e.g. a certificate pinning error.
+ return false;
+ }
+
+ // An example of one we might want to retry with a different route is a problem connecting to a
+ // proxy and would manifest as a standard IOException. Unless it is one we know we should not
+ // retry, we return true and try a new route.
+ return true;
+ }
+
+ @Override public String toString() {
+ return address.toString();
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java
new file mode 100644
index 0000000..3bdcbc9
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Access to read and write files on a hierarchical data store. Most callers should use the {@link
+ * #SYSTEM} implementation, which uses the host machine's local file system. Alternate
+ * implementations may be used to inject faults (for testing) or to transform stored data (to add
+ * encryption, for example).
+ *
+ * <p>All operations on a file system are racy. For example, guarding a call to {@link #source}
+ * with {@link #exists} does not guarantee that {@link FileNotFoundException} will not be thrown.
+ * The file may be moved between the two calls!
+ *
+ * <p>This interface is less ambitious than {@link java.nio.file.FileSystem} introduced in Java 7.
+ * It lacks important features like file watching, metadata, permissions, and disk space
+ * information. In exchange for these limitations, this interface is easier to implement and works
+ * on all versions of Java and Android.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface FileSystem {
+ /** The host machine's local file system. */
+ FileSystem SYSTEM = new FileSystem() {
+ @Override public Source source(File file) throws FileNotFoundException {
+ return Okio.source(file);
+ }
+
+ @Override public Sink sink(File file) throws FileNotFoundException {
+ try {
+ return Okio.sink(file);
+ } catch (FileNotFoundException e) {
+ // Maybe the parent directory doesn't exist? Try creating it first.
+ file.getParentFile().mkdirs();
+ return Okio.sink(file);
+ }
+ }
+
+ @Override public Sink appendingSink(File file) throws FileNotFoundException {
+ try {
+ return Okio.appendingSink(file);
+ } catch (FileNotFoundException e) {
+ // Maybe the parent directory doesn't exist? Try creating it first.
+ file.getParentFile().mkdirs();
+ return Okio.appendingSink(file);
+ }
+ }
+
+ @Override public void delete(File file) throws IOException {
+ // If delete() fails, make sure it's because the file didn't exist!
+ if (!file.delete() && file.exists()) {
+ throw new IOException("failed to delete " + file);
+ }
+ }
+
+ @Override public boolean exists(File file) throws IOException {
+ return file.exists();
+ }
+
+ @Override public long size(File file) {
+ return file.length();
+ }
+
+ @Override public void rename(File from, File to) throws IOException {
+ delete(to);
+ if (!from.renameTo(to)) {
+ throw new IOException("failed to rename " + from + " to " + to);
+ }
+ }
+
+ @Override public void deleteContents(File directory) throws IOException {
+ File[] files = directory.listFiles();
+ if (files == null) {
+ throw new IOException("not a readable directory: " + directory);
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ if (!file.delete()) {
+ throw new IOException("failed to delete " + file);
+ }
+ }
+ }
+ };
+
+ /** Reads from {@code file}. */
+ Source source(File file) throws FileNotFoundException;
+
+ /**
+ * Writes to {@code file}, discarding any data already present. Creates parent directories if
+ * necessary.
+ */
+ Sink sink(File file) throws FileNotFoundException;
+
+ /**
+ * Writes to {@code file}, appending if data is already present. Creates parent directories if
+ * necessary.
+ */
+ Sink appendingSink(File file) throws FileNotFoundException;
+
+ /** Deletes {@code file} if it exists. Throws if the file exists and cannot be deleted. */
+ void delete(File file) throws IOException;
+
+ /** Returns true if {@code file} exists on the file system. */
+ boolean exists(File file) throws IOException;
+
+ /** Returns the number of bytes stored in {@code file}, or 0 if it does not exist. */
+ long size(File file);
+
+ /** Renames {@code from} to {@code to}. Throws if the file cannot be renamed. */
+ void rename(File from, File to) throws IOException;
+
+ /**
+ * Recursively delete the contents of {@code directory}. Throws an IOException if any file could
+ * not be deleted, or if {@code dir} is not a readable directory.
+ */
+ void deleteContents(File directory) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java
new file mode 100644
index 0000000..719890e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java
@@ -0,0 +1,411 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.io;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.CertificatePinner;
+import com.android.okhttp.Connection;
+import com.android.okhttp.ConnectionSpec;
+import com.android.okhttp.Handshake;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.ConnectionSpecSelector;
+import com.android.okhttp.internal.Platform;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import com.android.okhttp.internal.framed.FramedConnection;
+import com.android.okhttp.internal.http.Http1xStream;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.tls.CertificateChainCleaner;
+import com.android.okhttp.internal.tls.OkHostnameVerifier;
+import com.android.okhttp.internal.tls.TrustRootIndex;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.net.ConnectException;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.UnknownServiceException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+import static com.android.okhttp.internal.Util.closeQuietly;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RealConnection implements Connection {
+ private final Route route;
+
+ /** The low-level TCP socket. */
+ private Socket rawSocket;
+
+ /**
+ * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
+ * {@link #rawSocket} itself if this connection does not use SSL.
+ */
+ public Socket socket;
+ private Handshake handshake;
+ private Protocol protocol;
+ public volatile FramedConnection framedConnection;
+ public int streamCount;
+ public BufferedSource source;
+ public BufferedSink sink;
+ public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
+ public boolean noNewStreams;
+ public long idleAtNanos = Long.MAX_VALUE;
+
+ public RealConnection(Route route) {
+ this.route = route;
+ }
+
+ public void connect(int connectTimeout, int readTimeout, int writeTimeout,
+ List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
+ if (protocol != null) throw new IllegalStateException("already connected");
+
+ RouteException routeException = null;
+ ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
+ Proxy proxy = route.getProxy();
+ Address address = route.getAddress();
+
+ if (route.getAddress().getSslSocketFactory() == null
+ && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
+ throw new RouteException(new UnknownServiceException(
+ "CLEARTEXT communication not supported: " + connectionSpecs));
+ }
+
+ while (protocol == null) {
+ try {
+ rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
+ ? address.getSocketFactory().createSocket()
+ : new Socket(proxy);
+ connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
+ } catch (IOException e) {
+ Util.closeQuietly(socket);
+ Util.closeQuietly(rawSocket);
+ socket = null;
+ rawSocket = null;
+ source = null;
+ sink = null;
+ handshake = null;
+ protocol = null;
+
+ if (routeException == null) {
+ routeException = new RouteException(e);
+ } else {
+ routeException.addConnectException(e);
+ }
+
+ if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
+ throw routeException;
+ }
+ }
+ }
+ }
+
+ /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
+ private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
+ ConnectionSpecSelector connectionSpecSelector) throws IOException {
+ rawSocket.setSoTimeout(readTimeout);
+ try {
+ Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
+ } catch (ConnectException e) {
+ throw new ConnectException("Failed to connect to " + route.getSocketAddress());
+ }
+ source = Okio.buffer(Okio.source(rawSocket));
+ sink = Okio.buffer(Okio.sink(rawSocket));
+
+ if (route.getAddress().getSslSocketFactory() != null) {
+ connectTls(readTimeout, writeTimeout, connectionSpecSelector);
+ } else {
+ protocol = Protocol.HTTP_1_1;
+ socket = rawSocket;
+ }
+
+ if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
+ socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
+
+ FramedConnection framedConnection = new FramedConnection.Builder(true)
+ .socket(socket, route.getAddress().url().host(), source, sink)
+ .protocol(protocol)
+ .build();
+ framedConnection.sendConnectionPreface();
+
+ // Only assign the framed connection once the preface has been sent successfully.
+ this.framedConnection = framedConnection;
+ }
+ }
+
+ private void connectTls(int readTimeout, int writeTimeout,
+ ConnectionSpecSelector connectionSpecSelector) throws IOException {
+ if (route.requiresTunnel()) {
+ createTunnel(readTimeout, writeTimeout);
+ }
+
+ Address address = route.getAddress();
+ SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
+ boolean success = false;
+ SSLSocket sslSocket = null;
+ try {
+ // Create the wrapper over the connected socket.
+ sslSocket = (SSLSocket) sslSocketFactory.createSocket(
+ rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);
+
+ // Configure the socket's ciphers, TLS versions, and extensions.
+ ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
+ if (connectionSpec.supportsTlsExtensions()) {
+ Platform.get().configureTlsExtensions(
+ sslSocket, address.getUriHost(), address.getProtocols());
+ }
+
+ // Force handshake. This can throw!
+ sslSocket.startHandshake();
+ Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
+
+ // Verify that the socket's certificates are acceptable for the target host.
+ if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
+ X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
+ throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:"
+ + "\n certificate: " + CertificatePinner.pin(cert)
+ + "\n DN: " + cert.getSubjectDN().getName()
+ + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
+ }
+
+ // Check that the certificate pinner is satisfied by the certificates presented.
+ if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
+ TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
+ List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
+ .clean(unverifiedHandshake.peerCertificates());
+ address.getCertificatePinner().check(address.getUriHost(), certificates);
+ }
+
+ // Success! Save the handshake and the ALPN protocol.
+ String maybeProtocol = connectionSpec.supportsTlsExtensions()
+ ? Platform.get().getSelectedProtocol(sslSocket)
+ : null;
+ socket = sslSocket;
+ source = Okio.buffer(Okio.source(socket));
+ sink = Okio.buffer(Okio.sink(socket));
+ handshake = unverifiedHandshake;
+ protocol = maybeProtocol != null
+ ? Protocol.get(maybeProtocol)
+ : Protocol.HTTP_1_1;
+ success = true;
+ } catch (AssertionError e) {
+ if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
+ throw e;
+ } finally {
+ if (sslSocket != null) {
+ Platform.get().afterHandshake(sslSocket);
+ }
+ if (!success) {
+ closeQuietly(sslSocket);
+ }
+ }
+ }
+
+ private static SSLSocketFactory lastSslSocketFactory;
+ private static TrustRootIndex lastTrustRootIndex;
+
+ /**
+ * Returns a trust root index for {@code sslSocketFactory}. This uses a static, single-element
+ * cache to avoid redoing reflection and SSL indexing in the common case where most SSL
+ * connections use the same SSL socket factory.
+ */
+ private static synchronized TrustRootIndex trustRootIndex(SSLSocketFactory sslSocketFactory) {
+ if (sslSocketFactory != lastSslSocketFactory) {
+ X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
+ lastTrustRootIndex = Platform.get().trustRootIndex(trustManager);
+ lastSslSocketFactory = sslSocketFactory;
+ }
+ return lastTrustRootIndex;
+ }
+
+ /**
+ * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+ * CONNECT request to create the proxy connection. This may need to be
+ * retried if the proxy requires authorization.
+ */
+ private void createTunnel(int readTimeout, int writeTimeout) throws IOException {
+ // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
+ Request tunnelRequest = createTunnelRequest();
+ HttpUrl url = tunnelRequest.httpUrl();
+ String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
+ while (true) {
+ Http1xStream tunnelConnection = new Http1xStream(null, source, sink);
+ source.timeout().timeout(readTimeout, MILLISECONDS);
+ sink.timeout().timeout(writeTimeout, MILLISECONDS);
+ tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
+ tunnelConnection.finishRequest();
+ Response response = tunnelConnection.readResponse().request(tunnelRequest).build();
+ // The response body from a CONNECT should be empty, but if it is not then we should consume
+ // it before proceeding.
+ long contentLength = OkHeaders.contentLength(response);
+ if (contentLength == -1L) {
+ contentLength = 0L;
+ }
+ Source body = tunnelConnection.newFixedLengthSource(contentLength);
+ Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
+ body.close();
+
+ switch (response.code()) {
+ case HTTP_OK:
+ // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
+ // that happens, then we will have buffered bytes that are needed by the SSLSocket!
+ // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
+ // that it will almost certainly fail because the proxy has sent unexpected data.
+ if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
+ throw new IOException("TLS tunnel buffered too many bytes!");
+ }
+ return;
+
+ case HTTP_PROXY_AUTH:
+ tunnelRequest = OkHeaders.processAuthHeader(
+ route.getAddress().getAuthenticator(), response, route.getProxy());
+ if (tunnelRequest != null) continue;
+ throw new IOException("Failed to authenticate with proxy");
+
+ default:
+ throw new IOException(
+ "Unexpected response code for CONNECT: " + response.code());
+ }
+ }
+ }
+
+ /**
+ * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if
+ * no tunnel is necessary. Everything in the tunnel request is sent
+ * unencrypted to the proxy server, so tunnels include only the minimum set of
+ * headers. This avoids sending potentially sensitive data like HTTP cookies
+ * to the proxy unencrypted.
+ */
+ private Request createTunnelRequest() throws IOException {
+ return new Request.Builder()
+ .url(route.getAddress().url())
+ .header("Host", Util.hostHeader(route.getAddress().url(), true))
+ .header("Proxy-Connection", "Keep-Alive")
+ .header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid.
+ .build();
+ }
+
+ /** Returns true if {@link #connect} has been attempted on this connection. */
+ boolean isConnected() {
+ return protocol != null;
+ }
+
+ @Override public Route getRoute() {
+ return route;
+ }
+
+ public void cancel() {
+ // Close the raw socket so we don't end up doing synchronous I/O.
+ Util.closeQuietly(rawSocket);
+ }
+
+ @Override public Socket getSocket() {
+ 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()) {
+ return false;
+ }
+
+ if (framedConnection != null) {
+ return true; // TODO: check framedConnection.shutdown.
+ }
+
+ if (doExtensiveChecks) {
+ try {
+ int readTimeout = socket.getSoTimeout();
+ try {
+ socket.setSoTimeout(1);
+ if (source.exhausted()) {
+ return false; // Stream is exhausted; socket is closed.
+ }
+ return true;
+ } finally {
+ socket.setSoTimeout(readTimeout);
+ }
+ } catch (SocketTimeoutException ignored) {
+ // Read timed out; socket is good.
+ } catch (IOException e) {
+ return false; // Couldn't read; socket is closed.
+ }
+ }
+
+ return true;
+ }
+
+ @Override public Handshake getHandshake() {
+ return handshake;
+ }
+
+ /**
+ * Returns true if this is a SPDY connection. Such connections can be used
+ * in multiple HTTP requests simultaneously.
+ */
+ public boolean isMultiplexed() {
+ return framedConnection != null;
+ }
+
+ @Override public Protocol getProtocol() {
+ return protocol != null ? protocol : Protocol.HTTP_1_1;
+ }
+
+ @Override public String toString() {
+ return "Connection{"
+ + route.getAddress().url().host() + ":" + route.getAddress().url().port()
+ + ", proxy="
+ + route.getProxy()
+ + " hostAddress="
+ + route.getSocketAddress()
+ + " cipherSuite="
+ + (handshake != null ? handshake.cipherSuite() : "none")
+ + " protocol="
+ + protocol
+ + '}';
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java
new file mode 100644
index 0000000..207985e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java
@@ -0,0 +1,67 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.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.
+ * @hide This class is not part of the Android public SDK API
+ */
+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 != null
+ ? trustAnchor.getTrustedCert()
+ : null;
+ } 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;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java
new file mode 100644
index 0000000..6bcdc25
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.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}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CertificateChainCleaner {
+ /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */
+ private static final int MAX_SIGNERS = 9;
+
+ 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());
+ boolean foundTrustedCertificate = false;
+
+ followIssuerChain:
+ for (int c = 0; c < MAX_SIGNERS; c++) {
+ X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1);
+
+ // If this cert has been signed by a trusted cert, use that. Add the trusted 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 trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify);
+ if (trustedCert != null) {
+ if (result.size() > 1 || !toVerify.equals(trustedCert)) {
+ result.add(trustedCert);
+ }
+ if (verifySignature(trustedCert, trustedCert)) {
+ return result; // The self-signed cert is a root CA. We're done.
+ }
+ foundTrustedCertificate = true;
+ continue;
+ }
+
+ // 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 (verifySignature(toVerify, signingCert)) {
+ i.remove();
+ result.add(signingCert);
+ continue followIssuerChain;
+ }
+ }
+
+ // We've reached the end of the chain. If any cert in the chain is trusted, we're done.
+ if (foundTrustedCertificate) {
+ return result;
+ }
+
+ // The last link isn't trusted. Fail.
+ throw new SSLPeerUnverifiedException("Failed to find a trusted cert that signed " + toVerify);
+ }
+
+ throw new SSLPeerUnverifiedException("Certificate chain too long: " + result);
+ }
+
+ /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */
+ private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) {
+ if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false;
+ try {
+ toVerify.verify(signingCert.getPublicKey());
+ return true;
+ } catch (GeneralSecurityException verifyFailed) {
+ return false;
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java
new file mode 100644
index 0000000..15f0a6a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java
@@ -0,0 +1,408 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.tls;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A distinguished name (DN) parser. This parser only supports extracting a
+ * string value from a DN. It doesn't support values in the hex-string style.
+ */
+final class DistinguishedNameParser {
+ private final String dn;
+ private final int length;
+ private int pos;
+ private int beg;
+ private int end;
+
+ /** Temporary variable to store positions of the currently parsed item. */
+ private int cur;
+
+ /** Distinguished name characters. */
+ private char[] chars;
+
+ public DistinguishedNameParser(X500Principal principal) {
+ // RFC2253 is used to ensure we get attributes in the reverse
+ // order of the underlying ASN.1 encoding, so that the most
+ // significant values of repeated attributes occur first.
+ this.dn = principal.getName(X500Principal.RFC2253);
+ this.length = this.dn.length();
+ }
+
+ // gets next attribute type: (ALPHA 1*keychar) / oid
+ private String nextAT() {
+ // skip preceding space chars, they can present after
+ // comma or semicolon (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ if (pos == length) {
+ return null; // reached the end of DN
+ }
+
+ // mark the beginning of attribute type
+ beg = pos;
+
+ // attribute type chars
+ pos++;
+ for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+ // we don't follow exact BNF syntax here:
+ // accept any char except space and '='
+ }
+ if (pos >= length) {
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+
+ // mark the end of attribute type
+ end = pos;
+
+ // skip trailing space chars between attribute type and '='
+ // (compatibility with RFC 1779)
+ if (chars[pos] == ' ') {
+ for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+ }
+
+ if (chars[pos] != '=' || pos == length) {
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+ }
+
+ pos++; //skip '=' char
+
+ // skip space chars between '=' and attribute value
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ // in case of oid attribute type skip its prefix: "oid." or "OID."
+ // (compatibility with RFC 1779)
+ if ((end - beg > 4) && (chars[beg + 3] == '.')
+ && (chars[beg] == 'O' || chars[beg] == 'o')
+ && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+ && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+ beg += 4;
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+ private String quotedAV() {
+ pos++;
+ beg = pos;
+ end = beg;
+ while (true) {
+
+ if (pos == length) {
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+
+ if (chars[pos] == '"') {
+ // enclosing quotation was found
+ pos++;
+ break;
+ } else if (chars[pos] == '\\') {
+ chars[end] = getEscaped();
+ } else {
+ // shift char: required for string with escaped chars
+ chars[end] = chars[pos];
+ }
+ pos++;
+ end++;
+ }
+
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets hex string attribute value: "#" hexstring
+ private String hexAV() {
+ if (pos + 4 >= length) {
+ // encoded byte array must be not less then 4 c
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+
+ beg = pos; // store '#' position
+ pos++;
+ while (true) {
+
+ // check for end of attribute value
+ // looks for space and component separators
+ if (pos == length || chars[pos] == '+' || chars[pos] == ','
+ || chars[pos] == ';') {
+ end = pos;
+ break;
+ }
+
+ if (chars[pos] == ' ') {
+ end = pos;
+ pos++;
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ break;
+ } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+ chars[pos] += 32; //to low case
+ }
+
+ pos++;
+ }
+
+ // verify length of hex string
+ // encoded byte array must be not less then 4 and must be even number
+ int hexLen = end - beg; // skip first '#' char
+ if (hexLen < 5 || (hexLen & 1) == 0) {
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+
+ // get byte encoding from string representation
+ byte[] encoded = new byte[hexLen / 2];
+ for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+ encoded[i] = (byte) getByte(p);
+ }
+
+ return new String(chars, beg, hexLen);
+ }
+
+ // gets string attribute value: *( stringchar / pair )
+ private String escapedAV() {
+ beg = pos;
+ end = pos;
+ while (true) {
+ if (pos >= length) {
+ // the end of DN has been found
+ return new String(chars, beg, end - beg);
+ }
+
+ switch (chars[pos]) {
+ case '+':
+ case ',':
+ case ';':
+ // separator char has been found
+ return new String(chars, beg, end - beg);
+ case '\\':
+ // escaped char
+ chars[end++] = getEscaped();
+ pos++;
+ break;
+ case ' ':
+ // need to figure out whether space defines
+ // the end of attribute value or not
+ cur = end;
+
+ pos++;
+ chars[end++] = ' ';
+
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ chars[end++] = ' ';
+ }
+ if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+ || chars[pos] == ';') {
+ // separator char or the end of DN has been found
+ return new String(chars, beg, cur - beg);
+ }
+ break;
+ default:
+ chars[end++] = chars[pos];
+ pos++;
+ }
+ }
+ }
+
+ // returns escaped char
+ private char getEscaped() {
+ pos++;
+ if (pos == length) {
+ throw new IllegalStateException("Unexpected end of DN: " + dn);
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ case '\\':
+ case ',':
+ case '=':
+ case '+':
+ case '<':
+ case '>':
+ case '#':
+ case ';':
+ case ' ':
+ case '*':
+ case '%':
+ case '_':
+ //FIXME: escaping is allowed only for leading or trailing space char
+ return chars[pos];
+ default:
+ // RFC doesn't explicitly say that escaped hex pair is
+ // interpreted as UTF-8 char. It only contains an example of such DN.
+ return getUTF8();
+ }
+ }
+
+ // decodes UTF-8 char
+ // see http://www.unicode.org for UTF-8 bit distribution table
+ private char getUTF8() {
+ int res = getByte(pos);
+ pos++; //FIXME tmp
+
+ if (res < 128) { // one byte: 0-7F
+ return (char) res;
+ } else if (res >= 192 && res <= 247) {
+
+ int count;
+ if (res <= 223) { // two bytes: C0-DF
+ count = 1;
+ res = res & 0x1F;
+ } else if (res <= 239) { // three bytes: E0-EF
+ count = 2;
+ res = res & 0x0F;
+ } else { // four bytes: F0-F7
+ count = 3;
+ res = res & 0x07;
+ }
+
+ int b;
+ for (int i = 0; i < count; i++) {
+ pos++;
+ if (pos == length || chars[pos] != '\\') {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ pos++;
+
+ b = getByte(pos);
+ pos++; //FIXME tmp
+ if ((b & 0xC0) != 0x80) {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+
+ res = (res << 6) + (b & 0x3F);
+ }
+ return (char) res;
+ } else {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ }
+
+ // Returns byte representation of a char pair
+ // The char pair is composed of DN char in
+ // specified 'position' and the next char
+ // According to BNF syntax:
+ // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+ // / "a" / "b" / "c" / "d" / "e" / "f"
+ private int getByte(int position) {
+ if (position + 1 >= length) {
+ throw new IllegalStateException("Malformed DN: " + dn);
+ }
+
+ int b1, b2;
+
+ b1 = chars[position];
+ if (b1 >= '0' && b1 <= '9') {
+ b1 = b1 - '0';
+ } else if (b1 >= 'a' && b1 <= 'f') {
+ b1 = b1 - 87; // 87 = 'a' - 10
+ } else if (b1 >= 'A' && b1 <= 'F') {
+ b1 = b1 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IllegalStateException("Malformed DN: " + dn);
+ }
+
+ b2 = chars[position + 1];
+ if (b2 >= '0' && b2 <= '9') {
+ b2 = b2 - '0';
+ } else if (b2 >= 'a' && b2 <= 'f') {
+ b2 = b2 - 87; // 87 = 'a' - 10
+ } else if (b2 >= 'A' && b2 <= 'F') {
+ b2 = b2 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IllegalStateException("Malformed DN: " + dn);
+ }
+
+ return (b1 << 4) + b2;
+ }
+
+ /**
+ * Parses the DN and returns the most significant attribute value
+ * for an attribute type, or null if none found.
+ *
+ * @param attributeType attribute type to look for (e.g. "ca")
+ */
+ public String findMostSpecific(String attributeType) {
+ // Initialize internal state.
+ pos = 0;
+ beg = 0;
+ end = 0;
+ cur = 0;
+ chars = dn.toCharArray();
+
+ String attType = nextAT();
+ if (attType == null) {
+ return null;
+ }
+ while (true) {
+ String attValue = "";
+
+ if (pos == length) {
+ return null;
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ attValue = quotedAV();
+ break;
+ case '#':
+ attValue = hexAV();
+ break;
+ case '+':
+ case ',':
+ case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+ //empty attribute value
+ break;
+ default:
+ attValue = escapedAV();
+ }
+
+ // Values are ordered from most specific to least specific
+ // due to the RFC2253 formatting. So take the first match
+ // we see.
+ if (attributeType.equalsIgnoreCase(attType)) {
+ return attValue;
+ }
+
+ if (pos >= length) {
+ return null;
+ }
+
+ if (chars[pos] == ',' || chars[pos] == ';') {
+ } else if (chars[pos] != '+') {
+ throw new IllegalStateException("Malformed DN: " + dn);
+ }
+
+ pos++;
+ attType = nextAT();
+ if (attType == null) {
+ throw new IllegalStateException("Malformed DN: " + dn);
+ }
+ }
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java
new file mode 100644
index 0000000..3cdd1bf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java
@@ -0,0 +1,257 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.tls;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A HostnameVerifier consistent with <a
+ * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class OkHostnameVerifier implements HostnameVerifier {
+ public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier();
+
+ /**
+ * Quick and dirty pattern to differentiate IP addresses from hostnames. This
+ * is an approximation of Android's private InetAddress#isNumeric API.
+ *
+ * <p>This matches IPv6 addresses as a hex string containing at least one
+ * colon, and possibly including dots after the first colon. It matches IPv4
+ * addresses as strings containing only decimal digits and dots. This pattern
+ * matches strings like "a:.23" and "54" that are neither IP addresses nor
+ * hostnames; they will be verified as IP addresses (which is a more strict
+ * verification).
+ */
+ private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
+ "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
+
+ private static final int ALT_DNS_NAME = 2;
+ private static final int ALT_IPA_NAME = 7;
+
+ private OkHostnameVerifier() {
+ }
+
+ @Override
+ public boolean verify(String host, SSLSession session) {
+ try {
+ Certificate[] certificates = session.getPeerCertificates();
+ return verify(host, (X509Certificate) certificates[0]);
+ } catch (SSLException e) {
+ return false;
+ }
+ }
+
+ public boolean verify(String host, X509Certificate certificate) {
+ return verifyAsIpAddress(host)
+ ? verifyIpAddress(host, certificate)
+ : verifyHostName(host, certificate);
+ }
+
+ static boolean verifyAsIpAddress(String host) {
+ return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
+ }
+
+ /**
+ * Returns true if {@code certificate} matches {@code ipAddress}.
+ */
+ private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
+ List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+ for (int i = 0, size = altNames.size(); i < size; i++) {
+ if (ipAddress.equalsIgnoreCase(altNames.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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))) {
+ return true;
+ }
+ }
+
+ // BEGIN Android-removed: Ignore common name in hostname verification. http://b/70278814
+ /*
+ if (!hasDns) {
+ X500Principal principal = certificate.getSubjectX500Principal();
+ // RFC 2818 advises using the most specific name for matching.
+ String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
+ if (cn != null) {
+ return verifyHostName(hostName, cn);
+ }
+ }
+ */
+ // END Android-removed: Ignore common name in hostname verification. http://b/70278814
+
+ return false;
+ }
+
+ public static List<String> allSubjectAltNames(X509Certificate certificate) {
+ List<String> altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+ List<String> altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+ List<String> result = new ArrayList<>(altIpaNames.size() + altDnsNames.size());
+ result.addAll(altIpaNames);
+ result.addAll(altDnsNames);
+ return result;
+ }
+
+ private static List<String> getSubjectAltNames(X509Certificate certificate, int type) {
+ List<String> result = new ArrayList<>();
+ try {
+ Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
+ if (subjectAltNames == null) {
+ return Collections.emptyList();
+ }
+ for (Object subjectAltName : subjectAltNames) {
+ List<?> entry = (List<?>) subjectAltName;
+ if (entry == null || entry.size() < 2) {
+ continue;
+ }
+ Integer altNameType = (Integer) entry.get(0);
+ if (altNameType == null) {
+ continue;
+ }
+ if (altNameType == type) {
+ String altName = (String) entry.get(1);
+ if (altName != null) {
+ result.add(altName);
+ }
+ }
+ }
+ return result;
+ } catch (CertificateParsingException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
+ *
+ * @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) {
+ // Basic sanity checks
+ // Check length == 0 instead of .isEmpty() to support Java 5.
+ if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith("."))
+ || (hostName.endsWith(".."))) {
+ // Invalid domain name
+ return false;
+ }
+ if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith("."))
+ || (pattern.endsWith(".."))) {
+ // Invalid pattern/domain name
+ return false;
+ }
+
+ // 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
+ // 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 (!pattern.endsWith(".")) {
+ pattern += '.';
+ }
+ // 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.
+
+ if (!pattern.contains("*")) {
+ // Not a wildcard pattern -- hostName and pattern must match exactly.
+ return hostName.equals(pattern);
+ }
+ // Wildcard pattern
+
+ // WILDCARD PATTERN RULES:
+ // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+ // only character in that label (i.e., must match the whole left-most label).
+ // For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+ // a*b.example.com, a.*.example.com are not permitted.
+ // 2. Asterisk (*) cannot match across domain name labels.
+ // For example, *.example.com matches test.example.com but does not match
+ // sub.test.example.com.
+ // 3. Wildcard patterns for single-label domain names are not permitted.
+
+ if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) {
+ // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+ // character in that label
+ return false;
+ }
+
+ // 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.
+ return false;
+ }
+
+ if ("*.".equals(pattern)) {
+ // Wildcard pattern for single-label domain name -- not permitted.
+ return false;
+ }
+
+ // 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
+ 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)) {
+ // Asterisk is matching across domain name labels -- not permitted.
+ return false;
+ }
+
+ // hostName matches pattern
+ return true;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java
new file mode 100644
index 0000000..f4696a5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java
@@ -0,0 +1,62 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.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;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+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;
+ }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java
new file mode 100644
index 0000000..86111a8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java
@@ -0,0 +1,27 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.internal.tls;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface TrustRootIndex {
+ /** Returns the trusted CA certificate that signed {@code cert}. */
+ X509Certificate findByIssuerAndSignature(X509Certificate cert);
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java
new file mode 100644
index 0000000..e801ae7
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java
@@ -0,0 +1,334 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+/**
+ * This timeout uses a background thread to take action exactly when the timeout
+ * occurs. Use this to implement timeouts where they aren't supported natively,
+ * such as to sockets that are blocked on writing.
+ *
+ * <p>Subclasses should override {@link #timedOut} to take action when a timeout
+ * occurs. This method will be invoked by the shared watchdog thread so it
+ * should not do any long-running operations. Otherwise we risk starving other
+ * timeouts from being triggered.
+ *
+ * <p>Use {@link #sink} and {@link #source} to apply this timeout to a stream.
+ * The returned value will apply the timeout to each operation on the wrapped
+ * stream.
+ *
+ * <p>Callers should call {@link #enter} before doing work that is subject to
+ * timeouts, and {@link #exit} afterwards. The return value of {@link #exit}
+ * indicates whether a timeout was triggered. Note that the call to {@link
+ * #timedOut} is asynchronous, and may be called after {@link #exit}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AsyncTimeout extends Timeout {
+ /**
+ * The watchdog thread processes a linked list of pending timeouts, sorted in
+ * the order to be triggered. This class synchronizes on AsyncTimeout.class.
+ * This lock guards the queue.
+ *
+ * <p>Head's 'next' points to the first element of the linked list. The first
+ * element is the next node to time out, or null if the queue is empty. The
+ * head is null until the watchdog thread is started.
+ */
+ private static AsyncTimeout head;
+
+ /** True if this node is currently in the queue. */
+ private boolean inQueue;
+
+ /** The next node in the linked list. */
+ private AsyncTimeout next;
+
+ /** If scheduled, this is the time that the watchdog should time this out. */
+ private long timeoutAt;
+
+ public final void enter() {
+ if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
+ long timeoutNanos = timeoutNanos();
+ boolean hasDeadline = hasDeadline();
+ if (timeoutNanos == 0 && !hasDeadline) {
+ return; // No timeout and no deadline? Don't bother with the queue.
+ }
+ inQueue = true;
+ scheduleTimeout(this, timeoutNanos, hasDeadline);
+ }
+
+ private static synchronized void scheduleTimeout(
+ AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
+ // Start the watchdog thread and create the head node when the first timeout is scheduled.
+ if (head == null) {
+ head = new AsyncTimeout();
+ new Watchdog().start();
+ }
+
+ long now = System.nanoTime();
+ if (timeoutNanos != 0 && hasDeadline) {
+ // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around,
+ // Math.min() is undefined for absolute values, but meaningful for relative ones.
+ node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
+ } else if (timeoutNanos != 0) {
+ node.timeoutAt = now + timeoutNanos;
+ } else if (hasDeadline) {
+ node.timeoutAt = node.deadlineNanoTime();
+ } else {
+ throw new AssertionError();
+ }
+
+ // Insert the node in sorted order.
+ long remainingNanos = node.remainingNanos(now);
+ for (AsyncTimeout prev = head; true; prev = prev.next) {
+ if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
+ node.next = prev.next;
+ prev.next = node;
+ if (prev == head) {
+ AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front.
+ }
+ break;
+ }
+ }
+ }
+
+ /** Returns true if the timeout occurred. */
+ public final boolean exit() {
+ if (!inQueue) return false;
+ inQueue = false;
+ return cancelScheduledTimeout(this);
+ }
+
+ /** Returns true if the timeout occurred. */
+ private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
+ // Remove the node from the linked list.
+ for (AsyncTimeout prev = head; prev != null; prev = prev.next) {
+ if (prev.next == node) {
+ prev.next = node.next;
+ node.next = null;
+ return false;
+ }
+ }
+
+ // The node wasn't found in the linked list: it must have timed out!
+ return true;
+ }
+
+ /**
+ * Returns the amount of time left until the time out. This will be negative
+ * if the timeout has elapsed and the timeout should occur immediately.
+ */
+ private long remainingNanos(long now) {
+ return timeoutAt - now;
+ }
+
+ /**
+ * Invoked by the watchdog thread when the time between calls to {@link
+ * #enter()} and {@link #exit()} has exceeded the timeout.
+ */
+ protected void timedOut() {
+ }
+
+ /**
+ * Returns a new sink that delegates to {@code sink}, using this to implement
+ * timeouts. This works best if {@link #timedOut} is overridden to interrupt
+ * {@code sink}'s current operation.
+ */
+ public final Sink sink(final Sink sink) {
+ return new Sink() {
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ boolean throwOnTimeout = false;
+ enter();
+ try {
+ sink.write(source, byteCount);
+ throwOnTimeout = true;
+ } catch (IOException e) {
+ throw exit(e);
+ } finally {
+ exit(throwOnTimeout);
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ boolean throwOnTimeout = false;
+ enter();
+ try {
+ sink.flush();
+ throwOnTimeout = true;
+ } catch (IOException e) {
+ throw exit(e);
+ } finally {
+ exit(throwOnTimeout);
+ }
+ }
+
+ @Override public void close() throws IOException {
+ boolean throwOnTimeout = false;
+ enter();
+ try {
+ sink.close();
+ throwOnTimeout = true;
+ } catch (IOException e) {
+ throw exit(e);
+ } finally {
+ exit(throwOnTimeout);
+ }
+ }
+
+ @Override public Timeout timeout() {
+ return AsyncTimeout.this;
+ }
+
+ @Override public String toString() {
+ return "AsyncTimeout.sink(" + sink + ")";
+ }
+ };
+ }
+
+ /**
+ * Returns a new source that delegates to {@code source}, using this to
+ * implement timeouts. This works best if {@link #timedOut} is overridden to
+ * interrupt {@code sink}'s current operation.
+ */
+ public final Source source(final Source source) {
+ return new Source() {
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ boolean throwOnTimeout = false;
+ enter();
+ try {
+ long result = source.read(sink, byteCount);
+ throwOnTimeout = true;
+ return result;
+ } catch (IOException e) {
+ throw exit(e);
+ } finally {
+ exit(throwOnTimeout);
+ }
+ }
+
+ @Override public void close() throws IOException {
+ boolean throwOnTimeout = false;
+ try {
+ source.close();
+ throwOnTimeout = true;
+ } catch (IOException e) {
+ throw exit(e);
+ } finally {
+ exit(throwOnTimeout);
+ }
+ }
+
+ @Override public Timeout timeout() {
+ return AsyncTimeout.this;
+ }
+
+ @Override public String toString() {
+ return "AsyncTimeout.source(" + source + ")";
+ }
+ };
+ }
+
+ /**
+ * Throws an IOException if {@code throwOnTimeout} is {@code true} and a
+ * timeout occurred. See {@link #newTimeoutException(java.io.IOException)}
+ * for the type of exception thrown.
+ */
+ final void exit(boolean throwOnTimeout) throws IOException {
+ boolean timedOut = exit();
+ if (timedOut && throwOnTimeout) throw newTimeoutException(null);
+ }
+
+ /**
+ * Returns either {@code cause} or an IOException that's caused by
+ * {@code cause} if a timeout occurred. See
+ * {@link #newTimeoutException(java.io.IOException)} for the type of
+ * exception returned.
+ */
+ final IOException exit(IOException cause) throws IOException {
+ if (!exit()) return cause;
+ return newTimeoutException(cause);
+ }
+
+ /**
+ * Returns an {@link IOException} to represent a timeout. By default this method returns
+ * {@link java.io.InterruptedIOException}. If {@code cause} is non-null it is set as the cause of
+ * the returned exception.
+ */
+ protected IOException newTimeoutException(IOException cause) {
+ InterruptedIOException e = new InterruptedIOException("timeout");
+ if (cause != null) {
+ e.initCause(cause);
+ }
+ return e;
+ }
+
+ private static final class Watchdog extends Thread {
+ public Watchdog() {
+ super("Okio Watchdog");
+ setDaemon(true);
+ }
+
+ public void run() {
+ while (true) {
+ try {
+ AsyncTimeout timedOut = awaitTimeout();
+
+ // Didn't find a node to interrupt. Try again.
+ if (timedOut == null) continue;
+
+ // Close the timed out node.
+ timedOut.timedOut();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes and returns the node at the head of the list, waiting for it to
+ * time out if necessary. Returns null if the situation changes while waiting:
+ * either a newer node is inserted at the head, or the node being waited on
+ * has been removed.
+ */
+ private static synchronized AsyncTimeout awaitTimeout() throws InterruptedException {
+ // Get the next eligible node.
+ AsyncTimeout node = head.next;
+
+ // The queue is empty. Wait for something to be enqueued.
+ if (node == null) {
+ AsyncTimeout.class.wait();
+ return null;
+ }
+
+ long waitNanos = node.remainingNanos(System.nanoTime());
+
+ // The head of the queue hasn't timed out yet. Await that.
+ if (waitNanos > 0) {
+ // Waiting is made complicated by the fact that we work in nanoseconds,
+ // but the API wants (millis, nanos) in two arguments.
+ long waitMillis = waitNanos / 1000000L;
+ waitNanos -= (waitMillis * 1000000L);
+ AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
+ return null;
+ }
+
+ // The head of the queue has timed out. Remove it.
+ head.next = node.next;
+ node.next = null;
+ return node;
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java
new file mode 100644
index 0000000..3260c96
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java
@@ -0,0 +1,163 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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.
+ */
+
+/**
+ * @author Alexander Y. Kleymenov
+ */
+package com.android.okhttp.okio;
+
+import java.io.UnsupportedEncodingException;
+
+final class Base64 {
+ private Base64() {
+ }
+
+ public static byte[] decode(String in) {
+ // Ignore trailing '=' padding and whitespace from the input.
+ int limit = in.length();
+ for (; limit > 0; limit--) {
+ char c = in.charAt(limit - 1);
+ if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') {
+ break;
+ }
+ }
+
+ // If the input includes whitespace, this output array will be longer than necessary.
+ byte[] out = new byte[(int) (limit * 6L / 8L)];
+ int outCount = 0;
+ int inCount = 0;
+
+ int word = 0;
+ for (int pos = 0; pos < limit; pos++) {
+ char c = in.charAt(pos);
+
+ int bits;
+ if (c >= 'A' && c <= 'Z') {
+ // char ASCII value
+ // A 65 0
+ // Z 90 25 (ASCII - 65)
+ bits = c - 65;
+ } else if (c >= 'a' && c <= 'z') {
+ // char ASCII value
+ // a 97 26
+ // z 122 51 (ASCII - 71)
+ bits = c - 71;
+ } else if (c >= '0' && c <= '9') {
+ // char ASCII value
+ // 0 48 52
+ // 9 57 61 (ASCII + 4)
+ bits = c + 4;
+ } else if (c == '+' || c == '-') {
+ bits = 62;
+ } else if (c == '/' || c == '_') {
+ bits = 63;
+ } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
+ continue;
+ } else {
+ return null;
+ }
+
+ // Append this char's 6 bits to the word.
+ word = (word << 6) | (byte) bits;
+
+ // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes.
+ inCount++;
+ if (inCount % 4 == 0) {
+ out[outCount++] = (byte) (word >> 16);
+ out[outCount++] = (byte) (word >> 8);
+ out[outCount++] = (byte) word;
+ }
+ }
+
+ int lastWordChars = inCount % 4;
+ if (lastWordChars == 1) {
+ // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail.
+ return null;
+ } else if (lastWordChars == 2) {
+ // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits.
+ word = word << 12;
+ out[outCount++] = (byte) (word >> 16);
+ } else if (lastWordChars == 3) {
+ // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits.
+ word = word << 6;
+ out[outCount++] = (byte) (word >> 16);
+ out[outCount++] = (byte) (word >> 8);
+ }
+
+ // If we sized our out array perfectly, we're done.
+ if (outCount == out.length) return out;
+
+ // Copy the decoded bytes to a new, right-sized array.
+ byte[] prefix = new byte[outCount];
+ System.arraycopy(out, 0, prefix, 0, outCount);
+ return prefix;
+ }
+
+ private static final byte[] MAP = new byte[] {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+ 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+ '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ private static final byte[] URL_MAP = new byte[] {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+ 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+ '5', '6', '7', '8', '9', '-', '_'
+ };
+
+ public static String encode(byte[] in) {
+ return encode(in, MAP);
+ }
+
+ public static String encodeUrl(byte[] in) {
+ return encode(in, URL_MAP);
+ }
+
+ private static String encode(byte[] in, byte[] map) {
+ int length = (in.length + 2) * 4 / 3;
+ byte[] out = new byte[length];
+ int index = 0, end = in.length - in.length % 3;
+ for (int i = 0; i < end; i += 3) {
+ out[index++] = map[(in[i] & 0xff) >> 2];
+ out[index++] = map[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
+ out[index++] = map[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
+ out[index++] = map[(in[i + 2] & 0x3f)];
+ }
+ switch (in.length % 3) {
+ case 1:
+ out[index++] = map[(in[end] & 0xff) >> 2];
+ out[index++] = map[(in[end] & 0x03) << 4];
+ out[index++] = '=';
+ out[index++] = '=';
+ break;
+ case 2:
+ out[index++] = map[(in[end] & 0xff) >> 2];
+ out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
+ out[index++] = map[((in[end + 1] & 0x0f) << 2)];
+ out[index++] = '=';
+ break;
+ }
+ try {
+ return new String(out, 0, index, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java
new file mode 100644
index 0000000..04752d8
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java
@@ -0,0 +1,1439 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+import static com.android.okhttp.okio.Util.reverseBytesLong;
+
+/**
+ * A collection of bytes in memory.
+ *
+ * <p><strong>Moving data from one buffer to another is fast.</strong> Instead
+ * of copying bytes from one place in memory to another, this class just changes
+ * ownership of the underlying byte arrays.
+ *
+ * <p><strong>This buffer grows with your data.</strong> Just like ArrayList,
+ * each buffer starts small. It consumes only the memory it needs to.
+ *
+ * <p><strong>This buffer pools its byte arrays.</strong> When you allocate a
+ * byte array in Java, the runtime must zero-fill the requested array before
+ * returning it to you. Even if you're going to write over that space anyway.
+ * This class avoids zero-fill and GC churn by pooling byte arrays.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
+ private static final byte[] DIGITS =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ static final int REPLACEMENT_CHARACTER = '\ufffd';
+
+ Segment head;
+ long size;
+
+ public Buffer() {
+ }
+
+ /** Returns the number of bytes currently in this buffer. */
+ public long size() {
+ return size;
+ }
+
+ @Override public Buffer buffer() {
+ return this;
+ }
+
+ @Override public OutputStream outputStream() {
+ return new OutputStream() {
+ @Override public void write(int b) {
+ writeByte((byte) b);
+ }
+
+ @Override public void write(byte[] data, int offset, int byteCount) {
+ Buffer.this.write(data, offset, byteCount);
+ }
+
+ @Override public void flush() {
+ }
+
+ @Override public void close() {
+ }
+
+ @Override public String toString() {
+ return this + ".outputStream()";
+ }
+ };
+ }
+
+ @Override public Buffer emitCompleteSegments() {
+ return this; // Nowhere to emit to!
+ }
+
+ @Override public BufferedSink emit() {
+ return this; // Nowhere to emit to!
+ }
+
+ @Override public boolean exhausted() {
+ return size == 0;
+ }
+
+ @Override public void require(long byteCount) throws EOFException {
+ if (size < byteCount) throw new EOFException();
+ }
+
+ @Override public boolean request(long byteCount) {
+ return size >= byteCount;
+ }
+
+ @Override public InputStream inputStream() {
+ return new InputStream() {
+ @Override public int read() {
+ if (size > 0) return readByte() & 0xff;
+ return -1;
+ }
+
+ @Override public int read(byte[] sink, int offset, int byteCount) {
+ return Buffer.this.read(sink, offset, byteCount);
+ }
+
+ @Override public int available() {
+ return (int) Math.min(size, Integer.MAX_VALUE);
+ }
+
+ @Override public void close() {
+ }
+
+ @Override public String toString() {
+ return Buffer.this + ".inputStream()";
+ }
+ };
+ }
+
+ /** Copy the contents of this to {@code out}. */
+ public Buffer copyTo(OutputStream out) throws IOException {
+ return copyTo(out, 0, size);
+ }
+
+ /**
+ * Copy {@code byteCount} bytes from this, starting at {@code offset}, to
+ * {@code out}.
+ */
+ public Buffer copyTo(OutputStream out, long offset, long byteCount) throws IOException {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ checkOffsetAndCount(size, offset, byteCount);
+ if (byteCount == 0) return this;
+
+ // Skip segments that we aren't copying from.
+ Segment s = head;
+ for (; offset >= (s.limit - s.pos); s = s.next) {
+ offset -= (s.limit - s.pos);
+ }
+
+ // Copy from one segment at a time.
+ for (; byteCount > 0; s = s.next) {
+ int pos = (int) (s.pos + offset);
+ int toCopy = (int) Math.min(s.limit - pos, byteCount);
+ out.write(s.data, pos, toCopy);
+ byteCount -= toCopy;
+ offset = 0;
+ }
+
+ return this;
+ }
+
+ /** Copy {@code byteCount} bytes from this, starting at {@code offset}, to {@code out}. */
+ public Buffer copyTo(Buffer out, long offset, long byteCount) {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ checkOffsetAndCount(size, offset, byteCount);
+ if (byteCount == 0) return this;
+
+ out.size += byteCount;
+
+ // Skip segments that we aren't copying from.
+ Segment s = head;
+ for (; offset >= (s.limit - s.pos); s = s.next) {
+ offset -= (s.limit - s.pos);
+ }
+
+ // Copy one segment at a time.
+ for (; byteCount > 0; s = s.next) {
+ Segment copy = new Segment(s);
+ copy.pos += offset;
+ copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
+ if (out.head == null) {
+ out.head = copy.next = copy.prev = copy;
+ } else {
+ out.head.prev.push(copy);
+ }
+ byteCount -= copy.limit - copy.pos;
+ offset = 0;
+ }
+
+ return this;
+ }
+
+ /** Write the contents of this to {@code out}. */
+ public Buffer writeTo(OutputStream out) throws IOException {
+ return writeTo(out, size);
+ }
+
+ /** Write {@code byteCount} bytes from this to {@code out}. */
+ public Buffer writeTo(OutputStream out, long byteCount) throws IOException {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ checkOffsetAndCount(size, 0, byteCount);
+
+ Segment s = head;
+ while (byteCount > 0) {
+ int toCopy = (int) Math.min(byteCount, s.limit - s.pos);
+ out.write(s.data, s.pos, toCopy);
+
+ s.pos += toCopy;
+ size -= toCopy;
+ byteCount -= toCopy;
+
+ if (s.pos == s.limit) {
+ Segment toRecycle = s;
+ head = s = toRecycle.pop();
+ SegmentPool.recycle(toRecycle);
+ }
+ }
+
+ return this;
+ }
+
+ /** Read and exhaust bytes from {@code in} to this. */
+ public Buffer readFrom(InputStream in) throws IOException {
+ readFrom(in, Long.MAX_VALUE, true);
+ return this;
+ }
+
+ /** Read {@code byteCount} bytes from {@code in} to this. */
+ public Buffer readFrom(InputStream in, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ readFrom(in, byteCount, false);
+ return this;
+ }
+
+ private void readFrom(InputStream in, long byteCount, boolean forever) throws IOException {
+ if (in == null) throw new IllegalArgumentException("in == null");
+ while (byteCount > 0 || forever) {
+ Segment tail = writableSegment(1);
+ int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
+ int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
+ if (bytesRead == -1) {
+ if (forever) return;
+ throw new EOFException();
+ }
+ tail.limit += bytesRead;
+ size += bytesRead;
+ byteCount -= bytesRead;
+ }
+ }
+
+ /**
+ * Returns the number of bytes in segments that are not writable. This is the
+ * number of bytes that can be flushed immediately to an underlying sink
+ * without harming throughput.
+ */
+ public long completeSegmentByteCount() {
+ long result = size;
+ if (result == 0) return 0;
+
+ // Omit the tail if it's still writable.
+ Segment tail = head.prev;
+ if (tail.limit < Segment.SIZE && tail.owner) {
+ result -= tail.limit - tail.pos;
+ }
+
+ return result;
+ }
+
+ @Override public byte readByte() {
+ if (size == 0) throw new IllegalStateException("size == 0");
+
+ Segment segment = head;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ byte[] data = segment.data;
+ byte b = data[pos++];
+ size -= 1;
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+
+ return b;
+ }
+
+ /** Returns the byte at {@code pos}. */
+ public byte getByte(long pos) {
+ checkOffsetAndCount(size, pos, 1);
+ for (Segment s = head; true; s = s.next) {
+ int segmentByteCount = s.limit - s.pos;
+ if (pos < segmentByteCount) return s.data[s.pos + (int) pos];
+ pos -= segmentByteCount;
+ }
+ }
+
+ @Override public short readShort() {
+ if (size < 2) throw new IllegalStateException("size < 2: " + size);
+
+ Segment segment = head;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ // If the short is split across multiple segments, delegate to readByte().
+ if (limit - pos < 2) {
+ int s = (readByte() & 0xff) << 8
+ | (readByte() & 0xff);
+ return (short) s;
+ }
+
+ byte[] data = segment.data;
+ int s = (data[pos++] & 0xff) << 8
+ | (data[pos++] & 0xff);
+ size -= 2;
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+
+ return (short) s;
+ }
+
+ @Override public int readInt() {
+ if (size < 4) throw new IllegalStateException("size < 4: " + size);
+
+ Segment segment = head;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ // If the int is split across multiple segments, delegate to readByte().
+ if (limit - pos < 4) {
+ return (readByte() & 0xff) << 24
+ | (readByte() & 0xff) << 16
+ | (readByte() & 0xff) << 8
+ | (readByte() & 0xff);
+ }
+
+ byte[] data = segment.data;
+ int i = (data[pos++] & 0xff) << 24
+ | (data[pos++] & 0xff) << 16
+ | (data[pos++] & 0xff) << 8
+ | (data[pos++] & 0xff);
+ size -= 4;
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+
+ return i;
+ }
+
+ @Override public long readLong() {
+ if (size < 8) throw new IllegalStateException("size < 8: " + size);
+
+ Segment segment = head;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ // If the long is split across multiple segments, delegate to readInt().
+ if (limit - pos < 8) {
+ return (readInt() & 0xffffffffL) << 32
+ | (readInt() & 0xffffffffL);
+ }
+
+ byte[] data = segment.data;
+ long v = (data[pos++] & 0xffL) << 56
+ | (data[pos++] & 0xffL) << 48
+ | (data[pos++] & 0xffL) << 40
+ | (data[pos++] & 0xffL) << 32
+ | (data[pos++] & 0xffL) << 24
+ | (data[pos++] & 0xffL) << 16
+ | (data[pos++] & 0xffL) << 8
+ | (data[pos++] & 0xffL);
+ size -= 8;
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+
+ return v;
+ }
+
+ @Override public short readShortLe() {
+ return Util.reverseBytesShort(readShort());
+ }
+
+ @Override public int readIntLe() {
+ return Util.reverseBytesInt(readInt());
+ }
+
+ @Override public long readLongLe() {
+ return Util.reverseBytesLong(readLong());
+ }
+
+ @Override public long readDecimalLong() {
+ if (size == 0) throw new IllegalStateException("size == 0");
+
+ // This value is always built negatively in order to accommodate Long.MIN_VALUE.
+ long value = 0;
+ int seen = 0;
+ boolean negative = false;
+ boolean done = false;
+
+ long overflowZone = Long.MIN_VALUE / 10;
+ long overflowDigit = (Long.MIN_VALUE % 10) + 1;
+
+ do {
+ Segment segment = head;
+
+ byte[] data = segment.data;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ for (; pos < limit; pos++, seen++) {
+ byte b = data[pos];
+ if (b >= '0' && b <= '9') {
+ int digit = '0' - b;
+
+ // Detect when the digit would cause an overflow.
+ if (value < overflowZone || value == overflowZone && digit < overflowDigit) {
+ Buffer buffer = new Buffer().writeDecimalLong(value).writeByte(b);
+ if (!negative) buffer.readByte(); // Skip negative sign.
+ throw new NumberFormatException("Number too large: " + buffer.readUtf8());
+ }
+ value *= 10;
+ value += digit;
+ } else if (b == '-' && seen == 0) {
+ negative = true;
+ overflowDigit -= 1;
+ } else {
+ if (seen == 0) {
+ throw new NumberFormatException(
+ "Expected leading [0-9] or '-' character but was 0x" + Integer.toHexString(b));
+ }
+ // Set a flag to stop iteration. We still need to run through segment updating below.
+ done = true;
+ break;
+ }
+ }
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+ } while (!done && head != null);
+
+ size -= seen;
+ return negative ? value : -value;
+ }
+
+ @Override public long readHexadecimalUnsignedLong() {
+ if (size == 0) throw new IllegalStateException("size == 0");
+
+ long value = 0;
+ int seen = 0;
+ boolean done = false;
+
+ do {
+ Segment segment = head;
+
+ byte[] data = segment.data;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ for (; pos < limit; pos++, seen++) {
+ int digit;
+
+ byte b = data[pos];
+ if (b >= '0' && b <= '9') {
+ digit = b - '0';
+ } else if (b >= 'a' && b <= 'f') {
+ digit = b - 'a' + 10;
+ } else if (b >= 'A' && b <= 'F') {
+ digit = b - 'A' + 10; // We never write uppercase, but we support reading it.
+ } else {
+ if (seen == 0) {
+ throw new NumberFormatException(
+ "Expected leading [0-9a-fA-F] character but was 0x" + Integer.toHexString(b));
+ }
+ // Set a flag to stop iteration. We still need to run through segment updating below.
+ done = true;
+ break;
+ }
+
+ // Detect when the shift will overflow.
+ if ((value & 0xf000000000000000L) != 0) {
+ Buffer buffer = new Buffer().writeHexadecimalUnsignedLong(value).writeByte(b);
+ throw new NumberFormatException("Number too large: " + buffer.readUtf8());
+ }
+
+ value <<= 4;
+ value |= digit;
+ }
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+ } while (!done && head != null);
+
+ size -= seen;
+ return value;
+ }
+
+ @Override public ByteString readByteString() {
+ return new ByteString(readByteArray());
+ }
+
+ @Override public ByteString readByteString(long byteCount) throws EOFException {
+ return new ByteString(readByteArray(byteCount));
+ }
+
+ @Override public void readFully(Buffer sink, long byteCount) throws EOFException {
+ if (size < byteCount) {
+ sink.write(this, size); // Exhaust ourselves.
+ throw new EOFException();
+ }
+ sink.write(this, byteCount);
+ }
+
+ @Override public long readAll(Sink sink) throws IOException {
+ long byteCount = size;
+ if (byteCount > 0) {
+ sink.write(this, byteCount);
+ }
+ return byteCount;
+ }
+
+ @Override public String readUtf8() {
+ try {
+ return readString(size, Util.UTF_8);
+ } catch (EOFException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public String readUtf8(long byteCount) throws EOFException {
+ return readString(byteCount, Util.UTF_8);
+ }
+
+ @Override public String readString(Charset charset) {
+ try {
+ return readString(size, charset);
+ } catch (EOFException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public String readString(long byteCount, Charset charset) throws EOFException {
+ checkOffsetAndCount(size, 0, byteCount);
+ if (charset == null) throw new IllegalArgumentException("charset == null");
+ if (byteCount > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
+ }
+ if (byteCount == 0) return "";
+
+ Segment s = head;
+ if (s.pos + byteCount > s.limit) {
+ // If the string spans multiple segments, delegate to readBytes().
+ return new String(readByteArray(byteCount), charset);
+ }
+
+ String result = new String(s.data, s.pos, (int) byteCount, charset);
+ s.pos += byteCount;
+ size -= byteCount;
+
+ if (s.pos == s.limit) {
+ head = s.pop();
+ SegmentPool.recycle(s);
+ }
+
+ return result;
+ }
+
+ @Override public String readUtf8Line() throws EOFException {
+ long newline = indexOf((byte) '\n');
+
+ if (newline == -1) {
+ return size != 0 ? readUtf8(size) : null;
+ }
+
+ return readUtf8Line(newline);
+ }
+
+ @Override public String readUtf8LineStrict() throws EOFException {
+ long newline = indexOf((byte) '\n');
+ if (newline == -1) {
+ Buffer data = new Buffer();
+ copyTo(data, 0, Math.min(32, size));
+ throw new EOFException("\\n not found: size=" + size()
+ + " content=" + data.readByteString().hex() + "...");
+ }
+ return readUtf8Line(newline);
+ }
+
+ String readUtf8Line(long newline) throws EOFException {
+ if (newline > 0 && getByte(newline - 1) == '\r') {
+ // Read everything until '\r\n', then skip the '\r\n'.
+ String result = readUtf8((newline - 1));
+ skip(2);
+ return result;
+
+ } else {
+ // Read everything until '\n', then skip the '\n'.
+ String result = readUtf8(newline);
+ skip(1);
+ return result;
+ }
+ }
+
+ @Override public int readUtf8CodePoint() throws EOFException {
+ if (size == 0) throw new EOFException();
+
+ byte b0 = getByte(0);
+ int codePoint;
+ int byteCount;
+ int min;
+
+ if ((b0 & 0x80) == 0) {
+ // 0xxxxxxx.
+ codePoint = b0 & 0x7f;
+ byteCount = 1; // 7 bits (ASCII).
+ min = 0x0;
+
+ } else if ((b0 & 0xe0) == 0xc0) {
+ // 0x110xxxxx
+ codePoint = b0 & 0x1f;
+ byteCount = 2; // 11 bits (5 + 6).
+ min = 0x80;
+
+ } else if ((b0 & 0xf0) == 0xe0) {
+ // 0x1110xxxx
+ codePoint = b0 & 0x0f;
+ byteCount = 3; // 16 bits (4 + 6 + 6).
+ min = 0x800;
+
+ } else if ((b0 & 0xf8) == 0xf0) {
+ // 0x11110xxx
+ codePoint = b0 & 0x07;
+ byteCount = 4; // 21 bits (3 + 6 + 6 + 6).
+ min = 0x10000;
+
+ } else {
+ // We expected the first byte of a code point but got something else.
+ skip(1);
+ return REPLACEMENT_CHARACTER;
+ }
+
+ if (size < byteCount) {
+ throw new EOFException("size < " + byteCount + ": " + size
+ + " (to read code point prefixed 0x" + Integer.toHexString(b0) + ")");
+ }
+
+ // Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed
+ // thus far is truncated and is decoded as the replacement character. That non-continuation byte
+ // is left in the stream for processing by the next call to readUtf8CodePoint().
+ for (int i = 1; i < byteCount; i++) {
+ byte b = getByte(i);
+ if ((b & 0xc0) == 0x80) {
+ // 0x10xxxxxx
+ codePoint <<= 6;
+ codePoint |= b & 0x3f;
+ } else {
+ skip(i);
+ return REPLACEMENT_CHARACTER;
+ }
+ }
+
+ skip(byteCount);
+
+ if (codePoint > 0x10ffff) {
+ return REPLACEMENT_CHARACTER; // Reject code points larger than the Unicode maximum.
+ }
+
+ if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
+ return REPLACEMENT_CHARACTER; // Reject partial surrogates.
+ }
+
+ if (codePoint < min) {
+ return REPLACEMENT_CHARACTER; // Reject overlong code points.
+ }
+
+ return codePoint;
+ }
+
+ @Override public byte[] readByteArray() {
+ try {
+ return readByteArray(size);
+ } catch (EOFException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public byte[] readByteArray(long byteCount) throws EOFException {
+ checkOffsetAndCount(size, 0, byteCount);
+ if (byteCount > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
+ }
+
+ byte[] result = new byte[(int) byteCount];
+ readFully(result);
+ return result;
+ }
+
+ @Override public int read(byte[] sink) {
+ return read(sink, 0, sink.length);
+ }
+
+ @Override public void readFully(byte[] sink) throws EOFException {
+ int offset = 0;
+ while (offset < sink.length) {
+ int read = read(sink, offset, sink.length - offset);
+ if (read == -1) throw new EOFException();
+ offset += read;
+ }
+ }
+
+ @Override public int read(byte[] sink, int offset, int byteCount) {
+ checkOffsetAndCount(sink.length, offset, byteCount);
+
+ Segment s = head;
+ if (s == null) return -1;
+ int toCopy = Math.min(byteCount, s.limit - s.pos);
+ System.arraycopy(s.data, s.pos, sink, offset, toCopy);
+
+ s.pos += toCopy;
+ size -= toCopy;
+
+ if (s.pos == s.limit) {
+ head = s.pop();
+ SegmentPool.recycle(s);
+ }
+
+ return toCopy;
+ }
+
+ /**
+ * Discards all bytes in this buffer. Calling this method when you're done
+ * with a buffer will return its segments to the pool.
+ */
+ public void clear() {
+ try {
+ skip(size);
+ } catch (EOFException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Discards {@code byteCount} bytes from the head of this buffer. */
+ @Override public void skip(long byteCount) throws EOFException {
+ while (byteCount > 0) {
+ if (head == null) throw new EOFException();
+
+ int toSkip = (int) Math.min(byteCount, head.limit - head.pos);
+ size -= toSkip;
+ byteCount -= toSkip;
+ head.pos += toSkip;
+
+ if (head.pos == head.limit) {
+ Segment toRecycle = head;
+ head = toRecycle.pop();
+ SegmentPool.recycle(toRecycle);
+ }
+ }
+ }
+
+ @Override public Buffer write(ByteString byteString) {
+ if (byteString == null) throw new IllegalArgumentException("byteString == null");
+ byteString.write(this);
+ return this;
+ }
+
+ @Override public Buffer writeUtf8(String string) {
+ return writeUtf8(string, 0, string.length());
+ }
+
+ @Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
+ if (string == null) throw new IllegalArgumentException("string == null");
+ if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
+ if (endIndex < beginIndex) {
+ throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
+ }
+ if (endIndex > string.length()) {
+ throw new IllegalArgumentException(
+ "endIndex > string.length: " + endIndex + " > " + string.length());
+ }
+
+ // Transcode a UTF-16 Java String to UTF-8 bytes.
+ for (int i = beginIndex; i < endIndex;) {
+ int c = string.charAt(i);
+
+ if (c < 0x80) {
+ Segment tail = writableSegment(1);
+ byte[] data = tail.data;
+ int segmentOffset = tail.limit - i;
+ int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset);
+
+ // Emit a 7-bit character with 1 byte.
+ data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
+
+ // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
+ // improvement over independent calls to writeByte().
+ while (i < runLimit) {
+ c = string.charAt(i);
+ if (c >= 0x80) break;
+ data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
+ }
+
+ int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i).
+ tail.limit += runSize;
+ size += runSize;
+
+ } else if (c < 0x800) {
+ // Emit a 11-bit character with 2 bytes.
+ writeByte(c >> 6 | 0xc0); // 110xxxxx
+ writeByte(c & 0x3f | 0x80); // 10xxxxxx
+ i++;
+
+ } else if (c < 0xd800 || c > 0xdfff) {
+ // Emit a 16-bit character with 3 bytes.
+ writeByte(c >> 12 | 0xe0); // 1110xxxx
+ writeByte(c >> 6 & 0x3f | 0x80); // 10xxxxxx
+ writeByte(c & 0x3f | 0x80); // 10xxxxxx
+ i++;
+
+ } else {
+ // c is a surrogate. Make sure it is a high surrogate & that its successor is a low
+ // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character.
+ int low = i + 1 < endIndex ? string.charAt(i + 1) : 0;
+ if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
+ writeByte('?');
+ i++;
+ continue;
+ }
+
+ // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
+ // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
+ // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
+ int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00);
+
+ // Emit a 21-bit character with 4 bytes.
+ writeByte(codePoint >> 18 | 0xf0); // 11110xxx
+ writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
+ writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxyyyy
+ writeByte(codePoint & 0x3f | 0x80); // 10yyyyyy
+ i += 2;
+ }
+ }
+
+ return this;
+ }
+
+ @Override public Buffer writeUtf8CodePoint(int codePoint) {
+ if (codePoint < 0x80) {
+ // Emit a 7-bit code point with 1 byte.
+ writeByte(codePoint);
+
+ } else if (codePoint < 0x800) {
+ // Emit a 11-bit code point with 2 bytes.
+ writeByte(codePoint >> 6 | 0xc0); // 110xxxxx
+ writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
+
+ } else if (codePoint < 0x10000) {
+ if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
+ throw new IllegalArgumentException(
+ "Unexpected code point: " + Integer.toHexString(codePoint));
+ }
+
+ // Emit a 16-bit code point with 3 bytes.
+ writeByte(codePoint >> 12 | 0xe0); // 1110xxxx
+ writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx
+ writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
+
+ } else if (codePoint <= 0x10ffff) {
+ // Emit a 21-bit code point with 4 bytes.
+ writeByte(codePoint >> 18 | 0xf0); // 11110xxx
+ writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
+ writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx
+ writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx
+
+ } else {
+ throw new IllegalArgumentException(
+ "Unexpected code point: " + Integer.toHexString(codePoint));
+ }
+
+ return this;
+ }
+
+ @Override public Buffer writeString(String string, Charset charset) {
+ return writeString(string, 0, string.length(), charset);
+ }
+
+ @Override
+ public Buffer writeString(String string, int beginIndex, int endIndex, Charset charset) {
+ if (string == null) throw new IllegalArgumentException("string == null");
+ if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
+ if (endIndex < beginIndex) {
+ throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
+ }
+ if (endIndex > string.length()) {
+ throw new IllegalArgumentException(
+ "endIndex > string.length: " + endIndex + " > " + string.length());
+ }
+ if (charset == null) throw new IllegalArgumentException("charset == null");
+ if (charset.equals(Util.UTF_8)) return writeUtf8(string);
+ byte[] data = string.substring(beginIndex, endIndex).getBytes(charset);
+ return write(data, 0, data.length);
+ }
+
+ @Override public Buffer write(byte[] source) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ return write(source, 0, source.length);
+ }
+
+ @Override public Buffer write(byte[] source, int offset, int byteCount) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ checkOffsetAndCount(source.length, offset, byteCount);
+
+ int limit = offset + byteCount;
+ while (offset < limit) {
+ Segment tail = writableSegment(1);
+
+ int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
+ System.arraycopy(source, offset, tail.data, tail.limit, toCopy);
+
+ offset += toCopy;
+ tail.limit += toCopy;
+ }
+
+ size += byteCount;
+ return this;
+ }
+
+ @Override public long writeAll(Source source) throws IOException {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ long totalBytesRead = 0;
+ for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
+ totalBytesRead += readCount;
+ }
+ return totalBytesRead;
+ }
+
+ @Override public BufferedSink write(Source source, long byteCount) throws IOException {
+ while (byteCount > 0) {
+ long read = source.read(this, byteCount);
+ if (read == -1) throw new EOFException();
+ byteCount -= read;
+ }
+ return this;
+ }
+
+ @Override public Buffer writeByte(int b) {
+ Segment tail = writableSegment(1);
+ tail.data[tail.limit++] = (byte) b;
+ size += 1;
+ return this;
+ }
+
+ @Override public Buffer writeShort(int s) {
+ Segment tail = writableSegment(2);
+ byte[] data = tail.data;
+ int limit = tail.limit;
+ data[limit++] = (byte) ((s >>> 8) & 0xff);
+ data[limit++] = (byte) (s & 0xff);
+ tail.limit = limit;
+ size += 2;
+ return this;
+ }
+
+ @Override public Buffer writeShortLe(int s) {
+ return writeShort(Util.reverseBytesShort((short) s));
+ }
+
+ @Override public Buffer writeInt(int i) {
+ Segment tail = writableSegment(4);
+ byte[] data = tail.data;
+ int limit = tail.limit;
+ data[limit++] = (byte) ((i >>> 24) & 0xff);
+ data[limit++] = (byte) ((i >>> 16) & 0xff);
+ data[limit++] = (byte) ((i >>> 8) & 0xff);
+ data[limit++] = (byte) (i & 0xff);
+ tail.limit = limit;
+ size += 4;
+ return this;
+ }
+
+ @Override public Buffer writeIntLe(int i) {
+ return writeInt(Util.reverseBytesInt(i));
+ }
+
+ @Override public Buffer writeLong(long v) {
+ Segment tail = writableSegment(8);
+ byte[] data = tail.data;
+ int limit = tail.limit;
+ data[limit++] = (byte) ((v >>> 56L) & 0xff);
+ data[limit++] = (byte) ((v >>> 48L) & 0xff);
+ data[limit++] = (byte) ((v >>> 40L) & 0xff);
+ data[limit++] = (byte) ((v >>> 32L) & 0xff);
+ data[limit++] = (byte) ((v >>> 24L) & 0xff);
+ data[limit++] = (byte) ((v >>> 16L) & 0xff);
+ data[limit++] = (byte) ((v >>> 8L) & 0xff);
+ data[limit++] = (byte) (v & 0xff);
+ tail.limit = limit;
+ size += 8;
+ return this;
+ }
+
+ @Override public Buffer writeLongLe(long v) {
+ return writeLong(reverseBytesLong(v));
+ }
+
+ @Override public Buffer writeDecimalLong(long v) {
+ if (v == 0) {
+ // Both a shortcut and required since the following code can't handle zero.
+ return writeByte('0');
+ }
+
+ boolean negative = false;
+ if (v < 0) {
+ v = -v;
+ if (v < 0) { // Only true for Long.MIN_VALUE.
+ return writeUtf8("-9223372036854775808");
+ }
+ negative = true;
+ }
+
+ // Binary search for character width which favors matching lower numbers.
+ int width = //
+ v < 100000000L
+ ? v < 10000L
+ ? v < 100L
+ ? v < 10L ? 1 : 2
+ : v < 1000L ? 3 : 4
+ : v < 1000000L
+ ? v < 100000L ? 5 : 6
+ : v < 10000000L ? 7 : 8
+ : v < 1000000000000L
+ ? v < 10000000000L
+ ? v < 1000000000L ? 9 : 10
+ : v < 100000000000L ? 11 : 12
+ : v < 1000000000000000L
+ ? v < 10000000000000L ? 13
+ : v < 100000000000000L ? 14 : 15
+ : v < 100000000000000000L
+ ? v < 10000000000000000L ? 16 : 17
+ : v < 1000000000000000000L ? 18 : 19;
+ if (negative) {
+ ++width;
+ }
+
+ Segment tail = writableSegment(width);
+ byte[] data = tail.data;
+ int pos = tail.limit + width; // We write backwards from right to left.
+ while (v != 0) {
+ int digit = (int) (v % 10);
+ data[--pos] = DIGITS[digit];
+ v /= 10;
+ }
+ if (negative) {
+ data[--pos] = '-';
+ }
+
+ tail.limit += width;
+ this.size += width;
+ return this;
+ }
+
+ @Override public Buffer writeHexadecimalUnsignedLong(long v) {
+ if (v == 0) {
+ // Both a shortcut and required since the following code can't handle zero.
+ return writeByte('0');
+ }
+
+ int width = Long.numberOfTrailingZeros(Long.highestOneBit(v)) / 4 + 1;
+
+ Segment tail = writableSegment(width);
+ byte[] data = tail.data;
+ for (int pos = tail.limit + width - 1, start = tail.limit; pos >= start; pos--) {
+ data[pos] = DIGITS[(int) (v & 0xF)];
+ v >>>= 4;
+ }
+ tail.limit += width;
+ size += width;
+ return this;
+ }
+
+ /**
+ * Returns a tail segment that we can write at least {@code minimumCapacity}
+ * bytes to, creating it if necessary.
+ */
+ Segment writableSegment(int minimumCapacity) {
+ if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
+
+ if (head == null) {
+ head = SegmentPool.take(); // Acquire a first segment.
+ return head.next = head.prev = head;
+ }
+
+ Segment tail = head.prev;
+ if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
+ tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
+ }
+ return tail;
+ }
+
+ @Override public void write(Buffer source, long byteCount) {
+ // Move bytes from the head of the source buffer to the tail of this buffer
+ // while balancing two conflicting goals: don't waste CPU and don't waste
+ // memory.
+ //
+ //
+ // Don't waste CPU (ie. don't copy data around).
+ //
+ // Copying large amounts of data is expensive. Instead, we prefer to
+ // reassign entire segments from one buffer to the other.
+ //
+ //
+ // Don't waste memory.
+ //
+ // As an invariant, adjacent pairs of segments in a buffer should be at
+ // least 50% full, except for the head segment and the tail segment.
+ //
+ // The head segment cannot maintain the invariant because the application is
+ // consuming bytes from this segment, decreasing its level.
+ //
+ // The tail segment cannot maintain the invariant because the application is
+ // producing bytes, which may require new nearly-empty tail segments to be
+ // appended.
+ //
+ //
+ // Moving segments between buffers
+ //
+ // When writing one buffer to another, we prefer to reassign entire segments
+ // over copying bytes into their most compact form. Suppose we have a buffer
+ // with these segment levels [91%, 61%]. If we append a buffer with a
+ // single [72%] segment, that yields [91%, 61%, 72%]. No bytes are copied.
+ //
+ // Or suppose we have a buffer with these segment levels: [100%, 2%], and we
+ // want to append it to a buffer with these segment levels [99%, 3%]. This
+ // operation will yield the following segments: [100%, 2%, 99%, 3%]. That
+ // is, we do not spend time copying bytes around to achieve more efficient
+ // memory use like [100%, 100%, 4%].
+ //
+ // When combining buffers, we will compact adjacent buffers when their
+ // combined level doesn't exceed 100%. For example, when we start with
+ // [100%, 40%] and append [30%, 80%], the result is [100%, 70%, 80%].
+ //
+ //
+ // Splitting segments
+ //
+ // Occasionally we write only part of a source buffer to a sink buffer. For
+ // example, given a sink [51%, 91%], we may want to write the first 30% of
+ // a source [92%, 82%] to it. To simplify, we first transform the source to
+ // an equivalent buffer [30%, 62%, 82%] and then move the head segment,
+ // yielding sink [51%, 91%, 30%] and source [62%, 82%].
+
+ if (source == null) throw new IllegalArgumentException("source == null");
+ if (source == this) throw new IllegalArgumentException("source == this");
+ checkOffsetAndCount(source.size, 0, byteCount);
+
+ while (byteCount > 0) {
+ // Is a prefix of the source's head segment all that we need to move?
+ if (byteCount < (source.head.limit - source.head.pos)) {
+ Segment tail = head != null ? head.prev : null;
+ if (tail != null && tail.owner
+ && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {
+ // Our existing segments are sufficient. Move bytes from source's head to our tail.
+ source.head.writeTo(tail, (int) byteCount);
+ source.size -= byteCount;
+ size += byteCount;
+ return;
+ } else {
+ // We're going to need another segment. Split the source's head
+ // segment in two, then move the first of those two to this buffer.
+ source.head = source.head.split((int) byteCount);
+ }
+ }
+
+ // Remove the source's head segment and append it to our tail.
+ Segment segmentToMove = source.head;
+ long movedByteCount = segmentToMove.limit - segmentToMove.pos;
+ source.head = segmentToMove.pop();
+ if (head == null) {
+ head = segmentToMove;
+ head.next = head.prev = head;
+ } else {
+ Segment tail = head.prev;
+ tail = tail.push(segmentToMove);
+ tail.compact();
+ }
+ source.size -= movedByteCount;
+ size += movedByteCount;
+ byteCount -= movedByteCount;
+ }
+ }
+
+ @Override public long read(Buffer sink, long byteCount) {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (size == 0) return -1L;
+ if (byteCount > size) byteCount = size;
+ sink.write(this, byteCount);
+ return byteCount;
+ }
+
+ @Override public long indexOf(byte b) {
+ return indexOf(b, 0);
+ }
+
+ /**
+ * Returns the index of {@code b} in this at or beyond {@code fromIndex}, or
+ * -1 if this buffer does not contain {@code b} in that range.
+ */
+ @Override public long indexOf(byte b, long fromIndex) {
+ if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
+
+ Segment s = head;
+ if (s == null) return -1L;
+ long offset = 0L;
+ do {
+ int segmentByteCount = s.limit - s.pos;
+ if (fromIndex >= segmentByteCount) {
+ fromIndex -= segmentByteCount;
+ } else {
+ byte[] data = s.data;
+ for (int pos = (int) (s.pos + fromIndex), limit = s.limit; pos < limit; pos++) {
+ if (data[pos] == b) return offset + pos - s.pos;
+ }
+ fromIndex = 0;
+ }
+ offset += segmentByteCount;
+ s = s.next;
+ } while (s != head);
+ return -1L;
+ }
+
+ @Override public long indexOf(ByteString bytes) throws IOException {
+ return indexOf(bytes, 0);
+ }
+
+ @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
+ if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty");
+ while (true) {
+ fromIndex = indexOf(bytes.getByte(0), fromIndex);
+ if (fromIndex == -1) {
+ return -1;
+ }
+ if (rangeEquals(fromIndex, bytes)) {
+ return fromIndex;
+ }
+ fromIndex++;
+ }
+ }
+
+ @Override public long indexOfElement(ByteString targetBytes) {
+ return indexOfElement(targetBytes, 0);
+ }
+
+ @Override public long indexOfElement(ByteString targetBytes, long fromIndex) {
+ if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
+
+ Segment s = head;
+ if (s == null) return -1L;
+ long offset = 0L;
+ byte[] toFind = targetBytes.toByteArray();
+ do {
+ int segmentByteCount = s.limit - s.pos;
+ if (fromIndex >= segmentByteCount) {
+ fromIndex -= segmentByteCount;
+ } else {
+ byte[] data = s.data;
+ for (long pos = s.pos + fromIndex, limit = s.limit; pos < limit; pos++) {
+ byte b = data[(int) pos];
+ for (byte targetByte : toFind) {
+ if (b == targetByte) return offset + pos - s.pos;
+ }
+ }
+ fromIndex = 0;
+ }
+ offset += segmentByteCount;
+ s = s.next;
+ } while (s != head);
+ return -1L;
+ }
+
+ boolean rangeEquals(long offset, ByteString bytes) {
+ int byteCount = bytes.size();
+ if (size - offset < byteCount) {
+ return false;
+ }
+ for (int i = 0; i < byteCount; i++) {
+ if (getByte(offset + i) != bytes.getByte(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override public void flush() {
+ }
+
+ @Override public void close() {
+ }
+
+ @Override public Timeout timeout() {
+ return Timeout.NONE;
+ }
+
+ /** For testing. This returns the sizes of the segments in this buffer. */
+ List<Integer> segmentSizes() {
+ if (head == null) return Collections.emptyList();
+ List<Integer> result = new ArrayList<>();
+ result.add(head.limit - head.pos);
+ for (Segment s = head.next; s != head; s = s.next) {
+ result.add(s.limit - s.pos);
+ }
+ return result;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Buffer)) return false;
+ Buffer that = (Buffer) o;
+ if (size != that.size) return false;
+ if (size == 0) return true; // Both buffers are empty.
+
+ Segment sa = this.head;
+ Segment sb = that.head;
+ int posA = sa.pos;
+ int posB = sb.pos;
+
+ for (long pos = 0, count; pos < size; pos += count) {
+ count = Math.min(sa.limit - posA, sb.limit - posB);
+
+ for (int i = 0; i < count; i++) {
+ if (sa.data[posA++] != sb.data[posB++]) return false;
+ }
+
+ if (posA == sa.limit) {
+ sa = sa.next;
+ posA = sa.pos;
+ }
+
+ if (posB == sb.limit) {
+ sb = sb.next;
+ posB = sb.pos;
+ }
+ }
+
+ return true;
+ }
+
+ @Override public int hashCode() {
+ Segment s = head;
+ if (s == null) return 0;
+ int result = 1;
+ do {
+ for (int pos = s.pos, limit = s.limit; pos < limit; pos++) {
+ result = 31 * result + s.data[pos];
+ }
+ s = s.next;
+ } while (s != head);
+ return result;
+ }
+
+ @Override public String toString() {
+ if (size == 0) {
+ return "Buffer[size=0]";
+ }
+
+ if (size <= 16) {
+ ByteString data = clone().readByteString();
+ return String.format("Buffer[size=%s data=%s]", size, data.hex());
+ }
+
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(head.data, head.pos, head.limit - head.pos);
+ for (Segment s = head.next; s != head; s = s.next) {
+ md5.update(s.data, s.pos, s.limit - s.pos);
+ }
+ return String.format("Buffer[size=%s md5=%s]",
+ size, ByteString.of(md5.digest()).hex());
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** Returns a deep copy of this buffer. */
+ @Override public Buffer clone() {
+ Buffer result = new Buffer();
+ if (size == 0) return result;
+
+ result.head = new Segment(head);
+ result.head.next = result.head.prev = result.head;
+ for (Segment s = head.next; s != head; s = s.next) {
+ result.head.prev.push(new Segment(s));
+ }
+ result.size = size;
+ return result;
+ }
+
+ /** Returns an immutable copy of this buffer as a byte string. */
+ public ByteString snapshot() {
+ if (size > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size);
+ }
+ return snapshot((int) size);
+ }
+
+ /**
+ * Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string.
+ */
+ public ByteString snapshot(int byteCount) {
+ if (byteCount == 0) return ByteString.EMPTY;
+ return new SegmentedByteString(this, byteCount);
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java
new file mode 100644
index 0000000..7dd3f13
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+/**
+ * A sink that keeps a buffer internally so that callers can do small writes
+ * without a performance penalty.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface BufferedSink extends Sink {
+ /** Returns this sink's internal buffer. */
+ Buffer buffer();
+
+ BufferedSink write(ByteString byteString) throws IOException;
+
+ /**
+ * Like {@link OutputStream#write(byte[])}, this writes a complete byte array to
+ * this sink.
+ */
+ BufferedSink write(byte[] source) throws IOException;
+
+ /**
+ * Like {@link OutputStream#write(byte[], int, int)}, this writes {@code byteCount}
+ * bytes of {@code source}, starting at {@code offset}.
+ */
+ BufferedSink write(byte[] source, int offset, int byteCount) throws IOException;
+
+ /**
+ * Removes all bytes from {@code source} and appends them to this sink. Returns the
+ * number of bytes read which will be 0 if {@code source} is exhausted.
+ */
+ long writeAll(Source source) throws IOException;
+
+ /** Removes {@code byteCount} bytes from {@code source} and appends them to this sink. */
+ BufferedSink write(Source source, long byteCount) throws IOException;
+
+ /** Encodes {@code string} in UTF-8 and writes it to this sink. */
+ BufferedSink writeUtf8(String string) throws IOException;
+
+ /**
+ * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in
+ * UTF-8 and writes it to this sink.
+ */
+ BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException;
+
+ /** Encodes {@code codePoint} in UTF-8 and writes it to this sink. */
+ BufferedSink writeUtf8CodePoint(int codePoint) throws IOException;
+
+ /** Encodes {@code string} in {@code charset} and writes it to this sink. */
+ BufferedSink writeString(String string, Charset charset) throws IOException;
+
+ /**
+ * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in
+ * {@code charset} and writes it to this sink.
+ */
+ BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset)
+ throws IOException;
+
+ /** Writes a byte to this sink. */
+ BufferedSink writeByte(int b) throws IOException;
+
+ /** Writes a big-endian short to this sink using two bytes. */
+ BufferedSink writeShort(int s) throws IOException;
+
+ /** Writes a little-endian short to this sink using two bytes. */
+ BufferedSink writeShortLe(int s) throws IOException;
+
+ /** Writes a big-endian int to this sink using four bytes. */
+ BufferedSink writeInt(int i) throws IOException;
+
+ /** Writes a little-endian int to this sink using four bytes. */
+ BufferedSink writeIntLe(int i) throws IOException;
+
+ /** Writes a big-endian long to this sink using eight bytes. */
+ BufferedSink writeLong(long v) throws IOException;
+
+ /** Writes a little-endian long to this sink using eight bytes. */
+ BufferedSink writeLongLe(long v) throws IOException;
+
+ /** Writes a long to this sink in signed decimal form (i.e., as a string in base 10). */
+ BufferedSink writeDecimalLong(long v) throws IOException;
+
+ /** Writes a long to this sink in hexadecimal form (i.e., as a string in base 16). */
+ BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException;
+
+ /**
+ * Writes complete segments to the underlying sink, if one exists. Like {@link #flush}, but
+ * weaker. Use this to limit the memory held in the buffer to a single segment.
+ */
+ BufferedSink emitCompleteSegments() throws IOException;
+
+ /**
+ * Writes all buffered data to the underlying sink, if one exists. Like {@link #flush}, but
+ * weaker. Call this before this buffered sink goes out of scope so that its data can reach its
+ * destination.
+ */
+ BufferedSink emit() throws IOException;
+
+ /** Returns an output stream that writes to this sink. */
+ OutputStream outputStream();
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java
new file mode 100644
index 0000000..a2fdad8
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java
@@ -0,0 +1,253 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+/**
+ * A source that keeps a buffer internally so that callers can do small reads
+ * without a performance penalty. It also allows clients to read ahead,
+ * buffering as much as necessary before consuming input.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface BufferedSource extends Source {
+ /** Returns this source's internal buffer. */
+ Buffer buffer();
+
+ /**
+ * Returns true if there are no more bytes in this source. This will block
+ * until there are bytes to read or the source is definitely exhausted.
+ */
+ boolean exhausted() throws IOException;
+
+ /**
+ * Returns when the buffer contains at least {@code byteCount} bytes. Throws
+ * an {@link java.io.EOFException} if the source is exhausted before the
+ * required bytes can be read.
+ */
+ void require(long byteCount) throws IOException;
+
+ /**
+ * Returns true when the buffer contains at least {@code byteCount} bytes,
+ * expanding it as necessary. Returns false if the source is exhausted before
+ * the requested bytes can be read.
+ */
+ boolean request(long byteCount) throws IOException;
+
+ /** Removes a byte from this source and returns it. */
+ byte readByte() throws IOException;
+
+ /** Removes two bytes from this source and returns a big-endian short. */
+ short readShort() throws IOException;
+
+ /** Removes two bytes from this source and returns a little-endian short. */
+ short readShortLe() throws IOException;
+
+ /** Removes four bytes from this source and returns a big-endian int. */
+ int readInt() throws IOException;
+
+ /** Removes four bytes from this source and returns a little-endian int. */
+ int readIntLe() throws IOException;
+
+ /** Removes eight bytes from this source and returns a big-endian long. */
+ long readLong() throws IOException;
+
+ /** Removes eight bytes from this source and returns a little-endian long. */
+ long readLongLe() throws IOException;
+
+ /**
+ * Reads a long from this source in signed decimal form (i.e., as a string in base 10 with
+ * optional leading '-'). This will iterate until a non-digit character is found.
+ *
+ * @throws NumberFormatException if the found digits do not fit into a {@code long} or a decimal
+ * number was not present.
+ */
+ long readDecimalLong() throws IOException;
+
+ /**
+ * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). This will
+ * iterate until a non-hexadecimal character is found.
+ *
+ * @throws NumberFormatException if the found hexadecimal does not fit into a {@code long} or
+ * hexadecimal was not found.
+ */
+ long readHexadecimalUnsignedLong() throws IOException;
+
+ /**
+ * Reads and discards {@code byteCount} bytes from this source. Throws an
+ * {@link java.io.EOFException} if the source is exhausted before the
+ * requested bytes can be skipped.
+ */
+ void skip(long byteCount) throws IOException;
+
+ /** Removes all bytes bytes from this and returns them as a byte string. */
+ ByteString readByteString() throws IOException;
+
+ /** Removes {@code byteCount} bytes from this and returns them as a byte string. */
+ ByteString readByteString(long byteCount) throws IOException;
+
+ /** Removes all bytes from this and returns them as a byte array. */
+ byte[] readByteArray() throws IOException;
+
+ /** Removes {@code byteCount} bytes from this and returns them as a byte array. */
+ byte[] readByteArray(long byteCount) throws IOException;
+
+ /**
+ * Removes up to {@code sink.length} bytes from this and copies them into {@code sink}.
+ * Returns the number of bytes read, or -1 if this source is exhausted.
+ */
+ int read(byte[] sink) throws IOException;
+
+ /**
+ * Removes exactly {@code sink.length} bytes from this and copies them into {@code sink}.
+ * Throws an {@link java.io.EOFException} if the requested number of bytes cannot be read.
+ */
+ void readFully(byte[] sink) throws IOException;
+
+ /**
+ * Removes up to {@code byteCount} bytes from this and copies them into {@code sink} at
+ * {@code offset}. Returns the number of bytes read, or -1 if this source is exhausted.
+ */
+ int read(byte[] sink, int offset, int byteCount) throws IOException;
+
+ /**
+ * Removes exactly {@code byteCount} bytes from this and appends them to
+ * {@code sink}. Throws an {@link java.io.EOFException} if the requested
+ * number of bytes cannot be read.
+ */
+ void readFully(Buffer sink, long byteCount) throws IOException;
+
+ /**
+ * Removes all bytes from this and appends them to {@code sink}. Returns the
+ * total number of bytes written to {@code sink} which will be 0 if this is
+ * exhausted.
+ */
+ long readAll(Sink sink) throws IOException;
+
+ /** Removes all bytes from this, decodes them as UTF-8, and returns the string. */
+ String readUtf8() throws IOException;
+
+ /**
+ * Removes {@code byteCount} bytes from this, decodes them as UTF-8, and
+ * returns the string.
+ */
+ String readUtf8(long byteCount) throws IOException;
+
+ /**
+ * Removes and returns characters up to but not including the next line break.
+ * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
+ * not included in the result.
+ *
+ * <p><strong>On the end of the stream this method returns null,</strong> just
+ * like {@link java.io.BufferedReader}. If the source doesn't end with a line
+ * break then an implicit line break is assumed. Null is returned once the
+ * source is exhausted. Use this for human-generated data, where a trailing
+ * line break is optional.
+ */
+ String readUtf8Line() throws IOException;
+
+ /**
+ * Removes and returns characters up to but not including the next line break.
+ * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
+ * not included in the result.
+ *
+ * <p><strong>On the end of the stream this method throws.</strong> Every call
+ * must consume either '\r\n' or '\n'. If these characters are absent in the
+ * stream, an {@link java.io.EOFException} is thrown. Use this for
+ * machine-generated data where a missing line break implies truncated input.
+ */
+ String readUtf8LineStrict() throws IOException;
+
+ /**
+ * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary.
+ *
+ * <p>If this source is exhausted before a complete code point can be read, this throws an {@link
+ * java.io.EOFException} and consumes no input.
+ *
+ * <p>If this source doesn't start with a properly-encoded UTF-8 code point, this method will
+ * remove 1 or more non-UTF-8 bytes and return the replacement character ({@code U+FFFD}). This
+ * covers encoding problems (the input is not properly-encoded UTF-8), characters out of range
+ * (beyond the 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and
+ * overlong encodings (such as {@code 0xc080} for the NUL character in modified UTF-8).
+ */
+ int readUtf8CodePoint() throws IOException;
+
+ /**
+ * Removes all bytes from this, decodes them as {@code charset}, and returns
+ * the string.
+ */
+ String readString(Charset charset) throws IOException;
+
+ /**
+ * Removes {@code byteCount} bytes from this, decodes them as {@code charset},
+ * and returns the string.
+ */
+ String readString(long byteCount, Charset charset) throws IOException;
+
+ /**
+ * Returns the index of the first {@code b} in the buffer. This expands the
+ * buffer as necessary until {@code b} is found. This reads an unbounded
+ * number of bytes into the buffer. Returns -1 if the stream is exhausted
+ * before the requested byte is found.
+ */
+ long indexOf(byte b) throws IOException;
+
+ /**
+ * Returns the index of the first {@code b} in the buffer at or after {@code
+ * fromIndex}. This expands the buffer as necessary until {@code b} is found.
+ * This reads an unbounded number of bytes into the buffer. Returns -1 if the
+ * stream is exhausted before the requested byte is found.
+ */
+ long indexOf(byte b, long fromIndex) throws IOException;
+
+ /**
+ * Returns the index of the first match for {@code bytes} in the buffer. This expands the buffer
+ * as necessary until {@code bytes} is found. This reads an unbounded number of bytes into the
+ * buffer. Returns -1 if the stream is exhausted before the requested bytes are found.
+ */
+ long indexOf(ByteString bytes) throws IOException;
+
+ /**
+ * Returns the index of the first match for {@code bytes} in the buffer at or after {@code
+ * fromIndex}. This expands the buffer as necessary until {@code bytes} is found. This reads an
+ * unbounded number of bytes into the buffer. Returns -1 if the stream is exhausted before the
+ * requested bytes are found.
+ */
+ long indexOf(ByteString bytes, long fromIndex) throws IOException;
+
+ /**
+ * Returns the index of the first byte in {@code targetBytes} in the buffer.
+ * This expands the buffer as necessary until a target byte is found. This
+ * reads an unbounded number of bytes into the buffer. Returns -1 if the
+ * stream is exhausted before the requested byte is found.
+ */
+ long indexOfElement(ByteString targetBytes) throws IOException;
+
+ /**
+ * Returns the index of the first byte in {@code targetBytes} in the buffer
+ * at or after {@code fromIndex}. This expands the buffer as necessary until
+ * a target byte is found. This reads an unbounded number of bytes into the
+ * buffer. Returns -1 if the stream is exhausted before the requested byte is
+ * found.
+ */
+ long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException;
+
+ /** Returns an input stream that reads from this source. */
+ InputStream inputStream();
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java
new file mode 100644
index 0000000..ade1ea2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java
@@ -0,0 +1,381 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import static com.android.okhttp.okio.Util.arrayRangeEquals;
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * An immutable sequence of bytes.
+ *
+ * <p>Byte strings compare lexicographically as a sequence of <strong>unsigned</strong> bytes. That
+ * is, the byte string {@code ff} sorts after {@code 00}. This is counter to the sort order of the
+ * corresponding bytes, where {@code -1} sorts before {@code 0}.
+ *
+ * <p><strong>Full disclosure:</strong> this class provides untrusted input and output streams with
+ * raw access to the underlying byte array. A hostile stream implementation could keep a reference
+ * to the mutable byte string, violating the immutable guarantee of this class. For this reason a
+ * byte string's immutability guarantee cannot be relied upon for security in applets and other
+ * environments that run both trusted and untrusted code in the same process.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class ByteString implements Serializable, Comparable<ByteString> {
+ static final char[] HEX_DIGITS =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ private static final long serialVersionUID = 1L;
+
+ /** A singleton empty {@code ByteString}. */
+ public static final ByteString EMPTY = ByteString.of();
+
+ final byte[] data;
+ transient int hashCode; // Lazily computed; 0 if unknown.
+ transient String utf8; // Lazily computed.
+
+ ByteString(byte[] data) {
+ this.data = data; // Trusted internal constructor doesn't clone data.
+ }
+
+ /**
+ * Returns a new byte string containing a clone of the bytes of {@code data}.
+ */
+ public static ByteString of(byte... data) {
+ if (data == null) throw new IllegalArgumentException("data == null");
+ return new ByteString(data.clone());
+ }
+
+ /**
+ * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting
+ * at {@code offset}.
+ */
+ public static ByteString of(byte[] data, int offset, int byteCount) {
+ if (data == null) throw new IllegalArgumentException("data == null");
+ checkOffsetAndCount(data.length, offset, byteCount);
+
+ byte[] copy = new byte[byteCount];
+ System.arraycopy(data, offset, copy, 0, byteCount);
+ return new ByteString(copy);
+ }
+
+ /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */
+ public static ByteString encodeUtf8(String s) {
+ if (s == null) throw new IllegalArgumentException("s == null");
+ ByteString byteString = new ByteString(s.getBytes(Util.UTF_8));
+ byteString.utf8 = s;
+ return byteString;
+ }
+
+ /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */
+ public String utf8() {
+ String result = utf8;
+ // We don't care if we double-allocate in racy code.
+ return result != null ? result : (utf8 = new String(data, Util.UTF_8));
+ }
+
+ /**
+ * Returns this byte string encoded as <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the
+ * RFC, the returned string does not wrap lines at 76 columns.
+ */
+ public String base64() {
+ return Base64.encode(data);
+ }
+
+ /** Returns the MD5 hash of this byte string. */
+ public ByteString md5() {
+ return digest("MD5");
+ }
+
+ /** Returns the SHA-256 hash of this byte string. */
+ public ByteString sha256() {
+ return digest("SHA-256");
+ }
+
+ private ByteString digest(String digest) {
+ try {
+ return ByteString.of(MessageDigest.getInstance(digest).digest(data));
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Returns this byte string encoded as <a href="http://www.ietf.org/rfc/rfc4648.txt">URL-safe
+ * Base64</a>.
+ */
+ public String base64Url() {
+ return Base64.encodeUrl(data);
+ }
+
+ /**
+ * Decodes the Base64-encoded bytes and returns their value as a byte string.
+ * Returns null if {@code base64} is not a Base64-encoded sequence of bytes.
+ */
+ public static ByteString decodeBase64(String base64) {
+ if (base64 == null) throw new IllegalArgumentException("base64 == null");
+ byte[] decoded = Base64.decode(base64);
+ return decoded != null ? new ByteString(decoded) : null;
+ }
+
+ /** Returns this byte string encoded in hexadecimal. */
+ public String hex() {
+ char[] result = new char[data.length * 2];
+ int c = 0;
+ for (byte b : data) {
+ result[c++] = HEX_DIGITS[(b >> 4) & 0xf];
+ result[c++] = HEX_DIGITS[b & 0xf];
+ }
+ return new String(result);
+ }
+
+ /** Decodes the hex-encoded bytes and returns their value a byte string. */
+ public static ByteString decodeHex(String hex) {
+ if (hex == null) throw new IllegalArgumentException("hex == null");
+ if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex);
+
+ byte[] result = new byte[hex.length() / 2];
+ for (int i = 0; i < result.length; i++) {
+ int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4;
+ int d2 = decodeHexDigit(hex.charAt(i * 2 + 1));
+ result[i] = (byte) (d1 + d2);
+ }
+ return of(result);
+ }
+
+ private static int decodeHexDigit(char c) {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ throw new IllegalArgumentException("Unexpected hex digit: " + c);
+ }
+
+ /**
+ * Reads {@code count} bytes from {@code in} and returns the result.
+ *
+ * @throws java.io.EOFException if {@code in} has fewer than {@code count}
+ * bytes to read.
+ */
+ public static ByteString read(InputStream in, int byteCount) throws IOException {
+ if (in == null) throw new IllegalArgumentException("in == null");
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+
+ byte[] result = new byte[byteCount];
+ for (int offset = 0, read; offset < byteCount; offset += read) {
+ read = in.read(result, offset, byteCount - offset);
+ if (read == -1) throw new EOFException();
+ }
+ return new ByteString(result);
+ }
+
+ /**
+ * Returns a byte string equal to this byte string, but with the bytes 'A'
+ * through 'Z' replaced with the corresponding byte in 'a' through 'z'.
+ * Returns this byte string if it contains no bytes in 'A' through 'Z'.
+ */
+ public ByteString toAsciiLowercase() {
+ // Search for an uppercase character. If we don't find one, return this.
+ for (int i = 0; i < data.length; i++) {
+ byte c = data[i];
+ if (c < 'A' || c > 'Z') continue;
+
+ // If we reach this point, this string is not not lowercase. Create and
+ // return a new byte string.
+ byte[] lowercase = data.clone();
+ lowercase[i++] = (byte) (c - ('A' - 'a'));
+ for (; i < lowercase.length; i++) {
+ c = lowercase[i];
+ if (c < 'A' || c > 'Z') continue;
+ lowercase[i] = (byte) (c - ('A' - 'a'));
+ }
+ return new ByteString(lowercase);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a byte string equal to this byte string, but with the bytes 'a'
+ * through 'z' replaced with the corresponding byte in 'A' through 'Z'.
+ * Returns this byte string if it contains no bytes in 'a' through 'z'.
+ */
+ public ByteString toAsciiUppercase() {
+ // Search for an lowercase character. If we don't find one, return this.
+ for (int i = 0; i < data.length; i++) {
+ byte c = data[i];
+ if (c < 'a' || c > 'z') continue;
+
+ // If we reach this point, this string is not not uppercase. Create and
+ // return a new byte string.
+ byte[] lowercase = data.clone();
+ lowercase[i++] = (byte) (c - ('a' - 'A'));
+ for (; i < lowercase.length; i++) {
+ c = lowercase[i];
+ if (c < 'a' || c > 'z') continue;
+ lowercase[i] = (byte) (c - ('a' - 'A'));
+ }
+ return new ByteString(lowercase);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a byte string that is a substring of this byte string, beginning at the specified
+ * index until the end of this string. Returns this byte string if {@code beginIndex} is 0.
+ */
+ public ByteString substring(int beginIndex) {
+ return substring(beginIndex, data.length);
+ }
+
+ /**
+ * Returns a byte string that is a substring of this byte string, beginning at the specified
+ * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if
+ * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string.
+ */
+ public ByteString substring(int beginIndex, int endIndex) {
+ if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0");
+ if (endIndex > data.length) {
+ throw new IllegalArgumentException("endIndex > length(" + data.length + ")");
+ }
+
+ int subLen = endIndex - beginIndex;
+ if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex");
+
+ if ((beginIndex == 0) && (endIndex == data.length)) {
+ return this;
+ }
+
+ byte[] copy = new byte[subLen];
+ System.arraycopy(data, beginIndex, copy, 0, subLen);
+ return new ByteString(copy);
+ }
+
+ /** Returns the byte at {@code pos}. */
+ public byte getByte(int pos) {
+ return data[pos];
+ }
+
+ /**
+ * Returns the number of bytes in this ByteString.
+ */
+ public int size() {
+ return data.length;
+ }
+
+ /**
+ * Returns a byte array containing a copy of the bytes in this {@code ByteString}.
+ */
+ public byte[] toByteArray() {
+ return data.clone();
+ }
+
+ /** Writes the contents of this byte string to {@code out}. */
+ public void write(OutputStream out) throws IOException {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ out.write(data);
+ }
+
+ /** Writes the contents of this byte string to {@code buffer}. */
+ void write(Buffer buffer) {
+ buffer.write(data, 0, data.length);
+ }
+
+ /**
+ * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
+ * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
+ * out of bounds.
+ */
+ public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) {
+ return other.rangeEquals(otherOffset, this.data, offset, byteCount);
+ }
+
+ /**
+ * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
+ * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
+ * out of bounds.
+ */
+ public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) {
+ return offset <= data.length - byteCount
+ && otherOffset <= other.length - byteCount
+ && arrayRangeEquals(data, offset, other, otherOffset, byteCount);
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o == this) return true;
+ return o instanceof ByteString
+ && ((ByteString) o).size() == data.length
+ && ((ByteString) o).rangeEquals(0, data, 0, data.length);
+ }
+
+ @Override public int hashCode() {
+ int result = hashCode;
+ return result != 0 ? result : (hashCode = Arrays.hashCode(data));
+ }
+
+ @Override public int compareTo(ByteString byteString) {
+ int sizeA = size();
+ int sizeB = byteString.size();
+ for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) {
+ int byteA = getByte(i) & 0xff;
+ int byteB = byteString.getByte(i) & 0xff;
+ if (byteA == byteB) continue;
+ return byteA < byteB ? -1 : 1;
+ }
+ if (sizeA == sizeB) return 0;
+ return sizeA < sizeB ? -1 : 1;
+ }
+
+ @Override public String toString() {
+ if (data.length == 0) {
+ return "ByteString[size=0]";
+ }
+
+ if (data.length <= 16) {
+ return String.format("ByteString[size=%s data=%s]", data.length, hex());
+ }
+
+ return String.format("ByteString[size=%s md5=%s]", data.length, md5().hex());
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException {
+ int dataLength = in.readInt();
+ ByteString byteString = ByteString.read(in, dataLength);
+ try {
+ Field field = ByteString.class.getDeclaredField("data");
+ field.setAccessible(true);
+ field.set(this, byteString.data);
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError();
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeInt(data.length);
+ out.write(data);
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java
new file mode 100644
index 0000000..7df783e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java
@@ -0,0 +1,160 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.zip.Deflater;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * A sink that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a> to
+ * compress data written to another source.
+ *
+ * <h3>Sync flush</h3>
+ * Aggressive flushing of this stream may result in reduced compression. Each
+ * call to {@link #flush} immediately compresses all currently-buffered data;
+ * this early compression may be less effective than compression performed
+ * without flushing.
+ *
+ * <p>This is equivalent to using {@link Deflater} with the sync flush option.
+ * This class does not offer any partial flush mechanism. For best performance,
+ * only call {@link #flush} when application behavior requires it.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class DeflaterSink implements Sink {
+ private final BufferedSink sink;
+ private final Deflater deflater;
+ private boolean closed;
+
+ public DeflaterSink(Sink sink, Deflater deflater) {
+ this(Okio.buffer(sink), deflater);
+ }
+
+ /**
+ * This package-private constructor shares a buffer with its trusted caller.
+ * In general we can't share a BufferedSource because the deflater holds input
+ * bytes until they are inflated.
+ */
+ DeflaterSink(BufferedSink sink, Deflater deflater) {
+ if (sink == null) throw new IllegalArgumentException("source == null");
+ if (deflater == null) throw new IllegalArgumentException("inflater == null");
+ this.sink = sink;
+ this.deflater = deflater;
+ }
+
+ @Override public void write(Buffer source, long byteCount)
+ throws IOException {
+ checkOffsetAndCount(source.size, 0, byteCount);
+ while (byteCount > 0) {
+ // Share bytes from the head segment of 'source' with the deflater.
+ Segment head = source.head;
+ int toDeflate = (int) Math.min(byteCount, head.limit - head.pos);
+ deflater.setInput(head.data, head.pos, toDeflate);
+
+ // Deflate those bytes into sink.
+ deflate(false);
+
+ // Mark those bytes as read.
+ source.size -= toDeflate;
+ head.pos += toDeflate;
+ if (head.pos == head.limit) {
+ source.head = head.pop();
+ SegmentPool.recycle(head);
+ }
+
+ byteCount -= toDeflate;
+ }
+ }
+
+ // ANDROID-BEGIN
+ // @IgnoreJRERequirement
+ // ANDROID-END
+ private void deflate(boolean syncFlush) throws IOException {
+ Buffer buffer = sink.buffer();
+ while (true) {
+ Segment s = buffer.writableSegment(1);
+
+ // The 4-parameter overload of deflate() doesn't exist in the RI until
+ // Java 1.7, and is public (although with @hide) on Android since 2.3.
+ // The @hide tag means that this code won't compile against the Android
+ // 2.3 SDK, but it will run fine there.
+ int deflated = syncFlush
+ ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
+ : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit);
+
+ if (deflated > 0) {
+ s.limit += deflated;
+ buffer.size += deflated;
+ sink.emitCompleteSegments();
+ } else if (deflater.needsInput()) {
+ if (s.pos == s.limit) {
+ // We allocated a tail segment, but didn't end up needing it. Recycle!
+ buffer.head = s.pop();
+ SegmentPool.recycle(s);
+ }
+ return;
+ }
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ deflate(true);
+ sink.flush();
+ }
+
+ void finishDeflate() throws IOException {
+ deflater.finish();
+ deflate(false);
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ // Emit deflated data to the underlying sink. If this fails, we still need
+ // to close the deflater and the sink; otherwise we risk leaking resources.
+ Throwable thrown = null;
+ try {
+ finishDeflate();
+ } catch (Throwable e) {
+ thrown = e;
+ }
+
+ try {
+ deflater.end();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+
+ try {
+ sink.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ closed = true;
+
+ if (thrown != null) Util.sneakyRethrow(thrown);
+ }
+
+ @Override public Timeout timeout() {
+ return sink.timeout();
+ }
+
+ @Override public String toString() {
+ return "DeflaterSink(" + sink + ")";
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java
new file mode 100644
index 0000000..5dcad18
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java
@@ -0,0 +1,55 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+
+/** A {@link Sink} which forwards calls to another. Useful for subclassing.
+ * @hide This class is not part of the Android public SDK API*/
+public abstract class ForwardingSink implements Sink {
+ private final Sink delegate;
+
+ public ForwardingSink(Sink delegate) {
+ if (delegate == null) throw new IllegalArgumentException("delegate == null");
+ this.delegate = delegate;
+ }
+
+ /** {@link Sink} to which this instance is delegating. */
+ public final Sink delegate() {
+ return delegate;
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ delegate.write(source, byteCount);
+ }
+
+ @Override public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ @Override public Timeout timeout() {
+ return delegate.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName() + "(" + delegate.toString() + ")";
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java
new file mode 100644
index 0000000..12fd15e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java
@@ -0,0 +1,51 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+
+/** A {@link Source} which forwards calls to another. Useful for subclassing.
+ * @hide This class is not part of the Android public SDK API*/
+public abstract class ForwardingSource implements Source {
+ private final Source delegate;
+
+ public ForwardingSource(Source delegate) {
+ if (delegate == null) throw new IllegalArgumentException("delegate == null");
+ this.delegate = delegate;
+ }
+
+ /** {@link Source} to which this instance is delegating. */
+ public final Source delegate() {
+ return delegate;
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ return delegate.read(sink, byteCount);
+ }
+
+ @Override public Timeout timeout() {
+ return delegate.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName() + "(" + delegate.toString() + ")";
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java
new file mode 100644
index 0000000..8f6e890
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java
@@ -0,0 +1,74 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/** A {@link Timeout} which forwards calls to another. Useful for subclassing.
+ * @hide This class is not part of the Android public SDK API*/
+public class ForwardingTimeout extends Timeout {
+ private Timeout delegate;
+
+ public ForwardingTimeout(Timeout delegate) {
+ if (delegate == null) throw new IllegalArgumentException("delegate == null");
+ this.delegate = delegate;
+ }
+
+ /** {@link Timeout} instance to which this instance is currently delegating. */
+ public final Timeout delegate() {
+ return delegate;
+ }
+
+ public final ForwardingTimeout setDelegate(Timeout delegate) {
+ if (delegate == null) throw new IllegalArgumentException("delegate == null");
+ this.delegate = delegate;
+ return this;
+ }
+
+ @Override public Timeout timeout(long timeout, TimeUnit unit) {
+ return delegate.timeout(timeout, unit);
+ }
+
+ @Override public long timeoutNanos() {
+ return delegate.timeoutNanos();
+ }
+
+ @Override public boolean hasDeadline() {
+ return delegate.hasDeadline();
+ }
+
+ @Override public long deadlineNanoTime() {
+ return delegate.deadlineNanoTime();
+ }
+
+ @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
+ return delegate.deadlineNanoTime(deadlineNanoTime);
+ }
+
+ @Override public Timeout clearTimeout() {
+ return delegate.clearTimeout();
+ }
+
+ @Override public Timeout clearDeadline() {
+ return delegate.clearDeadline();
+ }
+
+ @Override public void throwIfReached() throws IOException {
+ delegate.throwIfReached();
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java
new file mode 100644
index 0000000..a2d02b2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+
+import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
+
+/**
+ * A sink that uses <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> to
+ * compress written data to another sink.
+ *
+ * <h3>Sync flush</h3>
+ * Aggressive flushing of this stream may result in reduced compression. Each
+ * call to {@link #flush} immediately compresses all currently-buffered data;
+ * this early compression may be less effective than compression performed
+ * without flushing.
+ *
+ * <p>This is equivalent to using {@link Deflater} with the sync flush option.
+ * This class does not offer any partial flush mechanism. For best performance,
+ * only call {@link #flush} when application behavior requires it.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class GzipSink implements Sink {
+ /** Sink into which the GZIP format is written. */
+ private final BufferedSink sink;
+
+ /** The deflater used to compress the body. */
+ private final Deflater deflater;
+
+ /**
+ * The deflater sink takes care of moving data between decompressed source and
+ * compressed sink buffers.
+ */
+ private final DeflaterSink deflaterSink;
+
+ private boolean closed;
+
+ /** Checksum calculated for the compressed body. */
+ private final CRC32 crc = new CRC32();
+
+ public GzipSink(Sink sink) {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+ this.deflater = new Deflater(DEFAULT_COMPRESSION, true /* No wrap */);
+ this.sink = Okio.buffer(sink);
+ this.deflaterSink = new DeflaterSink(this.sink, deflater);
+
+ writeHeader();
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (byteCount == 0) return;
+
+ updateCrc(source, byteCount);
+ deflaterSink.write(source, byteCount);
+ }
+
+ @Override public void flush() throws IOException {
+ deflaterSink.flush();
+ }
+
+ @Override public Timeout timeout() {
+ return sink.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ // This method delegates to the DeflaterSink for finishing the deflate process
+ // but keeps responsibility for releasing the deflater's resources. This is
+ // necessary because writeFooter needs to query the processed byte count which
+ // only works when the deflater is still open.
+
+ Throwable thrown = null;
+ try {
+ deflaterSink.finishDeflate();
+ writeFooter();
+ } catch (Throwable e) {
+ thrown = e;
+ }
+
+ try {
+ deflater.end();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+
+ try {
+ sink.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ closed = true;
+
+ if (thrown != null) Util.sneakyRethrow(thrown);
+ }
+
+ private void writeHeader() {
+ // Write the Gzip header directly into the buffer for the sink to avoid handling IOException.
+ Buffer buffer = this.sink.buffer();
+ buffer.writeShort(0x1f8b); // Two-byte Gzip ID.
+ buffer.writeByte(0x08); // 8 == Deflate compression method.
+ buffer.writeByte(0x00); // No flags.
+ buffer.writeInt(0x00); // No modification time.
+ buffer.writeByte(0x00); // No extra flags.
+ buffer.writeByte(0x00); // No OS.
+ }
+
+ private void writeFooter() throws IOException {
+ sink.writeIntLe((int) crc.getValue()); // CRC of original data.
+ sink.writeIntLe(deflater.getTotalIn()); // Length of original data.
+ }
+
+ /** Updates the CRC with the given bytes. */
+ private void updateCrc(Buffer buffer, long byteCount) {
+ for (Segment head = buffer.head; byteCount > 0; head = head.next) {
+ int segmentLength = (int) Math.min(byteCount, head.limit - head.pos);
+ crc.update(head.data, head.pos, segmentLength);
+ byteCount -= segmentLength;
+ }
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java
new file mode 100644
index 0000000..f9586c0
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java
@@ -0,0 +1,211 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.zip.CRC32;
+import java.util.zip.Inflater;
+
+/**
+ * A source that uses <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> to
+ * decompress data read from another source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class GzipSource implements Source {
+ private static final byte FHCRC = 1;
+ private static final byte FEXTRA = 2;
+ private static final byte FNAME = 3;
+ private static final byte FCOMMENT = 4;
+
+ private static final byte SECTION_HEADER = 0;
+ private static final byte SECTION_BODY = 1;
+ private static final byte SECTION_TRAILER = 2;
+ private static final byte SECTION_DONE = 3;
+
+ /** The current section. Always progresses forward. */
+ private int section = SECTION_HEADER;
+
+ /**
+ * Our source should yield a GZIP header (which we consume directly), followed
+ * by deflated bytes (which we consume via an InflaterSource), followed by a
+ * GZIP trailer (which we also consume directly).
+ */
+ private final BufferedSource source;
+
+ /** The inflater used to decompress the deflated body. */
+ private final Inflater inflater;
+
+ /**
+ * The inflater source takes care of moving data between compressed source and
+ * decompressed sink buffers.
+ */
+ private final InflaterSource inflaterSource;
+
+ /** Checksum used to check both the GZIP header and decompressed body. */
+ private final CRC32 crc = new CRC32();
+
+ public GzipSource(Source source) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ this.inflater = new Inflater(true);
+ this.source = Okio.buffer(source);
+ this.inflaterSource = new InflaterSource(this.source, inflater);
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (byteCount == 0) return 0;
+
+ // If we haven't consumed the header, we must consume it before anything else.
+ if (section == SECTION_HEADER) {
+ consumeHeader();
+ section = SECTION_BODY;
+ }
+
+ // Attempt to read at least a byte of the body. If we do, we're done.
+ if (section == SECTION_BODY) {
+ long offset = sink.size;
+ long result = inflaterSource.read(sink, byteCount);
+ if (result != -1) {
+ updateCrc(sink, offset, result);
+ return result;
+ }
+ section = SECTION_TRAILER;
+ }
+
+ // The body is exhausted; time to read the trailer. We always consume the
+ // trailer before returning a -1 exhausted result; that way if you read to
+ // the end of a GzipSource you guarantee that the CRC has been checked.
+ if (section == SECTION_TRAILER) {
+ consumeTrailer();
+ section = SECTION_DONE;
+
+ // Gzip streams self-terminate: they return -1 before their underlying
+ // source returns -1. Here we attempt to force the underlying stream to
+ // return -1 which may trigger it to release its resources. If it doesn't
+ // return -1, then our Gzip data finished prematurely!
+ if (!source.exhausted()) {
+ throw new IOException("gzip finished without exhausting source");
+ }
+ }
+
+ return -1;
+ }
+
+ private void consumeHeader() throws IOException {
+ // Read the 10-byte header. We peek at the flags byte first so we know if we
+ // need to CRC the entire header. Then we read the magic ID1ID2 sequence.
+ // We can skip everything else in the first 10 bytes.
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->)
+ // +---+---+---+---+---+---+---+---+---+---+
+ source.require(10);
+ byte flags = source.buffer().getByte(3);
+ boolean fhcrc = ((flags >> FHCRC) & 1) == 1;
+ if (fhcrc) updateCrc(source.buffer(), 0, 10);
+
+ short id1id2 = source.readShort();
+ checkEqual("ID1ID2", (short) 0x1f8b, id1id2);
+ source.skip(8);
+
+ // Skip optional extra fields.
+ // +---+---+=================================+
+ // | XLEN |...XLEN bytes of "extra field"...| (more-->)
+ // +---+---+=================================+
+ if (((flags >> FEXTRA) & 1) == 1) {
+ source.require(2);
+ if (fhcrc) updateCrc(source.buffer(), 0, 2);
+ int xlen = source.buffer().readShortLe();
+ source.require(xlen);
+ if (fhcrc) updateCrc(source.buffer(), 0, xlen);
+ source.skip(xlen);
+ }
+
+ // Skip an optional 0-terminated name.
+ // +=========================================+
+ // |...original file name, zero-terminated...| (more-->)
+ // +=========================================+
+ if (((flags >> FNAME) & 1) == 1) {
+ long index = source.indexOf((byte) 0);
+ if (index == -1) throw new EOFException();
+ if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
+ source.skip(index + 1);
+ }
+
+ // Skip an optional 0-terminated comment.
+ // +===================================+
+ // |...file comment, zero-terminated...| (more-->)
+ // +===================================+
+ if (((flags >> FCOMMENT) & 1) == 1) {
+ long index = source.indexOf((byte) 0);
+ if (index == -1) throw new EOFException();
+ if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
+ source.skip(index + 1);
+ }
+
+ // Confirm the optional header CRC.
+ // +---+---+
+ // | CRC16 |
+ // +---+---+
+ if (fhcrc) {
+ checkEqual("FHCRC", source.readShortLe(), (short) crc.getValue());
+ crc.reset();
+ }
+ }
+
+ private void consumeTrailer() throws IOException {
+ // Read the eight-byte trailer. Confirm the body's CRC and size.
+ // +---+---+---+---+---+---+---+---+
+ // | CRC32 | ISIZE |
+ // +---+---+---+---+---+---+---+---+
+ checkEqual("CRC", source.readIntLe(), (int) crc.getValue());
+ checkEqual("ISIZE", source.readIntLe(), inflater.getTotalOut());
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ inflaterSource.close();
+ }
+
+ /** Updates the CRC with the given bytes. */
+ private void updateCrc(Buffer buffer, long offset, long byteCount) {
+ // Skip segments that we aren't checksumming.
+ Segment s = buffer.head;
+ for (; offset >= (s.limit - s.pos); s = s.next) {
+ offset -= (s.limit - s.pos);
+ }
+
+ // Checksum one segment at a time.
+ for (; byteCount > 0; s = s.next) {
+ int pos = (int) (s.pos + offset);
+ int toUpdate = (int) Math.min(s.limit - pos, byteCount);
+ crc.update(s.data, pos, toUpdate);
+ byteCount -= toUpdate;
+ offset = 0;
+ }
+ }
+
+ private void checkEqual(String name, int expected, int actual) throws IOException {
+ if (actual != expected) {
+ throw new IOException(String.format(
+ "%s: actual 0x%08x != expected 0x%08x", name, actual, expected));
+ }
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java
new file mode 100644
index 0000000..f94fe9f
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java
@@ -0,0 +1,130 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A source that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a>
+ * to decompress data read from another source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class InflaterSource implements Source {
+ private final BufferedSource source;
+ private final Inflater inflater;
+
+ /**
+ * When we call Inflater.setInput(), the inflater keeps our byte array until
+ * it needs input again. This tracks how many bytes the inflater is currently
+ * holding on to.
+ */
+ private int bufferBytesHeldByInflater;
+ private boolean closed;
+
+ public InflaterSource(Source source, Inflater inflater) {
+ this(Okio.buffer(source), inflater);
+ }
+
+ /**
+ * This package-private constructor shares a buffer with its trusted caller.
+ * In general we can't share a BufferedSource because the inflater holds input
+ * bytes until they are inflated.
+ */
+ InflaterSource(BufferedSource source, Inflater inflater) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ if (inflater == null) throw new IllegalArgumentException("inflater == null");
+ this.source = source;
+ this.inflater = inflater;
+ }
+
+ @Override public long read(
+ Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (byteCount == 0) return 0;
+
+ while (true) {
+ boolean sourceExhausted = refill();
+
+ // Decompress the inflater's compressed data into the sink.
+ try {
+ Segment tail = sink.writableSegment(1);
+ int bytesInflated = inflater.inflate(tail.data, tail.limit, Segment.SIZE - tail.limit);
+ if (bytesInflated > 0) {
+ tail.limit += bytesInflated;
+ sink.size += bytesInflated;
+ return bytesInflated;
+ }
+ if (inflater.finished() || inflater.needsDictionary()) {
+ releaseInflatedBytes();
+ if (tail.pos == tail.limit) {
+ // We allocated a tail segment, but didn't end up needing it. Recycle!
+ sink.head = tail.pop();
+ SegmentPool.recycle(tail);
+ }
+ return -1;
+ }
+ if (sourceExhausted) throw new EOFException("source exhausted prematurely");
+ } catch (DataFormatException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ /**
+ * Refills the inflater with compressed data if it needs input. (And only if
+ * it needs input). Returns true if the inflater required input but the source
+ * was exhausted.
+ */
+ public boolean refill() throws IOException {
+ if (!inflater.needsInput()) return false;
+
+ releaseInflatedBytes();
+ if (inflater.getRemaining() != 0) throw new IllegalStateException("?"); // TODO: possible?
+
+ // If there are compressed bytes in the source, assign them to the inflater.
+ if (source.exhausted()) return true;
+
+ // Assign buffer bytes to the inflater.
+ Segment head = source.buffer().head;
+ bufferBytesHeldByInflater = head.limit - head.pos;
+ inflater.setInput(head.data, head.pos, bufferBytesHeldByInflater);
+ return false;
+ }
+
+ /** When the inflater has processed compressed data, remove it from the buffer. */
+ private void releaseInflatedBytes() throws IOException {
+ if (bufferBytesHeldByInflater == 0) return;
+ int toRelease = bufferBytesHeldByInflater - inflater.getRemaining();
+ bufferBytesHeldByInflater -= toRelease;
+ source.skip(toRelease);
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ inflater.end();
+ closed = true;
+ source.close();
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java
new file mode 100644
index 0000000..d1dd017
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java
@@ -0,0 +1,247 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/** Essential APIs for working with Okio.
+ * @hide This class is not part of the Android public SDK API*/
+public final class Okio {
+ private static final Logger logger = Logger.getLogger(Okio.class.getName());
+
+ private Okio() {
+ }
+
+ /**
+ * Returns a new source that buffers reads from {@code source}. The returned
+ * source will perform bulk reads into its in-memory buffer. Use this wherever
+ * you read a source to get an ergonomic and efficient access to data.
+ */
+ public static BufferedSource buffer(Source source) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ return new RealBufferedSource(source);
+ }
+
+ /**
+ * Returns a new sink that buffers writes to {@code sink}. The returned sink
+ * will batch writes to {@code sink}. Use this wherever you write to a sink to
+ * get an ergonomic and efficient access to data.
+ */
+ public static BufferedSink buffer(Sink sink) {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+ return new RealBufferedSink(sink);
+ }
+
+ /** Returns a sink that writes to {@code out}. */
+ public static Sink sink(OutputStream out) {
+ return sink(out, new Timeout());
+ }
+
+ private static Sink sink(final OutputStream out, final Timeout timeout) {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ if (timeout == null) throw new IllegalArgumentException("timeout == null");
+
+ return new Sink() {
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ checkOffsetAndCount(source.size, 0, byteCount);
+ while (byteCount > 0) {
+ timeout.throwIfReached();
+ Segment head = source.head;
+ int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
+ out.write(head.data, head.pos, toCopy);
+
+ head.pos += toCopy;
+ byteCount -= toCopy;
+ source.size -= toCopy;
+
+ if (head.pos == head.limit) {
+ source.head = head.pop();
+ SegmentPool.recycle(head);
+ }
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ out.flush();
+ }
+
+ @Override public void close() throws IOException {
+ out.close();
+ }
+
+ @Override public Timeout timeout() {
+ return timeout;
+ }
+
+ @Override public String toString() {
+ return "sink(" + out + ")";
+ }
+ };
+ }
+
+ /**
+ * Returns a sink that writes to {@code socket}. Prefer this over {@link
+ * #sink(OutputStream)} because this method honors timeouts. When the socket
+ * write times out, the socket is asynchronously closed by a watchdog thread.
+ */
+ public static Sink sink(Socket socket) throws IOException {
+ if (socket == null) throw new IllegalArgumentException("socket == null");
+ AsyncTimeout timeout = timeout(socket);
+ Sink sink = sink(socket.getOutputStream(), timeout);
+ return timeout.sink(sink);
+ }
+
+ /** Returns a source that reads from {@code in}. */
+ public static Source source(InputStream in) {
+ return source(in, new Timeout());
+ }
+
+ private static Source source(final InputStream in, final Timeout timeout) {
+ if (in == null) throw new IllegalArgumentException("in == null");
+ if (timeout == null) throw new IllegalArgumentException("timeout == null");
+
+ return new Source() {
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (byteCount == 0) return 0;
+ try {
+ timeout.throwIfReached();
+ Segment tail = sink.writableSegment(1);
+ int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
+ int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
+ if (bytesRead == -1) return -1;
+ tail.limit += bytesRead;
+ sink.size += bytesRead;
+ return bytesRead;
+ } catch (AssertionError e) {
+ if (isAndroidGetsocknameError(e)) throw new IOException(e);
+ throw e;
+ }
+ }
+
+ @Override public void close() throws IOException {
+ in.close();
+ }
+
+ @Override public Timeout timeout() {
+ return timeout;
+ }
+
+ @Override public String toString() {
+ return "source(" + in + ")";
+ }
+ };
+ }
+
+ /** Returns a source that reads from {@code file}. */
+ public static Source source(File file) throws FileNotFoundException {
+ if (file == null) throw new IllegalArgumentException("file == null");
+ return source(new FileInputStream(file));
+ }
+
+ // ANDROID-BEGIN
+ // /** Returns a source that reads from {@code path}. */
+ // @IgnoreJRERequirement // Should only be invoked on Java 7+.
+ // public static Source source(Path path, OpenOption... options) throws IOException {
+ // if (path == null) throw new IllegalArgumentException("path == null");
+ // return source(Files.newInputStream(path, options));
+ // }
+ // ANDROID-END
+
+ /** Returns a sink that writes to {@code file}. */
+ public static Sink sink(File file) throws FileNotFoundException {
+ if (file == null) throw new IllegalArgumentException("file == null");
+ return sink(new FileOutputStream(file));
+ }
+
+ /** Returns a sink that appends to {@code file}. */
+ public static Sink appendingSink(File file) throws FileNotFoundException {
+ if (file == null) throw new IllegalArgumentException("file == null");
+ return sink(new FileOutputStream(file, true));
+ }
+
+ // ANDROID-BEGIN
+ // /** Returns a sink that writes to {@code path}. */
+ // @IgnoreJRERequirement // Should only be invoked on Java 7+.
+ // public static Sink sink(Path path, OpenOption... options) throws IOException {
+ // if (path == null) throw new IllegalArgumentException("path == null");
+ // return sink(Files.newOutputStream(path, options));
+ // }
+ // ANDROID-END
+
+ /**
+ * Returns a source that reads from {@code socket}. Prefer this over {@link
+ * #source(InputStream)} because this method honors timeouts. When the socket
+ * read times out, the socket is asynchronously closed by a watchdog thread.
+ */
+ public static Source source(Socket socket) throws IOException {
+ if (socket == null) throw new IllegalArgumentException("socket == null");
+ AsyncTimeout timeout = timeout(socket);
+ Source source = source(socket.getInputStream(), timeout);
+ return timeout.source(source);
+ }
+
+ private static AsyncTimeout timeout(final Socket socket) {
+ return new AsyncTimeout() {
+ @Override protected IOException newTimeoutException(IOException cause) {
+ InterruptedIOException ioe = new SocketTimeoutException("timeout");
+ if (cause != null) {
+ ioe.initCause(cause);
+ }
+ return ioe;
+ }
+
+ @Override protected void timedOut() {
+ try {
+ socket.close();
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
+ } catch (AssertionError e) {
+ if (isAndroidGetsocknameError(e)) {
+ // Catch this exception due to a Firmware issue up to android 4.2.2
+ // https://code.google.com/p/android/issues/detail?id=54072
+ logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
+ } else {
+ throw e;
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
+ * https://code.google.com/p/android/issues/detail?id=54072
+ */
+ private static boolean isAndroidGetsocknameError(AssertionError e) {
+ return e.getCause() != null && e.getMessage() != null
+ && e.getMessage().contains("getsockname failed");
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java
new file mode 100644
index 0000000..410e122
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java
@@ -0,0 +1,258 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+final class RealBufferedSink implements BufferedSink {
+ public final Buffer buffer;
+ public final Sink sink;
+ private boolean closed;
+
+ public RealBufferedSink(Sink sink, Buffer buffer) {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+ this.buffer = buffer;
+ this.sink = sink;
+ }
+
+ public RealBufferedSink(Sink sink) {
+ this(sink, new Buffer());
+ }
+
+ @Override public Buffer buffer() {
+ return buffer;
+ }
+
+ @Override public void write(Buffer source, long byteCount)
+ throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.write(source, byteCount);
+ emitCompleteSegments();
+ }
+
+ @Override public BufferedSink write(ByteString byteString) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.write(byteString);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeUtf8(String string) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeUtf8(string);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
+ throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeUtf8(string, beginIndex, endIndex);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeUtf8CodePoint(int codePoint) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeUtf8CodePoint(codePoint);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeString(String string, Charset charset) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeString(string, charset);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeString(String string, int beginIndex, int endIndex,
+ Charset charset) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeString(string, beginIndex, endIndex, charset);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink write(byte[] source) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.write(source);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.write(source, offset, byteCount);
+ return emitCompleteSegments();
+ }
+
+ @Override public long writeAll(Source source) throws IOException {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ long totalBytesRead = 0;
+ for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {
+ totalBytesRead += readCount;
+ emitCompleteSegments();
+ }
+ return totalBytesRead;
+ }
+
+ @Override public BufferedSink write(Source source, long byteCount) throws IOException {
+ while (byteCount > 0) {
+ long read = source.read(buffer, byteCount);
+ if (read == -1) throw new EOFException();
+ byteCount -= read;
+ emitCompleteSegments();
+ }
+ return this;
+ }
+
+ @Override public BufferedSink writeByte(int b) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeByte(b);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeShort(int s) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeShort(s);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeShortLe(int s) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeShortLe(s);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeInt(int i) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeInt(i);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeIntLe(int i) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeIntLe(i);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeLong(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeLong(v);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeLongLe(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeLongLe(v);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeDecimalLong(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeDecimalLong(v);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeHexadecimalUnsignedLong(v);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink emitCompleteSegments() throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ long byteCount = buffer.completeSegmentByteCount();
+ if (byteCount > 0) sink.write(buffer, byteCount);
+ return this;
+ }
+
+ @Override public BufferedSink emit() throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ long byteCount = buffer.size();
+ if (byteCount > 0) sink.write(buffer, byteCount);
+ return this;
+ }
+
+ @Override public OutputStream outputStream() {
+ return new OutputStream() {
+ @Override public void write(int b) throws IOException {
+ if (closed) throw new IOException("closed");
+ buffer.writeByte((byte) b);
+ emitCompleteSegments();
+ }
+
+ @Override public void write(byte[] data, int offset, int byteCount) throws IOException {
+ if (closed) throw new IOException("closed");
+ buffer.write(data, offset, byteCount);
+ emitCompleteSegments();
+ }
+
+ @Override public void flush() throws IOException {
+ // For backwards compatibility, a flush() on a closed stream is a no-op.
+ if (!closed) {
+ RealBufferedSink.this.flush();
+ }
+ }
+
+ @Override public void close() throws IOException {
+ RealBufferedSink.this.close();
+ }
+
+ @Override public String toString() {
+ return RealBufferedSink.this + ".outputStream()";
+ }
+ };
+ }
+
+ @Override public void flush() throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ if (buffer.size > 0) {
+ sink.write(buffer, buffer.size);
+ }
+ sink.flush();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ // Emit buffered data to the underlying sink. If this fails, we still need
+ // to close the sink; otherwise we risk leaking resources.
+ Throwable thrown = null;
+ try {
+ if (buffer.size > 0) {
+ sink.write(buffer, buffer.size);
+ }
+ } catch (Throwable e) {
+ thrown = e;
+ }
+
+ try {
+ sink.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ closed = true;
+
+ if (thrown != null) Util.sneakyRethrow(thrown);
+ }
+
+ @Override public Timeout timeout() {
+ return sink.timeout();
+ }
+
+ @Override public String toString() {
+ return "buffer(" + sink + ")";
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java
new file mode 100644
index 0000000..5e49106
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java
@@ -0,0 +1,408 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+final class RealBufferedSource implements BufferedSource {
+ public final Buffer buffer;
+ public final Source source;
+ private boolean closed;
+
+ public RealBufferedSource(Source source, Buffer buffer) {
+ if (source == null) throw new IllegalArgumentException("source == null");
+ this.buffer = buffer;
+ this.source = source;
+ }
+
+ public RealBufferedSource(Source source) {
+ this(source, new Buffer());
+ }
+
+ @Override public Buffer buffer() {
+ return buffer;
+ }
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+
+ if (buffer.size == 0) {
+ long read = source.read(buffer, Segment.SIZE);
+ if (read == -1) return -1;
+ }
+
+ long toRead = Math.min(byteCount, buffer.size);
+ return buffer.read(sink, toRead);
+ }
+
+ @Override public boolean exhausted() throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1;
+ }
+
+ @Override public void require(long byteCount) throws IOException {
+ if (!request(byteCount)) throw new EOFException();
+ }
+
+ @Override public boolean request(long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ while (buffer.size < byteCount) {
+ if (source.read(buffer, Segment.SIZE) == -1) return false;
+ }
+ return true;
+ }
+
+ @Override public byte readByte() throws IOException {
+ require(1);
+ return buffer.readByte();
+ }
+
+ @Override public ByteString readByteString() throws IOException {
+ buffer.writeAll(source);
+ return buffer.readByteString();
+ }
+
+ @Override public ByteString readByteString(long byteCount) throws IOException {
+ require(byteCount);
+ return buffer.readByteString(byteCount);
+ }
+
+ @Override public byte[] readByteArray() throws IOException {
+ buffer.writeAll(source);
+ return buffer.readByteArray();
+ }
+
+ @Override public byte[] readByteArray(long byteCount) throws IOException {
+ require(byteCount);
+ return buffer.readByteArray(byteCount);
+ }
+
+ @Override public int read(byte[] sink) throws IOException {
+ return read(sink, 0, sink.length);
+ }
+
+ @Override public void readFully(byte[] sink) throws IOException {
+ try {
+ require(sink.length);
+ } catch (EOFException e) {
+ // The underlying source is exhausted. Copy the bytes we got before rethrowing.
+ int offset = 0;
+ while (buffer.size > 0) {
+ int read = buffer.read(sink, offset, (int) buffer.size);
+ if (read == -1) throw new AssertionError();
+ offset += read;
+ }
+ throw e;
+ }
+ buffer.readFully(sink);
+ }
+
+ @Override public int read(byte[] sink, int offset, int byteCount) throws IOException {
+ checkOffsetAndCount(sink.length, offset, byteCount);
+
+ if (buffer.size == 0) {
+ long read = source.read(buffer, Segment.SIZE);
+ if (read == -1) return -1;
+ }
+
+ int toRead = (int) Math.min(byteCount, buffer.size);
+ return buffer.read(sink, offset, toRead);
+ }
+
+ @Override public void readFully(Buffer sink, long byteCount) throws IOException {
+ try {
+ require(byteCount);
+ } catch (EOFException e) {
+ // The underlying source is exhausted. Copy the bytes we got before rethrowing.
+ sink.writeAll(buffer);
+ throw e;
+ }
+ buffer.readFully(sink, byteCount);
+ }
+
+ @Override public long readAll(Sink sink) throws IOException {
+ if (sink == null) throw new IllegalArgumentException("sink == null");
+
+ long totalBytesWritten = 0;
+ while (source.read(buffer, Segment.SIZE) != -1) {
+ long emitByteCount = buffer.completeSegmentByteCount();
+ if (emitByteCount > 0) {
+ totalBytesWritten += emitByteCount;
+ sink.write(buffer, emitByteCount);
+ }
+ }
+ if (buffer.size() > 0) {
+ totalBytesWritten += buffer.size();
+ sink.write(buffer, buffer.size());
+ }
+ return totalBytesWritten;
+ }
+
+ @Override public String readUtf8() throws IOException {
+ buffer.writeAll(source);
+ return buffer.readUtf8();
+ }
+
+ @Override public String readUtf8(long byteCount) throws IOException {
+ require(byteCount);
+ return buffer.readUtf8(byteCount);
+ }
+
+ @Override public String readString(Charset charset) throws IOException {
+ if (charset == null) throw new IllegalArgumentException("charset == null");
+
+ buffer.writeAll(source);
+ return buffer.readString(charset);
+ }
+
+ @Override public String readString(long byteCount, Charset charset) throws IOException {
+ require(byteCount);
+ if (charset == null) throw new IllegalArgumentException("charset == null");
+ return buffer.readString(byteCount, charset);
+ }
+
+ @Override public String readUtf8Line() throws IOException {
+ long newline = indexOf((byte) '\n');
+
+ if (newline == -1) {
+ return buffer.size != 0 ? readUtf8(buffer.size) : null;
+ }
+
+ return buffer.readUtf8Line(newline);
+ }
+
+ @Override public String readUtf8LineStrict() throws IOException {
+ long newline = indexOf((byte) '\n');
+ if (newline == -1L) {
+ Buffer data = new Buffer();
+ buffer.copyTo(data, 0, Math.min(32, buffer.size()));
+ throw new EOFException("\\n not found: size=" + buffer.size()
+ + " content=" + data.readByteString().hex() + "...");
+ }
+ return buffer.readUtf8Line(newline);
+ }
+
+ @Override public int readUtf8CodePoint() throws IOException {
+ require(1);
+
+ byte b0 = buffer.getByte(0);
+ if ((b0 & 0xe0) == 0xc0) {
+ require(2);
+ } else if ((b0 & 0xf0) == 0xe0) {
+ require(3);
+ } else if ((b0 & 0xf8) == 0xf0) {
+ require(4);
+ }
+
+ return buffer.readUtf8CodePoint();
+ }
+
+ @Override public short readShort() throws IOException {
+ require(2);
+ return buffer.readShort();
+ }
+
+ @Override public short readShortLe() throws IOException {
+ require(2);
+ return buffer.readShortLe();
+ }
+
+ @Override public int readInt() throws IOException {
+ require(4);
+ return buffer.readInt();
+ }
+
+ @Override public int readIntLe() throws IOException {
+ require(4);
+ return buffer.readIntLe();
+ }
+
+ @Override public long readLong() throws IOException {
+ require(8);
+ return buffer.readLong();
+ }
+
+ @Override public long readLongLe() throws IOException {
+ require(8);
+ return buffer.readLongLe();
+ }
+
+ @Override public long readDecimalLong() throws IOException {
+ require(1);
+
+ for (int pos = 0; request(pos + 1); pos++) {
+ byte b = buffer.getByte(pos);
+ if ((b < '0' || b > '9') && (pos != 0 || b != '-')) {
+ // Non-digit, or non-leading negative sign.
+ if (pos == 0) {
+ throw new NumberFormatException(String.format(
+ "Expected leading [0-9] or '-' character but was %#x", b));
+ }
+ break;
+ }
+ }
+
+ return buffer.readDecimalLong();
+ }
+
+ @Override public long readHexadecimalUnsignedLong() throws IOException {
+ require(1);
+
+ for (int pos = 0; request(pos + 1); pos++) {
+ byte b = buffer.getByte(pos);
+ if ((b < '0' || b > '9') && (b < 'a' || b > 'f') && (b < 'A' || b > 'F')) {
+ // Non-digit, or non-leading negative sign.
+ if (pos == 0) {
+ throw new NumberFormatException(String.format(
+ "Expected leading [0-9a-fA-F] character but was %#x", b));
+ }
+ break;
+ }
+ }
+
+ return buffer.readHexadecimalUnsignedLong();
+ }
+
+ @Override public void skip(long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ while (byteCount > 0) {
+ if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) {
+ throw new EOFException();
+ }
+ long toSkip = Math.min(byteCount, buffer.size());
+ buffer.skip(toSkip);
+ byteCount -= toSkip;
+ }
+ }
+
+ @Override public long indexOf(byte b) throws IOException {
+ return indexOf(b, 0);
+ }
+
+ @Override public long indexOf(byte b, long fromIndex) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ while (fromIndex >= buffer.size) {
+ if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+ }
+ long index;
+ while ((index = buffer.indexOf(b, fromIndex)) == -1) {
+ fromIndex = buffer.size;
+ if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+ }
+ return index;
+ }
+
+ @Override public long indexOf(ByteString bytes) throws IOException {
+ return indexOf(bytes, 0);
+ }
+
+ @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
+ if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty");
+ while (true) {
+ fromIndex = indexOf(bytes.getByte(0), fromIndex);
+ if (fromIndex == -1) {
+ return -1;
+ }
+ if (rangeEquals(fromIndex, bytes)) {
+ return fromIndex;
+ }
+ fromIndex++;
+ }
+ }
+
+ @Override public long indexOfElement(ByteString targetBytes) throws IOException {
+ return indexOfElement(targetBytes, 0);
+ }
+
+ @Override public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ while (fromIndex >= buffer.size) {
+ if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+ }
+ long index;
+ while ((index = buffer.indexOfElement(targetBytes, fromIndex)) == -1) {
+ fromIndex = buffer.size;
+ if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+ }
+ return index;
+ }
+
+ private boolean rangeEquals(long offset, ByteString bytes) throws IOException {
+ return request(offset + bytes.size()) && buffer.rangeEquals(offset, bytes);
+ }
+
+ @Override public InputStream inputStream() {
+ return new InputStream() {
+ @Override public int read() throws IOException {
+ if (closed) throw new IOException("closed");
+ if (buffer.size == 0) {
+ long count = source.read(buffer, Segment.SIZE);
+ if (count == -1) return -1;
+ }
+ return buffer.readByte() & 0xff;
+ }
+
+ @Override public int read(byte[] data, int offset, int byteCount) throws IOException {
+ if (closed) throw new IOException("closed");
+ checkOffsetAndCount(data.length, offset, byteCount);
+
+ if (buffer.size == 0) {
+ long count = source.read(buffer, Segment.SIZE);
+ if (count == -1) return -1;
+ }
+
+ return buffer.read(data, offset, byteCount);
+ }
+
+ @Override public int available() throws IOException {
+ if (closed) throw new IOException("closed");
+ return (int) Math.min(buffer.size, Integer.MAX_VALUE);
+ }
+
+ @Override public void close() throws IOException {
+ RealBufferedSource.this.close();
+ }
+
+ @Override public String toString() {
+ return RealBufferedSource.this + ".inputStream()";
+ }
+ };
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ source.close();
+ buffer.clear();
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public String toString() {
+ return "buffer(" + source + ")";
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java
new file mode 100644
index 0000000..5056a4f
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java
@@ -0,0 +1,150 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+/**
+ * A segment of a buffer.
+ *
+ * <p>Each segment in a buffer is a circularly-linked list node referencing the following and
+ * preceding segments in the buffer.
+ *
+ * <p>Each segment in the pool is a singly-linked list node referencing the rest of segments in the
+ * pool.
+ *
+ * <p>The underlying byte arrays of segments may be shared between buffers and byte strings. When a
+ * segment's byte array is shared the segment may not be recycled, nor may its byte data be changed.
+ * The lone exception is that the owner segment is allowed to append to the segment, writing data at
+ * {@code limit} and beyond. There is a single owning segment for each byte array. Positions,
+ * limits, prev, and next references are not shared.
+ */
+final class Segment {
+ /** The size of all segments in bytes. */
+ static final int SIZE = 8192;
+
+ final byte[] data;
+
+ /** The next byte of application data byte to read in this segment. */
+ int pos;
+
+ /** The first byte of available data ready to be written to. */
+ int limit;
+
+ /** True if other segments or byte strings use the same byte array. */
+ boolean shared;
+
+ /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
+ boolean owner;
+
+ /** Next segment in a linked or circularly-linked list. */
+ Segment next;
+
+ /** Previous segment in a circularly-linked list. */
+ Segment prev;
+
+ Segment() {
+ this.data = new byte[SIZE];
+ this.owner = true;
+ this.shared = false;
+ }
+
+ Segment(Segment shareFrom) {
+ this(shareFrom.data, shareFrom.pos, shareFrom.limit);
+ shareFrom.shared = true;
+ }
+
+ Segment(byte[] data, int pos, int limit) {
+ this.data = data;
+ this.pos = pos;
+ this.limit = limit;
+ this.owner = false;
+ this.shared = true;
+ }
+
+ /**
+ * Removes this segment of a circularly-linked list and returns its successor.
+ * Returns null if the list is now empty.
+ */
+ public Segment pop() {
+ Segment result = next != this ? next : null;
+ prev.next = next;
+ next.prev = prev;
+ next = null;
+ prev = null;
+ return result;
+ }
+
+ /**
+ * Appends {@code segment} after this segment in the circularly-linked list.
+ * Returns the pushed segment.
+ */
+ public Segment push(Segment segment) {
+ segment.prev = this;
+ segment.next = next;
+ next.prev = segment;
+ next = segment;
+ return segment;
+ }
+
+ /**
+ * Splits this head of a circularly-linked list into two segments. The first
+ * segment contains the data in {@code [pos..pos+byteCount)}. The second
+ * segment contains the data in {@code [pos+byteCount..limit)}. This can be
+ * useful when moving partial segments from one buffer to another.
+ *
+ * <p>Returns the new head of the circularly-linked list.
+ */
+ public Segment split(int byteCount) {
+ if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
+ Segment prefix = new Segment(this);
+ prefix.limit = prefix.pos + byteCount;
+ pos += byteCount;
+ prev.push(prefix);
+ return prefix;
+ }
+
+ /**
+ * Call this when the tail and its predecessor may both be less than half
+ * full. This will copy data so that segments can be recycled.
+ */
+ public void compact() {
+ if (prev == this) throw new IllegalStateException();
+ if (!prev.owner) return; // Cannot compact: prev isn't writable.
+ int byteCount = limit - pos;
+ int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
+ if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
+ writeTo(prev, byteCount);
+ pop();
+ SegmentPool.recycle(this);
+ }
+
+ /** Moves {@code byteCount} bytes from this segment to {@code sink}. */
+ public void writeTo(Segment sink, int byteCount) {
+ if (!sink.owner) throw new IllegalArgumentException();
+ if (sink.limit + byteCount > SIZE) {
+ // We can't fit byteCount bytes at the sink's current position. Shift sink first.
+ if (sink.shared) throw new IllegalArgumentException();
+ if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
+ System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
+ sink.limit -= sink.pos;
+ sink.pos = 0;
+ }
+
+ System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
+ sink.limit += byteCount;
+ pos += byteCount;
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java
new file mode 100644
index 0000000..fdc1395
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java
@@ -0,0 +1,61 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+/**
+ * A collection of unused segments, necessary to avoid GC churn and zero-fill.
+ * This pool is a thread-safe static singleton.
+ */
+final class SegmentPool {
+ /** The maximum number of bytes to pool. */
+ // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
+ static final long MAX_SIZE = 64 * 1024; // 64 KiB.
+
+ /** Singly-linked list of segments. */
+ static Segment next;
+
+ /** Total bytes in this pool. */
+ static long byteCount;
+
+ private SegmentPool() {
+ }
+
+ static Segment take() {
+ synchronized (SegmentPool.class) {
+ if (next != null) {
+ Segment result = next;
+ next = result.next;
+ result.next = null;
+ byteCount -= Segment.SIZE;
+ return result;
+ }
+ }
+ return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
+ }
+
+ static void recycle(Segment segment) {
+ if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
+ if (segment.shared) return; // This segment cannot be recycled.
+ synchronized (SegmentPool.class) {
+ if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
+ byteCount += Segment.SIZE;
+ segment.next = next;
+ segment.pos = segment.limit = 0;
+ next = segment;
+ }
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java
new file mode 100644
index 0000000..bb2dd98
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java
@@ -0,0 +1,259 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import static com.android.okhttp.okio.Util.arrayRangeEquals;
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * An immutable byte string composed of segments of byte arrays. This class exists to implement
+ * efficient snapshots of buffers. It is implemented as an array of segments, plus a directory in
+ * two halves that describes how the segments compose this byte string.
+ *
+ * <p>The first half of the directory is the cumulative byte count covered by each segment. The
+ * element at {@code directory[0]} contains the number of bytes held in {@code segments[0]}; the
+ * element at {@code directory[1]} contains the number of bytes held in {@code segments[0] +
+ * segments[1]}, and so on. The element at {@code directory[segments.length - 1]} contains the total
+ * size of this byte string. The first half of the directory is always monotonically increasing.
+ *
+ * <p>The second half of the directory is the offset in {@code segments} of the first content byte.
+ * Bytes preceding this offset are unused, as are bytes beyond the segment's effective size.
+ *
+ * <p>Suppose we have a byte string, {@code [A, B, C, D, E, F, G, H, I, J, K, L, M]} that is stored
+ * across three byte arrays: {@code [x, x, x, x, A, B, C, D, E, x, x, x]}, {@code [x, F, G]}, and
+ * {@code [H, I, J, K, L, M, x, x, x, x, x, x]}. The three byte arrays would be stored in {@code
+ * segments} in order. Since the arrays contribute 5, 2, and 6 elements respectively, the directory
+ * starts with {@code [5, 7, 13} to hold the cumulative total at each position. Since the offsets
+ * into the arrays are 4, 1, and 0 respectively, the directory ends with {@code 4, 1, 0]}.
+ * Concatenating these two halves, the complete directory is {@code [5, 7, 13, 4, 1, 0]}.
+ *
+ * <p>This structure is chosen so that the segment holding a particular offset can be found by
+ * binary search. We use one array rather than two for the directory as a micro-optimization.
+ */
+final class SegmentedByteString extends ByteString {
+ transient final byte[][] segments;
+ transient final int[] directory;
+
+ SegmentedByteString(Buffer buffer, int byteCount) {
+ super(null);
+ checkOffsetAndCount(buffer.size, 0, byteCount);
+
+ // Walk through the buffer to count how many segments we'll need.
+ int offset = 0;
+ int segmentCount = 0;
+ for (Segment s = buffer.head; offset < byteCount; s = s.next) {
+ if (s.limit == s.pos) {
+ throw new AssertionError("s.limit == s.pos"); // Empty segment. This should not happen!
+ }
+ offset += s.limit - s.pos;
+ segmentCount++;
+ }
+
+ // Walk through the buffer again to assign segments and build the directory.
+ this.segments = new byte[segmentCount][];
+ this.directory = new int[segmentCount * 2];
+ offset = 0;
+ segmentCount = 0;
+ for (Segment s = buffer.head; offset < byteCount; s = s.next) {
+ segments[segmentCount] = s.data;
+ offset += s.limit - s.pos;
+ directory[segmentCount] = offset;
+ directory[segmentCount + segments.length] = s.pos;
+ s.shared = true;
+ segmentCount++;
+ }
+ }
+
+ @Override public String utf8() {
+ return toByteString().utf8();
+ }
+
+ @Override public String base64() {
+ return toByteString().base64();
+ }
+
+ @Override public String hex() {
+ return toByteString().hex();
+ }
+
+ @Override public ByteString toAsciiLowercase() {
+ return toByteString().toAsciiLowercase();
+ }
+
+ @Override public ByteString toAsciiUppercase() {
+ return toByteString().toAsciiUppercase();
+ }
+
+ @Override public ByteString md5() {
+ return toByteString().md5();
+ }
+
+ @Override public ByteString sha256() {
+ return toByteString().sha256();
+ }
+
+ @Override public String base64Url() {
+ return toByteString().base64Url();
+ }
+
+ @Override public ByteString substring(int beginIndex) {
+ return toByteString().substring(beginIndex);
+ }
+
+ @Override public ByteString substring(int beginIndex, int endIndex) {
+ return toByteString().substring(beginIndex, endIndex);
+ }
+
+ @Override public byte getByte(int pos) {
+ checkOffsetAndCount(directory[segments.length - 1], pos, 1);
+ int segment = segment(pos);
+ int segmentOffset = segment == 0 ? 0 : directory[segment - 1];
+ int segmentPos = directory[segment + segments.length];
+ return segments[segment][pos - segmentOffset + segmentPos];
+ }
+
+ /** Returns the index of the segment that contains the byte at {@code pos}. */
+ private int segment(int pos) {
+ // Search for (pos + 1) instead of (pos) because the directory holds sizes, not indexes.
+ int i = Arrays.binarySearch(directory, 0, segments.length, pos + 1);
+ return i >= 0 ? i : ~i; // If i is negative, bitflip to get the insert position.
+ }
+
+ @Override public int size() {
+ return directory[segments.length - 1];
+ }
+
+ @Override public byte[] toByteArray() {
+ byte[] result = new byte[directory[segments.length - 1]];
+ int segmentOffset = 0;
+ for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+ int segmentPos = directory[segmentCount + s];
+ int nextSegmentOffset = directory[s];
+ System.arraycopy(segments[s], segmentPos, result, segmentOffset,
+ nextSegmentOffset - segmentOffset);
+ segmentOffset = nextSegmentOffset;
+ }
+ return result;
+ }
+
+ @Override public void write(OutputStream out) throws IOException {
+ if (out == null) throw new IllegalArgumentException("out == null");
+ int segmentOffset = 0;
+ for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+ int segmentPos = directory[segmentCount + s];
+ int nextSegmentOffset = directory[s];
+ out.write(segments[s], segmentPos, nextSegmentOffset - segmentOffset);
+ segmentOffset = nextSegmentOffset;
+ }
+ }
+
+ @Override void write(Buffer buffer) {
+ int segmentOffset = 0;
+ for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+ int segmentPos = directory[segmentCount + s];
+ int nextSegmentOffset = directory[s];
+ Segment segment = new Segment(segments[s], segmentPos,
+ segmentPos + nextSegmentOffset - segmentOffset);
+ if (buffer.head == null) {
+ buffer.head = segment.next = segment.prev = segment;
+ } else {
+ buffer.head.prev.push(segment);
+ }
+ segmentOffset = nextSegmentOffset;
+ }
+ buffer.size += segmentOffset;
+ }
+
+ @Override public boolean rangeEquals(
+ int offset, ByteString other, int otherOffset, int byteCount) {
+ if (offset > size() - byteCount) return false;
+ // Go segment-by-segment through this, passing arrays to other's rangeEquals().
+ for (int s = segment(offset); byteCount > 0; s++) {
+ int segmentOffset = s == 0 ? 0 : directory[s - 1];
+ int segmentSize = directory[s] - segmentOffset;
+ int stepSize = Math.min(byteCount, segmentOffset + segmentSize - offset);
+ int segmentPos = directory[segments.length + s];
+ int arrayOffset = offset - segmentOffset + segmentPos;
+ if (!other.rangeEquals(otherOffset, segments[s], arrayOffset, stepSize)) return false;
+ offset += stepSize;
+ otherOffset += stepSize;
+ byteCount -= stepSize;
+ }
+ return true;
+ }
+
+ @Override public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) {
+ if (offset > size() - byteCount || otherOffset > other.length - byteCount) return false;
+ // Go segment-by-segment through this, comparing ranges of arrays.
+ for (int s = segment(offset); byteCount > 0; s++) {
+ int segmentOffset = s == 0 ? 0 : directory[s - 1];
+ int segmentSize = directory[s] - segmentOffset;
+ int stepSize = Math.min(byteCount, segmentOffset + segmentSize - offset);
+ int segmentPos = directory[segments.length + s];
+ int arrayOffset = offset - segmentOffset + segmentPos;
+ if (!arrayRangeEquals(segments[s], arrayOffset, other, otherOffset, stepSize)) return false;
+ offset += stepSize;
+ otherOffset += stepSize;
+ byteCount -= stepSize;
+ }
+ return true;
+ }
+
+ /** Returns a copy as a non-segmented byte string. */
+ private ByteString toByteString() {
+ return new ByteString(toByteArray());
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o == this) return true;
+ return o instanceof ByteString
+ && ((ByteString) o).size() == size()
+ && rangeEquals(0, ((ByteString) o), 0, size());
+ }
+
+ @Override public int hashCode() {
+ int result = hashCode;
+ if (result != 0) return result;
+
+ // Equivalent to Arrays.hashCode(toByteArray()).
+ result = 1;
+ int segmentOffset = 0;
+ for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+ byte[] segment = segments[s];
+ int segmentPos = directory[segmentCount + s];
+ int nextSegmentOffset = directory[s];
+ int segmentSize = nextSegmentOffset - segmentOffset;
+ for (int i = segmentPos, limit = segmentPos + segmentSize; i < limit; i++) {
+ result = (31 * result) + segment[i];
+ }
+ segmentOffset = nextSegmentOffset;
+ }
+ return (hashCode = result);
+ }
+
+ @Override public String toString() {
+ return toByteString().toString();
+ }
+
+ private Object writeReplace() {
+ return toByteString();
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java
new file mode 100644
index 0000000..e0a4eea
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java
@@ -0,0 +1,69 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+/**
+ * Receives a stream of bytes. Use this interface to write data wherever it's
+ * needed: to the network, storage, or a buffer in memory. Sinks may be layered
+ * to transform received data, such as to compress, encrypt, throttle, or add
+ * protocol framing.
+ *
+ * <p>Most application code shouldn't operate on a sink directly, but rather
+ * {@link BufferedSink} which is both more efficient and more convenient. Use
+ * {@link Okio#buffer(Sink)} to wrap any sink with a buffer.
+ *
+ * <p>Sinks are easy to test: just use an {@link Buffer} in your tests, and
+ * read from it to confirm it received the data that was expected.
+ *
+ * <h3>Comparison with OutputStream</h3>
+ * This interface is functionally equivalent to {@link java.io.OutputStream}.
+ *
+ * <p>{@code OutputStream} requires multiple layers when emitted data is
+ * heterogeneous: a {@code DataOutputStream} for primitive values, a {@code
+ * BufferedOutputStream} for buffering, and {@code OutputStreamWriter} for
+ * charset encoding. This class uses {@code BufferedSink} for all of the above.
+ *
+ * <p>Sink is also easier to layer: there is no {@linkplain
+ * java.io.OutputStream#write(int) single-byte write} method that is awkward to
+ * implement efficiently.
+ *
+ * <h3>Interop with OutputStream</h3>
+ * Use {@link Okio#sink} to adapt an {@code OutputStream} to a sink. Use {@link
+ * BufferedSink#outputStream} to adapt a sink to an {@code OutputStream}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Sink extends Closeable, Flushable {
+ /** Removes {@code byteCount} bytes from {@code source} and appends them to this. */
+ void write(Buffer source, long byteCount) throws IOException;
+
+ /** Pushes all buffered bytes to their final destination. */
+ @Override void flush() throws IOException;
+
+ /** Returns the timeout for this sink. */
+ Timeout timeout();
+
+ /**
+ * Pushes all buffered bytes to their final destination and releases the
+ * resources held by this sink. It is an error to write a closed sink. It is
+ * safe to close a sink more than once.
+ */
+ @Override void close() throws IOException;
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java
new file mode 100644
index 0000000..6af20cd
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java
@@ -0,0 +1,80 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Supplies a stream of bytes. Use this interface to read data from wherever
+ * it's located: from the network, storage, or a buffer in memory. Sources may
+ * be layered to transform supplied data, such as to decompress, decrypt, or
+ * remove protocol framing.
+ *
+ * <p>Most applications shouldn't operate on a source directly, but rather
+ * {@link BufferedSource} which is both more efficient and more convenient. Use
+ * {@link Okio#buffer(Source)} to wrap any source with a buffer.
+ *
+ * <p>Sources are easy to test: just use an {@link Buffer} in your tests, and
+ * fill it with the data your application is to read.
+ *
+ * <h3>Comparison with InputStream</h3>
+ * This interface is functionally equivalent to {@link java.io.InputStream}.
+ *
+ * <p>{@code InputStream} requires multiple layers when consumed data is
+ * heterogeneous: a {@code DataInputStream} for primitive values, a {@code
+ * BufferedInputStream} for buffering, and {@code InputStreamReader} for
+ * strings. This class uses {@code BufferedSource} for all of the above.
+ *
+ * <p>Source avoids the impossible-to-implement {@linkplain
+ * java.io.InputStream#available available()} method. Instead callers specify
+ * how many bytes they {@link BufferedSource#require require}.
+ *
+ * <p>Source omits the unsafe-to-compose {@linkplain java.io.InputStream#mark
+ * mark and reset} state that's tracked by {@code InputStream}; callers instead
+ * just buffer what they need.
+ *
+ * <p>When implementing a source, you need not worry about the {@linkplain
+ * java.io.InputStream#read single-byte read} method that is awkward to
+ * implement efficiently and that returns one of 257 possible values.
+ *
+ * <p>And source has a stronger {@code skip} method: {@link BufferedSource#skip}
+ * won't return prematurely.
+ *
+ * <h3>Interop with InputStream</h3>
+ * Use {@link Okio#source} to adapt an {@code InputStream} to a source. Use
+ * {@link BufferedSource#inputStream} to adapt a source to an {@code
+ * InputStream}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Source extends Closeable {
+ /**
+ * Removes at least 1, and up to {@code byteCount} bytes from this and appends
+ * them to {@code sink}. Returns the number of bytes read, or -1 if this
+ * source is exhausted.
+ */
+ long read(Buffer sink, long byteCount) throws IOException;
+
+ /** Returns the timeout for this source. */
+ Timeout timeout();
+
+ /**
+ * Closes this source and releases the resources held by this source. It is an
+ * error to read a closed source. It is safe to close a source more than once.
+ */
+ @Override void close() throws IOException;
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java
new file mode 100644
index 0000000..7b757d2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java
@@ -0,0 +1,154 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A policy on how much time to spend on a task before giving up. When a task
+ * times out, it is left in an unspecified state and should be abandoned. For
+ * example, if reading from a source times out, that source should be closed and
+ * the read should be retried later. If writing to a sink times out, the same
+ * rules apply: close the sink and retry later.
+ *
+ * <h3>Timeouts and Deadlines</h3>
+ * This class offers two complementary controls to define a timeout policy.
+ *
+ * <p><strong>Timeouts</strong> specify the maximum time to wait for a single
+ * operation to complete. Timeouts are typically used to detect problems like
+ * network partitions. For example, if a remote peer doesn't return <i>any</i>
+ * data for ten seconds, we may assume that the peer is unavailable.
+ *
+ * <p><strong>Deadlines</strong> specify the maximum time to spend on a job,
+ * composed of one or more operations. Use deadlines to set an upper bound on
+ * the time invested on a job. For example, a battery-conscious app may limit
+ * how much time it spends preloading content.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class Timeout {
+ /**
+ * An empty timeout that neither tracks nor detects timeouts. Use this when
+ * timeouts aren't necessary, such as in implementations whose operations
+ * do not block.
+ */
+ public static final Timeout NONE = new Timeout() {
+ @Override public Timeout timeout(long timeout, TimeUnit unit) {
+ return this;
+ }
+
+ @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
+ return this;
+ }
+
+ @Override public void throwIfReached() throws IOException {
+ }
+ };
+
+ /**
+ * True if {@code deadlineNanoTime} is defined. There is no equivalent to null
+ * or 0 for {@link System#nanoTime}.
+ */
+ private boolean hasDeadline;
+ private long deadlineNanoTime;
+ private long timeoutNanos;
+
+ public Timeout() {
+ }
+
+ /**
+ * Wait at most {@code timeout} time before aborting an operation. Using a
+ * per-operation timeout means that as long as forward progress is being made,
+ * no sequence of operations will fail.
+ *
+ * <p>If {@code timeout == 0}, operations will run indefinitely. (Operating
+ * system timeouts may still apply.)
+ */
+ public Timeout timeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout);
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ this.timeoutNanos = unit.toNanos(timeout);
+ return this;
+ }
+
+ /** Returns the timeout in nanoseconds, or {@code 0} for no timeout. */
+ public long timeoutNanos() {
+ return timeoutNanos;
+ }
+
+ /** Returns true if a deadline is enabled. */
+ public boolean hasDeadline() {
+ return hasDeadline;
+ }
+
+ /**
+ * Returns the {@linkplain System#nanoTime() nano time} when the deadline will
+ * be reached.
+ *
+ * @throws IllegalStateException if no deadline is set.
+ */
+ public long deadlineNanoTime() {
+ if (!hasDeadline) throw new IllegalStateException("No deadline");
+ return deadlineNanoTime;
+ }
+
+ /**
+ * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be
+ * reached. All operations must complete before this time. Use a deadline to
+ * set a maximum bound on the time spent on a sequence of operations.
+ */
+ public Timeout deadlineNanoTime(long deadlineNanoTime) {
+ this.hasDeadline = true;
+ this.deadlineNanoTime = deadlineNanoTime;
+ return this;
+ }
+
+ /** Set a deadline of now plus {@code duration} time. */
+ public final Timeout deadline(long duration, TimeUnit unit) {
+ if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration);
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration));
+ }
+
+ /** Clears the timeout. Operating system timeouts may still apply. */
+ public Timeout clearTimeout() {
+ this.timeoutNanos = 0;
+ return this;
+ }
+
+ /** Clears the deadline. */
+ public Timeout clearDeadline() {
+ this.hasDeadline = false;
+ return this;
+ }
+
+ /**
+ * Throws an {@link InterruptedIOException} if the deadline has been reached or if the current
+ * thread has been interrupted. This method doesn't detect timeouts; that should be implemented to
+ * asynchronously abort an in-progress operation.
+ */
+ public void throwIfReached() throws IOException {
+ if (Thread.interrupted()) {
+ throw new InterruptedIOException("thread interrupted");
+ }
+
+ if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
+ throw new InterruptedIOException("deadline reached");
+ }
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java
new file mode 100644
index 0000000..11ecf96
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java
@@ -0,0 +1,81 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * 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 com.android.okhttp.okio;
+
+import java.nio.charset.Charset;
+
+final class Util {
+ /** A cheap and type-safe constant for the UTF-8 Charset. */
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private Util() {
+ }
+
+ public static void checkOffsetAndCount(long size, long offset, long byteCount) {
+ if ((offset | byteCount) < 0 || offset > size || size - offset < byteCount) {
+ throw new ArrayIndexOutOfBoundsException(
+ String.format("size=%s offset=%s byteCount=%s", size, offset, byteCount));
+ }
+ }
+
+ public static short reverseBytesShort(short s) {
+ int i = s & 0xffff;
+ int reversed = (i & 0xff00) >>> 8
+ | (i & 0x00ff) << 8;
+ return (short) reversed;
+ }
+
+ public static int reverseBytesInt(int i) {
+ return (i & 0xff000000) >>> 24
+ | (i & 0x00ff0000) >>> 8
+ | (i & 0x0000ff00) << 8
+ | (i & 0x000000ff) << 24;
+ }
+
+ public static long reverseBytesLong(long v) {
+ return (v & 0xff00000000000000L) >>> 56
+ | (v & 0x00ff000000000000L) >>> 40
+ | (v & 0x0000ff0000000000L) >>> 24
+ | (v & 0x000000ff00000000L) >>> 8
+ | (v & 0x00000000ff000000L) << 8
+ | (v & 0x0000000000ff0000L) << 24
+ | (v & 0x000000000000ff00L) << 40
+ | (v & 0x00000000000000ffL) << 56;
+ }
+
+ /**
+ * Throws {@code t}, even if the declared throws clause doesn't permit it.
+ * This is a terrible – but terribly convenient – hack that makes it easy to
+ * catch and rethrow exceptions after cleanup. See Java Puzzlers #43.
+ */
+ public static void sneakyRethrow(Throwable t) {
+ Util.<Error>sneakyThrow2(t);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> void sneakyThrow2(Throwable t) throws T {
+ throw (T) t;
+ }
+
+ public static boolean arrayRangeEquals(
+ byte[] a, int aOffset, byte[] b, int bOffset, int byteCount) {
+ for (int i = 0; i < byteCount; i++) {
+ if (a[i + aOffset] != b[i + bOffset]) return false;
+ }
+ return true;
+ }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java
new file mode 100644
index 0000000..f4f659e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java
@@ -0,0 +1,6 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/**
+ * Okio complements {@link java.io} and {@link java.nio} to make it much easier to access, store,
+ * and process your data.
+ */
+package com.android.okhttp.okio;
diff --git a/srcgen/generate_android_src.sh b/srcgen/generate_android_src.sh
new file mode 100755
index 0000000..b50c90d
--- /dev/null
+++ b/srcgen/generate_android_src.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Missing environment variables. Did you run build/envsetup.sh and lunch?" 1>&2
+ exit 1
+fi
+
+CLASSPATH=${ANDROID_HOST_OUT}/framework/currysrc.jar
+PROJECT_DIR=${ANDROID_BUILD_TOP}/external/okhttp
+
+cd ${ANDROID_BUILD_TOP}
+make -j15 currysrc
+
+function do_transform() {
+ local SRC_IN_DIR=$1
+ local SRC_OUT_DIR=$2
+
+ if [ ! -d $SRC_OUT_DIR ]; then
+ echo ${SRC_OUT_DIR} does not exist >&2
+ exit 1
+ fi
+ rm -rf ${SRC_OUT_DIR}
+ mkdir -p ${SRC_OUT_DIR}
+
+ java -cp ${CLASSPATH} com.google.currysrc.aosp.RepackagingTransform \
+ --source-dir ${SRC_IN_DIR} \
+ --target-dir ${SRC_OUT_DIR} \
+ --package-transformation "com.squareup:com.android" \
+ --package-transformation "okio:com.android.okhttp.okio" \
+ --tab-size 2 \
+
+}
+
+REPACKAGED_DIR=${PROJECT_DIR}/repackaged
+for i in android okhttp okhttp-urlconnection okhttp-android-support okio/okio
+do
+ for s in src/main/java
+ do
+ IN=${PROJECT_DIR}/$i/$s
+ if [ -d $IN ]; then
+ OUT=${REPACKAGED_DIR}/$i/$s
+ do_transform ${IN} ${OUT}
+ fi
+ done
+done
+
+# Remove an unused source file:
+rm ${REPACKAGED_DIR}/okhttp/src/main/java/com/android/okhttp/internal/Platform.java