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:57 UTC
[36/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/HttpResponseCache.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/HttpResponseCache.java b/lib/cordova-android/framework/src/com/squareup/okhttp/HttpResponseCache.java
new file mode 100644
index 0000000..a6d380a
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/HttpResponseCache.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.Base64;
+import com.squareup.okhttp.internal.DiskLruCache;
+import com.squareup.okhttp.internal.StrictLineReader;
+import com.squareup.okhttp.internal.Util;
+import com.squareup.okhttp.internal.http.HttpEngine;
+import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
+import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
+import com.squareup.okhttp.internal.http.OkResponseCache;
+import com.squareup.okhttp.internal.http.RawHeaders;
+import com.squareup.okhttp.internal.http.ResponseHeaders;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+import static com.squareup.okhttp.internal.Util.US_ASCII;
+import static com.squareup.okhttp.internal.Util.UTF_8;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
+ * saving time and bandwidth.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ * <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
+ * of HTTP requests issued since this cache was created.
+ * <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
+ * number of those requests that required network use.
+ * <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
+ * those requests whose responses were served by the cache.
+ * </ul>
+ * Sometimes a request will result in a conditional cache hit. If the cache
+ * contains a stale copy of the response, the client will issue a conditional
+ * {@code GET}. The server will then send either the updated response if it has
+ * changed, or a short 'not modified' response if the client's copy is still
+ * valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web
+ * server to return cacheable responses. Although this client honors all <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
+ * headers, it doesn't cache partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be
+ * necessary to skip the cache, and fetch data directly from the server. To force
+ * a full refresh, add the {@code no-cache} directive: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "no-cache");
+ * }</pre>
+ * If it is only necessary to force a cached response to be validated by the
+ * server, use the more efficient {@code max-age=0} instead: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately,
+ * but not otherwise. This can be used so your application can show
+ * <i>something</i> while waiting for the latest data to be downloaded. To
+ * restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre> {@code
+ * try {
+ * connection.addRequestProperty("Cache-Control", "only-if-cached");
+ * InputStream cached = connection.getInputStream();
+ * // the resource was cached! show it
+ * } catch (FileNotFoundException e) {
+ * // the resource was not cached
+ * }
+ * }</pre>
+ * This technique works even better in situations where a stale response is
+ * better than no response. To permit stale cached responses, use the {@code
+ * max-stale} directive with the maximum staleness in seconds: <pre> {@code
+ * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }</pre>
+ */
+public final class HttpResponseCache extends ResponseCache {
+ private static final char[] DIGITS =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ // TODO: add APIs to iterate the cache?
+ private static final int VERSION = 201105;
+ private static final int ENTRY_METADATA = 0;
+ private static final int ENTRY_BODY = 1;
+ private static final int ENTRY_COUNT = 2;
+
+ private final DiskLruCache cache;
+
+ /* read and write statistics, all guarded by 'this' */
+ private int writeSuccessCount;
+ private int writeAbortCount;
+ private int networkCount;
+ private int hitCount;
+ private int requestCount;
+
+ /**
+ * Although this class only exposes the limited ResponseCache API, it
+ * implements the full OkResponseCache interface. This field is used as a
+ * package private handle to the complete implementation. It delegates to
+ * public and private members of this type.
+ */
+ final OkResponseCache okResponseCache = new OkResponseCache() {
+ @Override public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ return HttpResponseCache.this.get(uri, requestMethod, requestHeaders);
+ }
+
+ @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+ return HttpResponseCache.this.put(uri, connection);
+ }
+
+ @Override public void update(
+ CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException {
+ HttpResponseCache.this.update(conditionalCacheHit, connection);
+ }
+
+ @Override public void trackConditionalCacheHit() {
+ HttpResponseCache.this.trackConditionalCacheHit();
+ }
+
+ @Override public void trackResponse(ResponseSource source) {
+ HttpResponseCache.this.trackResponse(source);
+ }
+ };
+
+ public HttpResponseCache(File directory, long maxSize) throws IOException {
+ cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
+ }
+
+ private String uriToKey(URI uri) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
+ return bytesToHexString(md5bytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static String bytesToHexString(byte[] bytes) {
+ char[] digits = DIGITS;
+ char[] buf = new char[bytes.length * 2];
+ int c = 0;
+ for (byte b : bytes) {
+ buf[c++] = digits[(b >> 4) & 0xf];
+ buf[c++] = digits[b & 0xf];
+ }
+ return new String(buf);
+ }
+
+ @Override public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) {
+ String key = uriToKey(uri);
+ DiskLruCache.Snapshot snapshot;
+ Entry entry;
+ try {
+ snapshot = cache.get(key);
+ if (snapshot == null) {
+ return null;
+ }
+ entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
+ } catch (IOException e) {
+ // Give up because the cache cannot be read.
+ return null;
+ }
+
+ if (!entry.matches(uri, requestMethod, requestHeaders)) {
+ snapshot.close();
+ return null;
+ }
+
+ return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot)
+ : new EntryCacheResponse(entry, snapshot);
+ }
+
+ @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+ if (!(urlConnection instanceof HttpURLConnection)) {
+ return null;
+ }
+
+ HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
+ String requestMethod = httpConnection.getRequestMethod();
+ String key = uriToKey(uri);
+
+ if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
+ "DELETE")) {
+ try {
+ cache.remove(key);
+ } catch (IOException ignored) {
+ // The cache cannot be written.
+ }
+ return null;
+ } else if (!requestMethod.equals("GET")) {
+ // Don't cache non-GET responses. We're technically allowed to cache
+ // HEAD requests and some POST requests, but the complexity of doing
+ // so is high and the benefit is low.
+ return null;
+ }
+
+ HttpEngine httpEngine = getHttpEngine(httpConnection);
+ if (httpEngine == null) {
+ // Don't cache unless the HTTP implementation is ours.
+ return null;
+ }
+
+ ResponseHeaders response = httpEngine.getResponseHeaders();
+ if (response.hasVaryAll()) {
+ return null;
+ }
+
+ RawHeaders varyHeaders =
+ httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
+ Entry entry = new Entry(uri, varyHeaders, httpConnection);
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = cache.edit(key);
+ if (editor == null) {
+ return null;
+ }
+ entry.writeTo(editor);
+ return new CacheRequestImpl(editor);
+ } catch (IOException e) {
+ abortQuietly(editor);
+ return null;
+ }
+ }
+
+ private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
+ throws IOException {
+ HttpEngine httpEngine = getHttpEngine(httpConnection);
+ URI uri = httpEngine.getUri();
+ ResponseHeaders response = httpEngine.getResponseHeaders();
+ RawHeaders varyHeaders =
+ httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
+ Entry entry = new Entry(uri, varyHeaders, httpConnection);
+ DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse)
+ ? ((EntryCacheResponse) conditionalCacheHit).snapshot
+ : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot;
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = snapshot.edit(); // returns null if snapshot is not current
+ if (editor != null) {
+ entry.writeTo(editor);
+ editor.commit();
+ }
+ } catch (IOException e) {
+ abortQuietly(editor);
+ }
+ }
+
+ private void abortQuietly(DiskLruCache.Editor editor) {
+ // Give up because the cache cannot be written.
+ try {
+ if (editor != null) {
+ editor.abort();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ private HttpEngine getHttpEngine(URLConnection httpConnection) {
+ if (httpConnection instanceof HttpURLConnectionImpl) {
+ return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
+ } else if (httpConnection instanceof HttpsURLConnectionImpl) {
+ return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ cache.delete();
+ }
+
+ public synchronized int getWriteAbortCount() {
+ return writeAbortCount;
+ }
+
+ public synchronized int getWriteSuccessCount() {
+ return writeSuccessCount;
+ }
+
+ private synchronized void trackResponse(ResponseSource source) {
+ requestCount++;
+
+ switch (source) {
+ case CACHE:
+ hitCount++;
+ break;
+ case CONDITIONAL_CACHE:
+ case NETWORK:
+ networkCount++;
+ break;
+ }
+ }
+
+ private synchronized void trackConditionalCacheHit() {
+ hitCount++;
+ }
+
+ public synchronized int getNetworkCount() {
+ return networkCount;
+ }
+
+ public synchronized int getHitCount() {
+ return hitCount;
+ }
+
+ public synchronized int getRequestCount() {
+ return requestCount;
+ }
+
+ private final class CacheRequestImpl extends CacheRequest {
+ private final DiskLruCache.Editor editor;
+ private OutputStream cacheOut;
+ private boolean done;
+ private OutputStream body;
+
+ public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
+ this.editor = editor;
+ this.cacheOut = editor.newOutputStream(ENTRY_BODY);
+ this.body = new FilterOutputStream(cacheOut) {
+ @Override public void close() throws IOException {
+ synchronized (HttpResponseCache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeSuccessCount++;
+ }
+ super.close();
+ editor.commit();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ // Since we don't override "write(int oneByte)", we can write directly to "out"
+ // and avoid the inefficient implementation from the FilterOutputStream.
+ out.write(buffer, offset, length);
+ }
+ };
+ }
+
+ @Override public void abort() {
+ synchronized (HttpResponseCache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeAbortCount++;
+ }
+ Util.closeQuietly(cacheOut);
+ try {
+ editor.abort();
+ } catch (IOException ignored) {
+ }
+ }
+
+ @Override public OutputStream getBody() throws IOException {
+ return body;
+ }
+ }
+
+ private static final class Entry {
+ private final String uri;
+ private final RawHeaders varyHeaders;
+ private final String requestMethod;
+ private final RawHeaders responseHeaders;
+ private final String cipherSuite;
+ private final Certificate[] peerCertificates;
+ private final Certificate[] localCertificates;
+
+ /**
+ * Reads an entry from an input stream. A typical entry looks like this:
+ * <pre>{@code
+ * http://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ * }</pre>
+ *
+ * <p>A typical HTTPS file looks like this:
+ * <pre>{@code
+ * https://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ *
+ * AES_256_WITH_MD5
+ * 2
+ * base64-encoded peerCertificate[0]
+ * base64-encoded peerCertificate[1]
+ * -1
+ * }</pre>
+ * The file is newline separated. The first two lines are the URL and
+ * the request method. Next is the number of HTTP Vary request header
+ * lines, followed by those lines.
+ *
+ * <p>Next is the response status line, followed by the number of HTTP
+ * response header lines, followed by those lines.
+ *
+ * <p>HTTPS responses also contain SSL session information. This begins
+ * with a blank line, and then a line containing the cipher suite. Next
+ * is the length of the peer certificate chain. These certificates are
+ * base64-encoded and appear each on their own line. The next line
+ * contains the length of the local certificate chain. These
+ * certificates are also base64-encoded and appear each on their own
+ * line. A length of -1 is used to encode a null array.
+ */
+ public Entry(InputStream in) throws IOException {
+ try {
+ StrictLineReader reader = new StrictLineReader(in, US_ASCII);
+ uri = reader.readLine();
+ requestMethod = reader.readLine();
+ varyHeaders = new RawHeaders();
+ int varyRequestHeaderLineCount = reader.readInt();
+ for (int i = 0; i < varyRequestHeaderLineCount; i++) {
+ varyHeaders.addLine(reader.readLine());
+ }
+
+ responseHeaders = new RawHeaders();
+ responseHeaders.setStatusLine(reader.readLine());
+ int responseHeaderLineCount = reader.readInt();
+ for (int i = 0; i < responseHeaderLineCount; i++) {
+ responseHeaders.addLine(reader.readLine());
+ }
+
+ if (isHttps()) {
+ String blank = reader.readLine();
+ if (blank.length() > 0) {
+ throw new IOException("expected \"\" but was \"" + blank + "\"");
+ }
+ cipherSuite = reader.readLine();
+ peerCertificates = readCertArray(reader);
+ localCertificates = readCertArray(reader);
+ } else {
+ cipherSuite = null;
+ peerCertificates = null;
+ localCertificates = null;
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection)
+ throws IOException {
+ this.uri = uri.toString();
+ this.varyHeaders = varyHeaders;
+ this.requestMethod = httpConnection.getRequestMethod();
+ this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
+
+ if (isHttps()) {
+ HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
+ cipherSuite = httpsConnection.getCipherSuite();
+ Certificate[] peerCertificatesNonFinal = null;
+ try {
+ peerCertificatesNonFinal = httpsConnection.getServerCertificates();
+ } catch (SSLPeerUnverifiedException ignored) {
+ }
+ peerCertificates = peerCertificatesNonFinal;
+ localCertificates = httpsConnection.getLocalCertificates();
+ } else {
+ cipherSuite = null;
+ peerCertificates = null;
+ localCertificates = null;
+ }
+ }
+
+ public void writeTo(DiskLruCache.Editor editor) throws IOException {
+ OutputStream out = editor.newOutputStream(ENTRY_METADATA);
+ Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
+
+ writer.write(uri + '\n');
+ writer.write(requestMethod + '\n');
+ writer.write(Integer.toString(varyHeaders.length()) + '\n');
+ for (int i = 0; i < varyHeaders.length(); i++) {
+ writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n');
+ }
+
+ writer.write(responseHeaders.getStatusLine() + '\n');
+ writer.write(Integer.toString(responseHeaders.length()) + '\n');
+ for (int i = 0; i < responseHeaders.length(); i++) {
+ writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n');
+ }
+
+ if (isHttps()) {
+ writer.write('\n');
+ writer.write(cipherSuite + '\n');
+ writeCertArray(writer, peerCertificates);
+ writeCertArray(writer, localCertificates);
+ }
+ writer.close();
+ }
+
+ private boolean isHttps() {
+ return uri.startsWith("https://");
+ }
+
+ private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
+ int length = reader.readInt();
+ if (length == -1) {
+ return null;
+ }
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ Certificate[] result = new Certificate[length];
+ for (int i = 0; i < result.length; i++) {
+ String line = reader.readLine();
+ byte[] bytes = Base64.decode(line.getBytes("US-ASCII"));
+ result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ }
+ return result;
+ } catch (CertificateException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
+ if (certificates == null) {
+ writer.write("-1\n");
+ return;
+ }
+ try {
+ writer.write(Integer.toString(certificates.length) + '\n');
+ for (Certificate certificate : certificates) {
+ byte[] bytes = certificate.getEncoded();
+ String line = Base64.encode(bytes);
+ writer.write(line + '\n');
+ }
+ } catch (CertificateEncodingException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public boolean matches(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) {
+ return this.uri.equals(uri.toString())
+ && this.requestMethod.equals(requestMethod)
+ && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false),
+ requestHeaders);
+ }
+ }
+
+ /**
+ * Returns an input stream that reads the body of a snapshot, closing the
+ * snapshot when the stream is closed.
+ */
+ private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
+ return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
+ @Override public void close() throws IOException {
+ snapshot.close();
+ super.close();
+ }
+ };
+ }
+
+ static class EntryCacheResponse extends CacheResponse {
+ private final Entry entry;
+ private final DiskLruCache.Snapshot snapshot;
+ private final InputStream in;
+
+ public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
+ this.entry = entry;
+ this.snapshot = snapshot;
+ this.in = newBodyInputStream(snapshot);
+ }
+
+ @Override public Map<String, List<String>> getHeaders() {
+ return entry.responseHeaders.toMultimap(true);
+ }
+
+ @Override public InputStream getBody() {
+ return in;
+ }
+ }
+
+ static class EntrySecureCacheResponse extends SecureCacheResponse {
+ private final Entry entry;
+ private final DiskLruCache.Snapshot snapshot;
+ private final InputStream in;
+
+ public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
+ this.entry = entry;
+ this.snapshot = snapshot;
+ this.in = newBodyInputStream(snapshot);
+ }
+
+ @Override public Map<String, List<String>> getHeaders() {
+ return entry.responseHeaders.toMultimap(true);
+ }
+
+ @Override public InputStream getBody() {
+ return in;
+ }
+
+ @Override public String getCipherSuite() {
+ return entry.cipherSuite;
+ }
+
+ @Override public List<Certificate> getServerCertificateChain()
+ throws SSLPeerUnverifiedException {
+ if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
+ throw new SSLPeerUnverifiedException(null);
+ }
+ return Arrays.asList(entry.peerCertificates.clone());
+ }
+
+ @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
+ throw new SSLPeerUnverifiedException(null);
+ }
+ return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
+ }
+
+ @Override public List<Certificate> getLocalCertificateChain() {
+ if (entry.localCertificates == null || entry.localCertificates.length == 0) {
+ return null;
+ }
+ return Arrays.asList(entry.localCertificates.clone());
+ }
+
+ @Override public Principal getLocalPrincipal() {
+ if (entry.localCertificates == null || entry.localCertificates.length == 0) {
+ return null;
+ }
+ return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/OkHttpClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/OkHttpClient.java b/lib/cordova-android/framework/src/com/squareup/okhttp/OkHttpClient.java
new file mode 100644
index 0000000..7834bd6
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/OkHttpClient.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
+import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
+import com.squareup.okhttp.internal.http.OkResponseCache;
+import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ResponseCache;
+import java.net.URL;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+
+/** Configures and creates HTTP connections. */
+public final class OkHttpClient {
+ private Proxy proxy;
+ private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
+ private ProxySelector proxySelector;
+ private CookieHandler cookieHandler;
+ private ResponseCache responseCache;
+ private SSLSocketFactory sslSocketFactory;
+ private HostnameVerifier hostnameVerifier;
+ private ConnectionPool connectionPool;
+ private boolean followProtocolRedirects = true;
+
+ /**
+ * Sets the HTTP proxy that will be used by connections created by this
+ * client. This takes precedence over {@link #setProxySelector}, which is
+ * only honored when this proxy is null (which it is by default). To disable
+ * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+ */
+ public OkHttpClient setProxy(Proxy proxy) {
+ this.proxy = proxy;
+ return this;
+ }
+
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ /**
+ * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+ * is specified explicitly. The proxy selector may return multiple proxies;
+ * in that case they will be tried in sequence until a successful connection
+ * is established.
+ *
+ * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
+ * proxy selector will be used.
+ */
+ public OkHttpClient setProxySelector(ProxySelector proxySelector) {
+ this.proxySelector = proxySelector;
+ return this;
+ }
+
+ public ProxySelector getProxySelector() {
+ return proxySelector;
+ }
+
+ /**
+ * Sets the cookie handler to be used to read outgoing cookies and write
+ * incoming cookies.
+ *
+ * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
+ * cookie handler will be used.
+ */
+ public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+ this.cookieHandler = cookieHandler;
+ return this;
+ }
+
+ public CookieHandler getCookieHandler() {
+ return cookieHandler;
+ }
+
+ /**
+ * Sets the response cache to be used to read and write cached responses.
+ *
+ * <p>If unset, the {@link ResponseCache#getDefault() system-wide default}
+ * response cache will be used.
+ */
+ public OkHttpClient setResponseCache(ResponseCache responseCache) {
+ this.responseCache = responseCache;
+ return this;
+ }
+
+ public ResponseCache getResponseCache() {
+ return responseCache;
+ }
+
+ private OkResponseCache okResponseCache() {
+ if (responseCache instanceof HttpResponseCache) {
+ return ((HttpResponseCache) responseCache).okResponseCache;
+ } else if (responseCache != null) {
+ return new OkResponseCacheAdapter(responseCache);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the socket factory used to secure HTTPS connections.
+ *
+ * <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
+ * system-wide default} SSL socket factory will be used.
+ */
+ public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+ this.sslSocketFactory = sslSocketFactory;
+ return this;
+ }
+
+ public SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /**
+ * Sets the verifier used to confirm that response certificates apply to
+ * requested hostnames for HTTPS connections.
+ *
+ * <p>If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier()
+ * system-wide default} hostname verifier will be used.
+ */
+ public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ return this;
+ }
+
+ public HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ /**
+ * Sets the connection pool used to recycle HTTP and HTTPS connections.
+ *
+ * <p>If unset, the {@link ConnectionPool#getDefault() system-wide
+ * default} connection pool will be used.
+ */
+ public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+ this.connectionPool = connectionPool;
+ return this;
+ }
+
+ public ConnectionPool getConnectionPool() {
+ return connectionPool;
+ }
+
+ /**
+ * Configure this client to follow redirects from HTTPS to HTTP and from HTTP
+ * to HTTPS.
+ *
+ * <p>If unset, protocol redirects will be followed. This is different than
+ * the built-in {@code HttpURLConnection}'s default.
+ */
+ public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {
+ this.followProtocolRedirects = followProtocolRedirects;
+ return this;
+ }
+
+ public boolean getFollowProtocolRedirects() {
+ return followProtocolRedirects;
+ }
+
+ public HttpURLConnection open(URL url) {
+ String protocol = url.getProtocol();
+ OkHttpClient copy = copyWithDefaults();
+ if (protocol.equals("http")) {
+ return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
+ } else if (protocol.equals("https")) {
+ return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
+ } else {
+ throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+
+ /**
+ * Returns a shallow copy of this OkHttpClient that uses the system-wide default for
+ * each field that hasn't been explicitly configured.
+ */
+ private OkHttpClient copyWithDefaults() {
+ OkHttpClient result = new OkHttpClient();
+ result.proxy = proxy;
+ result.failedRoutes = failedRoutes;
+ result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
+ result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
+ result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
+ result.sslSocketFactory = sslSocketFactory != null
+ ? sslSocketFactory
+ : HttpsURLConnection.getDefaultSSLSocketFactory();
+ result.hostnameVerifier = hostnameVerifier != null
+ ? hostnameVerifier
+ : HttpsURLConnection.getDefaultHostnameVerifier();
+ result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
+ result.followProtocolRedirects = followProtocolRedirects;
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/OkResponseCache.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/OkResponseCache.java b/lib/cordova-android/framework/src/com/squareup/okhttp/OkResponseCache.java
new file mode 100644
index 0000000..b7e3801
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/OkResponseCache.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import java.io.IOException;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+
+/**
+ * A response cache that supports statistics tracking and updating stored
+ * responses. Implementations of {@link java.net.ResponseCache} should implement
+ * this interface to receive additional support from the HTTP engine.
+ */
+public interface OkResponseCache {
+
+ /** Track an HTTP response being satisfied by {@code source}. */
+ void trackResponse(ResponseSource source);
+
+ /** Track an conditional GET that was satisfied by this cache. */
+ void trackConditionalCacheHit();
+
+ /** Updates stored HTTP headers using a hit on a conditional GET. */
+ void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
+ throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/ResponseSource.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/ResponseSource.java b/lib/cordova-android/framework/src/com/squareup/okhttp/ResponseSource.java
new file mode 100644
index 0000000..4eca172
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/ResponseSource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+/** The source of an HTTP response. */
+public enum ResponseSource {
+
+ /** The response was returned from the local cache. */
+ CACHE,
+
+ /**
+ * The response is available in the cache but must be validated with the
+ * network. The cache result will be used if it is still valid; otherwise
+ * the network's response will be used.
+ */
+ CONDITIONAL_CACHE,
+
+ /** The response was returned from the network. */
+ NETWORK;
+
+ public boolean requiresConnection() {
+ return this == CONDITIONAL_CACHE || this == NETWORK;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/Route.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/Route.java b/lib/cordova-android/framework/src/com/squareup/okhttp/Route.java
new file mode 100644
index 0000000..6968c60
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/Route.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/** Represents the route used by a connection to reach an endpoint. */
+public class Route {
+ final Address address;
+ final Proxy proxy;
+ final InetSocketAddress inetSocketAddress;
+ final boolean modernTls;
+
+ public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
+ boolean modernTls) {
+ if (address == null) throw new NullPointerException("address == null");
+ if (proxy == null) throw new NullPointerException("proxy == null");
+ if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
+ this.address = address;
+ this.proxy = proxy;
+ this.inetSocketAddress = inetSocketAddress;
+ this.modernTls = modernTls;
+ }
+
+ /** Returns the {@link Address} of this route. */
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns the {@link Proxy} of this route.
+ *
+ * <strong>Warning:</strong> This may be different than the proxy returned
+ * by {@link #getAddress}! That is the proxy that the user asked to be
+ * connected to; this returns the proxy that they were actually connected
+ * to. The two may disagree when a proxy selector selects a different proxy
+ * for a connection.
+ */
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ /** Returns the {@link InetSocketAddress} of this route. */
+ public InetSocketAddress getSocketAddress() {
+ return inetSocketAddress;
+ }
+
+ /** Returns true if this route uses modern tls. */
+ public boolean isModernTls() {
+ return modernTls;
+ }
+
+ /** Returns a copy of this route with flipped tls mode. */
+ public Route flipTlsMode() {
+ return new Route(address, proxy, inetSocketAddress, !modernTls);
+ }
+
+ @Override public boolean equals(Object obj) {
+ if (obj instanceof Route) {
+ Route other = (Route) obj;
+ return (address.equals(other.address)
+ && proxy.equals(other.proxy)
+ && inetSocketAddress.equals(other.inetSocketAddress)
+ && modernTls == other.modernTls);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + address.hashCode();
+ result = 31 * result + proxy.hashCode();
+ result = 31 * result + inetSocketAddress.hashCode();
+ result = result + (modernTls ? (31 * result) : 0);
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/TunnelRequest.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/TunnelRequest.java b/lib/cordova-android/framework/src/com/squareup/okhttp/TunnelRequest.java
new file mode 100644
index 0000000..5260b87
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/TunnelRequest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.http.RawHeaders;
+
+import static com.squareup.okhttp.internal.Util.getDefaultPort;
+
+/**
+ * Routing and authentication information sent to an HTTP proxy to create a
+ * HTTPS to an origin server. Everything in the tunnel request is sent
+ * unencrypted to the proxy server.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section
+ * 5.2</a>.
+ */
+public final class TunnelRequest {
+ final String host;
+ final int port;
+ final String userAgent;
+ final String proxyAuthorization;
+
+ /**
+ * @param host the origin server's hostname. Not null.
+ * @param port the origin server's port, like 80 or 443.
+ * @param userAgent the client's user-agent. Not null.
+ * @param proxyAuthorization proxy authorization, or null if the proxy is
+ * used without an authorization header.
+ */
+ public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) {
+ if (host == null) throw new NullPointerException("host == null");
+ if (userAgent == null) throw new NullPointerException("userAgent == null");
+ this.host = host;
+ this.port = port;
+ this.userAgent = userAgent;
+ this.proxyAuthorization = proxyAuthorization;
+ }
+
+ /**
+ * If we're creating a TLS tunnel, send only the minimum set of headers.
+ * This avoids sending potentially sensitive data like HTTP cookies to
+ * the proxy unencrypted.
+ */
+ RawHeaders getRequestHeaders() {
+ RawHeaders result = new RawHeaders();
+ result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1");
+
+ // Always set Host and User-Agent.
+ result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
+ result.set("User-Agent", userAgent);
+
+ // Copy over the Proxy-Authorization header if it exists.
+ if (proxyAuthorization != null) {
+ result.set("Proxy-Authorization", proxyAuthorization);
+ }
+
+ // Always set the Proxy-Connection to Keep-Alive for the benefit of
+ // HTTP/1.0 proxies like Squid.
+ result.set("Proxy-Connection", "Keep-Alive");
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
new file mode 100644
index 0000000..78c9691
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream for an HTTP request body.
+ *
+ * <p>Since a single socket's output stream may be used to write multiple HTTP
+ * requests to the same server, subclasses should not close the socket stream.
+ */
+public abstract class AbstractOutputStream extends OutputStream {
+ protected boolean closed;
+
+ @Override public final void write(int data) throws IOException {
+ write(new byte[] { (byte) data });
+ }
+
+ protected final void checkNotClosed() throws IOException {
+ if (closed) {
+ throw new IOException("stream closed");
+ }
+ }
+
+ /** Returns true if this stream was closed locally. */
+ public boolean isClosed() {
+ return closed;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Base64.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Base64.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Base64.java
new file mode 100644
index 0000000..79cd020
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Base64.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author Alexander Y. Kleymenov
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.UnsupportedEncodingException;
+
+import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
+
+/**
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
+ * In violation of the RFC, this encoder doesn't wrap lines at 76 columns.
+ */
+public final class Base64 {
+ private Base64() {
+ }
+
+ public static byte[] decode(byte[] in) {
+ return decode(in, in.length);
+ }
+
+ public static byte[] decode(byte[] in, int len) {
+ // approximate output length
+ int length = len / 4 * 3;
+ // return an empty array on empty or short input without padding
+ if (length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ // temporary array
+ byte[] out = new byte[length];
+ // number of padding characters ('=')
+ int pad = 0;
+ byte chr;
+ // compute the number of the padding characters
+ // and adjust the length of the input
+ for (; ; len--) {
+ chr = in[len - 1];
+ // skip the neutral characters
+ if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
+ continue;
+ }
+ if (chr == '=') {
+ pad++;
+ } else {
+ break;
+ }
+ }
+ // index in the output array
+ int outIndex = 0;
+ // index in the input array
+ int inIndex = 0;
+ // holds the value of the input character
+ int bits = 0;
+ // holds the value of the input quantum
+ int quantum = 0;
+ for (int i = 0; i < len; i++) {
+ chr = in[i];
+ // skip the neutral characters
+ if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
+ continue;
+ }
+ if ((chr >= 'A') && (chr <= 'Z')) {
+ // char ASCII value
+ // A 65 0
+ // Z 90 25 (ASCII - 65)
+ bits = chr - 65;
+ } else if ((chr >= 'a') && (chr <= 'z')) {
+ // char ASCII value
+ // a 97 26
+ // z 122 51 (ASCII - 71)
+ bits = chr - 71;
+ } else if ((chr >= '0') && (chr <= '9')) {
+ // char ASCII value
+ // 0 48 52
+ // 9 57 61 (ASCII + 4)
+ bits = chr + 4;
+ } else if (chr == '+') {
+ bits = 62;
+ } else if (chr == '/') {
+ bits = 63;
+ } else {
+ return null;
+ }
+ // append the value to the quantum
+ quantum = (quantum << 6) | (byte) bits;
+ if (inIndex % 4 == 3) {
+ // 4 characters were read, so make the output:
+ out[outIndex++] = (byte) (quantum >> 16);
+ out[outIndex++] = (byte) (quantum >> 8);
+ out[outIndex++] = (byte) quantum;
+ }
+ inIndex++;
+ }
+ if (pad > 0) {
+ // adjust the quantum value according to the padding
+ quantum = quantum << (6 * pad);
+ // make output
+ out[outIndex++] = (byte) (quantum >> 16);
+ if (pad == 1) {
+ out[outIndex++] = (byte) (quantum >> 8);
+ }
+ }
+ // create the resulting array
+ byte[] result = new byte[outIndex];
+ System.arraycopy(out, 0, result, 0, outIndex);
+ return result;
+ }
+
+ private static final byte[] MAP = new byte[] {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+ 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+ '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ public static String encode(byte[] in) {
+ int length = (in.length + 2) * 4 / 3;
+ byte[] out = new byte[length];
+ int index = 0, end = in.length - in.length % 3;
+ for (int i = 0; i < end; i += 3) {
+ out[index++] = MAP[(in[i] & 0xff) >> 2];
+ out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
+ out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
+ out[index++] = MAP[(in[i + 2] & 0x3f)];
+ }
+ switch (in.length % 3) {
+ case 1:
+ out[index++] = MAP[(in[end] & 0xff) >> 2];
+ out[index++] = MAP[(in[end] & 0x03) << 4];
+ out[index++] = '=';
+ out[index++] = '=';
+ break;
+ case 2:
+ out[index++] = MAP[(in[end] & 0xff) >> 2];
+ out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
+ out[index++] = MAP[((in[end + 1] & 0x0f) << 2)];
+ out[index++] = '=';
+ break;
+ }
+ try {
+ return new String(out, 0, index, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/DiskLruCache.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/DiskLruCache.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/DiskLruCache.java
new file mode 100644
index 0000000..f7fcb1e
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/DiskLruCache.java
@@ -0,0 +1,926 @@
+/*
+ * 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;
+
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ * <li>When an entry is being <strong>created</strong> it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ * <li>When an entry is being <strong>edited</strong>, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TEMP = "journal.tmp";
+ static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+ * DIRTY 335c4c6028171cfddfbaae1a9c313c52
+ * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+ * REMOVE 335c4c6028171cfddfbaae1a9c313c52
+ * DIRTY 1ab96a171faeeee38496d8b330771a7a
+ * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+ * READ 335c4c6028171cfddfbaae1a9c313c52
+ * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final File journalFileBackup;
+ private final int appVersion;
+ private long maxSize;
+ private final int valueCount;
+ private long size = 0;
+ private Writer journalWriter;
+ private final LinkedHashMap<String, Entry> lruEntries =
+ new LinkedHashMap<String, Entry>(0, 0.75f, true);
+ private int redundantOpCount;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /** This cache uses a single background thread to evict entries. */
+ final ThreadPoolExecutor executorService =
+ new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+ private final Callable<Void> cleanupCallable = new Callable<Void>() {
+ public Void call() throws Exception {
+ synchronized (DiskLruCache.this) {
+ if (journalWriter == null) {
+ return null; // Closed.
+ }
+ trimToSize();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ }
+ return null;
+ }
+ };
+
+ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+ this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Opens the cache in {@code directory}, creating a cache if none exists
+ * there.
+ *
+ * @param directory a writable directory
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ * @throws IOException if reading or writing the cache directory fails
+ */
+ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+ throws IOException {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // If a bkp file exists, use it instead.
+ File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+ if (backupFile.exists()) {
+ File journalFile = new File(directory, JOURNAL_FILE);
+ // If journal file also exists just delete backup file.
+ if (journalFile.exists()) {
+ backupFile.delete();
+ } else {
+ renameTo(backupFile, journalFile, false);
+ }
+ }
+
+ // Prefer to pick up where we left off.
+ DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ if (cache.journalFile.exists()) {
+ try {
+ cache.readJournal();
+ cache.processJournal();
+ cache.journalWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
+ return cache;
+ } catch (IOException journalIsCorrupt) {
+ Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
+ + journalIsCorrupt.getMessage() + ", removing");
+ cache.delete();
+ }
+ }
+
+ // Create a new empty cache.
+ directory.mkdirs();
+ cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ cache.rebuildJournal();
+ return cache;
+ }
+
+ private void readJournal() throws IOException {
+ StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
+ try {
+ String magic = reader.readLine();
+ String version = reader.readLine();
+ String appVersionString = reader.readLine();
+ String valueCountString = reader.readLine();
+ String blank = reader.readLine();
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ + valueCountString + ", " + blank + "]");
+ }
+
+ int lineCount = 0;
+ while (true) {
+ try {
+ readJournalLine(reader.readLine());
+ lineCount++;
+ } catch (EOFException endOfJournal) {
+ break;
+ }
+ }
+ redundantOpCount = lineCount - lruEntries.size();
+ } finally {
+ Util.closeQuietly(reader);
+ }
+ }
+
+ private void readJournalLine(String line) throws IOException {
+ int firstSpace = line.indexOf(' ');
+ if (firstSpace == -1) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ int keyBegin = firstSpace + 1;
+ int secondSpace = line.indexOf(' ', keyBegin);
+ final String key;
+ if (secondSpace == -1) {
+ key = line.substring(keyBegin);
+ if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+ lruEntries.remove(key);
+ return;
+ }
+ } else {
+ key = line.substring(keyBegin, secondSpace);
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+ String[] parts = line.substring(secondSpace + 1).split(" ");
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(parts);
+ } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+ entry.currentEditor = new Editor(entry);
+ } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+ // This work was already done by calling lruEntries.get().
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ deleteIfExists(journalFileTmp);
+ for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+ Entry entry = i.next();
+ if (entry.currentEditor == null) {
+ for (int t = 0; t < valueCount; t++) {
+ size += entry.lengths[t];
+ }
+ } else {
+ entry.currentEditor = null;
+ for (int t = 0; t < valueCount; t++) {
+ deleteIfExists(entry.getCleanFile(t));
+ deleteIfExists(entry.getDirtyFile(t));
+ }
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Creates a new journal that omits redundant information. This replaces the
+ * current journal if it exists.
+ */
+ private synchronized void rebuildJournal() throws IOException {
+ if (journalWriter != null) {
+ journalWriter.close();
+ }
+
+ Writer writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
+ try {
+ writer.write(MAGIC);
+ writer.write("\n");
+ writer.write(VERSION_1);
+ writer.write("\n");
+ writer.write(Integer.toString(appVersion));
+ writer.write("\n");
+ writer.write(Integer.toString(valueCount));
+ writer.write("\n");
+ writer.write("\n");
+
+ for (Entry entry : lruEntries.values()) {
+ if (entry.currentEditor != null) {
+ writer.write(DIRTY + ' ' + entry.key + '\n');
+ } else {
+ writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ }
+ }
+ } finally {
+ writer.close();
+ }
+
+ if (journalFile.exists()) {
+ renameTo(journalFile, journalFileBackup, true);
+ }
+ renameTo(journalFileTmp, journalFile, false);
+ journalFileBackup.delete();
+
+ journalWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
+ }
+
+ private static void deleteIfExists(File file) throws IOException {
+ if (file.exists() && !file.delete()) {
+ throw new IOException();
+ }
+ }
+
+ private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
+ if (deleteDestination) {
+ deleteIfExists(to);
+ }
+ if (!from.renameTo(to)) {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+ * exist is not currently readable. If a value is returned, it is moved to
+ * the head of the LRU queue.
+ */
+ public synchronized Snapshot get(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ return null;
+ }
+
+ if (!entry.readable) {
+ return null;
+ }
+
+ // Open all streams eagerly to guarantee that we see a single published
+ // snapshot. If we opened streams lazily then the streams could come
+ // from different edits.
+ InputStream[] ins = new InputStream[valueCount];
+ try {
+ for (int i = 0; i < valueCount; i++) {
+ ins[i] = new FileInputStream(entry.getCleanFile(i));
+ }
+ } catch (FileNotFoundException e) {
+ // A file must have been deleted manually!
+ for (int i = 0; i < valueCount; i++) {
+ if (ins[i] != null) {
+ Util.closeQuietly(ins[i]);
+ } else {
+ break;
+ }
+ }
+ return null;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(READ + ' ' + key + '\n');
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
+ }
+
+ /**
+ * Returns an editor for the entry named {@code key}, or null if another
+ * edit is in progress.
+ */
+ public Editor edit(String key) throws IOException {
+ return edit(key, ANY_SEQUENCE_NUMBER);
+ }
+
+ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+ || entry.sequenceNumber != expectedSequenceNumber)) {
+ return null; // Snapshot is stale.
+ }
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ } else if (entry.currentEditor != null) {
+ return null; // Another edit is in progress.
+ }
+
+ Editor editor = new Editor(entry);
+ entry.currentEditor = editor;
+
+ // Flush the journal before creating files to prevent file leaks.
+ journalWriter.write(DIRTY + ' ' + key + '\n');
+ journalWriter.flush();
+ return editor;
+ }
+
+ /** Returns the directory where this cache stores its data. */
+ public File getDirectory() {
+ return directory;
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public long getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Changes the maximum number of bytes the cache can store and queues a job
+ * to trim the existing store, if necessary.
+ */
+ public synchronized void setMaxSize(long maxSize) {
+ this.maxSize = maxSize;
+ executorService.submit(cleanupCallable);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the max size if a background
+ * deletion is pending.
+ */
+ public synchronized long size() {
+ return size;
+ }
+
+ private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+ Entry entry = editor.entry;
+ if (entry.currentEditor != editor) {
+ throw new IllegalStateException();
+ }
+
+ // If this edit is creating the entry for the first time, every index must have a value.
+ if (success && !entry.readable) {
+ for (int i = 0; i < valueCount; i++) {
+ if (!editor.written[i]) {
+ editor.abort();
+ throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+ }
+ if (!entry.getDirtyFile(i).exists()) {
+ editor.abort();
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File dirty = entry.getDirtyFile(i);
+ if (success) {
+ if (dirty.exists()) {
+ File clean = entry.getCleanFile(i);
+ dirty.renameTo(clean);
+ long oldLength = entry.lengths[i];
+ long newLength = clean.length();
+ entry.lengths[i] = newLength;
+ size = size - oldLength + newLength;
+ }
+ } else {
+ deleteIfExists(dirty);
+ }
+ }
+
+ redundantOpCount++;
+ entry.currentEditor = null;
+ if (entry.readable | success) {
+ entry.readable = true;
+ journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ if (success) {
+ entry.sequenceNumber = nextSequenceNumber++;
+ }
+ } else {
+ lruEntries.remove(entry.key);
+ journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+ }
+ journalWriter.flush();
+
+ if (size > maxSize || journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+ }
+
+ /**
+ * We only rebuild the journal when it will halve the size of the journal
+ * and eliminate at least 2000 ops.
+ */
+ private boolean journalRebuildRequired() {
+ final int redundantOpCompactThreshold = 2000;
+ return redundantOpCount >= redundantOpCompactThreshold //
+ && redundantOpCount >= lruEntries.size();
+ }
+
+ /**
+ * Drops the entry for {@code key} if it exists and can be removed. Entries
+ * actively being edited cannot be removed.
+ *
+ * @return true if an entry was removed.
+ */
+ public synchronized boolean remove(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null || entry.currentEditor != null) {
+ return false;
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File file = entry.getCleanFile(i);
+ if (!file.delete()) {
+ throw new IOException("failed to delete " + file);
+ }
+ size -= entry.lengths[i];
+ entry.lengths[i] = 0;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(REMOVE + ' ' + key + '\n');
+ lruEntries.remove(key);
+
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return true;
+ }
+
+ /** Returns true if this cache has been closed. */
+ public boolean isClosed() {
+ return journalWriter == null;
+ }
+
+ private void checkNotClosed() {
+ if (journalWriter == null) {
+ throw new IllegalStateException("cache is closed");
+ }
+ }
+
+ /** Force buffered operations to the filesystem. */
+ public synchronized void flush() throws IOException {
+ checkNotClosed();
+ trimToSize();
+ journalWriter.flush();
+ }
+
+ /** Closes this cache. Stored values will remain on the filesystem. */
+ public synchronized void close() throws IOException {
+ if (journalWriter == null) {
+ return; // Already closed.
+ }
+ for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+ if (entry.currentEditor != null) {
+ entry.currentEditor.abort();
+ }
+ }
+ trimToSize();
+ journalWriter.close();
+ journalWriter = null;
+ }
+
+ private void trimToSize() throws IOException {
+ while (size > maxSize) {
+ Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
+ remove(toEvict.getKey());
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ close();
+ Util.deleteContents(directory);
+ }
+
+ private void validateKey(String key) {
+ Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
+ }
+ }
+
+ private static String inputStreamToString(InputStream in) throws IOException {
+ return Util.readFully(new InputStreamReader(in, Util.UTF_8));
+ }
+
+ /** A snapshot of the values for an entry. */
+ public final class Snapshot implements Closeable {
+ private final String key;
+ private final long sequenceNumber;
+ private final InputStream[] ins;
+ private final long[] lengths;
+
+ private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
+ this.key = key;
+ this.sequenceNumber = sequenceNumber;
+ this.ins = ins;
+ this.lengths = lengths;
+ }
+
+ /**
+ * Returns an editor for this snapshot's entry, or null if either the
+ * entry has changed since this snapshot was created or if another edit
+ * is in progress.
+ */
+ public Editor edit() throws IOException {
+ return DiskLruCache.this.edit(key, sequenceNumber);
+ }
+
+ /** Returns the unbuffered stream with the value for {@code index}. */
+ public InputStream getInputStream(int index) {
+ return ins[index];
+ }
+
+ /** Returns the string value for {@code index}. */
+ public String getString(int index) throws IOException {
+ return inputStreamToString(getInputStream(index));
+ }
+
+ /** Returns the byte length of the value for {@code index}. */
+ public long getLength(int index) {
+ return lengths[index];
+ }
+
+ public void close() {
+ for (InputStream in : ins) {
+ Util.closeQuietly(in);
+ }
+ }
+ }
+
+ private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // Eat all writes silently. Nom nom.
+ }
+ };
+
+ /** Edits the values for an entry. */
+ public final class Editor {
+ private final Entry entry;
+ private final boolean[] written;
+ private boolean hasErrors;
+ private boolean committed;
+
+ private Editor(Entry entry) {
+ this.entry = entry;
+ this.written = (entry.readable) ? null : new boolean[valueCount];
+ }
+
+ /**
+ * Returns an unbuffered input stream to read the last committed value,
+ * or null if no value has been committed.
+ */
+ public InputStream newInputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ return null;
+ }
+ try {
+ return new FileInputStream(entry.getCleanFile(index));
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns the last committed value as a string, or null if no value
+ * has been committed.
+ */
+ public String getString(int index) throws IOException {
+ InputStream in = newInputStream(index);
+ return in != null ? inputStreamToString(in) : null;
+ }
+
+ /**
+ * Returns a new unbuffered output stream to write the value at
+ * {@code index}. If the underlying output stream encounters errors
+ * when writing to the filesystem, this edit will be aborted when
+ * {@link #commit} is called. The returned output stream does not throw
+ * IOExceptions.
+ */
+ public OutputStream newOutputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ written[index] = true;
+ }
+ File dirtyFile = entry.getDirtyFile(index);
+ FileOutputStream outputStream;
+ try {
+ outputStream = new FileOutputStream(dirtyFile);
+ } catch (FileNotFoundException e) {
+ // Attempt to recreate the cache directory.
+ directory.mkdirs();
+ try {
+ outputStream = new FileOutputStream(dirtyFile);
+ } catch (FileNotFoundException e2) {
+ // We are unable to recover. Silently eat the writes.
+ return NULL_OUTPUT_STREAM;
+ }
+ }
+ return new FaultHidingOutputStream(outputStream);
+ }
+ }
+
+ /** Sets the value at {@code index} to {@code value}. */
+ public void set(int index, String value) throws IOException {
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
+ writer.write(value);
+ } finally {
+ Util.closeQuietly(writer);
+ }
+ }
+
+ /**
+ * Commits this edit so it is visible to readers. This releases the
+ * edit lock so another edit may be started on the same key.
+ */
+ public void commit() throws IOException {
+ if (hasErrors) {
+ completeEdit(this, false);
+ remove(entry.key); // The previous entry is stale.
+ } else {
+ completeEdit(this, true);
+ }
+ committed = true;
+ }
+
+ /**
+ * Aborts this edit. This releases the edit lock so another edit may be
+ * started on the same key.
+ */
+ public void abort() throws IOException {
+ completeEdit(this, false);
+ }
+
+ public void abortUnlessCommitted() {
+ if (!committed) {
+ try {
+ abort();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ private class FaultHidingOutputStream extends FilterOutputStream {
+ private FaultHidingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override public void write(int oneByte) {
+ try {
+ out.write(oneByte);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void write(byte[] buffer, int offset, int length) {
+ try {
+ out.write(buffer, offset, length);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void close() {
+ try {
+ out.close();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void flush() {
+ try {
+ out.flush();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ private final class Entry {
+ private final String key;
+
+ /** Lengths of this entry's files. */
+ private final long[] lengths;
+
+ /** True if this entry has ever been published. */
+ private boolean readable;
+
+ /** The ongoing edit or null if this entry is not being edited. */
+ private Editor currentEditor;
+
+ /** The sequence number of the most recently committed edit to this entry. */
+ private long sequenceNumber;
+
+ private Entry(String key) {
+ this.key = key;
+ this.lengths = new long[valueCount];
+ }
+
+ public String getLengths() throws IOException {
+ StringBuilder result = new StringBuilder();
+ for (long size : lengths) {
+ result.append(' ').append(size);
+ }
+ return result.toString();
+ }
+
+ /** Set lengths using decimal numbers like "10123". */
+ private void setLengths(String[] strings) throws IOException {
+ if (strings.length != valueCount) {
+ throw invalidLengths(strings);
+ }
+
+ try {
+ for (int i = 0; i < strings.length; i++) {
+ lengths[i] = Long.parseLong(strings[i]);
+ }
+ } catch (NumberFormatException e) {
+ throw invalidLengths(strings);
+ }
+ }
+
+ private IOException invalidLengths(String[] strings) throws IOException {
+ throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
+ }
+
+ public File getCleanFile(int i) {
+ return new File(directory, key + "." + i);
+ }
+
+ public File getDirtyFile(int i) {
+ return new File(directory, key + "." + i + ".tmp");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/88ad654c/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Dns.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Dns.java b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Dns.java
new file mode 100644
index 0000000..69b2d37
--- /dev/null
+++ b/lib/cordova-android/framework/src/com/squareup/okhttp/internal/Dns.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Domain name service. Prefer this over {@link InetAddress#getAllByName} to
+ * make code more testable.
+ */
+public interface Dns {
+ Dns DEFAULT = new Dns() {
+ @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
+ return InetAddress.getAllByName(host);
+ }
+ };
+
+ InetAddress[] getAllByName(String host) throws UnknownHostException;
+}