You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/01/15 17:37:04 UTC

[4/5] CB-5799 Update version of OkHTTP to 1.3

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/Platform.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/Platform.java b/framework/src/com/squareup/okhttp/internal/Platform.java
old mode 100644
new mode 100755
index 6b4ac34..d5884b1
--- a/framework/src/com/squareup/okhttp/internal/Platform.java
+++ b/framework/src/com/squareup/okhttp/internal/Platform.java
@@ -16,7 +16,6 @@
  */
 package com.squareup.okhttp.internal;
 
-import com.squareup.okhttp.OkHttpClient;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
@@ -25,7 +24,7 @@ import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
-import java.net.NetworkInterface;
+import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.SocketException;
 import java.net.URI;
@@ -57,6 +56,11 @@ public class Platform {
     return PLATFORM;
   }
 
+  /** Prefix used on custom headers. */
+  public String getPrefix() {
+    return "OkHttp";
+  }
+
   public void logW(String warning) {
     System.out.println(warning);
   }
@@ -99,6 +103,11 @@ public class Platform {
   public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
   }
 
+  public void connectSocket(Socket socket, InetSocketAddress address,
+      int connectTimeout) throws IOException {
+    socket.connect(address, connectTimeout);
+  }
+
   /**
    * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
    * value blocks. This throws an {@link UnsupportedOperationException} on
@@ -125,33 +134,21 @@ public class Platform {
     }
   }
 
-  /**
-   * Returns the maximum transmission unit of the network interface used by
-   * {@code socket}, or a reasonable default if this platform doesn't expose the
-   * MTU to the application layer.
-   *
-   * <p>The returned value should only be used as an optimization; such as to
-   * size buffers efficiently.
-   */
-  public int getMtu(Socket socket) throws IOException {
-    return 1400; // Smaller than 1500 to leave room for headers on interfaces like PPPoE.
-  }
-
   /** Attempt to match the host runtime to a capable Platform implementation. */
   private static Platform findPlatform() {
-    Method getMtu;
-    try {
-      getMtu = NetworkInterface.class.getMethod("getMTU");
-    } catch (NoSuchMethodException e) {
-      return new Platform(); // No Java 1.6 APIs. It's either Java 1.5, Android 2.2 or earlier.
-    }
-
     // Attempt to find Android 2.3+ APIs.
     Class<?> openSslSocketClass;
     Method setUseSessionTickets;
     Method setHostname;
     try {
-      openSslSocketClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+      try {
+        openSslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
+      } catch (ClassNotFoundException ignored) {
+        // Older platform before being unbundled.
+        openSslSocketClass = Class.forName(
+            "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+      }
+
       setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
       setHostname = openSslSocketClass.getMethod("setHostname", String.class);
 
@@ -159,10 +156,10 @@ public class Platform {
       try {
         Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
         Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
-        return new Android41(getMtu, openSslSocketClass, setUseSessionTickets, setHostname,
+        return new Android41(openSslSocketClass, setUseSessionTickets, setHostname,
             setNpnProtocols, getNpnSelectedProtocol);
       } catch (NoSuchMethodException ignored) {
-        return new Android23(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
+        return new Android23(openSslSocketClass, setUseSessionTickets, setHostname);
       }
     } catch (ClassNotFoundException ignored) {
       // This isn't an Android runtime.
@@ -179,55 +176,43 @@ public class Platform {
       Class<?> serverProviderClass = Class.forName(npnClassName + "$ServerProvider");
       Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
       Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
-      return new JdkWithJettyNpnPlatform(getMtu, putMethod, getMethod, clientProviderClass,
-          serverProviderClass);
+      return new JdkWithJettyNpnPlatform(
+          putMethod, getMethod, clientProviderClass, serverProviderClass);
     } catch (ClassNotFoundException ignored) {
       // NPN isn't on the classpath.
     } catch (NoSuchMethodException ignored) {
       // The NPN version isn't what we expect.
     }
 
-    return getMtu != null ? new Java5(getMtu) : new Platform();
-  }
-
-  private static class Java5 extends Platform {
-    private final Method getMtu;
-
-    private Java5(Method getMtu) {
-      this.getMtu = getMtu;
-    }
-
-    @Override public int getMtu(Socket socket) throws IOException {
-      try {
-        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(
-            socket.getLocalAddress());
-        return (Integer) getMtu.invoke(networkInterface);
-      } catch (IllegalAccessException e) {
-        throw new AssertionError(e);
-      } catch (InvocationTargetException e) {
-        if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
-        throw new RuntimeException(e.getCause());
-      }
-    }
+    return new Platform();
   }
 
-  /**
-   * Android version 2.3 and newer support TLS session tickets and server name
-   * indication (SNI).
-   */
-  private static class Android23 extends Java5 {
+  /** Android version 2.3 and newer support TLS session tickets and server name indication (SNI). */
+  private static class Android23 extends Platform {
     protected final Class<?> openSslSocketClass;
     private final Method setUseSessionTickets;
     private final Method setHostname;
 
-    private Android23(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
-        Method setHostname) {
-      super(getMtu);
+    private Android23(
+        Class<?> openSslSocketClass, Method setUseSessionTickets, Method setHostname) {
       this.openSslSocketClass = openSslSocketClass;
       this.setUseSessionTickets = setUseSessionTickets;
       this.setHostname = setHostname;
     }
 
+    @Override public void connectSocket(Socket socket, InetSocketAddress address,
+        int connectTimeout) throws IOException {
+      try {
+        socket.connect(address, connectTimeout);
+      } catch (SecurityException se) {
+        // Before android 4.3, socket.connect could throw a SecurityException
+        // if opening a socket resulted in an EACCES error.
+        IOException ioException = new IOException("Exception in connect");
+        ioException.initCause(se);
+        throw ioException;
+      }
+    }
+
     @Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
       super.enableTlsExtensions(socket, uriHost);
       if (openSslSocketClass.isInstance(socket)) {
@@ -249,9 +234,9 @@ public class Platform {
     private final Method setNpnProtocols;
     private final Method getNpnSelectedProtocol;
 
-    private Android41(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
-        Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) {
-      super(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
+    private Android41(Class<?> openSslSocketClass, Method setUseSessionTickets, Method setHostname,
+        Method setNpnProtocols, Method getNpnSelectedProtocol) {
+      super(openSslSocketClass, setUseSessionTickets, setHostname);
       this.setNpnProtocols = setNpnProtocols;
       this.getNpnSelectedProtocol = getNpnSelectedProtocol;
     }
@@ -283,19 +268,15 @@ public class Platform {
     }
   }
 
-  /**
-   * OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class
-   * path.
-   */
-  private static class JdkWithJettyNpnPlatform extends Java5 {
+  /** OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class path. */
+  private static class JdkWithJettyNpnPlatform extends Platform {
     private final Method getMethod;
     private final Method putMethod;
     private final Class<?> clientProviderClass;
     private final Class<?> serverProviderClass;
 
-    public JdkWithJettyNpnPlatform(Method getMtu, Method putMethod, Method getMethod,
-        Class<?> clientProviderClass, Class<?> serverProviderClass) {
-      super(getMtu);
+    public JdkWithJettyNpnPlatform(Method putMethod, Method getMethod, Class<?> clientProviderClass,
+        Class<?> serverProviderClass) {
       this.putMethod = putMethod;
       this.getMethod = getMethod;
       this.clientProviderClass = clientProviderClass;
@@ -328,7 +309,7 @@ public class Platform {
         JettyNpnProvider provider =
             (JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
         if (!provider.unsupported && provider.selected == null) {
-          Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+          Logger logger = Logger.getLogger("com.squareup.okhttp.OkHttpClient");
           logger.log(Level.INFO,
               "NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
           return null;

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/StrictLineReader.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/StrictLineReader.java b/framework/src/com/squareup/okhttp/internal/StrictLineReader.java
old mode 100644
new mode 100755
index 3ddc693..74af6fd
--- a/framework/src/com/squareup/okhttp/internal/StrictLineReader.java
+++ b/framework/src/com/squareup/okhttp/internal/StrictLineReader.java
@@ -146,8 +146,7 @@ public class StrictLineReader implements Closeable {
 
       // Let's anticipate up to 80 characters on top of those already read.
       ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
-        @Override
-        public String toString() {
+        @Override public String toString() {
           int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
           try {
             return new String(buf, 0, length, charset.name());

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/Util.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/Util.java b/framework/src/com/squareup/okhttp/internal/Util.java
old mode 100644
new mode 100755
index 290e5ea..9c5b008
--- a/framework/src/com/squareup/okhttp/internal/Util.java
+++ b/framework/src/com/squareup/okhttp/internal/Util.java
@@ -24,11 +24,19 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
 import java.net.Socket;
+import java.net.ServerSocket;
 import java.net.URI;
 import java.net.URL;
 import java.nio.ByteOrder;
 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 java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Junk drawer of utility methods. */
@@ -46,6 +54,9 @@ public final class Util {
   public static final Charset UTF_8 = Charset.forName("UTF-8");
   private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
 
+  private static final char[] DIGITS =
+      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
   private Util() {
   }
 
@@ -127,6 +138,21 @@ public final class Util {
   }
 
   /**
+   * 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.
    */
@@ -258,6 +284,8 @@ public final class Util {
    * buffer.
    */
   public static long skipByReading(InputStream in, long byteCount) throws IOException {
+    if (byteCount == 0) return 0L;
+
     // acquire the shared skip buffer.
     byte[] buffer = skipBuffer.getAndSet(null);
     if (buffer == null) {
@@ -324,4 +352,43 @@ public final class Util {
     }
     return result.toString();
   }
+
+  /** Returns a 32 character string containing a hash of {@code s}. */
+  public static String hash(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+      byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
+      return bytesToHexString(md5bytes);
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  private static String bytesToHexString(byte[] bytes) {
+    char[] digits = DIGITS;
+    char[] buf = new char[bytes.length * 2];
+    int c = 0;
+    for (byte b : bytes) {
+      buf[c++] = digits[(b >> 4) & 0xf];
+      buf[c++] = digits[b & 0xf];
+    }
+    return new String(buf);
+  }
+
+  /** Returns an immutable copy of {@code list}. */
+  public static <T> List<T> immutableList(List<T> list) {
+    return Collections.unmodifiableList(new ArrayList<T>(list));
+  }
+
+  public static ThreadFactory daemonThreadFactory(final String name) {
+    return new ThreadFactory() {
+      @Override public Thread newThread(Runnable runnable) {
+        Thread result = new Thread(runnable, name);
+        result.setDaemon(true);
+        return result;
+      }
+    };
+  }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
old mode 100644
new mode 100755
index 187f3b6..a5d39b3
--- a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
+++ b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -79,11 +79,11 @@ abstract class AbstractHttpInputStream extends InputStream {
    * 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(boolean streamCancelled) throws IOException {
+  protected final void endOfInput() throws IOException {
     if (cacheRequest != null) {
       cacheBody.close();
     }
-    httpEngine.release(streamCancelled);
+    httpEngine.release(false);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
deleted file mode 100644
index 90675b0..0000000
--- a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.squareup.okhttp.internal.http;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream for the body of an HTTP request.
- *
- * <p>Since a single socket's output stream may be used to write multiple HTTP
- * requests to the same server, subclasses should not close the socket stream.
- */
-abstract class AbstractHttpOutputStream extends OutputStream {
-  protected boolean closed;
-
-  @Override public final void write(int data) throws IOException {
-    write(new byte[] { (byte) data });
-  }
-
-  protected final void checkNotClosed() throws IOException {
-    if (closed) {
-      throw new IOException("stream closed");
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
old mode 100644
new mode 100755
index 12e6409..d5f0f4f
--- a/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
@@ -27,11 +27,11 @@ final class HeaderParser {
     int pos = 0;
     while (pos < value.length()) {
       int tokenStart = pos;
-      pos = skipUntil(value, pos, "=,");
+      pos = skipUntil(value, pos, "=,;");
       String directive = value.substring(tokenStart, pos).trim();
 
-      if (pos == value.length() || value.charAt(pos) == ',') {
-        pos++; // consume ',' (if necessary)
+      if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
+        pos++; // consume ',' or ';' (if necessary)
         handler.handle(directive, null);
         continue;
       }
@@ -52,7 +52,7 @@ final class HeaderParser {
         // unquoted string
       } else {
         int parameterStart = pos;
-        pos = skipUntil(value, pos, ",");
+        pos = skipUntil(value, pos, ",;");
         parameter = value.substring(parameterStart, pos).trim();
       }
 

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java b/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
old mode 100644
new mode 100755
index 4ccd12a..1ad3689
--- a/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
@@ -16,7 +16,8 @@
  */
 package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.internal.Base64;
+import com.squareup.okhttp.OkAuthenticator;
+import com.squareup.okhttp.OkAuthenticator.Challenge;
 import java.io.IOException;
 import java.net.Authenticator;
 import java.net.InetAddress;
@@ -27,11 +28,57 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.squareup.okhttp.OkAuthenticator.Credential;
 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
 
 /** Handles HTTP authentication headers from origin and proxy servers. */
 public final class HttpAuthenticator {
+  /** Uses the global authenticator to get the password. */
+  public static final OkAuthenticator SYSTEM_DEFAULT = new OkAuthenticator() {
+    @Override public Credential authenticate(
+        Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
+      for (Challenge challenge : challenges) {
+        if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
+          continue;
+        }
+
+        PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(url.getHost(),
+            getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(),
+            challenge.getRealm(), challenge.getScheme(), url, Authenticator.RequestorType.SERVER);
+        if (auth != null) {
+          return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
+        }
+      }
+      return null;
+    }
+
+    @Override public Credential authenticateProxy(
+        Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
+      for (Challenge challenge : challenges) {
+        if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
+          continue;
+        }
+
+        InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
+        PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(
+            proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
+            url.getProtocol(), challenge.getRealm(), challenge.getScheme(), url,
+            Authenticator.RequestorType.PROXY);
+        if (auth != null) {
+          return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
+        }
+      }
+      return null;
+    }
+
+    private InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
+      return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
+          ? ((InetSocketAddress) proxy.address()).getAddress()
+          : InetAddress.getByName(url.getHost());
+    }
+  };
+
   private HttpAuthenticator() {
   }
 
@@ -41,68 +88,33 @@ public final class HttpAuthenticator {
    * @return true if credentials have been added to successorRequestHeaders
    *         and another request should be attempted.
    */
-  public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders,
-      RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException {
-    if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
-      throw new IllegalArgumentException();
+  public static boolean processAuthHeader(OkAuthenticator authenticator, int responseCode,
+      RawHeaders responseHeaders, RawHeaders successorRequestHeaders, Proxy proxy, URL url)
+      throws IOException {
+    String responseField;
+    String requestField;
+    if (responseCode == HTTP_UNAUTHORIZED) {
+      responseField = "WWW-Authenticate";
+      requestField = "Authorization";
+    } else if (responseCode == HTTP_PROXY_AUTH) {
+      responseField = "Proxy-Authenticate";
+      requestField = "Proxy-Authorization";
+    } else {
+      throw new IllegalArgumentException(); // TODO: ProtocolException?
     }
-
-    // Keep asking for username/password until authorized.
-    String challengeHeader =
-        responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate";
-    String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url);
-    if (credentials == null) {
-      return false; // Could not find credentials so end the request cycle.
-    }
-
-    // Add authorization credentials, bypassing the already-connected check.
-    String fieldName = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authorization" : "Authorization";
-    successorRequestHeaders.set(fieldName, credentials);
-    return true;
-  }
-
-  /**
-   * Returns the authorization credentials that may satisfy the challenge.
-   * Returns null if a challenge header was not provided or if credentials
-   * were not available.
-   */
-  private static String getCredentials(RawHeaders responseHeaders, String challengeHeader,
-      Proxy proxy, URL url) throws IOException {
-    List<Challenge> challenges = parseChallenges(responseHeaders, challengeHeader);
+    List<Challenge> challenges = parseChallenges(responseHeaders, responseField);
     if (challenges.isEmpty()) {
-      return null;
+      return false; // Could not find a challenge so end the request cycle.
     }
-
-    for (Challenge challenge : challenges) {
-      // Use the global authenticator to get the password.
-      PasswordAuthentication auth;
-      if (responseHeaders.getResponseCode() == HTTP_PROXY_AUTH) {
-        InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
-        auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(),
-            getConnectToInetAddress(proxy, url), proxyAddress.getPort(), url.getProtocol(),
-            challenge.realm, challenge.scheme, url, Authenticator.RequestorType.PROXY);
-      } else {
-        auth = Authenticator.requestPasswordAuthentication(url.getHost(),
-            getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), challenge.realm,
-            challenge.scheme, url, Authenticator.RequestorType.SERVER);
-      }
-      if (auth == null) {
-        continue;
-      }
-
-      // Use base64 to encode the username and password.
-      String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword());
-      byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
-      String encoded = Base64.encode(bytes);
-      return challenge.scheme + " " + encoded;
+    Credential credential = responseHeaders.getResponseCode() == HTTP_PROXY_AUTH
+        ? authenticator.authenticateProxy(proxy, url, challenges)
+        : authenticator.authenticate(proxy, url, challenges);
+    if (credential == null) {
+      return false; // Could not satisfy the challenge so end the request cycle.
     }
-
-    return null;
-  }
-
-  private static InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
-    return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
-        ? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.getHost());
+    // Add authorization credentials, bypassing the already-connected check.
+    successorRequestHeaders.set(requestField, credential.getHeaderValue());
+    return true;
   }
 
   /**
@@ -134,7 +146,7 @@ public final class HttpAuthenticator {
         //       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(pos, "realm=\"", 0, "realm=\"".length())) {
+        if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
           break; // Unexpected challenge parameter; give up!
         }
 
@@ -151,25 +163,4 @@ public final class HttpAuthenticator {
     }
     return result;
   }
-
-  /** An RFC 2617 challenge. */
-  private static final class Challenge {
-    final String scheme;
-    final String realm;
-
-    Challenge(String scheme, String realm) {
-      this.scheme = scheme;
-      this.realm = realm;
-    }
-
-    @Override public boolean equals(Object o) {
-      return o instanceof Challenge
-          && ((Challenge) o).scheme.equals(scheme)
-          && ((Challenge) o).realm.equals(realm);
-    }
-
-    @Override public int hashCode() {
-      return scheme.hashCode() + 31 * realm.hashCode();
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpDate.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpDate.java b/framework/src/com/squareup/okhttp/internal/http/HttpDate.java
old mode 100644
new mode 100755
index acb5fda..b4d2c7c
--- a/framework/src/com/squareup/okhttp/internal/http/HttpDate.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpDate.java
@@ -36,14 +36,13 @@ final class HttpDate {
       new ThreadLocal<DateFormat>() {
         @Override protected DateFormat initialValue() {
           DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
-          rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+          rfc1123.setTimeZone(TimeZone.getTimeZone("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_FORMATS = new String[] {
-            /* This list comes from  {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
+  private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] {
       "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
       "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
       "EEE, dd-MMM-yyyy HH:mm:ss z", "EEE, dd-MMM-yyyy HH-mm-ss z", "EEE, dd MMM yy HH:mm:ss z",
@@ -54,19 +53,26 @@ final class HttpDate {
             /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
       "EEE MMM d yyyy HH:mm:ss z", };
 
-  /**
-   * Returns the date for {@code value}. Returns null if the value couldn't be
-   * parsed.
-   */
+  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) {
     try {
       return STANDARD_DATE_FORMAT.get().parse(value);
-    } catch (ParseException ignore) {
+    } catch (ParseException ignored) {
     }
-    for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
-      try {
-        return new SimpleDateFormat(formatString, Locale.US).parse(value);
-      } catch (ParseException ignore) {
+    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);
+          BROWSER_COMPATIBLE_DATE_FORMATS[i] = format;
+        }
+        try {
+          return format.parse(value);
+        } catch (ParseException ignored) {
+        }
       }
     }
     return null;

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java b/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
old mode 100644
new mode 100755
index 7a06dca..4a2dad4
--- a/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -19,6 +19,8 @@ package com.squareup.okhttp.internal.http;
 
 import com.squareup.okhttp.Address;
 import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.OkResponseCache;
 import com.squareup.okhttp.ResponseSource;
 import com.squareup.okhttp.TunnelRequest;
 import com.squareup.okhttp.internal.Dns;
@@ -31,6 +33,7 @@ import java.io.OutputStream;
 import java.net.CacheRequest;
 import java.net.CacheResponse;
 import java.net.CookieHandler;
+import java.net.HttpURLConnection;
 import java.net.Proxy;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -85,7 +88,8 @@ public class HttpEngine {
   };
   public static final int HTTP_CONTINUE = 100;
 
-  protected final HttpURLConnectionImpl policy;
+  protected final Policy policy;
+  protected final OkHttpClient client;
 
   protected final String method;
 
@@ -106,6 +110,9 @@ public class HttpEngine {
   /** The time when the request headers were written, or -1 if they haven't been written yet. */
   long sentRequestMillis = -1;
 
+  /** Whether the connection has been established. */
+  boolean connected;
+
   /**
    * True if this client added an "Accept-Encoding: gzip" header field and is
    * therefore responsible for also decompressing the transfer stream.
@@ -137,14 +144,15 @@ public class HttpEngine {
 
   /**
    * @param requestHeaders the client's supplied request headers. This class
-   * creates a private copy that it can mutate.
+   *     creates a private copy that it can mutate.
    * @param connection the connection used for an intermediate response
-   * immediately prior to this request/response pair, such as a same-host
-   * redirect. This engine assumes ownership of the connection and must
-   * release it when it is unneeded.
+   *     immediately prior to this request/response pair, such as a same-host
+   *     redirect. This engine assumes ownership of the connection and must
+   *     release it when it is unneeded.
    */
-  public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
+  public HttpEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
       Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
+    this.client = client;
     this.policy = policy;
     this.method = method;
     this.connection = connection;
@@ -175,8 +183,9 @@ public class HttpEngine {
 
     prepareRawRequestHeaders();
     initResponseSource();
-    if (policy.responseCache != null) {
-      policy.responseCache.trackResponse(responseSource);
+    OkResponseCache responseCache = client.getOkResponseCache();
+    if (responseCache != null) {
+      responseCache.trackResponse(responseSource);
     }
 
     // The raw response source may require the network, but the request
@@ -196,8 +205,7 @@ public class HttpEngine {
     if (responseSource.requiresConnection()) {
       sendSocketRequest();
     } else if (connection != null) {
-      policy.connectionPool.recycle(connection);
-      policy.getFailedRoutes().remove(connection.getRoute());
+      client.getConnectionPool().recycle(connection);
       connection = null;
     }
   }
@@ -208,15 +216,14 @@ public class HttpEngine {
    */
   private void initResponseSource() throws IOException {
     responseSource = ResponseSource.NETWORK;
-    if (!policy.getUseCaches() || policy.responseCache == null) {
-      return;
-    }
+    if (!policy.getUseCaches()) return;
 
-    CacheResponse candidate =
-        policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false));
-    if (candidate == null) {
-      return;
-    }
+    OkResponseCache responseCache = client.getOkResponseCache();
+    if (responseCache == null) return;
+
+    CacheResponse candidate = responseCache.get(
+        uri, method, requestHeaders.getHeaders().toMultimap(false));
+    if (candidate == null) return;
 
     Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
     cachedResponseBody = candidate.getBody();
@@ -274,22 +281,24 @@ public class HttpEngine {
       SSLSocketFactory sslSocketFactory = null;
       HostnameVerifier hostnameVerifier = null;
       if (uri.getScheme().equalsIgnoreCase("https")) {
-        sslSocketFactory = policy.sslSocketFactory;
-        hostnameVerifier = policy.hostnameVerifier;
+        sslSocketFactory = client.getSslSocketFactory();
+        hostnameVerifier = client.getHostnameVerifier();
       }
       Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
-          hostnameVerifier, policy.requestedProxy);
-      routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
-          Dns.DEFAULT, policy.getFailedRoutes());
+          hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
+      routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
+          client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
     }
-    connection = routeSelector.next();
+    connection = routeSelector.next(method);
     if (!connection.isConnected()) {
-      connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig());
-      policy.connectionPool.maybeShare(connection);
-      policy.getFailedRoutes().remove(connection.getRoute());
+      connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
+      client.getConnectionPool().maybeShare(connection);
+      client.getRoutesDatabase().connected(connection.getRoute());
+    } else if (!connection.isSpdy()) {
+        connection.updateReadTimeout(client.getReadTimeout());
     }
     connected(connection);
-    if (connection.getRoute().getProxy() != policy.requestedProxy) {
+    if (connection.getRoute().getProxy() != client.getProxy()) {
       // Update the request line if the proxy changed; it may need a host name.
       requestHeaders.getHeaders().setRequestLine(getRequestLine());
     }
@@ -300,6 +309,8 @@ public class HttpEngine {
    * pool. Subclasses use this hook to get a reference to the TLS data.
    */
   protected void connected(Connection connection) {
+    policy.setSelectedProxy(connection.getRoute().getProxy());
+    connected = true;
   }
 
   /**
@@ -328,7 +339,7 @@ public class HttpEngine {
   }
 
   boolean hasRequestBody() {
-    return method.equals("POST") || method.equals("PUT");
+    return method.equals("POST") || method.equals("PUT") || method.equals("PATCH");
   }
 
   /** Returns the request body or null if this request doesn't have a body. */
@@ -387,17 +398,20 @@ public class HttpEngine {
 
   private void maybeCache() throws IOException {
     // Are we caching at all?
-    if (!policy.getUseCaches() || policy.responseCache == null) {
-      return;
-    }
+    if (!policy.getUseCaches()) return;
+    OkResponseCache responseCache = client.getOkResponseCache();
+    if (responseCache == null) return;
+
+    HttpURLConnection connectionToCache = policy.getHttpConnectionToCache();
 
     // Should we cache this response for this request?
     if (!responseHeaders.isCacheable(requestHeaders)) {
+      responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
       return;
     }
 
     // Offer this request to the cache.
-    cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache());
+    cacheRequest = responseCache.put(uri, connectionToCache);
   }
 
   /**
@@ -409,7 +423,7 @@ public class HttpEngine {
   public final void automaticallyReleaseConnectionToPool() {
     automaticallyReleaseConnectionToPool = true;
     if (connection != null && connectionReleased) {
-      policy.connectionPool.recycle(connection);
+      client.getConnectionPool().recycle(connection);
       connection = null;
     }
   }
@@ -419,7 +433,7 @@ public class HttpEngine {
    * closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
    * the connection will be used to follow a redirect.
    */
-  public final void release(boolean streamCancelled) {
+  public final void release(boolean streamCanceled) {
     // If the response body comes from the cache, close it.
     if (responseBodyIn == cachedResponseBody) {
       Util.closeQuietly(responseBodyIn);
@@ -428,12 +442,12 @@ public class HttpEngine {
     if (!connectionReleased && connection != null) {
       connectionReleased = true;
 
-      if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
-          responseTransferIn)) {
+      if (transport == null
+          || !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
         Util.closeQuietly(connection);
         connection = null;
       } else if (automaticallyReleaseConnectionToPool) {
-        policy.connectionPool.recycle(connection);
+        client.getConnectionPool().recycle(connection);
         connection = null;
       }
     }
@@ -521,7 +535,7 @@ public class HttpEngine {
       requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
     }
 
-    CookieHandler cookieHandler = policy.cookieHandler;
+    CookieHandler cookieHandler = client.getCookieHandler();
     if (cookieHandler != null) {
       requestHeaders.addCookies(
           cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
@@ -635,9 +649,17 @@ public class HttpEngine {
       if (cachedResponseHeaders.validate(responseHeaders)) {
         release(false);
         ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
-        setResponse(combinedHeaders, cachedResponseBody);
-        policy.responseCache.trackConditionalCacheHit();
-        policy.responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
+        this.responseHeaders = combinedHeaders;
+
+        // Update the cache after applying the combined headers but before initializing the content
+        // stream, otherwise the Content-Encoding header (if present) will be stripped from the
+        // combined headers and not end up in the cache file if transparent gzip compression is
+        // turned on.
+        OkResponseCache responseCache = client.getOkResponseCache();
+        responseCache.trackConditionalCacheHit();
+        responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
+
+        initContentStream(cachedResponseBody);
         return;
       } else {
         Util.closeQuietly(cachedResponseBody);
@@ -656,7 +678,7 @@ public class HttpEngine {
   }
 
   public void receiveHeaders(RawHeaders headers) throws IOException {
-    CookieHandler cookieHandler = policy.cookieHandler;
+    CookieHandler cookieHandler = client.getCookieHandler();
     if (cookieHandler != null) {
       cookieHandler.put(uri, headers.toMultimap(true));
     }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpResponseCache.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpResponseCache.java b/framework/src/com/squareup/okhttp/internal/http/HttpResponseCache.java
deleted file mode 100644
index 8735166..0000000
--- a/framework/src/com/squareup/okhttp/internal/http/HttpResponseCache.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
- * 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.squareup.okhttp.internal.http;
-
-import com.squareup.okhttp.OkResponseCache;
-import com.squareup.okhttp.ResponseSource;
-import com.squareup.okhttp.internal.Base64;
-import com.squareup.okhttp.internal.DiskLruCache;
-import com.squareup.okhttp.internal.StrictLineReader;
-import com.squareup.okhttp.internal.Util;
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FilterInputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.HttpURLConnection;
-import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
-import java.net.URI;
-import java.net.URLConnection;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
-import static com.squareup.okhttp.internal.Util.US_ASCII;
-import static com.squareup.okhttp.internal.Util.UTF_8;
-
-/**
- * Cache responses in a directory on the file system. Most clients should use
- * {@code android.net.HttpResponseCache}, the stable, documented front end for
- * this.
- */
-public final class HttpResponseCache extends ResponseCache implements OkResponseCache {
-  private static final char[] DIGITS =
-      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
-  // TODO: add APIs to iterate the 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;
-
-  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 HttpResponseCache(File directory, long maxSize) throws IOException {
-    cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
-  }
-
-  private String uriToKey(URI uri) {
-    try {
-      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
-      byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
-      return bytesToHexString(md5bytes);
-    } catch (NoSuchAlgorithmException e) {
-      throw new AssertionError(e);
-    } catch (UnsupportedEncodingException e) {
-      throw new AssertionError(e);
-    }
-  }
-
-  private static String bytesToHexString(byte[] bytes) {
-    char[] digits = DIGITS;
-    char[] buf = new char[bytes.length * 2];
-    int c = 0;
-    for (byte b : bytes) {
-      buf[c++] = digits[(b >> 4) & 0xf];
-      buf[c++] = digits[b & 0xf];
-    }
-    return new String(buf);
-  }
-
-  @Override public CacheResponse get(URI uri, String requestMethod,
-      Map<String, List<String>> requestHeaders) {
-    String key = uriToKey(uri);
-    DiskLruCache.Snapshot snapshot;
-    Entry entry;
-    try {
-      snapshot = cache.get(key);
-      if (snapshot == null) {
-        return null;
-      }
-      entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
-    } catch (IOException e) {
-      // Give up because the cache cannot be read.
-      return null;
-    }
-
-    if (!entry.matches(uri, requestMethod, requestHeaders)) {
-      snapshot.close();
-      return null;
-    }
-
-    return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot)
-        : new EntryCacheResponse(entry, snapshot);
-  }
-
-  @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
-    if (!(urlConnection instanceof HttpURLConnection)) {
-      return null;
-    }
-
-    HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
-    String requestMethod = httpConnection.getRequestMethod();
-    String key = uriToKey(uri);
-
-    if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
-        "DELETE")) {
-      try {
-        cache.remove(key);
-      } catch (IOException ignored) {
-        // The cache cannot be written.
-      }
-      return null;
-    } else 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;
-    }
-
-    HttpEngine httpEngine = getHttpEngine(httpConnection);
-    if (httpEngine == null) {
-      // Don't cache unless the HTTP implementation is ours.
-      return null;
-    }
-
-    ResponseHeaders response = httpEngine.getResponseHeaders();
-    if (response.hasVaryAll()) {
-      return null;
-    }
-
-    RawHeaders varyHeaders =
-        httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
-    Entry entry = new Entry(uri, varyHeaders, httpConnection);
-    DiskLruCache.Editor editor = null;
-    try {
-      editor = cache.edit(key);
-      if (editor == null) {
-        return null;
-      }
-      entry.writeTo(editor);
-      return new CacheRequestImpl(editor);
-    } catch (IOException e) {
-      abortQuietly(editor);
-      return null;
-    }
-  }
-
-  /**
-   * Handles a conditional request hit by updating the stored cache response
-   * with the headers from {@code httpConnection}. The cached response body is
-   * not updated. If the stored response has changed since {@code
-   * conditionalCacheHit} was returned, this does nothing.
-   */
-  @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
-      throws IOException {
-    HttpEngine httpEngine = getHttpEngine(httpConnection);
-    URI uri = httpEngine.getUri();
-    ResponseHeaders response = httpEngine.getResponseHeaders();
-    RawHeaders varyHeaders =
-        httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
-    Entry entry = new Entry(uri, varyHeaders, httpConnection);
-    DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse)
-        ? ((EntryCacheResponse) conditionalCacheHit).snapshot
-        : ((EntrySecureCacheResponse) conditionalCacheHit).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) {
-    }
-  }
-
-  private HttpEngine getHttpEngine(URLConnection httpConnection) {
-    if (httpConnection instanceof HttpURLConnectionImpl) {
-      return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
-    } else if (httpConnection instanceof HttpsURLConnectionImpl) {
-      return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
-    } else {
-      return null;
-    }
-  }
-
-  public DiskLruCache getCache() {
-    return cache;
-  }
-
-  public synchronized int getWriteAbortCount() {
-    return writeAbortCount;
-  }
-
-  public synchronized int getWriteSuccessCount() {
-    return writeSuccessCount;
-  }
-
-  public synchronized void trackResponse(ResponseSource source) {
-    requestCount++;
-
-    switch (source) {
-      case CACHE:
-        hitCount++;
-        break;
-      case CONDITIONAL_CACHE:
-      case NETWORK:
-        networkCount++;
-        break;
-    }
-  }
-
-  public 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 extends CacheRequest {
-    private final DiskLruCache.Editor editor;
-    private OutputStream cacheOut;
-    private boolean done;
-    private OutputStream body;
-
-    public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
-      this.editor = editor;
-      this.cacheOut = editor.newOutputStream(ENTRY_BODY);
-      this.body = new FilterOutputStream(cacheOut) {
-        @Override public void close() throws IOException {
-          synchronized (HttpResponseCache.this) {
-            if (done) {
-              return;
-            }
-            done = true;
-            writeSuccessCount++;
-          }
-          super.close();
-          editor.commit();
-        }
-
-        @Override
-        public void write(byte[] buffer, int offset, int length) throws IOException {
-          // Since we don't override "write(int oneByte)", we can write directly to "out"
-          // and avoid the inefficient implementation from the FilterOutputStream.
-          out.write(buffer, offset, length);
-        }
-      };
-    }
-
-    @Override public void abort() {
-      synchronized (HttpResponseCache.this) {
-        if (done) {
-          return;
-        }
-        done = true;
-        writeAbortCount++;
-      }
-      Util.closeQuietly(cacheOut);
-      try {
-        editor.abort();
-      } catch (IOException ignored) {
-      }
-    }
-
-    @Override public OutputStream getBody() throws IOException {
-      return body;
-    }
-  }
-
-  private static final class Entry {
-    private final String uri;
-    private final RawHeaders varyHeaders;
-    private final String requestMethod;
-    private final RawHeaders responseHeaders;
-    private final String cipherSuite;
-    private final Certificate[] peerCertificates;
-    private final Certificate[] localCertificates;
-
-    /**
-     * 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(InputStream in) throws IOException {
-      try {
-        StrictLineReader reader = new StrictLineReader(in, US_ASCII);
-        uri = reader.readLine();
-        requestMethod = reader.readLine();
-        varyHeaders = new RawHeaders();
-        int varyRequestHeaderLineCount = reader.readInt();
-        for (int i = 0; i < varyRequestHeaderLineCount; i++) {
-          varyHeaders.addLine(reader.readLine());
-        }
-
-        responseHeaders = new RawHeaders();
-        responseHeaders.setStatusLine(reader.readLine());
-        int responseHeaderLineCount = reader.readInt();
-        for (int i = 0; i < responseHeaderLineCount; i++) {
-          responseHeaders.addLine(reader.readLine());
-        }
-
-        if (isHttps()) {
-          String blank = reader.readLine();
-          if (!blank.isEmpty()) {
-            throw new IOException("expected \"\" but was \"" + blank + "\"");
-          }
-          cipherSuite = reader.readLine();
-          peerCertificates = readCertArray(reader);
-          localCertificates = readCertArray(reader);
-        } else {
-          cipherSuite = null;
-          peerCertificates = null;
-          localCertificates = null;
-        }
-      } finally {
-        in.close();
-      }
-    }
-
-    public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection)
-        throws IOException {
-      this.uri = uri.toString();
-      this.varyHeaders = varyHeaders;
-      this.requestMethod = httpConnection.getRequestMethod();
-      this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
-
-      if (isHttps()) {
-        HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
-        cipherSuite = httpsConnection.getCipherSuite();
-        Certificate[] peerCertificatesNonFinal = null;
-        try {
-          peerCertificatesNonFinal = httpsConnection.getServerCertificates();
-        } catch (SSLPeerUnverifiedException ignored) {
-        }
-        peerCertificates = peerCertificatesNonFinal;
-        localCertificates = httpsConnection.getLocalCertificates();
-      } else {
-        cipherSuite = null;
-        peerCertificates = null;
-        localCertificates = null;
-      }
-    }
-
-    public void writeTo(DiskLruCache.Editor editor) throws IOException {
-      OutputStream out = editor.newOutputStream(ENTRY_METADATA);
-      Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
-
-      writer.write(uri + '\n');
-      writer.write(requestMethod + '\n');
-      writer.write(Integer.toString(varyHeaders.length()) + '\n');
-      for (int i = 0; i < varyHeaders.length(); i++) {
-        writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n');
-      }
-
-      writer.write(responseHeaders.getStatusLine() + '\n');
-      writer.write(Integer.toString(responseHeaders.length()) + '\n');
-      for (int i = 0; i < responseHeaders.length(); i++) {
-        writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n');
-      }
-
-      if (isHttps()) {
-        writer.write('\n');
-        writer.write(cipherSuite + '\n');
-        writeCertArray(writer, peerCertificates);
-        writeCertArray(writer, localCertificates);
-      }
-      writer.close();
-    }
-
-    private boolean isHttps() {
-      return uri.startsWith("https://");
-    }
-
-    private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
-      int length = reader.readInt();
-      if (length == -1) {
-        return null;
-      }
-      try {
-        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
-        Certificate[] result = new Certificate[length];
-        for (int i = 0; i < result.length; i++) {
-          String line = reader.readLine();
-          byte[] bytes = Base64.decode(line.getBytes("US-ASCII"));
-          result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes));
-        }
-        return result;
-      } catch (CertificateException e) {
-        throw new IOException(e);
-      }
-    }
-
-    private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
-      if (certificates == null) {
-        writer.write("-1\n");
-        return;
-      }
-      try {
-        writer.write(Integer.toString(certificates.length) + '\n');
-        for (Certificate certificate : certificates) {
-          byte[] bytes = certificate.getEncoded();
-          String line = Base64.encode(bytes);
-          writer.write(line + '\n');
-        }
-      } catch (CertificateEncodingException e) {
-        throw new IOException(e);
-      }
-    }
-
-    public boolean matches(URI uri, String requestMethod,
-        Map<String, List<String>> requestHeaders) {
-      return this.uri.equals(uri.toString())
-          && this.requestMethod.equals(requestMethod)
-          && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false),
-          requestHeaders);
-    }
-  }
-
-  /**
-   * Returns an input stream that reads the body of a snapshot, closing the
-   * snapshot when the stream is closed.
-   */
-  private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
-    return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
-      @Override public void close() throws IOException {
-        snapshot.close();
-        super.close();
-      }
-    };
-  }
-
-  static class EntryCacheResponse extends CacheResponse {
-    private final Entry entry;
-    private final DiskLruCache.Snapshot snapshot;
-    private final InputStream in;
-
-    public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
-      this.entry = entry;
-      this.snapshot = snapshot;
-      this.in = newBodyInputStream(snapshot);
-    }
-
-    @Override public Map<String, List<String>> getHeaders() {
-      return entry.responseHeaders.toMultimap(true);
-    }
-
-    @Override public InputStream getBody() {
-      return in;
-    }
-  }
-
-  static class EntrySecureCacheResponse extends SecureCacheResponse {
-    private final Entry entry;
-    private final DiskLruCache.Snapshot snapshot;
-    private final InputStream in;
-
-    public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
-      this.entry = entry;
-      this.snapshot = snapshot;
-      this.in = newBodyInputStream(snapshot);
-    }
-
-    @Override public Map<String, List<String>> getHeaders() {
-      return entry.responseHeaders.toMultimap(true);
-    }
-
-    @Override public InputStream getBody() {
-      return in;
-    }
-
-    @Override public String getCipherSuite() {
-      return entry.cipherSuite;
-    }
-
-    @Override public List<Certificate> getServerCertificateChain()
-        throws SSLPeerUnverifiedException {
-      if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
-        throw new SSLPeerUnverifiedException(null);
-      }
-      return Arrays.asList(entry.peerCertificates.clone());
-    }
-
-    @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-      if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
-        throw new SSLPeerUnverifiedException(null);
-      }
-      return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
-    }
-
-    @Override public List<Certificate> getLocalCertificateChain() {
-      if (entry.localCertificates == null || entry.localCertificates.length == 0) {
-        return null;
-      }
-      return Arrays.asList(entry.localCertificates.clone());
-    }
-
-    @Override public Principal getLocalPrincipal() {
-      if (entry.localCertificates == null || entry.localCertificates.length == 0) {
-        return null;
-      }
-      return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpTransport.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpTransport.java b/framework/src/com/squareup/okhttp/internal/http/HttpTransport.java
old mode 100644
new mode 100755
index f6d77b2..c967830
--- a/framework/src/com/squareup/okhttp/internal/http/HttpTransport.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpTransport.java
@@ -78,18 +78,23 @@ public final class HttpTransport implements Transport {
     }
 
     // Stream a request body of a known length.
-    int fixedContentLength = httpEngine.policy.getFixedContentLength();
+    long fixedContentLength = httpEngine.policy.getFixedContentLength();
     if (fixedContentLength != -1) {
       httpEngine.requestHeaders.setContentLength(fixedContentLength);
       writeRequestHeaders();
       return new FixedLengthOutputStream(requestOut, fixedContentLength);
     }
 
+    long contentLength = httpEngine.requestHeaders.getContentLength();
+    if (contentLength > Integer.MAX_VALUE) {
+      throw new IllegalArgumentException("Use setFixedLengthStreamingMode() or "
+          + "setChunkedStreamingMode() for requests larger than 2 GiB.");
+    }
+
     // Buffer a request body of a known length.
-    int contentLength = httpEngine.requestHeaders.getContentLength();
     if (contentLength != -1) {
       writeRequestHeaders();
-      return new RetryableOutputStream(contentLength);
+      return new RetryableOutputStream((int) contentLength);
     }
 
     // Buffer a request body of an unknown length. Don't write request
@@ -127,15 +132,18 @@ public final class HttpTransport implements Transport {
   }
 
   @Override public ResponseHeaders readResponseHeaders() throws IOException {
-    RawHeaders headers = RawHeaders.fromBytes(socketIn);
-    httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion());
-    httpEngine.receiveHeaders(headers);
-    return new ResponseHeaders(httpEngine.uri, headers);
+    RawHeaders rawHeaders = RawHeaders.fromBytes(socketIn);
+    httpEngine.connection.setHttpMinorVersion(rawHeaders.getHttpMinorVersion());
+    httpEngine.receiveHeaders(rawHeaders);
+
+    ResponseHeaders headers = new ResponseHeaders(httpEngine.uri, rawHeaders);
+    headers.setTransport("http/1.1");
+    return headers;
   }
 
-  public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
+  public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
       InputStream responseBodyIn) {
-    if (streamCancelled) {
+    if (streamCanceled) {
       return false;
     }
 
@@ -169,6 +177,10 @@ public final class HttpTransport implements Transport {
    * Discards the response body so that the connection can be reused. This
    * needs to be done judiciously, since it delays the current request in
    * order to speed up a potential future request that may never occur.
+   *
+   * <p>A stream may be discarded to encourage response caching (a response
+   * cannot be cached unless it is consumed completely) or to enable connection
+   * reuse.
    */
   private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
     Connection connection = httpEngine.connection;
@@ -212,9 +224,9 @@ public final class HttpTransport implements Transport {
   /** An HTTP body with a fixed length known in advance. */
   private static final class FixedLengthOutputStream extends AbstractOutputStream {
     private final OutputStream socketOut;
-    private int bytesRemaining;
+    private long bytesRemaining;
 
-    private FixedLengthOutputStream(OutputStream socketOut, int bytesRemaining) {
+    private FixedLengthOutputStream(OutputStream socketOut, long bytesRemaining) {
       this.socketOut = socketOut;
       this.bytesRemaining = bytesRemaining;
     }
@@ -358,14 +370,14 @@ public final class HttpTransport implements Transport {
 
   /** An HTTP body with a fixed length specified in advance. */
   private static class FixedLengthInputStream extends AbstractHttpInputStream {
-    private int bytesRemaining;
+    private long bytesRemaining;
 
     public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine,
-        int length) throws IOException {
+        long length) throws IOException {
       super(is, httpEngine, cacheRequest);
       bytesRemaining = length;
       if (bytesRemaining == 0) {
-        endOfInput(false);
+        endOfInput();
       }
     }
 
@@ -375,7 +387,7 @@ public final class HttpTransport implements Transport {
       if (bytesRemaining == 0) {
         return -1;
       }
-      int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
+      int read = in.read(buffer, offset, (int) Math.min(count, bytesRemaining));
       if (read == -1) {
         unexpectedEndOfInput(); // the server didn't supply the promised content length
         throw new ProtocolException("unexpected end of stream");
@@ -383,14 +395,14 @@ public final class HttpTransport implements Transport {
       bytesRemaining -= read;
       cacheWrite(buffer, offset, read);
       if (bytesRemaining == 0) {
-        endOfInput(false);
+        endOfInput();
       }
       return read;
     }
 
     @Override public int available() throws IOException {
       checkNotClosed();
-      return bytesRemaining == 0 ? 0 : Math.min(in.available(), bytesRemaining);
+      return bytesRemaining == 0 ? 0 : (int) Math.min(in.available(), bytesRemaining);
     }
 
     @Override public void close() throws IOException {
@@ -460,7 +472,7 @@ public final class HttpTransport implements Transport {
         RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders();
         RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders);
         httpEngine.receiveHeaders(rawResponseHeaders);
-        endOfInput(false);
+        endOfInput();
       }
     }
 

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e16cab6b/framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
old mode 100644
new mode 100755
index eabe649..fb4a704
--- a/framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
@@ -18,33 +18,28 @@
 package com.squareup.okhttp.internal.http;
 
 import com.squareup.okhttp.Connection;
-import com.squareup.okhttp.ConnectionPool;
 import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Route;
-import com.squareup.okhttp.internal.AbstractOutputStream;
-import com.squareup.okhttp.internal.FaultRecoveringOutputStream;
+import com.squareup.okhttp.internal.Platform;
 import com.squareup.okhttp.internal.Util;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.CookieHandler;
 import java.net.HttpRetryException;
 import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
 import java.net.ProtocolException;
 import java.net.Proxy;
-import java.net.ProxySelector;
 import java.net.SocketPermission;
 import java.net.URL;
 import java.security.Permission;
 import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import javax.net.ssl.HostnameVerifier;
+import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSocketFactory;
 
 import static com.squareup.okhttp.internal.Util.getEffectivePort;
 
@@ -62,10 +57,10 @@ import static com.squareup.okhttp.internal.Util.getEffectivePort;
  * connection} field on this class for null/non-null to determine of an instance
  * is currently connected to a server.
  */
-public class HttpURLConnectionImpl extends HttpURLConnection {
+public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
 
   /** Numeric status code, 307: Temporary Redirect. */
-  static final int HTTP_TEMP_REDIRECT = 307;
+  public static final int HTTP_TEMP_REDIRECT = 307;
 
   /**
    * How many redirects should we follow? Chrome follows 21; Firefox, curl,
@@ -73,51 +68,19 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
    */
   private static final int MAX_REDIRECTS = 20;
 
-  /**
-   * The minimum number of request body bytes to transmit before we're willing
-   * to let a routine {@link IOException} bubble up to the user. This is used to
-   * size a buffer for data that will be replayed upon error.
-   */
-  private static final int MAX_REPLAY_BUFFER_LENGTH = 8192;
-
-  private final boolean followProtocolRedirects;
-
-  /** The proxy requested by the client, or null for a proxy to be selected automatically. */
-  final Proxy requestedProxy;
-
-  final ProxySelector proxySelector;
-  final CookieHandler cookieHandler;
-  final OkResponseCache responseCache;
-  final ConnectionPool connectionPool;
-  /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
-  SSLSocketFactory sslSocketFactory;
-  HostnameVerifier hostnameVerifier;
-  final Set<Route> failedRoutes;
+  final OkHttpClient client;
 
   private final RawHeaders rawRequestHeaders = new RawHeaders();
-
+  /** Like the superclass field of the same name, but a long and available on all platforms. */
+  private long fixedContentLength = -1;
   private int redirectionCount;
-  private FaultRecoveringOutputStream faultRecoveringRequestBody;
-
   protected IOException httpEngineFailure;
   protected HttpEngine httpEngine;
+  private Proxy selectedProxy;
 
-  public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
-      Set<Route> failedRoutes) {
+  public HttpURLConnectionImpl(URL url, OkHttpClient client) {
     super(url);
-    this.followProtocolRedirects = client.getFollowProtocolRedirects();
-    this.failedRoutes = failedRoutes;
-    this.requestedProxy = client.getProxy();
-    this.proxySelector = client.getProxySelector();
-    this.cookieHandler = client.getCookieHandler();
-    this.connectionPool = client.getConnectionPool();
-    this.sslSocketFactory = client.getSslSocketFactory();
-    this.hostnameVerifier = client.getHostnameVerifier();
-    this.responseCache = responseCache;
-  }
-
-  Set<Route> getFailedRoutes() {
-    return failedRoutes;
+    this.client = client;
   }
 
   @Override public final void connect() throws IOException {
@@ -197,7 +160,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     try {
       return getResponse().getResponseHeaders().getHeaders().toMultimap(true);
     } catch (IOException e) {
-      return null;
+      return Collections.emptyMap();
     }
   }
 
@@ -241,29 +204,14 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
       throw new ProtocolException("cannot write request body after response has been read");
     }
 
-    if (faultRecoveringRequestBody == null) {
-      faultRecoveringRequestBody = new FaultRecoveringOutputStream(MAX_REPLAY_BUFFER_LENGTH, out) {
-        @Override protected OutputStream replacementStream(IOException e) throws IOException {
-          if (httpEngine.getRequestBody() instanceof AbstractOutputStream
-              && ((AbstractOutputStream) httpEngine.getRequestBody()).isClosed()) {
-            return null; // Don't recover once the underlying stream has been closed.
-          }
-          if (handleFailure(e)) {
-            return httpEngine.getRequestBody();
-          }
-          return null; // This is a permanent failure.
-        }
-      };
-    }
-
-    return faultRecoveringRequestBody;
+    return out;
   }
 
   @Override public final Permission getPermission() throws IOException {
     String hostName = getURL().getHost();
     int hostPort = Util.getEffectivePort(getURL());
     if (usingProxy()) {
-      InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address();
+      InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
       hostName = proxyAddress.getHostName();
       hostPort = proxyAddress.getPort();
     }
@@ -277,6 +225,22 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     return rawRequestHeaders.get(field);
   }
 
+  @Override public void setConnectTimeout(int timeoutMillis) {
+    client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+  }
+
+  @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;
@@ -290,8 +254,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
         if (method.equals("GET")) {
           // they are requesting a stream to write to. This implies a POST method
           method = "POST";
-        } else if (!method.equals("POST") && !method.equals("PUT")) {
-          // If the request method is neither POST nor PUT, then you're not writing
+        } else if (!method.equals("POST") && !method.equals("PUT") && !method.equals("PATCH")) {
+          // If the request method is neither POST nor PUT nor PATCH, then you're not writing
           throw new ProtocolException(method + " does not support writing");
         }
       }
@@ -302,17 +266,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     }
   }
 
-  protected HttpURLConnection getHttpConnectionToCache() {
+  @Override public HttpURLConnection getHttpConnectionToCache() {
     return this;
   }
 
   private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
       Connection connection, RetryableOutputStream requestBody) throws IOException {
     if (url.getProtocol().equals("http")) {
-      return new HttpEngine(this, method, requestHeaders, connection, requestBody);
+      return new HttpEngine(client, this, method, requestHeaders, connection, requestBody);
     } else if (url.getProtocol().equals("https")) {
-      return new HttpsURLConnectionImpl.HttpsEngine(
-          this, method, requestHeaders, connection, requestBody);
+      return new HttpsEngine(client, this, method, requestHeaders, connection, requestBody);
     } else {
       throw new AssertionError();
     }
@@ -348,7 +311,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
       // 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.
-      int responseCode = getResponseCode();
+      int responseCode = httpEngine.getResponseCode();
       if (responseCode == HTTP_MULT_CHOICE
           || responseCode == HTTP_MOVED_PERM
           || responseCode == HTTP_MOVED_TEMP
@@ -358,8 +321,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
       }
 
       if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
-        throw new HttpRetryException("Cannot retry streamed HTTP body",
-            httpEngine.getResponseCode());
+        throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
       }
 
       if (retry == Retry.DIFFERENT_CONNECTION) {
@@ -370,6 +332,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
 
       httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(),
           (RetryableOutputStream) requestBody);
+
+      if (requestBody == null) {
+        // Drop the Content-Length header when redirected from POST to GET.
+        httpEngine.getRequestHeaders().removeContentLength();
+      }
     }
   }
 
@@ -384,6 +351,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
       if (readResponse) {
         httpEngine.readResponse();
       }
+
       return true;
     } catch (IOException e) {
       if (handleFailure(e)) {
@@ -407,8 +375,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
 
     OutputStream requestBody = httpEngine.getRequestBody();
     boolean canRetryRequestBody = requestBody == null
-        || requestBody instanceof RetryableOutputStream
-        || (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable());
+        || requestBody instanceof RetryableOutputStream;
     if (routeSelector == null && httpEngine.connection == null // No connection.
         || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
         || !isRecoverable(e)
@@ -418,15 +385,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     }
 
     httpEngine.release(true);
-    RetryableOutputStream retryableOutputStream = requestBody instanceof RetryableOutputStream
-        ? (RetryableOutputStream) requestBody
-        : null;
+    RetryableOutputStream retryableOutputStream = (RetryableOutputStream) requestBody;
     httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream);
     httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
-    if (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()) {
-      httpEngine.sendRequest();
-      faultRecoveringRequestBody.replaceStream(httpEngine.getRequestBody());
-    }
     return true;
   }
 
@@ -451,13 +412,13 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
 
   /**
    * Returns the retry action to take for the current response headers. The
-   * headers, proxy and target URL or this connection may be adjusted to
+   * headers, proxy and target URL for this connection may be adjusted to
    * prepare for a follow up request.
    */
   private Retry processResponseHeaders() throws IOException {
     Proxy selectedProxy = httpEngine.connection != null
         ? httpEngine.connection.getRoute().getProxy()
-        : requestedProxy;
+        : client.getProxy();
     final int responseCode = getResponseCode();
     switch (responseCode) {
       case HTTP_PROXY_AUTH:
@@ -466,8 +427,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
         }
         // fall-through
       case HTTP_UNAUTHORIZED:
-        boolean credentialsFound = HttpAuthenticator.processAuthHeader(getResponseCode(),
-            httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, selectedProxy, url);
+        boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
+            getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders,
+            selectedProxy, url);
         return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
 
       case HTTP_MULT_CHOICE:
@@ -496,7 +458,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
           return Retry.NONE; // Don't follow redirects to unsupported protocols.
         }
         boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
-        if (!sameProtocol && !followProtocolRedirects) {
+        if (!sameProtocol && !client.getFollowProtocolRedirects()) {
           return Retry.NONE; // This client doesn't follow redirects across protocols.
         }
         boolean sameHost = previousUrl.getHost().equals(url.getHost());
@@ -513,17 +475,29 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
   }
 
   /** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
-  final int getFixedContentLength() {
+  @Override public final long getFixedContentLength() {
     return fixedContentLength;
   }
 
-  /** @see java.net.HttpURLConnection#setChunkedStreamingMode(int) */
-  final int getChunkLength() {
+  @Override public final int getChunkLength() {
     return chunkLength;
   }
 
   @Override public final boolean usingProxy() {
-    return (requestedProxy != null && requestedProxy.type() != Proxy.Type.DIRECT);
+    if (selectedProxy != null) {
+      return isValidNonDirectProxy(selectedProxy);
+    }
+
+    // This behavior is a bit odd (but is probably justified by the
+    // oddness of the APIs involved). Before a connection is established,
+    // this method will return true only if this connection was explicitly
+    // opened with a Proxy. We don't attempt to query the ProxySelector
+    // at all.
+    return isValidNonDirectProxy(client.getProxy());
+  }
+
+  private static boolean isValidNonDirectProxy(Proxy proxy) {
+    return proxy != null && proxy.type() != Proxy.Type.DIRECT;
   }
 
   @Override public String getResponseMessage() throws IOException {
@@ -541,7 +515,21 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     if (field == null) {
       throw new NullPointerException("field == null");
     }
-    rawRequestHeaders.set(field, newValue);
+    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;
+    }
+
+    if ("X-Android-Transports".equals(field)) {
+      setTransports(newValue, false /* append */);
+    } else {
+      rawRequestHeaders.set(field, newValue);
+    }
   }
 
   @Override public final void addRequestProperty(String field, String value) {
@@ -551,6 +539,52 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
     if (field == null) {
       throw new NullPointerException("field == null");
     }
-    rawRequestHeaders.add(field, value);
+    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;
+    }
+
+    if ("X-Android-Transports".equals(field)) {
+      setTransports(value, true /* append */);
+    } else {
+      rawRequestHeaders.add(field, value);
+    }
+  }
+
+  /*
+   * Splits and validates a comma-separated string of transports.
+   * When append == false, we require that the transport list contains "http/1.1".
+   */
+  private void setTransports(String transportsString, boolean append) {
+    List<String> transportsList = new ArrayList<String>();
+    if (append) {
+      transportsList.addAll(client.getTransports());
+    }
+    for (String transport : transportsString.split(",", -1)) {
+      transportsList.add(transport);
+    }
+    client.setTransports(transportsList);
+  }
+
+  @Override public void setFixedLengthStreamingMode(int contentLength) {
+    setFixedLengthStreamingMode((long) contentLength);
+  }
+
+  // @Override Don't override: this overload method doesn't exist prior to Java 1.7.
+  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);
+  }
+
+  @Override public final void setSelectedProxy(Proxy proxy) {
+    this.selectedProxy = proxy;
   }
 }