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));
+ }
+ }
+}