cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ste...@apache.org
Subject [13/17] added fireos code from https://github.com/archananaik/cordova-amazon-fireos sans history
Date Mon, 25 Nov 2013 23:43:46 GMT
http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/Connection.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/Connection.java b/framework/src/com/squareup/okhttp/Connection.java
new file mode 100644
index 0000000..6a6c84d
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/Connection.java
@@ -0,0 +1,291 @@
+/*
+ *  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;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.http.HttpAuthenticator;
+import com.squareup.okhttp.internal.http.HttpEngine;
+import com.squareup.okhttp.internal.http.HttpTransport;
+import com.squareup.okhttp.internal.http.RawHeaders;
+import com.squareup.okhttp.internal.http.SpdyTransport;
+import com.squareup.okhttp.internal.spdy.SpdyConnection;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.URL;
+import java.util.Arrays;
+import javax.net.ssl.SSLSocket;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+
+/**
+ * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection,
+ * which may be used for multiple HTTP request/response exchanges. Connections
+ * may be direct to the origin server or via a proxy.
+ *
+ * <p>Typically instances of this class are created, connected and exercised
+ * automatically by the HTTP client. Applications may use this class to monitor
+ * HTTP connections as members of a {@link ConnectionPool connection pool}.
+ *
+ * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
+ * which isn't so much a connection as a single request/response exchange.
+ *
+ * <h3>Modern TLS</h3>
+ * There are tradeoffs when selecting which options to include when negotiating
+ * a secure connection to a remote host. Newer TLS options are quite useful:
+ * <ul>
+ * <li>Server Name Indication (SNI) enables one IP address to negotiate secure
+ * connections for multiple domain names.
+ * <li>Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used
+ * for both HTTP and SPDY transports.
+ * </ul>
+ * Unfortunately, older HTTPS servers refuse to connect when such options are
+ * presented. Rather than avoiding these options entirely, this class allows a
+ * connection to be attempted with modern options and then retried without them
+ * should the attempt fail.
+ */
+public final class Connection implements Closeable {
+  private static final byte[] NPN_PROTOCOLS = new byte[] {
+      6, 's', 'p', 'd', 'y', '/', '3',
+      8, 'h', 't', 't', 'p', '/', '1', '.', '1'
+  };
+  private static final byte[] SPDY3 = new byte[] {
+      's', 'p', 'd', 'y', '/', '3'
+  };
+  private static final byte[] HTTP_11 = new byte[] {
+      'h', 't', 't', 'p', '/', '1', '.', '1'
+  };
+
+  private final Route route;
+
+  private Socket socket;
+  private InputStream in;
+  private OutputStream out;
+  private boolean connected = false;
+  private SpdyConnection spdyConnection;
+  private int httpMinorVersion = 1; // Assume HTTP/1.1
+  private long idleStartTimeNs;
+
+  public Connection(Route route) {
+    this.route = route;
+  }
+
+  public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
+      throws IOException {
+    if (connected) {
+      throw new IllegalStateException("already connected");
+    }
+    connected = true;
+    socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
+    socket.connect(route.inetSocketAddress, connectTimeout);
+    socket.setSoTimeout(readTimeout);
+    in = socket.getInputStream();
+    out = socket.getOutputStream();
+
+    if (route.address.sslSocketFactory != null) {
+      upgradeToTls(tunnelRequest);
+    }
+
+    // Use MTU-sized buffers to send fewer packets.
+    int mtu = Platform.get().getMtu(socket);
+    in = new BufferedInputStream(in, mtu);
+    out = new BufferedOutputStream(out, mtu);
+  }
+
+  /**
+   * Create an {@code SSLSocket} and perform the TLS handshake and certificate
+   * validation.
+   */
+  private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
+    Platform platform = Platform.get();
+
+    // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
+    if (requiresTunnel()) {
+      makeTunnel(tunnelRequest);
+    }
+
+    // Create the wrapper over connected socket.
+    socket = route.address.sslSocketFactory
+        .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */);
+    SSLSocket sslSocket = (SSLSocket) socket;
+    if (route.modernTls) {
+      platform.enableTlsExtensions(sslSocket, route.address.uriHost);
+    } else {
+      platform.supportTlsIntolerantServer(sslSocket);
+    }
+
+    if (route.modernTls) {
+      platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
+    }
+
+    // Force handshake. This can throw!
+    sslSocket.startHandshake();
+
+    // Verify that the socket's certificates are acceptable for the target host.
+    if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) {
+      throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
+    }
+
+    out = sslSocket.getOutputStream();
+    in = sslSocket.getInputStream();
+
+    byte[] selectedProtocol;
+    if (route.modernTls
+        && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
+      if (Arrays.equals(selectedProtocol, SPDY3)) {
+        sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
+        spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
+            .build();
+      } else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
+        throw new IOException(
+            "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
+      }
+    }
+  }
+
+  /** Returns true if {@link #connect} has been attempted on this connection. */
+  public boolean isConnected() {
+    return connected;
+  }
+
+  @Override public void close() throws IOException {
+    socket.close();
+  }
+
+  /** Returns the route used by this connection. */
+  public Route getRoute() {
+    return route;
+  }
+
+  /**
+   * Returns the socket that this connection uses, or null if the connection
+   * is not currently connected.
+   */
+  public Socket getSocket() {
+    return socket;
+  }
+
+  /** Returns true if this connection is alive. */
+  public boolean isAlive() {
+    return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
+  }
+
+  public void resetIdleStartTime() {
+    if (spdyConnection != null) {
+      throw new IllegalStateException("spdyConnection != null");
+    }
+    this.idleStartTimeNs = System.nanoTime();
+  }
+
+  /** Returns true if this connection is idle. */
+  public boolean isIdle() {
+    return spdyConnection == null || spdyConnection.isIdle();
+  }
+
+  /**
+   * Returns true if this connection has been idle for longer than
+   * {@code keepAliveDurationNs}.
+   */
+  public boolean isExpired(long keepAliveDurationNs) {
+    return isIdle() && System.nanoTime() - getIdleStartTimeNs() > keepAliveDurationNs;
+  }
+
+  /**
+   * Returns the time in ns when this connection became idle. Undefined if
+   * this connection is not idle.
+   */
+  public long getIdleStartTimeNs() {
+    return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs();
+  }
+
+  /** Returns the transport appropriate for this connection. */
+  public Object newTransport(HttpEngine httpEngine) throws IOException {
+    return (spdyConnection != null) ? new SpdyTransport(httpEngine, spdyConnection)
+        : new HttpTransport(httpEngine, out, in);
+  }
+
+  /**
+   * Returns true if this is a SPDY connection. Such connections can be used
+   * in multiple HTTP requests simultaneously.
+   */
+  public boolean isSpdy() {
+    return spdyConnection != null;
+  }
+
+  public SpdyConnection getSpdyConnection() {
+    return spdyConnection;
+  }
+
+  /**
+   * Returns the minor HTTP version that should be used for future requests on
+   * this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default
+   * value is 1 for new connections.
+   */
+  public int getHttpMinorVersion() {
+    return httpMinorVersion;
+  }
+
+  public void setHttpMinorVersion(int httpMinorVersion) {
+    this.httpMinorVersion = httpMinorVersion;
+  }
+
+  /**
+   * Returns true if the HTTP connection needs to tunnel one protocol over
+   * another, such as when using HTTPS through an HTTP proxy. When doing so,
+   * we must avoid buffering bytes intended for the higher-level protocol.
+   */
+  public boolean requiresTunnel() {
+    return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
+  }
+
+  /**
+   * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+   * CONNECT request to create the proxy connection. This may need to be
+   * retried if the proxy requires authorization.
+   */
+  private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
+    RawHeaders requestHeaders = tunnelRequest.getRequestHeaders();
+    while (true) {
+      out.write(requestHeaders.toBytes());
+      RawHeaders responseHeaders = RawHeaders.fromBytes(in);
+
+      switch (responseHeaders.getResponseCode()) {
+        case HTTP_OK:
+          return;
+        case HTTP_PROXY_AUTH:
+          requestHeaders = new RawHeaders(requestHeaders);
+          URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
+          boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
+              responseHeaders, requestHeaders, route.proxy, url);
+          if (credentialsFound) {
+            continue;
+          } else {
+            throw new IOException("Failed to authenticate with proxy");
+          }
+        default:
+          throw new IOException(
+              "Unexpected response code for CONNECT: " + responseHeaders.getResponseCode());
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/ConnectionPool.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/ConnectionPool.java b/framework/src/com/squareup/okhttp/ConnectionPool.java
new file mode 100644
index 0000000..933bd73
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/ConnectionPool.java
@@ -0,0 +1,273 @@
+/*
+ *  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;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
+ * requests that share the same {@link com.squareup.okhttp.Address} may share a
+ * {@link com.squareup.okhttp.Connection}. This class implements the policy of
+ * which connections to keep open for future use.
+ *
+ * <p>The {@link #getDefault() system-wide default} uses system properties for
+ * tuning parameters:
+ * <ul>
+ *     <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
+ *         pooled at all. Default is true.
+ *     <li>{@code http.maxConnections} maximum number of idle connections to
+ *         each to keep in the pool. Default is 5.
+ *     <li>{@code http.keepAliveDuration} Time in milliseconds to keep the
+ *         connection alive in the pool before closing it. Default is 5 minutes.
+ *         This property isn't used by {@code HttpURLConnection}.
+ * </ul>
+ *
+ * <p>The default instance <i>doesn't</i> adjust its configuration as system
+ * properties are changed. This assumes that the applications that set these
+ * parameters do so before making HTTP connections, and that this class is
+ * initialized lazily.
+ */
+public class ConnectionPool {
+  private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;
+  private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+  private static final ConnectionPool systemDefault;
+
+  static {
+    String keepAlive = System.getProperty("http.keepAlive");
+    String keepAliveDuration = System.getProperty("http.keepAliveDuration");
+    String maxIdleConnections = System.getProperty("http.maxConnections");
+    long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration)
+        : DEFAULT_KEEP_ALIVE_DURATION_MS;
+    if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
+      systemDefault = new ConnectionPool(0, keepAliveDurationMs);
+    } else if (maxIdleConnections != null) {
+      systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);
+    } else {
+      systemDefault = new ConnectionPool(5, keepAliveDurationMs);
+    }
+  }
+
+  /** The maximum number of idle connections for each address. */
+  private final int maxIdleConnections;
+  private final long keepAliveDurationNs;
+
+  private final LinkedList<Connection> connections = new LinkedList<Connection>();
+
+  /** We use a single background thread to cleanup expired connections. */
+  private final ExecutorService executorService =
+      new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+  private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() {
+    @Override public Void call() throws Exception {
+      List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);
+      int idleConnectionCount = 0;
+      synchronized (ConnectionPool.this) {
+        for (ListIterator<Connection> i = connections.listIterator(connections.size());
+            i.hasPrevious(); ) {
+          Connection connection = i.previous();
+          if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {
+            i.remove();
+            expiredConnections.add(connection);
+            if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;
+          } else if (connection.isIdle()) {
+            idleConnectionCount++;
+          }
+        }
+
+        for (ListIterator<Connection> i = connections.listIterator(connections.size());
+            i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {
+          Connection connection = i.previous();
+          if (connection.isIdle()) {
+            expiredConnections.add(connection);
+            i.remove();
+            --idleConnectionCount;
+          }
+        }
+      }
+      for (Connection expiredConnection : expiredConnections) {
+        Util.closeQuietly(expiredConnection);
+      }
+      return null;
+    }
+  };
+
+  public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
+    this.maxIdleConnections = maxIdleConnections;
+    this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;
+  }
+
+  /**
+   * Returns a snapshot of the connections in this pool, ordered from newest to
+   * oldest. Waits for the cleanup callable to run if it is currently scheduled.
+   */
+  List<Connection> getConnections() {
+    waitForCleanupCallableToRun();
+    synchronized (this) {
+      return new ArrayList<Connection>(connections);
+    }
+  }
+
+  /**
+   * Blocks until the executor service has processed all currently enqueued
+   * jobs.
+   */
+  private void waitForCleanupCallableToRun() {
+    try {
+      executorService.submit(new Runnable() {
+        @Override public void run() {
+        }
+      }).get();
+    } catch (Exception e) {
+      throw new AssertionError();
+    }
+  }
+
+  public static ConnectionPool getDefault() {
+    return systemDefault;
+  }
+
+  /** Returns total number of connections in the pool. */
+  public synchronized int getConnectionCount() {
+    return connections.size();
+  }
+
+  /** Returns total number of spdy connections in the pool. */
+  public synchronized int getSpdyConnectionCount() {
+    int total = 0;
+    for (Connection connection : connections) {
+      if (connection.isSpdy()) total++;
+    }
+    return total;
+  }
+
+  /** Returns total number of http connections in the pool. */
+  public synchronized int getHttpConnectionCount() {
+    int total = 0;
+    for (Connection connection : connections) {
+      if (!connection.isSpdy()) total++;
+    }
+    return total;
+  }
+
+  /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
+  public synchronized Connection get(Address address) {
+    Connection foundConnection = null;
+    for (ListIterator<Connection> i = connections.listIterator(connections.size());
+        i.hasPrevious(); ) {
+      Connection connection = i.previous();
+      if (!connection.getRoute().getAddress().equals(address)
+          || !connection.isAlive()
+          || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {
+        continue;
+      }
+      i.remove();
+      if (!connection.isSpdy()) {
+        try {
+          Platform.get().tagSocket(connection.getSocket());
+        } catch (SocketException e) {
+          Util.closeQuietly(connection);
+          // When unable to tag, skip recycling and close
+          Platform.get().logW("Unable to tagSocket(): " + e);
+          continue;
+        }
+      }
+      foundConnection = connection;
+      break;
+    }
+
+    if (foundConnection != null && foundConnection.isSpdy()) {
+      connections.addFirst(foundConnection); // Add it back after iteration.
+    }
+
+    executorService.submit(connectionsCleanupCallable);
+    return foundConnection;
+  }
+
+  /**
+   * Gives {@code connection} to the pool. The pool may store the connection,
+   * or close it, as its policy describes.
+   *
+   * <p>It is an error to use {@code connection} after calling this method.
+   */
+  public void recycle(Connection connection) {
+    executorService.submit(connectionsCleanupCallable);
+
+    if (connection.isSpdy()) {
+      return;
+    }
+
+    if (!connection.isAlive()) {
+      Util.closeQuietly(connection);
+      return;
+    }
+
+    try {
+      Platform.get().untagSocket(connection.getSocket());
+    } catch (SocketException e) {
+      // When unable to remove tagging, skip recycling and close.
+      Platform.get().logW("Unable to untagSocket(): " + e);
+      Util.closeQuietly(connection);
+      return;
+    }
+
+    synchronized (this) {
+      connections.addFirst(connection);
+      connection.resetIdleStartTime();
+    }
+  }
+
+  /**
+   * Shares the SPDY connection with the pool. Callers to this method may
+   * continue to use {@code connection}.
+   */
+  public void maybeShare(Connection connection) {
+    executorService.submit(connectionsCleanupCallable);
+    if (!connection.isSpdy()) {
+      // Only SPDY connections are sharable.
+      return;
+    }
+    if (connection.isAlive()) {
+      synchronized (this) {
+        connections.addFirst(connection);
+      }
+    }
+  }
+
+  /** Close and remove all connections in the pool. */
+  public void evictAll() {
+    List<Connection> connections;
+    synchronized (this) {
+      connections = new ArrayList<Connection>(this.connections);
+      this.connections.clear();
+    }
+
+    for (Connection connection : connections) {
+      Util.closeQuietly(connection);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/HttpResponseCache.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/HttpResponseCache.java b/framework/src/com/squareup/okhttp/HttpResponseCache.java
new file mode 100644
index 0000000..a6d380a
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/HttpResponseCache.java
@@ -0,0 +1,693 @@
+/*
+ * 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;
+
+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 com.squareup.okhttp.internal.http.HttpEngine;
+import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
+import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
+import com.squareup.okhttp.internal.http.OkResponseCache;
+import com.squareup.okhttp.internal.http.RawHeaders;
+import com.squareup.okhttp.internal.http.ResponseHeaders;
+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;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
+ * saving time and bandwidth.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ *     <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
+ *         of HTTP requests issued since this cache was created.
+ *     <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
+ *         number of those requests that required network use.
+ *     <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
+ *         those requests whose responses were served by the cache.
+ * </ul>
+ * Sometimes a request will result in a conditional cache hit. If the cache
+ * contains a stale copy of the response, the client will issue a conditional
+ * {@code GET}. The server will then send either the updated response if it has
+ * changed, or a short 'not modified' response if the client's copy is still
+ * valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web
+ * server to return cacheable responses. Although this client honors all <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
+ * headers, it doesn't cache partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be
+ * necessary to skip the cache, and fetch data directly from the server. To force
+ * a full refresh, add the {@code no-cache} directive: <pre>   {@code
+ *         connection.addRequestProperty("Cache-Control", "no-cache");
+ * }</pre>
+ * If it is only necessary to force a cached response to be validated by the
+ * server, use the more efficient {@code max-age=0} instead: <pre>   {@code
+ *         connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately,
+ * but not otherwise. This can be used so your application can show
+ * <i>something</i> while waiting for the latest data to be downloaded. To
+ * restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre>   {@code
+ *     try {
+ *         connection.addRequestProperty("Cache-Control", "only-if-cached");
+ *         InputStream cached = connection.getInputStream();
+ *         // the resource was cached! show it
+ *     } catch (FileNotFoundException e) {
+ *         // the resource was not cached
+ *     }
+ * }</pre>
+ * This technique works even better in situations where a stale response is
+ * better than no response. To permit stale cached responses, use the {@code
+ * max-stale} directive with the maximum staleness in seconds: <pre>   {@code
+ *         int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ *         connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }</pre>
+ */
+public final class HttpResponseCache extends ResponseCache {
+  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;
+
+  /**
+   * Although this class only exposes the limited ResponseCache API, it
+   * implements the full OkResponseCache interface. This field is used as a
+   * package private handle to the complete implementation. It delegates to
+   * public and private members of this type.
+   */
+  final OkResponseCache okResponseCache = new OkResponseCache() {
+    @Override public CacheResponse get(URI uri, String requestMethod,
+        Map<String, List<String>> requestHeaders) throws IOException {
+      return HttpResponseCache.this.get(uri, requestMethod, requestHeaders);
+    }
+
+    @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+      return HttpResponseCache.this.put(uri, connection);
+    }
+
+    @Override public void update(
+        CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException {
+      HttpResponseCache.this.update(conditionalCacheHit, connection);
+    }
+
+    @Override public void trackConditionalCacheHit() {
+      HttpResponseCache.this.trackConditionalCacheHit();
+    }
+
+    @Override public void trackResponse(ResponseSource source) {
+      HttpResponseCache.this.trackResponse(source);
+    }
+  };
+
+  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;
+    }
+  }
+
+  private 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;
+    }
+  }
+
+  /**
+   * Closes the cache and deletes all of its stored values. This will delete
+   * all files in the cache directory including files that weren't created by
+   * the cache.
+   */
+  public void delete() throws IOException {
+    cache.delete();
+  }
+
+  public synchronized int getWriteAbortCount() {
+    return writeAbortCount;
+  }
+
+  public synchronized int getWriteSuccessCount() {
+    return writeSuccessCount;
+  }
+
+  private synchronized void trackResponse(ResponseSource source) {
+    requestCount++;
+
+    switch (source) {
+      case CACHE:
+        hitCount++;
+        break;
+      case CONDITIONAL_CACHE:
+      case NETWORK:
+        networkCount++;
+        break;
+    }
+  }
+
+  private synchronized void trackConditionalCacheHit() {
+    hitCount++;
+  }
+
+  public synchronized int getNetworkCount() {
+    return networkCount;
+  }
+
+  public synchronized int getHitCount() {
+    return hitCount;
+  }
+
+  public synchronized int getRequestCount() {
+    return requestCount;
+  }
+
+  private final class CacheRequestImpl 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.length() > 0) {
+            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.getMessage());
+      }
+    }
+
+    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.getMessage());
+      }
+    }
+
+    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-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/OkHttpClient.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/OkHttpClient.java b/framework/src/com/squareup/okhttp/OkHttpClient.java
new file mode 100644
index 0000000..7834bd6
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/OkHttpClient.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
+import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
+import com.squareup.okhttp.internal.http.OkResponseCache;
+import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ResponseCache;
+import java.net.URL;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+
+/** Configures and creates HTTP connections. */
+public final class OkHttpClient {
+  private Proxy proxy;
+  private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
+  private ProxySelector proxySelector;
+  private CookieHandler cookieHandler;
+  private ResponseCache responseCache;
+  private SSLSocketFactory sslSocketFactory;
+  private HostnameVerifier hostnameVerifier;
+  private ConnectionPool connectionPool;
+  private boolean followProtocolRedirects = true;
+
+  /**
+   * Sets the HTTP proxy that will be used by connections created by this
+   * client. This takes precedence over {@link #setProxySelector}, which is
+   * only honored when this proxy is null (which it is by default). To disable
+   * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+   */
+  public OkHttpClient setProxy(Proxy proxy) {
+    this.proxy = proxy;
+    return this;
+  }
+
+  public Proxy getProxy() {
+    return proxy;
+  }
+
+  /**
+   * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+   * is specified explicitly. The proxy selector may return multiple proxies;
+   * in that case they will be tried in sequence until a successful connection
+   * is established.
+   *
+   * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
+   * proxy selector will be used.
+   */
+  public OkHttpClient setProxySelector(ProxySelector proxySelector) {
+    this.proxySelector = proxySelector;
+    return this;
+  }
+
+  public ProxySelector getProxySelector() {
+    return proxySelector;
+  }
+
+  /**
+   * Sets the cookie handler to be used to read outgoing cookies and write
+   * incoming cookies.
+   *
+   * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
+   * cookie handler will be used.
+   */
+  public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+    this.cookieHandler = cookieHandler;
+    return this;
+  }
+
+  public CookieHandler getCookieHandler() {
+    return cookieHandler;
+  }
+
+  /**
+   * Sets the response cache to be used to read and write cached responses.
+   *
+   * <p>If unset, the {@link ResponseCache#getDefault() system-wide default}
+   * response cache will be used.
+   */
+  public OkHttpClient setResponseCache(ResponseCache responseCache) {
+    this.responseCache = responseCache;
+    return this;
+  }
+
+  public ResponseCache getResponseCache() {
+    return responseCache;
+  }
+
+  private OkResponseCache okResponseCache() {
+    if (responseCache instanceof HttpResponseCache) {
+      return ((HttpResponseCache) responseCache).okResponseCache;
+    } else if (responseCache != null) {
+      return new OkResponseCacheAdapter(responseCache);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Sets the socket factory used to secure HTTPS connections.
+   *
+   * <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
+   * system-wide default} SSL socket factory will be used.
+   */
+  public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+    this.sslSocketFactory = sslSocketFactory;
+    return this;
+  }
+
+  public SSLSocketFactory getSslSocketFactory() {
+    return sslSocketFactory;
+  }
+
+  /**
+   * Sets the verifier used to confirm that response certificates apply to
+   * requested hostnames for HTTPS connections.
+   *
+   * <p>If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier()
+   * system-wide default} hostname verifier will be used.
+   */
+  public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+    this.hostnameVerifier = hostnameVerifier;
+    return this;
+  }
+
+  public HostnameVerifier getHostnameVerifier() {
+    return hostnameVerifier;
+  }
+
+  /**
+   * Sets the connection pool used to recycle HTTP and HTTPS connections.
+   *
+   * <p>If unset, the {@link ConnectionPool#getDefault() system-wide
+   * default} connection pool will be used.
+   */
+  public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+    this.connectionPool = connectionPool;
+    return this;
+  }
+
+  public ConnectionPool getConnectionPool() {
+    return connectionPool;
+  }
+
+  /**
+   * Configure this client to follow redirects from HTTPS to HTTP and from HTTP
+   * to HTTPS.
+   *
+   * <p>If unset, protocol redirects will be followed. This is different than
+   * the built-in {@code HttpURLConnection}'s default.
+   */
+  public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {
+    this.followProtocolRedirects = followProtocolRedirects;
+    return this;
+  }
+
+  public boolean getFollowProtocolRedirects() {
+    return followProtocolRedirects;
+  }
+
+  public HttpURLConnection open(URL url) {
+    String protocol = url.getProtocol();
+    OkHttpClient copy = copyWithDefaults();
+    if (protocol.equals("http")) {
+      return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
+    } else if (protocol.equals("https")) {
+      return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
+    } else {
+      throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+    }
+  }
+
+  /**
+   * Returns a shallow copy of this OkHttpClient that uses the system-wide default for
+   * each field that hasn't been explicitly configured.
+   */
+  private OkHttpClient copyWithDefaults() {
+    OkHttpClient result = new OkHttpClient();
+    result.proxy = proxy;
+    result.failedRoutes = failedRoutes;
+    result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
+    result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
+    result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
+    result.sslSocketFactory = sslSocketFactory != null
+        ? sslSocketFactory
+        : HttpsURLConnection.getDefaultSSLSocketFactory();
+    result.hostnameVerifier = hostnameVerifier != null
+        ? hostnameVerifier
+        : HttpsURLConnection.getDefaultHostnameVerifier();
+    result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
+    result.followProtocolRedirects = followProtocolRedirects;
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/OkResponseCache.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/OkResponseCache.java b/framework/src/com/squareup/okhttp/OkResponseCache.java
new file mode 100644
index 0000000..b7e3801
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/OkResponseCache.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+
+/**
+ * A response cache that supports statistics tracking and updating stored
+ * responses. Implementations of {@link java.net.ResponseCache} should implement
+ * this interface to receive additional support from the HTTP engine.
+ */
+public interface OkResponseCache {
+
+  /** Track an HTTP response being satisfied by {@code source}. */
+  void trackResponse(ResponseSource source);
+
+  /** Track an conditional GET that was satisfied by this cache. */
+  void trackConditionalCacheHit();
+
+  /** Updates stored HTTP headers using a hit on a conditional GET. */
+  void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
+      throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/ResponseSource.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/ResponseSource.java b/framework/src/com/squareup/okhttp/ResponseSource.java
new file mode 100644
index 0000000..4eca172
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/ResponseSource.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/** The source of an HTTP response. */
+public enum ResponseSource {
+
+  /** The response was returned from the local cache. */
+  CACHE,
+
+  /**
+   * The response is available in the cache but must be validated with the
+   * network. The cache result will be used if it is still valid; otherwise
+   * the network's response will be used.
+   */
+  CONDITIONAL_CACHE,
+
+  /** The response was returned from the network. */
+  NETWORK;
+
+  public boolean requiresConnection() {
+    return this == CONDITIONAL_CACHE || this == NETWORK;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/Route.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/Route.java b/framework/src/com/squareup/okhttp/Route.java
new file mode 100644
index 0000000..6968c60
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/Route.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/** Represents the route used by a connection to reach an endpoint. */
+public class Route {
+  final Address address;
+  final Proxy proxy;
+  final InetSocketAddress inetSocketAddress;
+  final boolean modernTls;
+
+  public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
+      boolean modernTls) {
+    if (address == null) throw new NullPointerException("address == null");
+    if (proxy == null) throw new NullPointerException("proxy == null");
+    if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
+    this.address = address;
+    this.proxy = proxy;
+    this.inetSocketAddress = inetSocketAddress;
+    this.modernTls = modernTls;
+  }
+
+  /** Returns the {@link Address} of this route. */
+  public Address getAddress() {
+    return address;
+  }
+
+  /**
+   * Returns the {@link Proxy} of this route.
+   *
+   * <strong>Warning:</strong> This may be different than the proxy returned
+   * by {@link #getAddress}! That is the proxy that the user asked to be
+   * connected to; this returns the proxy that they were actually connected
+   * to. The two may disagree when a proxy selector selects a different proxy
+   * for a connection.
+   */
+  public Proxy getProxy() {
+    return proxy;
+  }
+
+  /** Returns the {@link InetSocketAddress} of this route. */
+  public InetSocketAddress getSocketAddress() {
+    return inetSocketAddress;
+  }
+
+  /** Returns true if this route uses modern tls. */
+  public boolean isModernTls() {
+    return modernTls;
+  }
+
+  /** Returns a copy of this route with flipped tls mode. */
+  public Route flipTlsMode() {
+    return new Route(address, proxy, inetSocketAddress, !modernTls);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj instanceof Route) {
+      Route other = (Route) obj;
+      return (address.equals(other.address)
+          && proxy.equals(other.proxy)
+          && inetSocketAddress.equals(other.inetSocketAddress)
+          && modernTls == other.modernTls);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + address.hashCode();
+    result = 31 * result + proxy.hashCode();
+    result = 31 * result + inetSocketAddress.hashCode();
+    result = result + (modernTls ? (31 * result) : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/TunnelRequest.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/TunnelRequest.java b/framework/src/com/squareup/okhttp/TunnelRequest.java
new file mode 100644
index 0000000..5260b87
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/TunnelRequest.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import com.squareup.okhttp.internal.http.RawHeaders;
+
+import static com.squareup.okhttp.internal.Util.getDefaultPort;
+
+/**
+ * Routing and authentication information sent to an HTTP proxy to create a
+ * HTTPS to an origin server. Everything in the tunnel request is sent
+ * unencrypted to the proxy server.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section
+ * 5.2</a>.
+ */
+public final class TunnelRequest {
+  final String host;
+  final int port;
+  final String userAgent;
+  final String proxyAuthorization;
+
+  /**
+   * @param host the origin server's hostname. Not null.
+   * @param port the origin server's port, like 80 or 443.
+   * @param userAgent the client's user-agent. Not null.
+   * @param proxyAuthorization proxy authorization, or null if the proxy is
+   * used without an authorization header.
+   */
+  public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) {
+    if (host == null) throw new NullPointerException("host == null");
+    if (userAgent == null) throw new NullPointerException("userAgent == null");
+    this.host = host;
+    this.port = port;
+    this.userAgent = userAgent;
+    this.proxyAuthorization = proxyAuthorization;
+  }
+
+  /**
+   * If we're creating a TLS tunnel, send only the minimum set of headers.
+   * This avoids sending potentially sensitive data like HTTP cookies to
+   * the proxy unencrypted.
+   */
+  RawHeaders getRequestHeaders() {
+    RawHeaders result = new RawHeaders();
+    result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1");
+
+    // Always set Host and User-Agent.
+    result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
+    result.set("User-Agent", userAgent);
+
+    // Copy over the Proxy-Authorization header if it exists.
+    if (proxyAuthorization != null) {
+      result.set("Proxy-Authorization", proxyAuthorization);
+    }
+
+    // Always set the Proxy-Connection to Keep-Alive for the benefit of
+    // HTTP/1.0 proxies like Squid.
+    result.set("Proxy-Connection", "Keep-Alive");
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java b/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
new file mode 100644
index 0000000..78c9691
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream for an HTTP request body.
+ *
+ * <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.
+ */
+public abstract class AbstractOutputStream 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");
+    }
+  }
+
+  /** Returns true if this stream was closed locally. */
+  public boolean isClosed() {
+    return closed;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/Base64.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/Base64.java b/framework/src/com/squareup/okhttp/internal/Base64.java
new file mode 100644
index 0000000..79cd020
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/Base64.java
@@ -0,0 +1,164 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * @author Alexander Y. Kleymenov
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.UnsupportedEncodingException;
+
+import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
+
+/**
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
+ * In violation of the RFC, this encoder doesn't wrap lines at 76 columns.
+ */
+public final class Base64 {
+  private Base64() {
+  }
+
+  public static byte[] decode(byte[] in) {
+    return decode(in, in.length);
+  }
+
+  public static byte[] decode(byte[] in, int len) {
+    // approximate output length
+    int length = len / 4 * 3;
+    // return an empty array on empty or short input without padding
+    if (length == 0) {
+      return EMPTY_BYTE_ARRAY;
+    }
+    // temporary array
+    byte[] out = new byte[length];
+    // number of padding characters ('=')
+    int pad = 0;
+    byte chr;
+    // compute the number of the padding characters
+    // and adjust the length of the input
+    for (; ; len--) {
+      chr = in[len - 1];
+      // skip the neutral characters
+      if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
+        continue;
+      }
+      if (chr == '=') {
+        pad++;
+      } else {
+        break;
+      }
+    }
+    // index in the output array
+    int outIndex = 0;
+    // index in the input array
+    int inIndex = 0;
+    // holds the value of the input character
+    int bits = 0;
+    // holds the value of the input quantum
+    int quantum = 0;
+    for (int i = 0; i < len; i++) {
+      chr = in[i];
+      // skip the neutral characters
+      if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
+        continue;
+      }
+      if ((chr >= 'A') && (chr <= 'Z')) {
+        // char ASCII value
+        //  A    65    0
+        //  Z    90    25 (ASCII - 65)
+        bits = chr - 65;
+      } else if ((chr >= 'a') && (chr <= 'z')) {
+        // char ASCII value
+        //  a    97    26
+        //  z    122   51 (ASCII - 71)
+        bits = chr - 71;
+      } else if ((chr >= '0') && (chr <= '9')) {
+        // char ASCII value
+        //  0    48    52
+        //  9    57    61 (ASCII + 4)
+        bits = chr + 4;
+      } else if (chr == '+') {
+        bits = 62;
+      } else if (chr == '/') {
+        bits = 63;
+      } else {
+        return null;
+      }
+      // append the value to the quantum
+      quantum = (quantum << 6) | (byte) bits;
+      if (inIndex % 4 == 3) {
+        // 4 characters were read, so make the output:
+        out[outIndex++] = (byte) (quantum >> 16);
+        out[outIndex++] = (byte) (quantum >> 8);
+        out[outIndex++] = (byte) quantum;
+      }
+      inIndex++;
+    }
+    if (pad > 0) {
+      // adjust the quantum value according to the padding
+      quantum = quantum << (6 * pad);
+      // make output
+      out[outIndex++] = (byte) (quantum >> 16);
+      if (pad == 1) {
+        out[outIndex++] = (byte) (quantum >> 8);
+      }
+    }
+    // create the resulting array
+    byte[] result = new byte[outIndex];
+    System.arraycopy(out, 0, result, 0, outIndex);
+    return result;
+  }
+
+  private static final byte[] MAP = new byte[] {
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+      'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+      'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+      '5', '6', '7', '8', '9', '+', '/'
+  };
+
+  public static String encode(byte[] in) {
+    int length = (in.length + 2) * 4 / 3;
+    byte[] out = new byte[length];
+    int index = 0, end = in.length - in.length % 3;
+    for (int i = 0; i < end; i += 3) {
+      out[index++] = MAP[(in[i] & 0xff) >> 2];
+      out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
+      out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
+      out[index++] = MAP[(in[i + 2] & 0x3f)];
+    }
+    switch (in.length % 3) {
+      case 1:
+        out[index++] = MAP[(in[end] & 0xff) >> 2];
+        out[index++] = MAP[(in[end] & 0x03) << 4];
+        out[index++] = '=';
+        out[index++] = '=';
+        break;
+      case 2:
+        out[index++] = MAP[(in[end] & 0xff) >> 2];
+        out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
+        out[index++] = MAP[((in[end + 1] & 0x0f) << 2)];
+        out[index++] = '=';
+        break;
+    }
+    try {
+      return new String(out, 0, index, "US-ASCII");
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+}


Mime
View raw message