cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ste...@apache.org
Subject [12/17] added fireos code from https://github.com/archananaik/cordova-amazon-fireos sans history
Date Mon, 25 Nov 2013 23:43:45 GMT
http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/DiskLruCache.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/DiskLruCache.java b/framework/src/com/squareup/okhttp/internal/DiskLruCache.java
new file mode 100644
index 0000000..f7fcb1e
--- /dev/null
+++ b/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-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/Dns.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/Dns.java b/framework/src/com/squareup/okhttp/internal/Dns.java
new file mode 100644
index 0000000..69b2d37
--- /dev/null
+++ b/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;
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java b/framework/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
new file mode 100644
index 0000000..c32b27a
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
@@ -0,0 +1,163 @@
+/*
+ * 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.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
+
+/**
+ * An output stream wrapper that recovers from failures in the underlying stream
+ * by replacing it with another stream. This class buffers a fixed amount of
+ * data under the assumption that failures occur early in a stream's life.
+ * If a failure occurs after the buffer has been exhausted, no recovery is
+ * attempted.
+ *
+ * <p>Subclasses must override {@link #replacementStream} which will request a
+ * replacement stream each time an {@link IOException} is encountered on the
+ * current stream.
+ */
+public abstract class FaultRecoveringOutputStream extends AbstractOutputStream {
+  private final int maxReplayBufferLength;
+
+  /** Bytes to transmit on the replacement stream, or null if no recovery is possible. */
+  private ByteArrayOutputStream replayBuffer;
+  private OutputStream out;
+
+  /**
+   * @param maxReplayBufferLength the maximum number of successfully written
+   *     bytes to buffer so they can be replayed in the event of an error.
+   *     Failure recoveries are not possible once this limit has been exceeded.
+   */
+  public FaultRecoveringOutputStream(int maxReplayBufferLength, OutputStream out) {
+    if (maxReplayBufferLength < 0) throw new IllegalArgumentException();
+    this.maxReplayBufferLength = maxReplayBufferLength;
+    this.replayBuffer = new ByteArrayOutputStream(maxReplayBufferLength);
+    this.out = out;
+  }
+
+  @Override public final void write(byte[] buffer, int offset, int count) throws IOException {
+    if (closed) throw new IOException("stream closed");
+    checkOffsetAndCount(buffer.length, offset, count);
+
+    while (true) {
+      try {
+        out.write(buffer, offset, count);
+
+        if (replayBuffer != null) {
+          if (count + replayBuffer.size() > maxReplayBufferLength) {
+            // Failure recovery is no longer possible once we overflow the replay buffer.
+            replayBuffer = null;
+          } else {
+            // Remember the written bytes to the replay buffer.
+            replayBuffer.write(buffer, offset, count);
+          }
+        }
+        return;
+      } catch (IOException e) {
+        if (!recover(e)) throw e;
+      }
+    }
+  }
+
+  @Override public final void flush() throws IOException {
+    if (closed) {
+      return; // don't throw; this stream might have been closed on the caller's behalf
+    }
+    while (true) {
+      try {
+        out.flush();
+        return;
+      } catch (IOException e) {
+        if (!recover(e)) throw e;
+      }
+    }
+  }
+
+  @Override public final void close() throws IOException {
+    if (closed) {
+      return;
+    }
+    while (true) {
+      try {
+        out.close();
+        closed = true;
+        return;
+      } catch (IOException e) {
+        if (!recover(e)) throw e;
+      }
+    }
+  }
+
+  /**
+   * Attempt to replace {@code out} with another equivalent stream. Returns true
+   * if a suitable replacement stream was found.
+   */
+  private boolean recover(IOException e) {
+    if (replayBuffer == null) {
+      return false; // Can't recover because we've dropped data that we would need to replay.
+    }
+
+    while (true) {
+      OutputStream replacementStream = null;
+      try {
+        replacementStream = replacementStream(e);
+        if (replacementStream == null) {
+          return false;
+        }
+        replaceStream(replacementStream);
+        return true;
+      } catch (IOException replacementStreamFailure) {
+        // The replacement was also broken. Loop to ask for another replacement.
+        Util.closeQuietly(replacementStream);
+        e = replacementStreamFailure;
+      }
+    }
+  }
+
+  /**
+   * Returns true if errors in the underlying stream can currently be recovered.
+   */
+  public boolean isRecoverable() {
+    return replayBuffer != null;
+  }
+
+  /**
+   * Replaces the current output stream with {@code replacementStream}, writing
+   * any replay bytes to it if they exist. The current output stream is closed.
+   */
+  public final void replaceStream(OutputStream replacementStream) throws IOException {
+    if (!isRecoverable()) {
+      throw new IllegalStateException();
+    }
+    if (this.out == replacementStream) {
+      return; // Don't replace a stream with itself.
+    }
+    replayBuffer.writeTo(replacementStream);
+    Util.closeQuietly(out);
+    out = replacementStream;
+  }
+
+  /**
+   * Returns a replacement output stream to recover from {@code e} thrown by the
+   * previous stream. Returns a new OutputStream if recovery was successful, in
+   * which case all previously-written data will be replayed. Returns null if
+   * the failure cannot be recovered.
+   */
+  protected abstract OutputStream replacementStream(IOException e) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/NamedRunnable.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/NamedRunnable.java b/framework/src/com/squareup/okhttp/internal/NamedRunnable.java
new file mode 100644
index 0000000..ce430b2
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/NamedRunnable.java
@@ -0,0 +1,40 @@
+/*
+ * 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.internal;
+
+/**
+ * Runnable implementation which always sets its thread name.
+ */
+public abstract class NamedRunnable implements Runnable {
+  private String name;
+
+  public NamedRunnable(String name) {
+    this.name = name;
+  }
+
+  @Override public final void run() {
+    String oldName = Thread.currentThread().getName();
+    Thread.currentThread().setName(name);
+    try {
+      execute();
+    } finally {
+      Thread.currentThread().setName(oldName);
+    }
+  }
+
+  protected abstract void execute();
+}

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

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

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

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
new file mode 100644
index 0000000..187f3b6
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.internal.Util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+
+/**
+ * An input stream for the body of an HTTP response.
+ *
+ * <p>Since a single socket's input stream may be used to read multiple HTTP
+ * responses from the same server, subclasses shouldn't close the socket stream.
+ *
+ * <p>A side effect of reading an HTTP response is that the response cache
+ * is populated. If the stream is closed early, that cache entry will be
+ * invalidated.
+ */
+abstract class AbstractHttpInputStream extends InputStream {
+  protected final InputStream in;
+  protected final HttpEngine httpEngine;
+  private final CacheRequest cacheRequest;
+  private final OutputStream cacheBody;
+  protected boolean closed;
+
+  AbstractHttpInputStream(InputStream in, HttpEngine httpEngine, CacheRequest cacheRequest)
+      throws IOException {
+    this.in = in;
+    this.httpEngine = httpEngine;
+
+    OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
+
+    // some apps return a null body; for compatibility we treat that like a null cache request
+    if (cacheBody == null) {
+      cacheRequest = null;
+    }
+
+    this.cacheBody = cacheBody;
+    this.cacheRequest = cacheRequest;
+  }
+
+  /**
+   * read() is implemented using read(byte[], int, int) so subclasses only
+   * need to override the latter.
+   */
+  @Override public final int read() throws IOException {
+    return Util.readSingleByte(this);
+  }
+
+  protected final void checkNotClosed() throws IOException {
+    if (closed) {
+      throw new IOException("stream closed");
+    }
+  }
+
+  protected final void cacheWrite(byte[] buffer, int offset, int count) throws IOException {
+    if (cacheBody != null) {
+      cacheBody.write(buffer, offset, count);
+    }
+  }
+
+  /**
+   * Closes the cache entry and makes the socket available for reuse. This
+   * should be invoked when the end of the body has been reached.
+   */
+  protected final void endOfInput(boolean streamCancelled) throws IOException {
+    if (cacheRequest != null) {
+      cacheBody.close();
+    }
+    httpEngine.release(streamCancelled);
+  }
+
+  /**
+   * Calls abort on the cache entry and disconnects the socket. This
+   * should be invoked when the connection is closed unexpectedly to
+   * invalidate the cache entry and to prevent the HTTP connection from
+   * being reused. HTTP messages are sent in serial so whenever a message
+   * cannot be read to completion, subsequent messages cannot be read
+   * either and the connection must be discarded.
+   *
+   * <p>An earlier implementation skipped the remaining bytes, but this
+   * requires that the entire transfer be completed. If the intention was
+   * to cancel the transfer, closing the connection is the only solution.
+   */
+  protected final void unexpectedEndOfInput() {
+    if (cacheRequest != null) {
+      cacheRequest.abort();
+    }
+    httpEngine.release(true);
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
new file mode 100644
index 0000000..90675b0
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.http;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream for the body of an HTTP request.
+ *
+ * <p>Since a single socket's output stream may be used to write multiple HTTP
+ * requests to the same server, subclasses should not close the socket stream.
+ */
+abstract class AbstractHttpOutputStream extends OutputStream {
+  protected boolean closed;
+
+  @Override public final void write(int data) throws IOException {
+    write(new byte[] { (byte) data });
+  }
+
+  protected final void checkNotClosed() throws IOException {
+    if (closed) {
+      throw new IOException("stream closed");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
----------------------------------------------------------------------
diff --git a/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
new file mode 100644
index 0000000..12e6409
--- /dev/null
+++ b/framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.http;
+
+final class HeaderParser {
+
+  public interface CacheControlHandler {
+    void handle(String directive, String parameter);
+  }
+
+  /** Parse a comma-separated list of cache control header values. */
+  public static void parseCacheControl(String value, CacheControlHandler handler) {
+    int pos = 0;
+    while (pos < value.length()) {
+      int tokenStart = pos;
+      pos = skipUntil(value, pos, "=,");
+      String directive = value.substring(tokenStart, pos).trim();
+
+      if (pos == value.length() || value.charAt(pos) == ',') {
+        pos++; // consume ',' (if necessary)
+        handler.handle(directive, null);
+        continue;
+      }
+
+      pos++; // consume '='
+      pos = skipWhitespace(value, pos);
+
+      String parameter;
+
+      // quoted string
+      if (pos < value.length() && value.charAt(pos) == '\"') {
+        pos++; // consume '"' open quote
+        int parameterStart = pos;
+        pos = skipUntil(value, pos, "\"");
+        parameter = value.substring(parameterStart, pos);
+        pos++; // consume '"' close quote (if necessary)
+
+        // unquoted string
+      } else {
+        int parameterStart = pos;
+        pos = skipUntil(value, pos, ",");
+        parameter = value.substring(parameterStart, pos).trim();
+      }
+
+      handler.handle(directive, parameter);
+    }
+  }
+
+  /**
+   * Returns the next index in {@code input} at or after {@code pos} that
+   * contains a character from {@code characters}. Returns the input length if
+   * none of the requested characters can be found.
+   */
+  public static int skipUntil(String input, int pos, String characters) {
+    for (; pos < input.length(); pos++) {
+      if (characters.indexOf(input.charAt(pos)) != -1) {
+        break;
+      }
+    }
+    return pos;
+  }
+
+  /**
+   * Returns the next non-whitespace character in {@code input} that is white
+   * space. Result is undefined if input contains newline characters.
+   */
+  public static int skipWhitespace(String input, int pos) {
+    for (; pos < input.length(); pos++) {
+      char c = input.charAt(pos);
+      if (c != ' ' && c != '\t') {
+        break;
+      }
+    }
+    return pos;
+  }
+
+  /**
+   * Returns {@code value} as a positive integer, or 0 if it is negative, or
+   * -1 if it cannot be parsed.
+   */
+  public static int parseSeconds(String value) {
+    try {
+      long seconds = Long.parseLong(value);
+      if (seconds > Integer.MAX_VALUE) {
+        return Integer.MAX_VALUE;
+      } else if (seconds < 0) {
+        return 0;
+      } else {
+        return (int) seconds;
+      }
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
+  private HeaderParser() {
+  }
+}


Mime
View raw message