cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From i..@apache.org
Subject [4/7] [CB-2431] Switch to OkHttp for FileTransfer connections
Date Mon, 29 Apr 2013 13:39:53 GMT
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..75cd66f
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/Platform.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Access to Platform-specific features necessary for SPDY and advanced TLS.
+ *
+ * <h3>SPDY</h3>
+ * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's
+ * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It
+ * also requires a recent version of {@code DeflaterOutputStream} that is
+ * public API in Java 7 and callable via reflection in Android 4.1+.
+ */
+public class Platform {
+  private static final Platform PLATFORM = findPlatform();
+
+  private Constructor<DeflaterOutputStream> deflaterConstructor;
+
+  public static Platform get() {
+    return PLATFORM;
+  }
+
+  public void logW(String warning) {
+    System.out.println(warning);
+  }
+
+  public void tagSocket(Socket socket) throws SocketException {
+  }
+
+  public void untagSocket(Socket socket) throws SocketException {
+  }
+
+  public URI toUriLenient(URL url) throws URISyntaxException {
+    return url.toURI(); // this isn't as good as the built-in toUriLenient
+  }
+
+  /**
+   * Attempt a TLS connection with useful extensions enabled. This mode
+   * supports more features, but is less likely to be compatible with older
+   * HTTPS servers.
+   */
+  public void enableTlsExtensions(SSLSocket socket, String uriHost) {
+  }
+
+  /**
+   * Attempt a secure connection with basic functionality to maximize
+   * compatibility. Currently this uses SSL 3.0.
+   */
+  public void supportTlsIntolerantServer(SSLSocket socket) {
+    socket.setEnabledProtocols(new String[] {"SSLv3"});
+  }
+
+  /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+  public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+    return null;
+  }
+
+  /**
+   * Sets client-supported protocols on a socket to send to a server. The
+   * protocols are only sent if the socket implementation supports NPN.
+   */
+  public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+  }
+
+  /**
+   * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
+   * value blocks. This throws an {@link UnsupportedOperationException} on
+   * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH.
+   */
+  public OutputStream newDeflaterOutputStream(OutputStream out, Deflater deflater,
+      boolean syncFlush) {
+    try {
+      Constructor<DeflaterOutputStream> constructor = deflaterConstructor;
+      if (constructor == null) {
+        constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor(
+            OutputStream.class, Deflater.class, boolean.class);
+      }
+      return constructor.newInstance(out, deflater, syncFlush);
+    } catch (NoSuchMethodException e) {
+      throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available");
+    } catch (InvocationTargetException e) {
+      throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause()
+          : new RuntimeException(e.getCause());
+    } catch (InstantiationException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /** Attempt to match the host runtime to a capable Platform implementation. */
+  private static Platform findPlatform() {
+    // Attempt to find Android 2.3+ APIs.
+    Class<?> openSslSocketClass;
+    Method setUseSessionTickets;
+    Method setHostname;
+    try {
+      openSslSocketClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+      setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
+      setHostname = openSslSocketClass.getMethod("setHostname", String.class);
+
+      // Attempt to find Android 4.1+ APIs.
+      try {
+        Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
+        Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
+        return new Android41(openSslSocketClass, setUseSessionTickets, setHostname, setNpnProtocols,
+            getNpnSelectedProtocol);
+      } catch (NoSuchMethodException ignored) {
+        return new Android23(openSslSocketClass, setUseSessionTickets, setHostname);
+      }
+    } catch (ClassNotFoundException ignored) {
+      // This isn't an Android runtime.
+    } catch (NoSuchMethodException ignored) {
+      // This isn't Android 2.3 or better.
+    }
+
+    // Attempt to find the Jetty's NPN extension for OpenJDK.
+    try {
+      String npnClassName = "org.eclipse.jetty.npn.NextProtoNego";
+      Class<?> nextProtoNegoClass = Class.forName(npnClassName);
+      Class<?> providerClass = Class.forName(npnClassName + "$Provider");
+      Class<?> clientProviderClass = Class.forName(npnClassName + "$ClientProvider");
+      Class<?> serverProviderClass = Class.forName(npnClassName + "$ServerProvider");
+      Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
+      Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
+      return new JdkWithJettyNpnPlatform(putMethod, getMethod, clientProviderClass,
+          serverProviderClass);
+    } catch (ClassNotFoundException ignored) {
+      return new Platform(); // NPN isn't on the classpath.
+    } catch (NoSuchMethodException ignored) {
+      return new Platform(); // The NPN version isn't what we expect.
+    }
+  }
+
+  /**
+   * 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(Class<?> openSslSocketClass, Method setUseSessionTickets,
+        Method setHostname) {
+      this.openSslSocketClass = openSslSocketClass;
+      this.setUseSessionTickets = setUseSessionTickets;
+      this.setHostname = setHostname;
+    }
+
+    @Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
+      super.enableTlsExtensions(socket, uriHost);
+      if (openSslSocketClass.isInstance(socket)) {
+        // This is Android: use reflection on OpenSslSocketImpl.
+        try {
+          setUseSessionTickets.invoke(socket, true);
+          setHostname.invoke(socket, uriHost);
+        } catch (InvocationTargetException e) {
+          throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+          throw new AssertionError(e);
+        }
+      }
+    }
+  }
+
+  /** Android version 4.1 and newer support NPN. */
+  private static class Android41 extends Android23 {
+    private final Method setNpnProtocols;
+    private final Method getNpnSelectedProtocol;
+
+    private Android41(Class<?> openSslSocketClass, Method setUseSessionTickets, Method setHostname,
+        Method setNpnProtocols, Method getNpnSelectedProtocol) {
+      super(openSslSocketClass, setUseSessionTickets, setHostname);
+      this.setNpnProtocols = setNpnProtocols;
+      this.getNpnSelectedProtocol = getNpnSelectedProtocol;
+    }
+
+    @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+      if (!openSslSocketClass.isInstance(socket)) {
+        return;
+      }
+      try {
+        setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+      if (!openSslSocketClass.isInstance(socket)) {
+        return null;
+      }
+      try {
+        return (byte[]) getNpnSelectedProtocol.invoke(socket);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      }
+    }
+  }
+
+  /**
+   * 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 putMethod, Method getMethod, Class<?> clientProviderClass,
+        Class<?> serverProviderClass) {
+      this.putMethod = putMethod;
+      this.getMethod = getMethod;
+      this.clientProviderClass = clientProviderClass;
+      this.serverProviderClass = serverProviderClass;
+    }
+
+    @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+      try {
+        List<String> strings = new ArrayList<String>();
+        for (int i = 0; i < npnProtocols.length; ) {
+          int length = npnProtocols[i++];
+          strings.add(new String(npnProtocols, i, length, "US-ASCII"));
+          i += length;
+        }
+        Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+            new Class[] {clientProviderClass, serverProviderClass},
+            new JettyNpnProvider(strings));
+        putMethod.invoke(null, socket, provider);
+      } catch (UnsupportedEncodingException e) {
+        throw new AssertionError(e);
+      } catch (InvocationTargetException e) {
+        throw new AssertionError(e);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      }
+    }
+
+    @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+      try {
+        JettyNpnProvider provider =
+            (JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
+        if (!provider.unsupported && provider.selected == null) {
+          Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+          logger.log(Level.INFO,
+              "NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
+          return null;
+        }
+        return provider.unsupported ? null : provider.selected.getBytes("US-ASCII");
+      } catch (UnsupportedEncodingException e) {
+        throw new AssertionError();
+      } catch (InvocationTargetException e) {
+        throw new AssertionError();
+      } catch (IllegalAccessException e) {
+        throw new AssertionError();
+      }
+    }
+  }
+
+  /**
+   * Handle the methods of NextProtoNego's ClientProvider and ServerProvider
+   * without a compile-time dependency on those interfaces.
+   */
+  private static class JettyNpnProvider implements InvocationHandler {
+    private final List<String> protocols;
+    private boolean unsupported;
+    private String selected;
+
+    public JettyNpnProvider(List<String> protocols) {
+      this.protocols = protocols;
+    }
+
+    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      String methodName = method.getName();
+      Class<?> returnType = method.getReturnType();
+      if (args == null) {
+        args = Util.EMPTY_STRING_ARRAY;
+      }
+      if (methodName.equals("supports") && boolean.class == returnType) {
+        return true;
+      } else if (methodName.equals("unsupported") && void.class == returnType) {
+        this.unsupported = true;
+        return null;
+      } else if (methodName.equals("protocols") && args.length == 0) {
+        return protocols;
+      } else if (methodName.equals("selectProtocol")
+          && String.class == returnType
+          && args.length == 1
+          && (args[0] == null || args[0] instanceof List)) {
+        // TODO: use OpenSSL's algorithm which uses both lists
+        List<?> serverProtocols = (List) args[0];
+        this.selected = protocols.get(0);
+        return selected;
+      } else if (methodName.equals("protocolSelected") && args.length == 1) {
+        this.selected = (String) args[0];
+        return null;
+      } else {
+        return method.invoke(this, args);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..93f1754
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/StrictLineReader.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import static com.squareup.okhttp.internal.Util.ISO_8859_1;
+import static com.squareup.okhttp.internal.Util.US_ASCII;
+import static com.squareup.okhttp.internal.Util.UTF_8;
+
+/**
+ * Buffers input from an {@link InputStream} for reading lines.
+ *
+ * This class is used for buffered reading of lines. For purposes of this class, a line ends with
+ * "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated line at
+ * end of input is invalid and will be ignored, the caller may use {@code hasUnterminatedLine()}
+ * to detect it after catching the {@code EOFException}.
+ *
+ * This class is intended for reading input that strictly consists of lines, such as line-based
+ * cache entries or cache journal. Unlike the {@link BufferedReader} which in conjunction with
+ * {@link InputStreamReader} provides similar functionality, this class uses different
+ * end-of-input reporting and a more restrictive definition of a line.
+ *
+ * This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
+ * and 10, respectively, and the representation of no other character contains these values.
+ * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
+ * The default charset is US_ASCII.
+ */
+public class StrictLineReader implements Closeable {
+  private static final byte CR = (byte) '\r';
+  private static final byte LF = (byte) '\n';
+
+  private final InputStream in;
+  private final Charset charset;
+
+  // Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
+  // and the data in the range [pos, end) is buffered for reading. At end of input, if there is
+  // an unterminated line, we set end == -1, otherwise end == pos. If the underlying
+  // {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
+  private byte[] buf;
+  private int pos;
+  private int end;
+
+  /**
+   * Constructs a new {@code StrictLineReader} with the default capacity and charset.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @throws NullPointerException if {@code in} is null.
+   */
+  public StrictLineReader(InputStream in) {
+    this(in, 8192);
+  }
+
+  /**
+   * Constructs a new {@code LineReader} with the specified capacity and the default charset.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @param capacity the capacity of the buffer.
+   * @throws NullPointerException if {@code in} is null.
+   * @throws IllegalArgumentException for negative or zero {@code capacity}.
+   */
+  public StrictLineReader(InputStream in, int capacity) {
+    this(in, capacity, US_ASCII);
+  }
+
+  /**
+   * Constructs a new {@code LineReader} with the specified charset and the default capacity.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @param charset the charset used to decode data.
+   * Only US-ASCII, UTF-8 and ISO-8859-1 is supported.
+   * @throws NullPointerException if {@code in} or {@code charset} is null.
+   * @throws IllegalArgumentException if the specified charset is not supported.
+   */
+  public StrictLineReader(InputStream in, Charset charset) {
+    this(in, 8192, charset);
+  }
+
+  /**
+   * Constructs a new {@code LineReader} with the specified capacity and charset.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @param capacity the capacity of the buffer.
+   * @param charset the charset used to decode data.
+   * Only US-ASCII, UTF-8 and ISO-8859-1 is supported.
+   * @throws NullPointerException if {@code in} or {@code charset} is null.
+   * @throws IllegalArgumentException if {@code capacity} is negative or zero
+   * or the specified charset is not supported.
+   */
+  public StrictLineReader(InputStream in, int capacity, Charset charset) {
+    if (in == null || charset == null) {
+      throw new NullPointerException();
+    }
+    if (capacity < 0) {
+      throw new IllegalArgumentException("capacity <= 0");
+    }
+    if (!(charset.equals(US_ASCII) || charset.equals(UTF_8) || charset.equals(ISO_8859_1))) {
+      throw new IllegalArgumentException("Unsupported encoding");
+    }
+
+    this.in = in;
+    this.charset = charset;
+    buf = new byte[capacity];
+  }
+
+  /**
+   * Closes the reader by closing the underlying {@code InputStream} and
+   * marking this reader as closed.
+   *
+   * @throws IOException for errors when closing the underlying {@code InputStream}.
+   */
+  @Override
+  public void close() throws IOException {
+    synchronized (in) {
+      if (buf != null) {
+        buf = null;
+        in.close();
+      }
+    }
+  }
+
+  /**
+   * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
+   * this end of line marker is not included in the result.
+   *
+   * @return the next line from the input.
+   * @throws IOException for underlying {@code InputStream} errors.
+   * @throws EOFException for the end of source stream.
+   */
+  public String readLine() throws IOException {
+    synchronized (in) {
+      if (buf == null) {
+        throw new IOException("LineReader is closed");
+      }
+
+      // Read more data if we are at the end of the buffered data.
+      // Though it's an error to read after an exception, we will let {@code fillBuf()}
+      // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
+      if (pos >= end) {
+        fillBuf();
+      }
+      // Try to find LF in the buffered data and return the line if successful.
+      for (int i = pos; i != end; ++i) {
+        if (buf[i] == LF) {
+          int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
+          String res = new String(buf, pos, lineEnd - pos, charset);
+          pos = i + 1;
+          return res;
+        }
+      }
+
+      // Let's anticipate up to 80 characters on top of those already read.
+      ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
+        @Override
+        public String toString() {
+          int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
+          return new String(buf, 0, length, charset);
+        }
+      };
+
+      while (true) {
+        out.write(buf, pos, end - pos);
+        // Mark unterminated line in case fillBuf throws EOFException or IOException.
+        end = -1;
+        fillBuf();
+        // Try to find LF in the buffered data and return the line if successful.
+        for (int i = pos; i != end; ++i) {
+          if (buf[i] == LF) {
+            if (i != pos) {
+              out.write(buf, pos, i - pos);
+            }
+            pos = i + 1;
+            return out.toString();
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Read an {@code int} from a line containing its decimal representation.
+   *
+   * @return the value of the {@code int} from the next line.
+   * @throws IOException for underlying {@code InputStream} errors or conversion error.
+   * @throws EOFException for the end of source stream.
+   */
+  public int readInt() throws IOException {
+    String intString = readLine();
+    try {
+      return Integer.parseInt(intString);
+    } catch (NumberFormatException e) {
+      throw new IOException("expected an int but was \"" + intString + "\"");
+    }
+  }
+
+  /**
+   * Reads new input data into the buffer. Call only with pos == end or end == -1,
+   * depending on the desired outcome if the function throws.
+   *
+   * @throws IOException for underlying {@code InputStream} errors.
+   * @throws EOFException for the end of source stream.
+   */
+  private void fillBuf() throws IOException {
+    int result = in.read(buf, 0, buf.length);
+    if (result == -1) {
+      throw new EOFException();
+    }
+    pos = 0;
+    end = result;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..dc914cc
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/Util.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Junk drawer of utility methods. */
+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 ISO-8859-1 Charset. */
+  public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+
+  /** A cheap and type-safe constant for the US-ASCII Charset. */
+  public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+  /** A cheap and type-safe constant for the UTF-8 Charset. */
+  public static final Charset UTF_8 = Charset.forName("UTF-8");
+  private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
+
+  private Util() {
+  }
+
+  public static int getEffectivePort(URI uri) {
+    return getEffectivePort(uri.getScheme(), uri.getPort());
+  }
+
+  public static int getEffectivePort(URL url) {
+    return getEffectivePort(url.getProtocol(), url.getPort());
+  }
+
+  private static int getEffectivePort(String scheme, int specifiedPort) {
+    return specifiedPort != -1 ? specifiedPort : getDefaultPort(scheme);
+  }
+
+  public static int getDefaultPort(String scheme) {
+    if ("http".equalsIgnoreCase(scheme)) {
+      return 80;
+    } else if ("https".equalsIgnoreCase(scheme)) {
+      return 443;
+    } else {
+      return -1;
+    }
+  }
+
+  public static void checkOffsetAndCount(int arrayLength, int offset, int count) {
+    if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+      throw new ArrayIndexOutOfBoundsException();
+    }
+  }
+
+  public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) {
+    if (order == ByteOrder.BIG_ENDIAN) {
+      dst[offset++] = (byte) ((value >> 24) & 0xff);
+      dst[offset++] = (byte) ((value >> 16) & 0xff);
+      dst[offset++] = (byte) ((value >> 8) & 0xff);
+      dst[offset] = (byte) ((value >> 0) & 0xff);
+    } else {
+      dst[offset++] = (byte) ((value >> 0) & 0xff);
+      dst[offset++] = (byte) ((value >> 8) & 0xff);
+      dst[offset++] = (byte) ((value >> 16) & 0xff);
+      dst[offset] = (byte) ((value >> 24) & 0xff);
+    }
+  }
+
+  /** 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 (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);
+  }
+
+  /** Recursively delete everything in {@code dir}. */
+  // TODO: this should specify paths as Strings rather than as Files
+  public static void deleteContents(File dir) throws IOException {
+    File[] files = dir.listFiles();
+    if (files == null) {
+      throw new IllegalArgumentException("not a directory: " + dir);
+    }
+    for (File file : files) {
+      if (file.isDirectory()) {
+        deleteContents(file);
+      }
+      if (!file.delete()) {
+        throw new IOException("failed to delete file: " + file);
+      }
+    }
+  }
+
+  /**
+   * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int).
+   * InputStream assumes that you implement InputStream.read(int) and provides default
+   * implementations of the others, but often the opposite is more efficient.
+   */
+  public static int readSingleByte(InputStream in) throws IOException {
+    byte[] buffer = new byte[1];
+    int result = in.read(buffer, 0, 1);
+    return (result != -1) ? buffer[0] & 0xff : -1;
+  }
+
+  /**
+   * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int).
+   * OutputStream assumes that you implement OutputStream.write(int) and provides default
+   * implementations of the others, but often the opposite is more efficient.
+   */
+  public static void writeSingleByte(OutputStream out, int b) throws IOException {
+    byte[] buffer = new byte[1];
+    buffer[0] = (byte) (b & 0xff);
+    out.write(buffer);
+  }
+
+  /**
+   * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available.
+   */
+  public static void readFully(InputStream in, byte[] dst) throws IOException {
+    readFully(in, dst, 0, dst.length);
+  }
+
+  /**
+   * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws
+   * EOFException if insufficient bytes are available.
+   *
+   * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}.
+   */
+  public static void readFully(InputStream in, byte[] dst, int offset, int byteCount)
+      throws IOException {
+    if (byteCount == 0) {
+      return;
+    }
+    if (in == null) {
+      throw new NullPointerException("in == null");
+    }
+    if (dst == null) {
+      throw new NullPointerException("dst == null");
+    }
+    checkOffsetAndCount(dst.length, offset, byteCount);
+    while (byteCount > 0) {
+      int bytesRead = in.read(dst, offset, byteCount);
+      if (bytesRead < 0) {
+        throw new EOFException();
+      }
+      offset += bytesRead;
+      byteCount -= bytesRead;
+    }
+  }
+
+  /** Returns the remainder of 'reader' as a string, closing it when done. */
+  public static String readFully(Reader reader) throws IOException {
+    try {
+      StringWriter writer = new StringWriter();
+      char[] buffer = new char[1024];
+      int count;
+      while ((count = reader.read(buffer)) != -1) {
+        writer.write(buffer, 0, count);
+      }
+      return writer.toString();
+    } finally {
+      reader.close();
+    }
+  }
+
+  public static void skipAll(InputStream in) throws IOException {
+    do {
+      in.skip(Long.MAX_VALUE);
+    } while (in.read() != -1);
+  }
+
+  /**
+   * Call {@code in.read()} repeatedly until either the stream is exhausted or
+   * {@code byteCount} bytes have been read.
+   *
+   * <p>This method reuses the skip buffer but is careful to never use it at
+   * the same time that another stream is using it. Otherwise streams that use
+   * the caller's buffer for consistency checks like CRC could be clobbered by
+   * other threads. A thread-local buffer is also insufficient because some
+   * streams may call other streams in their skip() method, also clobbering the
+   * buffer.
+   */
+  public static long skipByReading(InputStream in, long byteCount) throws IOException {
+    // acquire the shared skip buffer.
+    byte[] buffer = skipBuffer.getAndSet(null);
+    if (buffer == null) {
+      buffer = new byte[4096];
+    }
+
+    long skipped = 0;
+    while (skipped < byteCount) {
+      int toRead = (int) Math.min(byteCount - skipped, buffer.length);
+      int read = in.read(buffer, 0, toRead);
+      if (read == -1) {
+        break;
+      }
+      skipped += read;
+      if (read < toRead) {
+        break;
+      }
+    }
+
+    // release the shared skip buffer.
+    skipBuffer.set(buffer);
+
+    return skipped;
+  }
+
+  /**
+   * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
+   * Returns the total number of bytes transferred.
+   */
+  public static int copy(InputStream in, OutputStream out) throws IOException {
+    int total = 0;
+    byte[] buffer = new byte[8192];
+    int c;
+    while ((c = in.read(buffer)) != -1) {
+      total += c;
+      out.write(buffer, 0, c);
+    }
+    return total;
+  }
+
+  /**
+   * Returns the ASCII characters up to but not including the next "\r\n", or
+   * "\n".
+   *
+   * @throws java.io.EOFException if the stream is exhausted before the next newline
+   * character.
+   */
+  public static String readAsciiLine(InputStream in) throws IOException {
+    // TODO: support UTF-8 here instead
+    StringBuilder result = new StringBuilder(80);
+    while (true) {
+      int c = in.read();
+      if (c == -1) {
+        throw new EOFException();
+      } else if (c == '\n') {
+        break;
+      }
+
+      result.append((char) c);
+    }
+    int length = result.length();
+    if (length > 0 && result.charAt(length - 1) == '\r') {
+      result.setLength(length - 1);
+    }
+    return result.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..187f3b6
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.Util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+
+/**
+ * An input stream for the body of an HTTP response.
+ *
+ * <p>Since a single socket's input stream may be used to read multiple HTTP
+ * responses from the same server, subclasses shouldn't close the socket stream.
+ *
+ * <p>A side effect of reading an HTTP response is that the response cache
+ * is populated. If the stream is closed early, that cache entry will be
+ * invalidated.
+ */
+abstract class AbstractHttpInputStream extends InputStream {
+  protected final InputStream in;
+  protected final HttpEngine httpEngine;
+  private final CacheRequest cacheRequest;
+  private final OutputStream cacheBody;
+  protected boolean closed;
+
+  AbstractHttpInputStream(InputStream in, HttpEngine httpEngine, CacheRequest cacheRequest)
+      throws IOException {
+    this.in = in;
+    this.httpEngine = httpEngine;
+
+    OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
+
+    // some apps return a null body; for compatibility we treat that like a null cache request
+    if (cacheBody == null) {
+      cacheRequest = null;
+    }
+
+    this.cacheBody = cacheBody;
+    this.cacheRequest = cacheRequest;
+  }
+
+  /**
+   * read() is implemented using read(byte[], int, int) so subclasses only
+   * need to override the latter.
+   */
+  @Override public final int read() throws IOException {
+    return Util.readSingleByte(this);
+  }
+
+  protected final void checkNotClosed() throws IOException {
+    if (closed) {
+      throw new IOException("stream closed");
+    }
+  }
+
+  protected final void cacheWrite(byte[] buffer, int offset, int count) throws IOException {
+    if (cacheBody != null) {
+      cacheBody.write(buffer, offset, count);
+    }
+  }
+
+  /**
+   * 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 {
+    if (cacheRequest != null) {
+      cacheBody.close();
+    }
+    httpEngine.release(streamCancelled);
+  }
+
+  /**
+   * Calls abort on the cache entry and disconnects the socket. This
+   * should be invoked when the connection is closed unexpectedly to
+   * invalidate the cache entry and to prevent the HTTP connection from
+   * being reused. HTTP messages are sent in serial so whenever a message
+   * cannot be read to completion, subsequent messages cannot be read
+   * either and the connection must be discarded.
+   *
+   * <p>An earlier implementation skipped the remaining bytes, but this
+   * requires that the entire transfer be completed. If the intention was
+   * to cancel the transfer, closing the connection is the only solution.
+   */
+  protected final void unexpectedEndOfInput() {
+    if (cacheRequest != null) {
+      cacheRequest.abort();
+    }
+    httpEngine.release(true);
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..90675b0
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
@@ -0,0 +1,40 @@
+/*
+ * 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/553a25ce/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
new file mode 100644
index 0000000..12e6409
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
@@ -0,0 +1,112 @@
+/*
+ * 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.squareup.okhttp.internal.http;
+
+final class HeaderParser {
+
+  public interface CacheControlHandler {
+    void handle(String directive, String parameter);
+  }
+
+  /** Parse a comma-separated list of cache control header values. */
+  public static void parseCacheControl(String value, CacheControlHandler handler) {
+    int pos = 0;
+    while (pos < value.length()) {
+      int tokenStart = pos;
+      pos = skipUntil(value, pos, "=,");
+      String directive = value.substring(tokenStart, pos).trim();
+
+      if (pos == value.length() || value.charAt(pos) == ',') {
+        pos++; // consume ',' (if necessary)
+        handler.handle(directive, null);
+        continue;
+      }
+
+      pos++; // consume '='
+      pos = skipWhitespace(value, pos);
+
+      String parameter;
+
+      // quoted string
+      if (pos < value.length() && value.charAt(pos) == '\"') {
+        pos++; // consume '"' open quote
+        int parameterStart = pos;
+        pos = skipUntil(value, pos, "\"");
+        parameter = value.substring(parameterStart, pos);
+        pos++; // consume '"' close quote (if necessary)
+
+        // unquoted string
+      } else {
+        int parameterStart = pos;
+        pos = skipUntil(value, pos, ",");
+        parameter = value.substring(parameterStart, pos).trim();
+      }
+
+      handler.handle(directive, parameter);
+    }
+  }
+
+  /**
+   * 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
+   * -1 if it cannot be parsed.
+   */
+  public static int parseSeconds(String value) {
+    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 -1;
+    }
+  }
+
+  private HeaderParser() {
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..4ccd12a
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * 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.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.internal.Base64;
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+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 {
+  private HttpAuthenticator() {
+  }
+
+  /**
+   * React to a failed authorization response by looking up new credentials.
+   *
+   * @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();
+    }
+
+    // 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);
+    if (challenges.isEmpty()) {
+      return null;
+    }
+
+    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;
+    }
+
+    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());
+  }
+
+  /**
+   * Parse RFC 2617 challenges. This API is only interested in the scheme
+   * name and realm.
+   */
+  private static List<Challenge> parseChallenges(RawHeaders 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<Challenge>();
+    for (int h = 0; h < responseHeaders.length(); h++) {
+      if (!challengeHeader.equalsIgnoreCase(responseHeaders.getFieldName(h))) {
+        continue;
+      }
+      String value = responseHeaders.getValue(h);
+      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(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;
+  }
+
+  /** 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/553a25ce/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
new file mode 100644
index 0000000..acb5fda
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpDate.java
@@ -0,0 +1,82 @@
+/*
+ * 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.squareup.okhttp.internal.http;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ */
+final class HttpDate {
+
+  /**
+   * 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() {
+          DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+          rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+          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}. */
+      "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",
+      "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", };
+
+  /**
+   * 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) {
+    }
+    for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
+      try {
+        return new SimpleDateFormat(formatString, Locale.US).parse(value);
+      } catch (ParseException ignore) {
+      }
+    }
+    return null;
+  }
+
+  /** Returns the string for {@code value}. */
+  public static String format(Date value) {
+    return STANDARD_DATE_FORMAT.get().format(value);
+  }
+
+  private HttpDate() {
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/553a25ce/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
new file mode 100644
index 0000000..9caeb19
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -0,0 +1,666 @@
+/*
+ *  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.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.Address;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.OkResponseCache;
+import com.squareup.okhttp.ResponseSource;
+import com.squareup.okhttp.TunnelRequest;
+import com.squareup.okhttp.internal.Dns;
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.CookieHandler;
+import java.net.Proxy;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
+import static com.squareup.okhttp.internal.Util.getDefaultPort;
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
+
+/**
+ * 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.
+ *
+ * <p>This class may hold a socket connection that needs to be released or
+ * recycled. By default, this socket connection is held when the last byte of
+ * the response is consumed. To release the connection when it is no longer
+ * required, use {@link #automaticallyReleaseConnectionToPool()}.
+ */
+public class HttpEngine {
+  private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() {
+    @Override public Map<String, List<String>> getHeaders() throws IOException {
+      Map<String, List<String>> result = new HashMap<String, List<String>>();
+      result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout"));
+      return result;
+    }
+    @Override public InputStream getBody() throws IOException {
+      return new ByteArrayInputStream(EMPTY_BYTE_ARRAY);
+    }
+  };
+  public static final int HTTP_CONTINUE = 100;
+
+  protected final HttpURLConnectionImpl policy;
+
+  protected final String method;
+
+  private ResponseSource responseSource;
+
+  protected Connection connection;
+  protected RouteSelector routeSelector;
+  private OutputStream requestBodyOut;
+
+  private Transport transport;
+
+  private InputStream responseTransferIn;
+  private InputStream responseBodyIn;
+
+  private CacheResponse cacheResponse;
+  private CacheRequest cacheRequest;
+
+  /** 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;
+
+  final URI uri;
+
+  final RequestHeaders requestHeaders;
+
+  /** Null until a response is received from the network or the cache. */
+  ResponseHeaders responseHeaders;
+
+  // The cache response currently being validated on a conditional get. Null
+  // if the cached response doesn't exist or doesn't need validation. If the
+  // conditional get succeeds, these will be used for the response headers and
+  // body. If it fails, these be closed and set to null.
+  private ResponseHeaders cachedResponseHeaders;
+  private InputStream cachedResponseBody;
+
+  /**
+   * True if the socket connection should be released to the connection pool
+   * when the response has been fully read.
+   */
+  private boolean automaticallyReleaseConnectionToPool;
+
+  /** True if the socket connection is no longer needed by this engine. */
+  private boolean connectionReleased;
+
+  /**
+   * @param requestHeaders the client's supplied request headers. This class
+   * 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.
+   */
+  public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
+      Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
+    this.policy = policy;
+    this.method = method;
+    this.connection = connection;
+    this.requestBodyOut = requestBodyOut;
+
+    try {
+      uri = Platform.get().toUriLenient(policy.getURL());
+    } catch (URISyntaxException e) {
+      throw new IOException(e);
+    }
+
+    this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
+  }
+
+  public URI getUri() {
+    return uri;
+  }
+
+  /**
+   * 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.
+   */
+  public final void sendRequest() throws IOException {
+    if (responseSource != null) {
+      return;
+    }
+
+    prepareRawRequestHeaders();
+    initResponseSource();
+    if (policy.responseCache instanceof OkResponseCache) {
+      ((OkResponseCache) policy.responseCache).trackResponse(responseSource);
+    }
+
+    // The raw response source may require the network, but the request
+    // headers may forbid network use. In that case, dispose of the network
+    // response and use a GATEWAY_TIMEOUT response instead, as specified
+    // by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
+    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
+      if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+        Util.closeQuietly(cachedResponseBody);
+      }
+      this.responseSource = ResponseSource.CACHE;
+      this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
+      RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true);
+      setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
+    }
+
+    if (responseSource.requiresConnection()) {
+      sendSocketRequest();
+    } else if (connection != null) {
+      policy.connectionPool.recycle(connection);
+      connection = null;
+    }
+  }
+
+  /**
+   * Initialize the source for this response. It may be corrected later if the
+   * request headers forbids network use.
+   */
+  private void initResponseSource() throws IOException {
+    responseSource = ResponseSource.NETWORK;
+    if (!policy.getUseCaches() || policy.responseCache == null) {
+      return;
+    }
+
+    CacheResponse candidate =
+        policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false));
+    if (candidate == null) {
+      return;
+    }
+
+    Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
+    cachedResponseBody = candidate.getBody();
+    if (!acceptCacheResponseType(candidate)
+        || responseHeadersMap == null
+        || cachedResponseBody == null) {
+      Util.closeQuietly(cachedResponseBody);
+      return;
+    }
+
+    RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true);
+    cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
+    long now = System.currentTimeMillis();
+    this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
+    if (responseSource == ResponseSource.CACHE) {
+      this.cacheResponse = candidate;
+      setResponse(cachedResponseHeaders, cachedResponseBody);
+    } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+      this.cacheResponse = candidate;
+    } else if (responseSource == ResponseSource.NETWORK) {
+      Util.closeQuietly(cachedResponseBody);
+    } else {
+      throw new AssertionError();
+    }
+  }
+
+  private void sendSocketRequest() throws IOException {
+    if (connection == null) {
+      connect();
+    }
+
+    if (transport != null) {
+      throw new IllegalStateException();
+    }
+
+    transport = (Transport) connection.newTransport(this);
+
+    if (hasRequestBody() && requestBodyOut == null) {
+      // Create a request body if we don't have one already. We'll already
+      // have one if we're retrying a failed POST.
+      requestBodyOut = transport.createRequestBody();
+    }
+  }
+
+  /** Connect to the origin server either directly or via a proxy. */
+  protected final void connect() throws IOException {
+    if (connection != null) {
+      return;
+    }
+    if (routeSelector == null) {
+      String uriHost = uri.getHost();
+      if (uriHost == null) {
+        throw new UnknownHostException(uri.toString());
+      }
+      SSLSocketFactory sslSocketFactory = null;
+      HostnameVerifier hostnameVerifier = null;
+      if (uri.getScheme().equalsIgnoreCase("https")) {
+        sslSocketFactory = policy.sslSocketFactory;
+        hostnameVerifier = policy.hostnameVerifier;
+      }
+      Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
+          hostnameVerifier, policy.requestedProxy);
+      routeSelector =
+          new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT);
+    }
+    connection = routeSelector.next();
+    if (!connection.isConnected()) {
+      connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig());
+      policy.connectionPool.maybeShare(connection);
+    }
+    connected(connection);
+    if (connection.getProxy() != policy.requestedProxy) {
+      // Update the request line if the proxy changed; it may need a host name.
+      requestHeaders.getHeaders().setRequestLine(getRequestLine());
+    }
+  }
+
+  /**
+   * Called after a socket connection has been created or retrieved from the
+   * pool. Subclasses use this hook to get a reference to the TLS data.
+   */
+  protected void connected(Connection connection) {
+  }
+
+  /**
+   * 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();
+  }
+
+  /**
+   * @param body the response body, or null if it doesn't exist or isn't
+   * available.
+   */
+  private void setResponse(ResponseHeaders headers, InputStream body) throws IOException {
+    if (this.responseBodyIn != null) {
+      throw new IllegalStateException();
+    }
+    this.responseHeaders = headers;
+    if (body != null) {
+      initContentStream(body);
+    }
+  }
+
+  boolean hasRequestBody() {
+    return method.equals("POST") || method.equals("PUT");
+  }
+
+  /** Returns the request body or null if this request doesn't have a body. */
+  public final OutputStream getRequestBody() {
+    if (responseSource == null) {
+      throw new IllegalStateException();
+    }
+    return requestBodyOut;
+  }
+
+  public final boolean hasResponse() {
+    return responseHeaders != null;
+  }
+
+  public final RequestHeaders getRequestHeaders() {
+    return requestHeaders;
+  }
+
+  public final ResponseHeaders getResponseHeaders() {
+    if (responseHeaders == null) {
+      throw new IllegalStateException();
+    }
+    return responseHeaders;
+  }
+
+  public final int getResponseCode() {
+    if (responseHeaders == null) {
+      throw new IllegalStateException();
+    }
+    return responseHeaders.getHeaders().getResponseCode();
+  }
+
+  public final InputStream getResponseBody() {
+    if (responseHeaders == null) {
+      throw new IllegalStateException();
+    }
+    return responseBodyIn;
+  }
+
+  public final CacheResponse getCacheResponse() {
+    return cacheResponse;
+  }
+
+  public final Connection getConnection() {
+    return connection;
+  }
+
+  /**
+   * Returns true if {@code cacheResponse} is of the right type. This
+   * condition is necessary but not sufficient for the cached response to
+   * be used.
+   */
+  protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
+    return true;
+  }
+
+  private void maybeCache() throws IOException {
+    // Are we caching at all?
+    if (!policy.getUseCaches() || policy.responseCache == null) {
+      return;
+    }
+
+    // Should we cache this response for this request?
+    if (!responseHeaders.isCacheable(requestHeaders)) {
+      return;
+    }
+
+    // Offer this request to the cache.
+    cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache());
+  }
+
+  /**
+   * Cause the socket connection to be released to the connection pool when
+   * it is no longer needed. If it is already unneeded, it will be pooled
+   * immediately. Otherwise the connection is held so that redirects can be
+   * handled by the same connection.
+   */
+  public final void automaticallyReleaseConnectionToPool() {
+    automaticallyReleaseConnectionToPool = true;
+    if (connection != null && connectionReleased) {
+      policy.connectionPool.recycle(connection);
+      connection = null;
+    }
+  }
+
+  /**
+   * Releases this engine so that its resources may be either reused or
+   * closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
+   * the connection will be used to follow a redirect.
+   */
+  public final void release(boolean streamCancelled) {
+    // If the response body comes from the cache, close it.
+    if (responseBodyIn == cachedResponseBody) {
+      Util.closeQuietly(responseBodyIn);
+    }
+
+    if (!connectionReleased && connection != null) {
+      connectionReleased = true;
+
+      if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
+          responseTransferIn)) {
+        Util.closeQuietly(connection);
+        connection = null;
+      } else if (automaticallyReleaseConnectionToPool) {
+        policy.connectionPool.recycle(connection);
+        connection = null;
+      }
+    }
+  }
+
+  private void initContentStream(InputStream transferStream) throws IOException {
+    responseTransferIn = transferStream;
+    if (transparentGzip && responseHeaders.isContentEncodingGzip()) {
+      // If the response was transparently gzipped, remove the gzip header field
+      // so clients don't double decompress. http://b/3009828
+      //
+      // Also remove the Content-Length in this case because it contains the
+      // length 528 of the gzipped response. This isn't terribly useful and is
+      // dangerous because 529 clients can query the content length, but not
+      // the content encoding.
+      responseHeaders.stripContentEncoding();
+      responseHeaders.stripContentLength();
+      responseBodyIn = new GZIPInputStream(transferStream);
+    } else {
+      responseBodyIn = transferStream;
+    }
+  }
+
+  /**
+   * Returns true if the response must have a (possibly 0-length) body.
+   * See RFC 2616 section 4.3.
+   */
+  public final boolean hasResponseBody() {
+    int responseCode = responseHeaders.getHeaders().getResponseCode();
+
+    // HEAD requests never yield a body regardless of the response headers.
+    if (method.equals("HEAD")) {
+      return false;
+    }
+
+    if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
+        && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT
+        && responseCode != HttpURLConnectionImpl.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 (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Populates requestHeaders 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 void prepareRawRequestHeaders() throws IOException {
+    requestHeaders.getHeaders().setRequestLine(getRequestLine());
+
+    if (requestHeaders.getUserAgent() == null) {
+      requestHeaders.setUserAgent(getDefaultUserAgent());
+    }
+
+    if (requestHeaders.getHost() == null) {
+      requestHeaders.setHost(getOriginAddress(policy.getURL()));
+    }
+
+    if ((connection == null || connection.getHttpMinorVersion() != 0)
+        && requestHeaders.getConnection() == null) {
+      requestHeaders.setConnection("Keep-Alive");
+    }
+
+    if (requestHeaders.getAcceptEncoding() == null) {
+      transparentGzip = true;
+      requestHeaders.setAcceptEncoding("gzip");
+    }
+
+    if (hasRequestBody() && requestHeaders.getContentType() == null) {
+      requestHeaders.setContentType("application/x-www-form-urlencoded");
+    }
+
+    long ifModifiedSince = policy.getIfModifiedSince();
+    if (ifModifiedSince != 0) {
+      requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
+    }
+
+    CookieHandler cookieHandler = policy.cookieHandler;
+    if (cookieHandler != null) {
+      requestHeaders.addCookies(
+          cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
+    }
+  }
+
+  /**
+   * Returns the request status line, like "GET / HTTP/1.1". This is exposed
+   * to the application by {@link HttpURLConnectionImpl#getHeaderFields}, so
+   * it needs to be set even if the transport is SPDY.
+   */
+  String getRequestLine() {
+    String protocol =
+        (connection == null || connection.getHttpMinorVersion() != 0) ? "HTTP/1.1" : "HTTP/1.0";
+    return method + " " + requestString() + " " + protocol;
+  }
+
+  private String requestString() {
+    URL url = policy.getURL();
+    if (includeAuthorityInRequestLine()) {
+      return url.toString();
+    } else {
+      return requestPath(url);
+    }
+  }
+
+  /**
+   * 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(URL url) {
+    String fileOnly = url.getFile();
+    if (fileOnly == null) {
+      return "/";
+    } else if (!fileOnly.startsWith("/")) {
+      return "/" + fileOnly;
+    } else {
+      return fileOnly;
+    }
+  }
+
+  /**
+   * 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").
+   *
+   * <p>This is non-final because for HTTPS it's never necessary to supply the
+   * full URL, even if a proxy is in use.
+   */
+  protected boolean includeAuthorityInRequestLine() {
+    return connection == null
+        ? policy.usingProxy() // A proxy was requested.
+        : connection.getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
+  }
+
+  public static String getDefaultUserAgent() {
+    String agent = System.getProperty("http.agent");
+    return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+  }
+
+  public static String getOriginAddress(URL url) {
+    int port = url.getPort();
+    String result = url.getHost();
+    if (port > 0 && port != getDefaultPort(url.getProtocol())) {
+      result = result + ":" + port;
+    }
+    return result;
+  }
+
+  /**
+   * Flushes the remaining request header and body, parses the HTTP response
+   * headers and starts reading the HTTP response body if it exists.
+   */
+  public final void readResponse() throws IOException {
+    if (hasResponse()) {
+      responseHeaders.setResponseSource(responseSource);
+      return;
+    }
+
+    if (responseSource == null) {
+      throw new IllegalStateException("readResponse() without sendRequest()");
+    }
+
+    if (!responseSource.requiresConnection()) {
+      return;
+    }
+
+    if (sentRequestMillis == -1) {
+      if (requestBodyOut instanceof RetryableOutputStream) {
+        int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength();
+        requestHeaders.setContentLength(contentLength);
+      }
+      transport.writeRequestHeaders();
+    }
+
+    if (requestBodyOut != null) {
+      requestBodyOut.close();
+      if (requestBodyOut instanceof RetryableOutputStream) {
+        transport.writeRequestBody((RetryableOutputStream) requestBodyOut);
+      }
+    }
+
+    transport.flushRequest();
+
+    responseHeaders = transport.readResponseHeaders();
+    responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
+    responseHeaders.setResponseSource(responseSource);
+
+    if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+      if (cachedResponseHeaders.validate(responseHeaders)) {
+        release(false);
+        ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
+        setResponse(combinedHeaders, cachedResponseBody);
+        if (policy.responseCache instanceof OkResponseCache) {
+          OkResponseCache httpResponseCache = (OkResponseCache) policy.responseCache;
+          httpResponseCache.trackConditionalCacheHit();
+          httpResponseCache.update(cacheResponse, policy.getHttpConnectionToCache());
+        }
+        return;
+      } else {
+        Util.closeQuietly(cachedResponseBody);
+      }
+    }
+
+    if (hasResponseBody()) {
+      maybeCache(); // reentrant. this calls into user code which may call back into this!
+    }
+
+    initContentStream(transport.getTransferStream(cacheRequest));
+  }
+
+  protected TunnelRequest getTunnelConfig() {
+    return null;
+  }
+
+  public void receiveHeaders(RawHeaders headers) throws IOException {
+    CookieHandler cookieHandler = policy.cookieHandler;
+    if (cookieHandler != null) {
+      cookieHandler.put(uri, headers.toMultimap(true));
+    }
+  }
+}


Mime
View raw message