You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/05/24 01:41:54 UTC

[33/38] updated android, ios, bb libraries to 2.8.x branch. fixed a few assertions with project changes. removed blackberry support until create script can be finalized.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RawHeaders.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RawHeaders.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RawHeaders.java
new file mode 100644
index 0000000..eba887e
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RawHeaders.java
@@ -0,0 +1,424 @@
+/*
+ *  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.internal.Util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * The HTTP status and unparsed header fields of a single HTTP message. Values
+ * are represented as uninterpreted strings; use {@link RequestHeaders} and
+ * {@link ResponseHeaders} for interpreted headers. This class maintains the
+ * order of the header fields within the HTTP message.
+ *
+ * <p>This class tracks fields line-by-line. A field with multiple comma-
+ * separated values on the same line will be treated as a field with a single
+ * value by this class. It is the caller's responsibility to detect and split
+ * on commas if their field permits multiple values. This simplifies use of
+ * single-valued fields whose values routinely contain commas, such as cookies
+ * or dates.
+ *
+ * <p>This class trims whitespace from values. It never returns values with
+ * leading or trailing whitespace.
+ */
+public final class RawHeaders {
+  private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+    // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
+    @Override public int compare(String a, String b) {
+      if (a == b) {
+        return 0;
+      } else if (a == null) {
+        return -1;
+      } else if (b == null) {
+        return 1;
+      } else {
+        return String.CASE_INSENSITIVE_ORDER.compare(a, b);
+      }
+    }
+  };
+
+  private final List<String> namesAndValues = new ArrayList<String>(20);
+  private String requestLine;
+  private String statusLine;
+  private int httpMinorVersion = 1;
+  private int responseCode = -1;
+  private String responseMessage;
+
+  public RawHeaders() {
+  }
+
+  public RawHeaders(RawHeaders copyFrom) {
+    namesAndValues.addAll(copyFrom.namesAndValues);
+    requestLine = copyFrom.requestLine;
+    statusLine = copyFrom.statusLine;
+    httpMinorVersion = copyFrom.httpMinorVersion;
+    responseCode = copyFrom.responseCode;
+    responseMessage = copyFrom.responseMessage;
+  }
+
+  /** Sets the request line (like "GET / HTTP/1.1"). */
+  public void setRequestLine(String requestLine) {
+    requestLine = requestLine.trim();
+    this.requestLine = requestLine;
+  }
+
+  /** Sets the response status line (like "HTTP/1.0 200 OK"). */
+  public void setStatusLine(String statusLine) throws IOException {
+    // H T T P / 1 . 1   2 0 0   T e m p o r a r y   R e d i r e c t
+    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
+    if (this.responseMessage != null) {
+      throw new IllegalStateException("statusLine is already set");
+    }
+    // We allow empty message without leading white space since some servers
+    // do not send the white space when the message is empty.
+    boolean hasMessage = statusLine.length() > 13;
+    if (!statusLine.startsWith("HTTP/1.")
+        || statusLine.length() < 12
+        || statusLine.charAt(8) != ' '
+        || (hasMessage && statusLine.charAt(12) != ' ')) {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+    int httpMinorVersion = statusLine.charAt(7) - '0';
+    if (httpMinorVersion < 0 || httpMinorVersion > 9) {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+    int responseCode;
+    try {
+      responseCode = Integer.parseInt(statusLine.substring(9, 12));
+    } catch (NumberFormatException e) {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+    this.responseMessage = hasMessage ? statusLine.substring(13) : "";
+    this.responseCode = responseCode;
+    this.statusLine = statusLine;
+    this.httpMinorVersion = httpMinorVersion;
+  }
+
+  public void computeResponseStatusLineFromSpdyHeaders() throws IOException {
+    String status = null;
+    String version = null;
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      String name = namesAndValues.get(i);
+      if (":status".equals(name)) {
+        status = namesAndValues.get(i + 1);
+      } else if (":version".equals(name)) {
+        version = namesAndValues.get(i + 1);
+      }
+    }
+    if (status == null || version == null) {
+      throw new ProtocolException("Expected ':status' and ':version' headers not present");
+    }
+    setStatusLine(version + " " + status);
+  }
+
+  /**
+   * @param method like "GET", "POST", "HEAD", etc.
+   * @param path like "/foo/bar.html"
+   * @param version like "HTTP/1.1"
+   * @param host like "www.android.com:1234"
+   * @param scheme like "https"
+   */
+  public void addSpdyRequestHeaders(String method, String path, String version, String host,
+      String scheme) {
+    // TODO: populate the statusLine for the client's benefit?
+    add(":method", method);
+    add(":scheme", scheme);
+    add(":path", path);
+    add(":version", version);
+    add(":host", host);
+  }
+
+  public String getStatusLine() {
+    return statusLine;
+  }
+
+  /**
+   * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0
+   * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown.
+   */
+  public int getHttpMinorVersion() {
+    return httpMinorVersion != -1 ? httpMinorVersion : 1;
+  }
+
+  /** Returns the HTTP status code or -1 if it is unknown. */
+  public int getResponseCode() {
+    return responseCode;
+  }
+
+  /** Returns the HTTP status message or null if it is unknown. */
+  public String getResponseMessage() {
+    return responseMessage;
+  }
+
+  /**
+   * Add an HTTP header line containing a field name, a literal colon, and a
+   * value.
+   */
+  public void addLine(String line) {
+    int index = line.indexOf(":");
+    if (index == -1) {
+      addLenient("", line);
+    } else {
+      addLenient(line.substring(0, index), line.substring(index + 1));
+    }
+  }
+
+  /** Add a field with the specified value. */
+  public void add(String fieldName, String value) {
+    if (fieldName == null) throw new IllegalArgumentException("fieldname == null");
+    if (value == null) throw new IllegalArgumentException("value == null");
+    if (fieldName.length() == 0 || fieldName.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+      throw new IllegalArgumentException("Unexpected header: " + fieldName + ": " + value);
+    }
+    addLenient(fieldName, value);
+  }
+
+  /**
+   * Add a field with the specified value without any validation. Only
+   * appropriate for headers from the remote peer.
+   */
+  private void addLenient(String fieldName, String value) {
+    namesAndValues.add(fieldName);
+    namesAndValues.add(value.trim());
+  }
+
+  public void removeAll(String fieldName) {
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
+        namesAndValues.remove(i); // field name
+        namesAndValues.remove(i); // value
+      }
+    }
+  }
+
+  public void addAll(String fieldName, List<String> headerFields) {
+    for (String value : headerFields) {
+      add(fieldName, value);
+    }
+  }
+
+  /**
+   * Set a field with the specified value. If the field is not found, it is
+   * added. If the field is found, the existing values are replaced.
+   */
+  public void set(String fieldName, String value) {
+    removeAll(fieldName);
+    add(fieldName, value);
+  }
+
+  /** Returns the number of field values. */
+  public int length() {
+    return namesAndValues.size() / 2;
+  }
+
+  /** Returns the field at {@code position} or null if that is out of range. */
+  public String getFieldName(int index) {
+    int fieldNameIndex = index * 2;
+    if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) {
+      return null;
+    }
+    return namesAndValues.get(fieldNameIndex);
+  }
+
+  /** Returns the value at {@code index} or null if that is out of range. */
+  public String getValue(int index) {
+    int valueIndex = index * 2 + 1;
+    if (valueIndex < 0 || valueIndex >= namesAndValues.size()) {
+      return null;
+    }
+    return namesAndValues.get(valueIndex);
+  }
+
+  /** Returns the last value corresponding to the specified field, or null. */
+  public String get(String fieldName) {
+    for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
+      if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
+        return namesAndValues.get(i + 1);
+      }
+    }
+    return null;
+  }
+
+  /** @param fieldNames a case-insensitive set of HTTP header field names. */
+  public RawHeaders getAll(Set<String> fieldNames) {
+    RawHeaders result = new RawHeaders();
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      String fieldName = namesAndValues.get(i);
+      if (fieldNames.contains(fieldName)) {
+        result.add(fieldName, namesAndValues.get(i + 1));
+      }
+    }
+    return result;
+  }
+
+  /** Returns bytes of a request header for sending on an HTTP transport. */
+  public byte[] toBytes() throws UnsupportedEncodingException {
+    StringBuilder result = new StringBuilder(256);
+    result.append(requestLine).append("\r\n");
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      result.append(namesAndValues.get(i))
+          .append(": ")
+          .append(namesAndValues.get(i + 1))
+          .append("\r\n");
+    }
+    result.append("\r\n");
+    return result.toString().getBytes("ISO-8859-1");
+  }
+
+  /** Parses bytes of a response header from an HTTP transport. */
+  public static RawHeaders fromBytes(InputStream in) throws IOException {
+    RawHeaders headers;
+    do {
+      headers = new RawHeaders();
+      headers.setStatusLine(Util.readAsciiLine(in));
+      readHeaders(in, headers);
+    } while (headers.getResponseCode() == HttpEngine.HTTP_CONTINUE);
+    return headers;
+  }
+
+  /** Reads headers or trailers into {@code out}. */
+  public static void readHeaders(InputStream in, RawHeaders out) throws IOException {
+    // parse the result headers until the first blank line
+    String line;
+    while ((line = Util.readAsciiLine(in)).length() != 0) {
+      out.addLine(line);
+    }
+  }
+
+  /**
+   * Returns an immutable map containing each field to its list of values. The
+   * status line is mapped to null.
+   */
+  public Map<String, List<String>> toMultimap(boolean response) {
+    Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR);
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      String fieldName = namesAndValues.get(i);
+      String value = namesAndValues.get(i + 1);
+
+      List<String> allValues = new ArrayList<String>();
+      List<String> otherValues = result.get(fieldName);
+      if (otherValues != null) {
+        allValues.addAll(otherValues);
+      }
+      allValues.add(value);
+      result.put(fieldName, Collections.unmodifiableList(allValues));
+    }
+    if (response && statusLine != null) {
+      result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
+    } else if (requestLine != null) {
+      result.put(null, Collections.unmodifiableList(Collections.singletonList(requestLine)));
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  /**
+   * Creates a new instance from the given map of fields to values. If
+   * present, the null field's last element will be used to set the status
+   * line.
+   */
+  public static RawHeaders fromMultimap(Map<String, List<String>> map, boolean response)
+      throws IOException {
+    if (!response) throw new UnsupportedOperationException();
+    RawHeaders result = new RawHeaders();
+    for (Entry<String, List<String>> entry : map.entrySet()) {
+      String fieldName = entry.getKey();
+      List<String> values = entry.getValue();
+      if (fieldName != null) {
+        for (String value : values) {
+          result.addLenient(fieldName, value);
+        }
+      } else if (!values.isEmpty()) {
+        result.setStatusLine(values.get(values.size() - 1));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns a list of alternating names and values. Names are all lower case.
+   * No names are repeated. If any name has multiple values, they are
+   * concatenated using "\0" as a delimiter.
+   */
+  public List<String> toNameValueBlock() {
+    Set<String> names = new HashSet<String>();
+    List<String> result = new ArrayList<String>();
+    for (int i = 0; i < namesAndValues.size(); i += 2) {
+      String name = namesAndValues.get(i).toLowerCase(Locale.US);
+      String value = namesAndValues.get(i + 1);
+
+      // Drop headers that are forbidden when layering HTTP over SPDY.
+      if (name.equals("connection")
+          || name.equals("host")
+          || name.equals("keep-alive")
+          || name.equals("proxy-connection")
+          || name.equals("transfer-encoding")) {
+        continue;
+      }
+
+      // If we haven't seen this name before, add the pair to the end of the list...
+      if (names.add(name)) {
+        result.add(name);
+        result.add(value);
+        continue;
+      }
+
+      // ...otherwise concatenate the existing values and this value.
+      for (int j = 0; j < result.size(); j += 2) {
+        if (name.equals(result.get(j))) {
+          result.set(j + 1, result.get(j + 1) + "\0" + value);
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  public static RawHeaders fromNameValueBlock(List<String> nameValueBlock) {
+    if (nameValueBlock.size() % 2 != 0) {
+      throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
+    }
+    RawHeaders result = new RawHeaders();
+    for (int i = 0; i < nameValueBlock.size(); i += 2) {
+      String name = nameValueBlock.get(i);
+      String values = nameValueBlock.get(i + 1);
+      for (int start = 0; start < values.length(); ) {
+        int end = values.indexOf('\0', start);
+        if (end == -1) {
+          end = values.length();
+        }
+        result.namesAndValues.add(name);
+        result.namesAndValues.add(values.substring(start, end));
+        start = end + 1;
+      }
+    }
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RequestHeaders.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RequestHeaders.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RequestHeaders.java
new file mode 100644
index 0000000..5ec4fcc
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RequestHeaders.java
@@ -0,0 +1,290 @@
+/*
+ * 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.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/** Parsed HTTP request headers. */
+public final class RequestHeaders {
+  private final URI uri;
+  private final RawHeaders headers;
+
+  /** Don't use a cache to satisfy this request. */
+  private boolean noCache;
+  private int maxAgeSeconds = -1;
+  private int maxStaleSeconds = -1;
+  private int minFreshSeconds = -1;
+
+  /**
+   * This field's name "only-if-cached" is misleading. It actually means "do
+   * not use the network". It is set by a client who only wants to make a
+   * request if it can be fully satisfied by the cache. Cached responses that
+   * would require validation (ie. conditional gets) are not permitted if this
+   * header is set.
+   */
+  private boolean onlyIfCached;
+
+  /**
+   * True if the request contains an authorization field. Although this isn't
+   * necessarily a shared cache, it follows the spec's strict requirements for
+   * shared caches.
+   */
+  private boolean hasAuthorization;
+
+  private int contentLength = -1;
+  private String transferEncoding;
+  private String userAgent;
+  private String host;
+  private String connection;
+  private String acceptEncoding;
+  private String contentType;
+  private String ifModifiedSince;
+  private String ifNoneMatch;
+  private String proxyAuthorization;
+
+  public RequestHeaders(URI uri, RawHeaders headers) {
+    this.uri = uri;
+    this.headers = headers;
+
+    HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
+      @Override public void handle(String directive, String parameter) {
+        if ("no-cache".equalsIgnoreCase(directive)) {
+          noCache = true;
+        } else if ("max-age".equalsIgnoreCase(directive)) {
+          maxAgeSeconds = HeaderParser.parseSeconds(parameter);
+        } else if ("max-stale".equalsIgnoreCase(directive)) {
+          maxStaleSeconds = HeaderParser.parseSeconds(parameter);
+        } else if ("min-fresh".equalsIgnoreCase(directive)) {
+          minFreshSeconds = HeaderParser.parseSeconds(parameter);
+        } else if ("only-if-cached".equalsIgnoreCase(directive)) {
+          onlyIfCached = true;
+        }
+      }
+    };
+
+    for (int i = 0; i < headers.length(); i++) {
+      String fieldName = headers.getFieldName(i);
+      String value = headers.getValue(i);
+      if ("Cache-Control".equalsIgnoreCase(fieldName)) {
+        HeaderParser.parseCacheControl(value, handler);
+      } else if ("Pragma".equalsIgnoreCase(fieldName)) {
+        if ("no-cache".equalsIgnoreCase(value)) {
+          noCache = true;
+        }
+      } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
+        ifNoneMatch = value;
+      } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
+        ifModifiedSince = value;
+      } else if ("Authorization".equalsIgnoreCase(fieldName)) {
+        hasAuthorization = true;
+      } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
+        try {
+          contentLength = Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+        }
+      } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
+        transferEncoding = value;
+      } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
+        userAgent = value;
+      } else if ("Host".equalsIgnoreCase(fieldName)) {
+        host = value;
+      } else if ("Connection".equalsIgnoreCase(fieldName)) {
+        connection = value;
+      } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
+        acceptEncoding = value;
+      } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
+        contentType = value;
+      } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
+        proxyAuthorization = value;
+      }
+    }
+  }
+
+  public boolean isChunked() {
+    return "chunked".equalsIgnoreCase(transferEncoding);
+  }
+
+  public boolean hasConnectionClose() {
+    return "close".equalsIgnoreCase(connection);
+  }
+
+  public URI getUri() {
+    return uri;
+  }
+
+  public RawHeaders getHeaders() {
+    return headers;
+  }
+
+  public boolean isNoCache() {
+    return noCache;
+  }
+
+  public int getMaxAgeSeconds() {
+    return maxAgeSeconds;
+  }
+
+  public int getMaxStaleSeconds() {
+    return maxStaleSeconds;
+  }
+
+  public int getMinFreshSeconds() {
+    return minFreshSeconds;
+  }
+
+  public boolean isOnlyIfCached() {
+    return onlyIfCached;
+  }
+
+  public boolean hasAuthorization() {
+    return hasAuthorization;
+  }
+
+  public int getContentLength() {
+    return contentLength;
+  }
+
+  public String getTransferEncoding() {
+    return transferEncoding;
+  }
+
+  public String getUserAgent() {
+    return userAgent;
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public String getConnection() {
+    return connection;
+  }
+
+  public String getAcceptEncoding() {
+    return acceptEncoding;
+  }
+
+  public String getContentType() {
+    return contentType;
+  }
+
+  public String getIfModifiedSince() {
+    return ifModifiedSince;
+  }
+
+  public String getIfNoneMatch() {
+    return ifNoneMatch;
+  }
+
+  public String getProxyAuthorization() {
+    return proxyAuthorization;
+  }
+
+  public void setChunked() {
+    if (this.transferEncoding != null) {
+      headers.removeAll("Transfer-Encoding");
+    }
+    headers.add("Transfer-Encoding", "chunked");
+    this.transferEncoding = "chunked";
+  }
+
+  public void setContentLength(int contentLength) {
+    if (this.contentLength != -1) {
+      headers.removeAll("Content-Length");
+    }
+    headers.add("Content-Length", Integer.toString(contentLength));
+    this.contentLength = contentLength;
+  }
+
+  public void setUserAgent(String userAgent) {
+    if (this.userAgent != null) {
+      headers.removeAll("User-Agent");
+    }
+    headers.add("User-Agent", userAgent);
+    this.userAgent = userAgent;
+  }
+
+  public void setHost(String host) {
+    if (this.host != null) {
+      headers.removeAll("Host");
+    }
+    headers.add("Host", host);
+    this.host = host;
+  }
+
+  public void setConnection(String connection) {
+    if (this.connection != null) {
+      headers.removeAll("Connection");
+    }
+    headers.add("Connection", connection);
+    this.connection = connection;
+  }
+
+  public void setAcceptEncoding(String acceptEncoding) {
+    if (this.acceptEncoding != null) {
+      headers.removeAll("Accept-Encoding");
+    }
+    headers.add("Accept-Encoding", acceptEncoding);
+    this.acceptEncoding = acceptEncoding;
+  }
+
+  public void setContentType(String contentType) {
+    if (this.contentType != null) {
+      headers.removeAll("Content-Type");
+    }
+    headers.add("Content-Type", contentType);
+    this.contentType = contentType;
+  }
+
+  public void setIfModifiedSince(Date date) {
+    if (ifModifiedSince != null) {
+      headers.removeAll("If-Modified-Since");
+    }
+    String formattedDate = HttpDate.format(date);
+    headers.add("If-Modified-Since", formattedDate);
+    ifModifiedSince = formattedDate;
+  }
+
+  public void setIfNoneMatch(String ifNoneMatch) {
+    if (this.ifNoneMatch != null) {
+      headers.removeAll("If-None-Match");
+    }
+    headers.add("If-None-Match", ifNoneMatch);
+    this.ifNoneMatch = ifNoneMatch;
+  }
+
+  /**
+   * Returns true if the request contains conditions that save the server from
+   * sending a response that the client has locally. When the caller adds
+   * conditions, this cache won't participate in the request.
+   */
+  public boolean hasConditions() {
+    return ifModifiedSince != null || ifNoneMatch != null;
+  }
+
+  public void addCookies(Map<String, List<String>> allCookieHeaders) {
+    for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
+      String key = entry.getKey();
+      if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
+        headers.addAll(key, entry.getValue());
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/ResponseHeaders.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/ResponseHeaders.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/ResponseHeaders.java
new file mode 100644
index 0000000..2ab564d
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/ResponseHeaders.java
@@ -0,0 +1,497 @@
+/*
+ * 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.ResponseSource;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+import static com.squareup.okhttp.internal.Util.equal;
+
+/** Parsed HTTP response headers. */
+public final class ResponseHeaders {
+
+  /** HTTP header name for the local time when the request was sent. */
+  private static final String SENT_MILLIS = "X-Android-Sent-Millis";
+
+  /** HTTP header name for the local time when the response was received. */
+  private static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
+
+  /** HTTP synthetic header with the response source. */
+  static final String RESPONSE_SOURCE = "X-Android-Response-Source";
+
+  private final URI uri;
+  private final RawHeaders headers;
+
+  /** The server's time when this response was served, if known. */
+  private Date servedDate;
+
+  /** The last modified date of the response, if known. */
+  private Date lastModified;
+
+  /**
+   * The expiration date of the response, if known. If both this field and the
+   * max age are set, the max age is preferred.
+   */
+  private Date expires;
+
+  /**
+   * Extension header set by HttpURLConnectionImpl specifying the timestamp
+   * when the HTTP request was first initiated.
+   */
+  private long sentRequestMillis;
+
+  /**
+   * Extension header set by HttpURLConnectionImpl specifying the timestamp
+   * when the HTTP response was first received.
+   */
+  private long receivedResponseMillis;
+
+  /**
+   * In the response, this field's name "no-cache" is misleading. It doesn't
+   * prevent us from caching the response; it only means we have to validate
+   * the response with the origin server before returning it. We can do this
+   * with a conditional get.
+   */
+  private boolean noCache;
+
+  /** If true, this response should not be cached. */
+  private boolean noStore;
+
+  /**
+   * The duration past the response's served date that it can be served
+   * without validation.
+   */
+  private int maxAgeSeconds = -1;
+
+  /**
+   * The "s-maxage" directive is the max age for shared caches. Not to be
+   * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
+   * this directive is not honored by this cache.
+   */
+  private int sMaxAgeSeconds = -1;
+
+  /**
+   * This request header field's name "only-if-cached" is misleading. It
+   * actually means "do not use the network". It is set by a client who only
+   * wants to make a request if it can be fully satisfied by the cache.
+   * Cached responses that would require validation (ie. conditional gets) are
+   * not permitted if this header is set.
+   */
+  private boolean isPublic;
+  private boolean mustRevalidate;
+  private String etag;
+  private int ageSeconds = -1;
+
+  /** Case-insensitive set of field names. */
+  private Set<String> varyFields = Collections.emptySet();
+
+  private String contentEncoding;
+  private String transferEncoding;
+  private int contentLength = -1;
+  private String connection;
+
+  public ResponseHeaders(URI uri, RawHeaders headers) {
+    this.uri = uri;
+    this.headers = headers;
+
+    HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
+      @Override public void handle(String directive, String parameter) {
+        if ("no-cache".equalsIgnoreCase(directive)) {
+          noCache = true;
+        } else if ("no-store".equalsIgnoreCase(directive)) {
+          noStore = true;
+        } else if ("max-age".equalsIgnoreCase(directive)) {
+          maxAgeSeconds = HeaderParser.parseSeconds(parameter);
+        } else if ("s-maxage".equalsIgnoreCase(directive)) {
+          sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
+        } else if ("public".equalsIgnoreCase(directive)) {
+          isPublic = true;
+        } else if ("must-revalidate".equalsIgnoreCase(directive)) {
+          mustRevalidate = true;
+        }
+      }
+    };
+
+    for (int i = 0; i < headers.length(); i++) {
+      String fieldName = headers.getFieldName(i);
+      String value = headers.getValue(i);
+      if ("Cache-Control".equalsIgnoreCase(fieldName)) {
+        HeaderParser.parseCacheControl(value, handler);
+      } else if ("Date".equalsIgnoreCase(fieldName)) {
+        servedDate = HttpDate.parse(value);
+      } else if ("Expires".equalsIgnoreCase(fieldName)) {
+        expires = HttpDate.parse(value);
+      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
+        lastModified = HttpDate.parse(value);
+      } else if ("ETag".equalsIgnoreCase(fieldName)) {
+        etag = value;
+      } else if ("Pragma".equalsIgnoreCase(fieldName)) {
+        if ("no-cache".equalsIgnoreCase(value)) {
+          noCache = true;
+        }
+      } else if ("Age".equalsIgnoreCase(fieldName)) {
+        ageSeconds = HeaderParser.parseSeconds(value);
+      } else if ("Vary".equalsIgnoreCase(fieldName)) {
+        // Replace the immutable empty set with something we can mutate.
+        if (varyFields.isEmpty()) {
+          varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+        }
+        for (String varyField : value.split(",")) {
+          varyFields.add(varyField.trim());
+        }
+      } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) {
+        contentEncoding = value;
+      } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
+        transferEncoding = value;
+      } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
+        try {
+          contentLength = Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+        }
+      } else if ("Connection".equalsIgnoreCase(fieldName)) {
+        connection = value;
+      } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+        sentRequestMillis = Long.parseLong(value);
+      } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+        receivedResponseMillis = Long.parseLong(value);
+      }
+    }
+  }
+
+  public boolean isContentEncodingGzip() {
+    return "gzip".equalsIgnoreCase(contentEncoding);
+  }
+
+  public void stripContentEncoding() {
+    contentEncoding = null;
+    headers.removeAll("Content-Encoding");
+  }
+
+  public void stripContentLength() {
+    contentLength = -1;
+    headers.removeAll("Content-Length");
+  }
+
+  public boolean isChunked() {
+    return "chunked".equalsIgnoreCase(transferEncoding);
+  }
+
+  public boolean hasConnectionClose() {
+    return "close".equalsIgnoreCase(connection);
+  }
+
+  public URI getUri() {
+    return uri;
+  }
+
+  public RawHeaders getHeaders() {
+    return headers;
+  }
+
+  public Date getServedDate() {
+    return servedDate;
+  }
+
+  public Date getLastModified() {
+    return lastModified;
+  }
+
+  public Date getExpires() {
+    return expires;
+  }
+
+  public boolean isNoCache() {
+    return noCache;
+  }
+
+  public boolean isNoStore() {
+    return noStore;
+  }
+
+  public int getMaxAgeSeconds() {
+    return maxAgeSeconds;
+  }
+
+  public int getSMaxAgeSeconds() {
+    return sMaxAgeSeconds;
+  }
+
+  public boolean isPublic() {
+    return isPublic;
+  }
+
+  public boolean isMustRevalidate() {
+    return mustRevalidate;
+  }
+
+  public String getEtag() {
+    return etag;
+  }
+
+  public Set<String> getVaryFields() {
+    return varyFields;
+  }
+
+  public String getContentEncoding() {
+    return contentEncoding;
+  }
+
+  public int getContentLength() {
+    return contentLength;
+  }
+
+  public String getConnection() {
+    return connection;
+  }
+
+  public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) {
+    this.sentRequestMillis = sentRequestMillis;
+    headers.add(SENT_MILLIS, Long.toString(sentRequestMillis));
+    this.receivedResponseMillis = receivedResponseMillis;
+    headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis));
+  }
+
+  public void setResponseSource(ResponseSource responseSource) {
+    headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode());
+  }
+
+  /**
+   * Returns the current age of the response, in milliseconds. The calculation
+   * is specified by RFC 2616, 13.2.3 Age Calculations.
+   */
+  private long computeAge(long nowMillis) {
+    long apparentReceivedAge =
+        servedDate != null ? Math.max(0, receivedResponseMillis - servedDate.getTime()) : 0;
+    long receivedAge =
+        ageSeconds != -1 ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds))
+            : apparentReceivedAge;
+    long responseDuration = receivedResponseMillis - sentRequestMillis;
+    long residentDuration = nowMillis - receivedResponseMillis;
+    return receivedAge + responseDuration + residentDuration;
+  }
+
+  /**
+   * Returns the number of milliseconds that the response was fresh for,
+   * starting from the served date.
+   */
+  private long computeFreshnessLifetime() {
+    if (maxAgeSeconds != -1) {
+      return TimeUnit.SECONDS.toMillis(maxAgeSeconds);
+    } else if (expires != null) {
+      long servedMillis = servedDate != null ? servedDate.getTime() : receivedResponseMillis;
+      long delta = expires.getTime() - servedMillis;
+      return delta > 0 ? delta : 0;
+    } else if (lastModified != null && uri.getRawQuery() == null) {
+      // As recommended by the HTTP RFC and implemented in Firefox, the
+      // max age of a document should be defaulted to 10% of the
+      // document's age at the time it was served. Default expiration
+      // dates aren't used for URIs containing a query.
+      long servedMillis = servedDate != null ? servedDate.getTime() : sentRequestMillis;
+      long delta = servedMillis - lastModified.getTime();
+      return delta > 0 ? (delta / 10) : 0;
+    }
+    return 0;
+  }
+
+  /**
+   * Returns true if computeFreshnessLifetime used a heuristic. If we used a
+   * heuristic to serve a cached response older than 24 hours, we are required
+   * to attach a warning.
+   */
+  private boolean isFreshnessLifetimeHeuristic() {
+    return maxAgeSeconds == -1 && expires == null;
+  }
+
+  /**
+   * Returns true if this response can be stored to later serve another
+   * request.
+   */
+  public boolean isCacheable(RequestHeaders request) {
+    // Always go to network for uncacheable response codes (RFC 2616, 13.4),
+    // This implementation doesn't support caching partial content.
+    int responseCode = headers.getResponseCode();
+    if (responseCode != HttpURLConnection.HTTP_OK
+        && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE
+        && responseCode != HttpURLConnection.HTTP_MULT_CHOICE
+        && responseCode != HttpURLConnection.HTTP_MOVED_PERM
+        && responseCode != HttpURLConnection.HTTP_GONE) {
+      return false;
+    }
+
+    // Responses to authorized requests aren't cacheable unless they include
+    // a 'public', 'must-revalidate' or 's-maxage' directive.
+    if (request.hasAuthorization() && !isPublic && !mustRevalidate && sMaxAgeSeconds == -1) {
+      return false;
+    }
+
+    if (noStore) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns true if a Vary header contains an asterisk. Such responses cannot
+   * be cached.
+   */
+  public boolean hasVaryAll() {
+    return varyFields.contains("*");
+  }
+
+  /**
+   * Returns true if none of the Vary headers on this response have changed
+   * between {@code cachedRequest} and {@code newRequest}.
+   */
+  public boolean varyMatches(Map<String, List<String>> cachedRequest,
+      Map<String, List<String>> newRequest) {
+    for (String field : varyFields) {
+      if (!equal(cachedRequest.get(field), newRequest.get(field))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /** Returns the source to satisfy {@code request} given this cached response. */
+  public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) {
+    // If this response shouldn't have been stored, it should never be used
+    // as a response source. This check should be redundant as long as the
+    // persistence store is well-behaved and the rules are constant.
+    if (!isCacheable(request)) {
+      return ResponseSource.NETWORK;
+    }
+
+    if (request.isNoCache() || request.hasConditions()) {
+      return ResponseSource.NETWORK;
+    }
+
+    long ageMillis = computeAge(nowMillis);
+    long freshMillis = computeFreshnessLifetime();
+
+    if (request.getMaxAgeSeconds() != -1) {
+      freshMillis = Math.min(freshMillis, TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds()));
+    }
+
+    long minFreshMillis = 0;
+    if (request.getMinFreshSeconds() != -1) {
+      minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds());
+    }
+
+    long maxStaleMillis = 0;
+    if (!mustRevalidate && request.getMaxStaleSeconds() != -1) {
+      maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds());
+    }
+
+    if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+      if (ageMillis + minFreshMillis >= freshMillis) {
+        headers.add("Warning", "110 HttpURLConnection \"Response is stale\"");
+      }
+      long oneDayMillis = 24 * 60 * 60 * 1000L;
+      if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
+        headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+      }
+      return ResponseSource.CACHE;
+    }
+
+    if (lastModified != null) {
+      request.setIfModifiedSince(lastModified);
+    } else if (servedDate != null) {
+      request.setIfModifiedSince(servedDate);
+    }
+
+    if (etag != null) {
+      request.setIfNoneMatch(etag);
+    }
+
+    return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK;
+  }
+
+  /**
+   * Returns true if this cached response should be used; false if the
+   * network response should be used.
+   */
+  public boolean validate(ResponseHeaders networkResponse) {
+    if (networkResponse.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+      return true;
+    }
+
+    // The HTTP spec says that if the network's response is older than our
+    // cached response, we may return the cache's response. Like Chrome (but
+    // unlike Firefox), this client prefers to return the newer response.
+    if (lastModified != null
+        && networkResponse.lastModified != null
+        && networkResponse.lastModified.getTime() < lastModified.getTime()) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Combines this cached header with a network header as defined by RFC 2616,
+   * 13.5.3.
+   */
+  public ResponseHeaders combine(ResponseHeaders network) throws IOException {
+    RawHeaders result = new RawHeaders();
+    result.setStatusLine(headers.getStatusLine());
+
+    for (int i = 0; i < headers.length(); i++) {
+      String fieldName = headers.getFieldName(i);
+      String value = headers.getValue(i);
+      if ("Warning".equals(fieldName) && value.startsWith("1")) {
+        continue; // drop 100-level freshness warnings
+      }
+      if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) {
+        result.add(fieldName, value);
+      }
+    }
+
+    for (int i = 0; i < network.headers.length(); i++) {
+      String fieldName = network.headers.getFieldName(i);
+      if (isEndToEnd(fieldName)) {
+        result.add(fieldName, network.headers.getValue(i));
+      }
+    }
+
+    return new ResponseHeaders(uri, result);
+  }
+
+  /**
+   * Returns true if {@code fieldName} is an end-to-end HTTP header, as
+   * defined by RFC 2616, 13.5.1.
+   */
+  private static boolean isEndToEnd(String fieldName) {
+    return !"Connection".equalsIgnoreCase(fieldName)
+        && !"Keep-Alive".equalsIgnoreCase(fieldName)
+        && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
+        && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
+        && !"TE".equalsIgnoreCase(fieldName)
+        && !"Trailers".equalsIgnoreCase(fieldName)
+        && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
+        && !"Upgrade".equalsIgnoreCase(fieldName);
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java
new file mode 100644
index 0000000..5eb6b76
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java
@@ -0,0 +1,75 @@
+/*
+ * 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.AbstractOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
+
+/**
+ * An HTTP request body that's completely buffered in memory. This allows
+ * the post body to be transparently re-sent if the HTTP request must be
+ * sent multiple times.
+ */
+final class RetryableOutputStream extends AbstractOutputStream {
+  private final int limit;
+  private final ByteArrayOutputStream content;
+
+  public RetryableOutputStream(int limit) {
+    this.limit = limit;
+    this.content = new ByteArrayOutputStream(limit);
+  }
+
+  public RetryableOutputStream() {
+    this.limit = -1;
+    this.content = new ByteArrayOutputStream();
+  }
+
+  @Override public synchronized void close() throws IOException {
+    if (closed) {
+      return;
+    }
+    closed = true;
+    if (content.size() < limit) {
+      throw new ProtocolException(
+          "content-length promised " + limit + " bytes, but received " + content.size());
+    }
+  }
+
+  @Override public synchronized void write(byte[] buffer, int offset, int count)
+      throws IOException {
+    checkNotClosed();
+    checkOffsetAndCount(buffer.length, offset, count);
+    if (limit != -1 && content.size() > limit - count) {
+      throw new ProtocolException("exceeded content-length limit of " + limit + " bytes");
+    }
+    content.write(buffer, offset, count);
+  }
+
+  public synchronized int contentLength() throws IOException {
+    close();
+    return content.size();
+  }
+
+  public void writeToSocket(OutputStream socketOut) throws IOException {
+    content.writeTo(socketOut);
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RouteSelector.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RouteSelector.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RouteSelector.java
new file mode 100644
index 0000000..ce0a71d
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/RouteSelector.java
@@ -0,0 +1,275 @@
+/*
+ * 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.internal.http;
+
+import com.squareup.okhttp.Address;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.ConnectionPool;
+import com.squareup.okhttp.Route;
+import com.squareup.okhttp.internal.Dns;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import javax.net.ssl.SSLHandshakeException;
+
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
+
+/**
+ * Selects routes to connect to an origin server. Each connection requires a
+ * choice of proxy server, IP address, and TLS mode. Connections may also be
+ * recycled.
+ */
+public final class RouteSelector {
+  /** Uses {@link com.squareup.okhttp.internal.Platform#enableTlsExtensions}. */
+  private static final int TLS_MODE_MODERN = 1;
+  /** Uses {@link com.squareup.okhttp.internal.Platform#supportTlsIntolerantServer}. */
+  private static final int TLS_MODE_COMPATIBLE = 0;
+  /** No TLS mode. */
+  private static final int TLS_MODE_NULL = -1;
+
+  private final Address address;
+  private final URI uri;
+  private final ProxySelector proxySelector;
+  private final ConnectionPool pool;
+  private final Dns dns;
+  private final Set<Route> failedRoutes;
+
+  /* The most recently attempted route. */
+  private Proxy lastProxy;
+  private InetSocketAddress lastInetSocketAddress;
+
+  /* State for negotiating the next proxy to use. */
+  private boolean hasNextProxy;
+  private Proxy userSpecifiedProxy;
+  private Iterator<Proxy> proxySelectorProxies;
+
+  /* State for negotiating the next InetSocketAddress to use. */
+  private InetAddress[] socketAddresses;
+  private int nextSocketAddressIndex;
+  private int socketPort;
+
+  /* State for negotiating the next TLS configuration */
+  private int nextTlsMode = TLS_MODE_NULL;
+
+  /* State for negotiating failed routes */
+  private final List<Route> postponedRoutes;
+
+  public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool,
+      Dns dns, Set<Route> failedRoutes) {
+    this.address = address;
+    this.uri = uri;
+    this.proxySelector = proxySelector;
+    this.pool = pool;
+    this.dns = dns;
+    this.failedRoutes = failedRoutes;
+    this.postponedRoutes = new LinkedList<Route>();
+
+    resetNextProxy(uri, address.getProxy());
+  }
+
+  /**
+   * Returns true if there's another route to attempt. Every address has at
+   * least one route.
+   */
+  public boolean hasNext() {
+    return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy() || hasNextPostponed();
+  }
+
+  /**
+   * Returns the next route address to attempt.
+   *
+   * @throws NoSuchElementException if there are no more routes to attempt.
+   */
+  public Connection next() throws IOException {
+    // Always prefer pooled connections over new connections.
+    Connection pooled = pool.get(address);
+    if (pooled != null) {
+      return pooled;
+    }
+
+    // Compute the next route to attempt.
+    if (!hasNextTlsMode()) {
+      if (!hasNextInetSocketAddress()) {
+        if (!hasNextProxy()) {
+          if (!hasNextPostponed()) {
+            throw new NoSuchElementException();
+          }
+          return new Connection(nextPostponed());
+        }
+        lastProxy = nextProxy();
+        resetNextInetSocketAddress(lastProxy);
+      }
+      lastInetSocketAddress = nextInetSocketAddress();
+      resetNextTlsMode();
+    }
+
+    boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
+    Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
+    if (failedRoutes.contains(route)) {
+      postponedRoutes.add(route);
+      // We will only recurse in order to skip previously failed routes. They will be
+      // tried last.
+      return next();
+    }
+
+    return new Connection(route);
+  }
+
+  /**
+   * Clients should invoke this method when they encounter a connectivity
+   * failure on a connection returned by this route selector.
+   */
+  public void connectFailed(Connection connection, IOException failure) {
+    Route failedRoute = connection.getRoute();
+    if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) {
+      // Tell the proxy selector when we fail to connect on a fresh connection.
+      proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure);
+    }
+
+    failedRoutes.add(failedRoute);
+    if (!(failure instanceof SSLHandshakeException)) {
+      // If the problem was not related to SSL then it will also fail with
+      // a different Tls mode therefore we can be proactive about it.
+      failedRoutes.add(failedRoute.flipTlsMode());
+    }
+  }
+
+  /** Resets {@link #nextProxy} to the first option. */
+  private void resetNextProxy(URI uri, Proxy proxy) {
+    this.hasNextProxy = true; // This includes NO_PROXY!
+    if (proxy != null) {
+      this.userSpecifiedProxy = proxy;
+    } else {
+      List<Proxy> proxyList = proxySelector.select(uri);
+      if (proxyList != null) {
+        this.proxySelectorProxies = proxyList.iterator();
+      }
+    }
+  }
+
+  /** Returns true if there's another proxy to try. */
+  private boolean hasNextProxy() {
+    return hasNextProxy;
+  }
+
+  /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
+  private Proxy nextProxy() {
+    // If the user specifies a proxy, try that and only that.
+    if (userSpecifiedProxy != null) {
+      hasNextProxy = false;
+      return userSpecifiedProxy;
+    }
+
+    // Try each of the ProxySelector choices until one connection succeeds. If none succeed
+    // then we'll try a direct connection below.
+    if (proxySelectorProxies != null) {
+      while (proxySelectorProxies.hasNext()) {
+        Proxy candidate = proxySelectorProxies.next();
+        if (candidate.type() != Proxy.Type.DIRECT) {
+          return candidate;
+        }
+      }
+    }
+
+    // Finally try a direct connection.
+    hasNextProxy = false;
+    return Proxy.NO_PROXY;
+  }
+
+  /** Resets {@link #nextInetSocketAddress} to the first option. */
+  private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
+    socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
+
+    String socketHost;
+    if (proxy.type() == Proxy.Type.DIRECT) {
+      socketHost = uri.getHost();
+      socketPort = getEffectivePort(uri);
+    } else {
+      SocketAddress proxyAddress = proxy.address();
+      if (!(proxyAddress instanceof InetSocketAddress)) {
+        throw new IllegalArgumentException(
+            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
+      }
+      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
+      socketHost = proxySocketAddress.getHostName();
+      socketPort = proxySocketAddress.getPort();
+    }
+
+    // Try each address for best behavior in mixed IPv4/IPv6 environments.
+    socketAddresses = dns.getAllByName(socketHost);
+    nextSocketAddressIndex = 0;
+  }
+
+  /** Returns true if there's another socket address to try. */
+  private boolean hasNextInetSocketAddress() {
+    return socketAddresses != null;
+  }
+
+  /** Returns the next socket address to try. */
+  private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
+    InetSocketAddress result =
+        new InetSocketAddress(socketAddresses[nextSocketAddressIndex++], socketPort);
+    if (nextSocketAddressIndex == socketAddresses.length) {
+      socketAddresses = null; // So that hasNextInetSocketAddress() returns false.
+      nextSocketAddressIndex = 0;
+    }
+
+    return result;
+  }
+
+  /** Resets {@link #nextTlsMode} to the first option. */
+  private void resetNextTlsMode() {
+    nextTlsMode = (address.getSslSocketFactory() != null) ? TLS_MODE_MODERN : TLS_MODE_COMPATIBLE;
+  }
+
+  /** Returns true if there's another TLS mode to try. */
+  private boolean hasNextTlsMode() {
+    return nextTlsMode != TLS_MODE_NULL;
+  }
+
+  /** Returns the next TLS mode to try. */
+  private int nextTlsMode() {
+    if (nextTlsMode == TLS_MODE_MODERN) {
+      nextTlsMode = TLS_MODE_COMPATIBLE;
+      return TLS_MODE_MODERN;
+    } else if (nextTlsMode == TLS_MODE_COMPATIBLE) {
+      nextTlsMode = TLS_MODE_NULL;  // So that hasNextTlsMode() returns false.
+      return TLS_MODE_COMPATIBLE;
+    } else {
+      throw new AssertionError();
+    }
+  }
+
+  /** Returns true if there is another postponed route to try. */
+  private boolean hasNextPostponed() {
+    return !postponedRoutes.isEmpty();
+  }
+
+  /** Returns the next postponed route to try. */
+  private Route nextPostponed() {
+    return postponedRoutes.remove(0);
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/SpdyTransport.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/SpdyTransport.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/SpdyTransport.java
new file mode 100644
index 0000000..18ab566
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/SpdyTransport.java
@@ -0,0 +1,96 @@
+/*
+ * 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.http;
+
+import com.squareup.okhttp.internal.spdy.SpdyConnection;
+import com.squareup.okhttp.internal.spdy.SpdyStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.URL;
+import java.util.List;
+
+public final class SpdyTransport implements Transport {
+  private final HttpEngine httpEngine;
+  private final SpdyConnection spdyConnection;
+  private SpdyStream stream;
+
+  public SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) {
+    this.httpEngine = httpEngine;
+    this.spdyConnection = spdyConnection;
+  }
+
+  @Override public OutputStream createRequestBody() throws IOException {
+    // TODO: if we aren't streaming up to the server, we should buffer the whole request
+    writeRequestHeaders();
+    return stream.getOutputStream();
+  }
+
+  @Override public void writeRequestHeaders() throws IOException {
+    if (stream != null) {
+      return;
+    }
+    httpEngine.writingRequestHeaders();
+    RawHeaders requestHeaders = httpEngine.requestHeaders.getHeaders();
+    String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0";
+    URL url = httpEngine.policy.getURL();
+    requestHeaders.addSpdyRequestHeaders(httpEngine.method, HttpEngine.requestPath(url), version,
+        HttpEngine.getOriginAddress(url), httpEngine.uri.getScheme());
+    boolean hasRequestBody = httpEngine.hasRequestBody();
+    boolean hasResponseBody = true;
+    stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody,
+        hasResponseBody);
+    stream.setReadTimeout(httpEngine.policy.getReadTimeout());
+  }
+
+  @Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override public void flushRequest() throws IOException {
+    stream.getOutputStream().close();
+  }
+
+  @Override public ResponseHeaders readResponseHeaders() throws IOException {
+    List<String> nameValueBlock = stream.getResponseHeaders();
+    RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
+    rawHeaders.computeResponseStatusLineFromSpdyHeaders();
+    httpEngine.receiveHeaders(rawHeaders);
+    return new ResponseHeaders(httpEngine.uri, rawHeaders);
+  }
+
+  @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
+    return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
+  }
+
+  @Override public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
+      InputStream responseBodyIn) {
+    if (streamCancelled) {
+      if (stream != null) {
+        stream.closeLater(SpdyStream.RST_CANCEL);
+        return true;
+      } else {
+        // If stream is null, it either means that writeRequestHeaders wasn't called
+        // or that SpdyConnection#newStream threw an IOEXception. In both cases there's
+        // nothing to do here and this stream can't be reused.
+        return false;
+      }
+    }
+    return true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/Transport.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/Transport.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/Transport.java
new file mode 100644
index 0000000..518827e
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/Transport.java
@@ -0,0 +1,64 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+
+interface Transport {
+  /**
+   * Returns an output stream where the request body can be written. The
+   * returned stream will of one of two types:
+   * <ul>
+   * <li><strong>Direct.</strong> Bytes are written to the socket and
+   * forgotten. This is most efficient, particularly for large request
+   * bodies. The returned stream may be buffered; the caller must call
+   * {@link #flushRequest} before reading the response.</li>
+   * <li><strong>Buffered.</strong> Bytes are written to an in memory
+   * buffer, and must be explicitly flushed with a call to {@link
+   * #writeRequestBody}. This allows HTTP authorization (401, 407)
+   * responses to be retransmitted transparently.</li>
+   * </ul>
+   */
+  // TODO: don't bother retransmitting the request body? It's quite a corner
+  // case and there's uncertainty whether Firefox or Chrome do this
+  OutputStream createRequestBody() throws IOException;
+
+  /** This should update the HTTP engine's sentRequestMillis field. */
+  void writeRequestHeaders() throws IOException;
+
+  /**
+   * Sends the request body returned by {@link #createRequestBody} to the
+   * remote peer.
+   */
+  void writeRequestBody(RetryableOutputStream requestBody) throws IOException;
+
+  /** Flush the request body to the underlying socket. */
+  void flushRequest() throws IOException;
+
+  /** Read response headers and update the cookie manager. */
+  ResponseHeaders readResponseHeaders() throws IOException;
+
+  // TODO: make this the content stream?
+  InputStream getTransferStream(CacheRequest cacheRequest) throws IOException;
+
+  /** Returns true if the underlying connection can be recycled. */
+  boolean makeReusable(boolean streamReusable, OutputStream requestBodyOut,
+      InputStream responseBodyIn);
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
new file mode 100644
index 0000000..729e0b9
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
@@ -0,0 +1,63 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.CacheRequest;
+
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
+
+/** An HTTP message body terminated by the end of the underlying stream. */
+final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
+  private boolean inputExhausted;
+
+  UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine)
+      throws IOException {
+    super(is, httpEngine, cacheRequest);
+  }
+
+  @Override public int read(byte[] buffer, int offset, int count) throws IOException {
+    checkOffsetAndCount(buffer.length, offset, count);
+    checkNotClosed();
+    if (in == null || inputExhausted) {
+      return -1;
+    }
+    int read = in.read(buffer, offset, count);
+    if (read == -1) {
+      inputExhausted = true;
+      endOfInput(false);
+      return -1;
+    }
+    cacheWrite(buffer, offset, read);
+    return read;
+  }
+
+  @Override public int available() throws IOException {
+    checkNotClosed();
+    return in == null ? 0 : in.available();
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) {
+      return;
+    }
+    closed = true;
+    if (!inputExhausted) {
+      unexpectedEndOfInput();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
new file mode 100644
index 0000000..875fff0
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.spdy;
+
+import java.io.IOException;
+
+/** Listener to be notified when a connected peer creates a new stream. */
+public interface IncomingStreamHandler {
+  IncomingStreamHandler REFUSE_INCOMING_STREAMS = new IncomingStreamHandler() {
+    @Override public void receive(SpdyStream stream) throws IOException {
+      stream.close(SpdyStream.RST_REFUSED_STREAM);
+    }
+  };
+
+  /**
+   * Handle a new stream from this connection's peer. Implementations should
+   * respond by either {@link SpdyStream#reply replying to the stream} or
+   * {@link SpdyStream#close closing it}. This response does not need to be
+   * synchronous.
+   */
+  void receive(SpdyStream stream) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Ping.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Ping.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Ping.java
new file mode 100644
index 0000000..c585255
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Ping.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.spdy;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A locally-originated ping.
+ */
+public final class Ping {
+  private final CountDownLatch latch = new CountDownLatch(1);
+  private long sent = -1;
+  private long received = -1;
+
+  Ping() {
+  }
+
+  void send() {
+    if (sent != -1) throw new IllegalStateException();
+    sent = System.nanoTime();
+  }
+
+  void receive() {
+    if (received != -1 || sent == -1) throw new IllegalStateException();
+    received = System.nanoTime();
+    latch.countDown();
+  }
+
+  void cancel() {
+    if (received != -1 || sent == -1) throw new IllegalStateException();
+    received = sent - 1;
+    latch.countDown();
+  }
+
+  /**
+   * Returns the round trip time for this ping in nanoseconds, waiting for the
+   * response to arrive if necessary. Returns -1 if the response was
+   * cancelled.
+   */
+  public long roundTripTime() throws InterruptedException {
+    latch.await();
+    return received - sent;
+  }
+
+  /**
+   * Returns the round trip time for this ping in nanoseconds, or -1 if the
+   * response was cancelled, or -2 if the timeout elapsed before the round
+   * trip completed.
+   */
+  public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException {
+    if (latch.await(timeout, unit)) {
+      return received - sent;
+    } else {
+      return -2;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Settings.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Settings.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Settings.java
new file mode 100644
index 0000000..774d791
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/spdy/Settings.java
@@ -0,0 +1,174 @@
+/*
+ * 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.internal.spdy;
+
+final class Settings {
+  /**
+   * From the spdy/3 spec, the default initial window size for all streams is
+   * 64 KiB. (Chrome 25 uses 10 MiB).
+   */
+  static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
+
+  /** Peer request to clear durable settings. */
+  static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
+
+  /** Sent by servers only. The peer requests this setting persisted for future connections. */
+  static final int PERSIST_VALUE = 0x1;
+  /** Sent by clients only. The client is reminding the server of a persisted value. */
+  static final int PERSISTED = 0x2;
+
+  /** Sender's estimate of max incoming kbps. */
+  static final int UPLOAD_BANDWIDTH = 0x1;
+  /** Sender's estimate of max outgoing kbps. */
+  static final int DOWNLOAD_BANDWIDTH = 0x2;
+  /** Sender's estimate of milliseconds between sending a request and receiving a response. */
+  static final int ROUND_TRIP_TIME = 0x3;
+  /** Sender's maximum number of concurrent streams. */
+  static final int MAX_CONCURRENT_STREAMS = 0x4;
+  /** Current CWND in Packets. */
+  static final int CURRENT_CWND = 0x5;
+  /** Retransmission rate. Percentage */
+  static final int DOWNLOAD_RETRANS_RATE = 0x6;
+  /** Window size in bytes. */
+  static final int INITIAL_WINDOW_SIZE = 0x7;
+  /** Window size in bytes. */
+  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 0x8;
+  /** Total number of settings. */
+  static final int COUNT = 0x9;
+
+  /** Bitfield of which flags that values. */
+  private int set;
+
+  /** Bitfield of flags that have {@link #PERSIST_VALUE}. */
+  private int persistValue;
+
+  /** Bitfield of flags that have {@link #PERSISTED}. */
+  private int persisted;
+
+  /** Flag values. */
+  private final int[] values = new int[COUNT];
+
+  void set(int id, int idFlags, int value) {
+    if (id >= values.length) {
+      return; // Discard unknown settings.
+    }
+
+    int bit = 1 << id;
+    set |= bit;
+    if ((idFlags & PERSIST_VALUE) != 0) {
+      persistValue |= bit;
+    } else {
+      persistValue &= ~bit;
+    }
+    if ((idFlags & PERSISTED) != 0) {
+      persisted |= bit;
+    } else {
+      persisted &= ~bit;
+    }
+
+    values[id] = value;
+  }
+
+  /** Returns true if a value has been assigned for the setting {@code id}. */
+  boolean isSet(int id) {
+    int bit = 1 << id;
+    return (set & bit) != 0;
+  }
+
+  /** Returns the value for the setting {@code id}, or 0 if unset. */
+  int get(int id) {
+    return values[id];
+  }
+
+  /** Returns the flags for the setting {@code id}, or 0 if unset. */
+  int flags(int id) {
+    int result = 0;
+    if (isPersisted(id)) result |= Settings.PERSISTED;
+    if (persistValue(id)) result |= Settings.PERSIST_VALUE;
+    return result;
+  }
+
+  /** Returns the number of settings that have values assigned. */
+  int size() {
+    return Integer.bitCount(set);
+  }
+
+  int getUploadBandwidth(int defaultValue) {
+    int bit = 1 << UPLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  int getDownloadBandwidth(int defaultValue) {
+    int bit = 1 << DOWNLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  int getRoundTripTime(int defaultValue) {
+    int bit = 1 << ROUND_TRIP_TIME;
+    return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
+  }
+
+  int getMaxConcurrentStreams(int defaultValue) {
+    int bit = 1 << MAX_CONCURRENT_STREAMS;
+    return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
+  }
+
+  int getCurrentCwnd(int defaultValue) {
+    int bit = 1 << CURRENT_CWND;
+    return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
+  }
+
+  int getDownloadRetransRate(int defaultValue) {
+    int bit = 1 << DOWNLOAD_RETRANS_RATE;
+    return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
+  }
+
+  int getInitialWindowSize(int defaultValue) {
+    int bit = 1 << INITIAL_WINDOW_SIZE;
+    return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
+  }
+
+  int getClientCertificateVectorSize(int defaultValue) {
+    int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
+    return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
+  }
+
+  /**
+   * Returns true if this user agent should use this setting in future SPDY
+   * connections to the same host.
+   */
+  boolean persistValue(int id) {
+    int bit = 1 << id;
+    return (persistValue & bit) != 0;
+  }
+
+  /** Returns true if this setting was persisted. */
+  boolean isPersisted(int id) {
+    int bit = 1 << id;
+    return (persisted & bit) != 0;
+  }
+
+  /**
+   * Writes {@code other} into this. If any setting is populated by this and
+   * {@code other}, the value and flags from {@code other} will be kept.
+   */
+  void merge(Settings other) {
+    for (int i = 0; i < COUNT; i++) {
+      if (!other.isSet(i)) continue;
+      set(i, other.flags(i), other.get(i));
+    }
+  }
+}