Update okhttp.

Updated to commit 19a21936ffbb5e358799af9e4fb7306af45f38.

This also moves src/ to okhttp/src/ to stay faithful
to the original okhttp tree & to make it easier to pull
updates.

Change-Id: Ia1971823f31e5c6957d831f368e3a1fcce38d44d
diff --git a/Android.mk b/Android.mk
index 17681b2..ede28fa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,12 +15,15 @@
 #
 LOCAL_PATH := $(call my-dir)
 
-okhttp_common_src_files := $(call all-java-files-under,src/main/java)
+okhttp_common_src_files := $(call all-java-files-under,okhttp/src/main/java)
+okhttp_common_src_files += $(call all-java-files-under,okhttp-protocols/src/main/java)
 okhttp_system_src_files := $(filter-out %/Platform.java, $(okhttp_common_src_files))
 okhttp_system_src_files += $(call all-java-files-under, android/main/java)
 
-okhttp_test_src_files := $(call all-java-files-under,src/test/java)
-okhttp_test_src_files := $(filter-out src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java, $(okhttp_test_src_files))
+okhttp_test_src_files := $(call all-java-files-under,okhttp/src/test/java)
+okhttp_test_src_files += $(call all-java-files-under,okhttp-protocols/src/test/java)
+okhttp_test_src_files += $(call all-java-files-under,mockwebserver/src/main/java)
+okhttp_test_src_files := $(filter-out mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java, $(okhttp_test_src_files))
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := okhttp
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..57820a4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,75 @@
+Change Log
+==========
+
+Version 1.2.1 *(2013-08-23)*
+----------------------------
+
+ * Resolve issue with 'jar-with-dependencies' artifact creation.
+ * Fix: Support empty SPDY header values.
+
+
+Version 1.2.0 *(2013-08-11)*
+----------------------------
+
+ *  New APIs on OkHttpClient to set default timeouts for connect and read.
+ *  Fix bug when caching SPDY responses.
+ *  Fix a bug with SPDY plus half-closed streams. (thanks kwuollett)
+ *  Fix a bug in `Content-Length` reporting for gzipped streams in the Apache
+    HTTP client adapter. (thanks kwuollett)
+ *  Work around the Alcatel `getByInetAddress` bug (thanks k.kocel)
+ *  Be more aggressive about testing pooled sockets before reuse. (thanks
+    warpspin)
+ *  Include `Content-Type` and `Content-Encoding` in the Apache HTTP client
+    adapter. (thanks kwuollett)
+ *  Add a media type class to OkHttp.
+ *  Change custom header prefix:
+
+    ```
+    X-Android-Sent-Millis is now OkHttp-Sent-Millis
+    X-Android-Received-Millis is now OkHttp-Received-Millis
+    X-Android-Response-Source is now OkHttp-Response-Source
+    X-Android-Selected-Transport is now OkHttp-Selected-Transport
+    ```
+ *  Improve cache invalidation for POST-like requests.
+ *  Bring MockWebServer into OkHttp and teach it SPDY.
+
+
+Version 1.1.1 *(2013-06-23)*
+----------------------------
+
+ * Fix: ClassCastException when caching responses that were redirected from
+   HTTP to HTTPS.
+
+
+Version 1.1.0 *(2013-06-15)*
+----------------------------
+
+ * Fix: Connection reuse was broken for most HTTPS connections due to a bug in
+   the way the hostname verifier was selected.
+ * Fix: Locking bug in SpdyConnection.
+ * Fix: Ignore null header values (for compatibility with HttpURLConnection).
+ * Add URLStreamHandlerFactory support so that `URL.openConnection()` uses
+   OkHttp.
+ * Expose the transport ("http/1.1", "spdy/3", etc.) via magic request headers.
+   Use `X-Android-Transports` to write the preferred transports and
+   `X-Android-Selected-Transport` to read the negotiated transport.
+
+Version 1.0.2 *(2013-05-11)*
+----------------------------
+
+ * Fix: Remove use of Java 6-only APIs.
+ * Fix: Properly handle exceptions from `NetworkInterface` when querying MTU.
+ * Fix: Ensure MTU has a reasonable default and upper-bound.
+
+
+Version 1.0.1 *(2013-05-06)*
+----------------------------
+
+ * Correct casing of SSL in method names (`getSslSocketFactory`/`setSslSocketFactory`).
+
+
+Version 1.0.0 *(2013-05-06)*
+----------------------------
+
+Initial release.
+
diff --git a/README.md b/README.md
index e206f23..7b62596 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,26 @@
     ./src/test/java
 ```
 
+MockWebServer
+-------------
+
+A library for testing HTTP, HTTPS, HTTP/2.0, and SPDY clients.
+
+MockWebServer coupling with OkHttp is essential for proper testing of SPDY and HTTP/2.0 so that code can be shared.
+
+### Download
+
+Download [the latest JAR][5] or grab via Maven:
+
+```xml
+<dependency>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>mockwebserver</artifactId>
+    <version>(insert latest version)</version>
+    <scope>test</scope>
+</dependency>
+```
+
 
 License
 -------
@@ -71,6 +91,7 @@
 
 
  [1]: http://square.github.io/okhttp
- [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=okhttp&v=LATEST
+ [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=okhttp&v=LATEST&c=jar-with-dependencies
  [3]: http://wiki.eclipse.org/Jetty/Feature/NPN
  [4]: https://code.google.com/p/vogar/
+ [5]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=mockwebserver&v=LATEST&c=jar-with-dependencies
diff --git a/checkstyle.xml b/checkstyle.xml
index 2079edc..794af42 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -1,132 +1,131 @@
-<?xml version="1.0"?>
-<!DOCTYPE module PUBLIC
-    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
-    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
-
-<module name="Checker">
-    <module name="NewlineAtEndOfFile"/>
-    <module name="FileLength"/>
-    <module name="FileTabCharacter"/>
-
-    <!-- Trailing spaces -->
-    <module name="RegexpSingleline">
-        <property name="format" value="\s+$"/>
-        <property name="message" value="Line has trailing spaces."/>
-    </module>
-
-    <!-- Space after 'for' and 'if' -->
-    <module name="RegexpSingleline">
-        <property name="format" value="^\s*(for|if)\b[^ ]"/>
-        <property name="message" value="Space needed before opening parenthesis."/>
-    </module>
-
-    <!-- For each spacing -->
-    <module name="RegexpSingleline">
-        <property name="format" value="^\s*for \(.*?([^ ]:|:[^ ])"/>
-        <property name="message" value="Space needed around ':' character."/>
-    </module>
-
-    <module name="TreeWalker">
-        <property name="cacheFile" value="${checkstyle.cache.file}"/>
-
-        <!-- Checks for Javadoc comments.                     -->
-        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
-        <!--module name="JavadocMethod"/-->
-        <!--module name="JavadocType"/-->
-        <!--module name="JavadocVariable"/-->
-        <module name="JavadocStyle"/>
-
-
-        <!-- Checks for Naming Conventions.                  -->
-        <!-- See http://checkstyle.sf.net/config_naming.html -->
-        <!--<module name="ConstantName"/>-->
-        <module name="LocalFinalVariableName"/>
-        <module name="LocalVariableName"/>
-        <module name="MemberName"/>
-        <module name="MethodName"/>
-        <module name="PackageName"/>
-        <module name="ParameterName"/>
-        <module name="StaticVariableName"/>
-        <module name="TypeName"/>
-
-
-        <!-- Checks for imports                              -->
-        <!-- See http://checkstyle.sf.net/config_import.html -->
-        <module name="AvoidStarImport"/>
-        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
-        <module name="RedundantImport"/>
-        <module name="UnusedImports"/>
-
-
-        <!-- Checks for Size Violations.                    -->
-        <!-- See http://checkstyle.sf.net/config_sizes.html -->
-        <module name="LineLength">
-            <property name="max" value="100"/>
-        </module>
-        <module name="MethodLength"/>
-        <module name="ParameterNumber"/>
-
-
-        <!-- Checks for whitespace                               -->
-        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
-        <module name="GenericWhitespace"/>
-        <!--<module name="EmptyForIteratorPad"/>-->
-        <module name="MethodParamPad"/>
-        <!--<module name="NoWhitespaceAfter"/>-->
-        <!--<module name="NoWhitespaceBefore"/>-->
-        <module name="OperatorWrap"/>
-        <module name="ParenPad"/>
-        <module name="TypecastParenPad"/>
-        <module name="WhitespaceAfter"/>
-        <module name="WhitespaceAround"/>
-
-
-        <!-- Modifier Checks                                    -->
-        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
-        <module name="ModifierOrder"/>
-        <module name="RedundantModifier"/>
-
-
-        <!-- Checks for blocks. You know, those {}'s         -->
-        <!-- See http://checkstyle.sf.net/config_blocks.html -->
-        <module name="AvoidNestedBlocks"/>
-        <!--module name="EmptyBlock"/-->
-        <module name="LeftCurly"/>
-        <!--<module name="NeedBraces"/>-->
-        <module name="RightCurly"/>
-
-
-        <!-- Checks for common coding problems               -->
-        <!-- See http://checkstyle.sf.net/config_coding.html -->
-        <!--module name="AvoidInlineConditionals"/-->
-        <module name="CovariantEquals"/>
-        <module name="DoubleCheckedLocking"/>
-        <module name="EmptyStatement"/>
-        <!--<module name="EqualsAvoidNull"/>-->
-        <module name="EqualsHashCode"/>
-        <!--module name="HiddenField"/-->
-        <module name="IllegalInstantiation"/>
-        <!--module name="InnerAssignment"/-->
-        <!--module name="MagicNumber"/-->
-        <!--module name="MissingSwitchDefault"/-->
-        <module name="RedundantThrows"/>
-        <module name="SimplifyBooleanExpression"/>
-        <module name="SimplifyBooleanReturn"/>
-
-        <!-- Checks for class design                         -->
-        <!-- See http://checkstyle.sf.net/config_design.html -->
-        <!--module name="DesignForExtension"/-->
-        <!--<module name="FinalClass"/>-->
-        <module name="HideUtilityClassConstructor"/>
-        <module name="InterfaceIsType"/>
-        <!--module name="VisibilityModifier"/-->
-
-
-        <!-- Miscellaneous other checks.                   -->
-        <!-- See http://checkstyle.sf.net/config_misc.html -->
-        <module name="ArrayTypeStyle"/>
-        <!--module name="FinalParameters"/-->
-        <!--module name="TodoComment"/-->
-        <module name="UpperEll"/>
-    </module>
-</module>
+<?xml version="1.0"?>

+<!DOCTYPE module PUBLIC

+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"

+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">

+

+<module name="Checker">

+  <module name="NewlineAtEndOfFile"/>

+  <module name="FileLength"/>

+  <module name="FileTabCharacter"/>

+

+  <!-- Trailing spaces -->

+  <module name="RegexpSingleline">

+    <property name="format" value="\s+$"/>

+    <property name="message" value="Line has trailing spaces."/>

+  </module>

+

+  <!-- Space after 'for' and 'if' -->

+  <module name="RegexpSingleline">

+    <property name="format" value="^\s*(for|if)\b[^ ]"/>

+    <property name="message" value="Space needed before opening parenthesis."/>

+  </module>

+

+  <!-- For each spacing -->

+  <module name="RegexpSingleline">

+    <property name="format" value="^\s*for \(.*?([^ ]:|:[^ ])"/>

+    <property name="message" value="Space needed around ':' character."/>

+  </module>

+

+  <module name="TreeWalker">

+    <property name="cacheFile" value="${checkstyle.cache.file}"/>

+

+    <!-- Checks for Javadoc comments.                     -->

+    <!-- See http://checkstyle.sf.net/config_javadoc.html -->

+    <!--module name="JavadocMethod"/-->

+    <!--module name="JavadocType"/-->

+    <!--module name="JavadocVariable"/-->

+    <module name="JavadocStyle"/>

+

+

+    <!-- Checks for Naming Conventions.                  -->

+    <!-- See http://checkstyle.sf.net/config_naming.html -->

+    <!--<module name="ConstantName"/>-->

+    <module name="LocalFinalVariableName"/>

+    <module name="LocalVariableName"/>

+    <module name="MemberName"/>

+    <module name="MethodName"/>

+    <module name="PackageName"/>

+    <module name="ParameterName"/>

+    <module name="StaticVariableName"/>

+    <module name="TypeName"/>

+

+

+    <!-- Checks for imports                              -->

+    <!-- See http://checkstyle.sf.net/config_import.html -->

+    <module name="AvoidStarImport"/>

+    <module name="IllegalImport"/>

+    <!-- defaults to sun.* packages -->

+    <module name="RedundantImport"/>

+    <module name="UnusedImports"/>

+

+

+    <!-- Checks for Size Violations.                    -->

+    <!-- See http://checkstyle.sf.net/config_sizes.html -->

+    <module name="LineLength">

+      <property name="max" value="100"/>

+    </module>

+    <module name="MethodLength"/>

+

+

+    <!-- Checks for whitespace                               -->

+    <!-- See http://checkstyle.sf.net/config_whitespace.html -->

+    <module name="GenericWhitespace"/>

+    <!--<module name="EmptyForIteratorPad"/>-->

+    <module name="MethodParamPad"/>

+    <!--<module name="NoWhitespaceAfter"/>-->

+    <!--<module name="NoWhitespaceBefore"/>-->

+    <module name="OperatorWrap"/>

+    <module name="ParenPad"/>

+    <module name="TypecastParenPad"/>

+    <module name="WhitespaceAfter"/>

+    <module name="WhitespaceAround"/>

+

+

+    <!-- Modifier Checks                                    -->

+    <!-- See http://checkstyle.sf.net/config_modifiers.html -->

+    <module name="ModifierOrder"/>

+    <module name="RedundantModifier"/>

+

+

+    <!-- Checks for blocks. You know, those {}'s         -->

+    <!-- See http://checkstyle.sf.net/config_blocks.html -->

+    <module name="AvoidNestedBlocks"/>

+    <!--module name="EmptyBlock"/-->

+    <module name="LeftCurly"/>

+    <!--<module name="NeedBraces"/>-->

+    <module name="RightCurly"/>

+

+

+    <!-- Checks for common coding problems               -->

+    <!-- See http://checkstyle.sf.net/config_coding.html -->

+    <!--module name="AvoidInlineConditionals"/-->

+    <module name="CovariantEquals"/>

+    <module name="EmptyStatement"/>

+    <!--<module name="EqualsAvoidNull"/>-->

+    <module name="EqualsHashCode"/>

+    <!--module name="HiddenField"/-->

+    <module name="IllegalInstantiation"/>

+    <!--module name="InnerAssignment"/-->

+    <!--module name="MagicNumber"/-->

+    <!--module name="MissingSwitchDefault"/-->

+    <module name="RedundantThrows"/>

+    <module name="SimplifyBooleanExpression"/>

+    <module name="SimplifyBooleanReturn"/>

+

+    <!-- Checks for class design                         -->

+    <!-- See http://checkstyle.sf.net/config_design.html -->

+    <!--module name="DesignForExtension"/-->

+    <!--<module name="FinalClass"/>-->

+    <module name="HideUtilityClassConstructor"/>

+    <module name="InterfaceIsType"/>

+    <!--module name="VisibilityModifier"/-->

+

+

+    <!-- Miscellaneous other checks.                   -->

+    <!-- See http://checkstyle.sf.net/config_misc.html -->

+    <module name="ArrayTypeStyle"/>

+    <!--module name="FinalParameters"/-->

+    <!--module name="TodoComment"/-->

+    <module name="UpperEll"/>

+  </module>

+</module>

diff --git a/deploy_website.sh b/deploy_website.sh
new file mode 100755
index 0000000..bac2744
--- /dev/null
+++ b/deploy_website.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -ex
+
+REPO="git@github.com:square/okhttp.git"
+GROUP_ID="com.squareup.okhttp"
+ARTIFACT_ID="okhttp"
+
+DIR=temp-clone
+
+# Delete any existing temporary website clone
+rm -rf $DIR
+
+# Clone the current repo into temp folder
+git clone $REPO $DIR
+
+# Move working directory into temp folder
+cd $DIR
+
+# Checkout and track the gh-pages branch
+git checkout -t origin/gh-pages
+
+# Delete everything
+rm -rf *
+
+# Copy website files from real repo
+cp -R ../website/* .
+
+# Download the latest javadoc
+curl -L "http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=$GROUP_ID&a=$ARTIFACT_ID&v=LATEST&c=javadoc" > javadoc.zip
+mkdir javadoc
+unzip javadoc.zip -d javadoc
+rm javadoc.zip
+
+# Stage all files in git and create a commit
+git add .
+git add -u
+git commit -m "Website at $(date)"
+
+# Push the new files up to GitHub
+git push origin gh-pages
+
+# Delete our temp folder
+cd ..
+rm -rf $DIR
diff --git a/mockwebserver/pom.xml b/mockwebserver/pom.xml
new file mode 100644
index 0000000..f33f156
--- /dev/null
+++ b/mockwebserver/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mockwebserver</artifactId>
+  <name>MockWebServer</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>okhttp-protocols</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcprov-jdk15on</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.mortbay.jetty.npn</groupId>
+      <artifactId>npn-boot</artifactId>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <!-- Don't restrict test code to Java 1.5 APIs. -->
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>animal-sniffer-maven-plugin</artifactId>
+        <configuration>
+          <signature>
+            <groupId>org.codehaus.mojo.signature</groupId>
+            <artifactId>java16</artifactId>
+            <version>1.0</version>
+          </signature>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java b/mockwebserver/src/main/java/com/squareup/okhttp/internal/SslContextBuilder.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java
rename to mockwebserver/src/main/java/com/squareup/okhttp/internal/SslContextBuilder.java
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java
rename to mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/Dispatcher.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/Dispatcher.java
new file mode 100644
index 0000000..ac6bac4
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/Dispatcher.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 Google 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.mockwebserver;
+
+/** Handler for mock server requests. */
+public abstract class Dispatcher {
+  /**
+   * Returns a response to satisfy {@code request}. This method may block (for
+   * instance, to wait on a CountdownLatch).
+   */
+  public abstract MockResponse dispatch(RecordedRequest request) throws InterruptedException;
+
+  /**
+   * Returns the socket policy of the next request.  Default implementation
+   * returns {@link SocketPolicy#KEEP_OPEN}. Mischievous implementations can
+   * return other values to test HTTP edge cases.
+   */
+  public SocketPolicy peekSocketPolicy() {
+    return SocketPolicy.KEEP_OPEN;
+  }
+}
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockResponse.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockResponse.java
new file mode 100644
index 0000000..b073c11
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockResponse.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2011 Google 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.mockwebserver;
+
+import com.squareup.okhttp.internal.Util;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/** A scripted response to be replayed by the mock web server. */
+public final class MockResponse implements Cloneable {
+  private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
+
+  private String status = "HTTP/1.1 200 OK";
+  private List<String> headers = new ArrayList<String>();
+
+  /** The response body content, or null if {@code bodyStream} is set. */
+  private byte[] body;
+  /** The response body content, or null if {@code body} is set. */
+  private InputStream bodyStream;
+
+  private int bytesPerSecond = Integer.MAX_VALUE;
+  private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
+
+  /** Creates a new mock response with an empty body. */
+  public MockResponse() {
+    setBody(new byte[0]);
+  }
+
+  @Override public MockResponse clone() {
+    try {
+      MockResponse result = (MockResponse) super.clone();
+      result.headers = new ArrayList<String>(result.headers);
+      return result;
+    } catch (CloneNotSupportedException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /** Returns the HTTP response line, such as "HTTP/1.1 200 OK". */
+  public String getStatus() {
+    return status;
+  }
+
+  public MockResponse setResponseCode(int code) {
+    this.status = "HTTP/1.1 " + code + " OK";
+    return this;
+  }
+
+  public MockResponse setStatus(String status) {
+    this.status = status;
+    return this;
+  }
+
+  /** Returns the HTTP headers, such as "Content-Length: 0". */
+  public List<String> getHeaders() {
+    return headers;
+  }
+
+  /**
+   * Removes all HTTP headers including any "Content-Length" and
+   * "Transfer-encoding" headers that were added by default.
+   */
+  public MockResponse clearHeaders() {
+    headers.clear();
+    return this;
+  }
+
+  /**
+   * Adds {@code header} as an HTTP header. For well-formed HTTP {@code header}
+   * should contain a name followed by a colon and a value.
+   */
+  public MockResponse addHeader(String header) {
+    headers.add(header);
+    return this;
+  }
+
+  /**
+   * Adds a new header with the name and value. This may be used to add multiple
+   * headers with the same name.
+   */
+  public MockResponse addHeader(String name, Object value) {
+    return addHeader(name + ": " + String.valueOf(value));
+  }
+
+  /**
+   * Removes all headers named {@code name}, then adds a new header with the
+   * name and value.
+   */
+  public MockResponse setHeader(String name, Object value) {
+    removeHeader(name);
+    return addHeader(name, value);
+  }
+
+  /** Removes all headers named {@code name}. */
+  public MockResponse removeHeader(String name) {
+    name += ":";
+    for (Iterator<String> i = headers.iterator(); i.hasNext(); ) {
+      String header = i.next();
+      if (name.regionMatches(true, 0, header, 0, name.length())) {
+        i.remove();
+      }
+    }
+    return this;
+  }
+
+  /** Returns the raw HTTP payload, or null if this response is streamed. */
+  public byte[] getBody() {
+    return body;
+  }
+
+  /** Returns an input stream containing the raw HTTP payload. */
+  InputStream getBodyStream() {
+    return bodyStream != null ? bodyStream : new ByteArrayInputStream(body);
+  }
+
+  public MockResponse setBody(byte[] body) {
+    setHeader("Content-Length", body.length);
+    this.body = body;
+    this.bodyStream = null;
+    return this;
+  }
+
+  public MockResponse setBody(InputStream bodyStream, long bodyLength) {
+    setHeader("Content-Length", bodyLength);
+    this.body = null;
+    this.bodyStream = bodyStream;
+    return this;
+  }
+
+  /** Sets the response body to the UTF-8 encoded bytes of {@code body}. */
+  public MockResponse setBody(String body) {
+    try {
+      return setBody(body.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /**
+   * Sets the response body to {@code body}, chunked every {@code maxChunkSize}
+   * bytes.
+   */
+  public MockResponse setChunkedBody(byte[] body, int maxChunkSize) {
+    removeHeader("Content-Length");
+    headers.add(CHUNKED_BODY_HEADER);
+
+    try {
+      ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+      int pos = 0;
+      while (pos < body.length) {
+        int chunkSize = Math.min(body.length - pos, maxChunkSize);
+        bytesOut.write(Integer.toHexString(chunkSize).getBytes(Util.US_ASCII));
+        bytesOut.write("\r\n".getBytes(Util.US_ASCII));
+        bytesOut.write(body, pos, chunkSize);
+        bytesOut.write("\r\n".getBytes(Util.US_ASCII));
+        pos += chunkSize;
+      }
+      bytesOut.write("0\r\n\r\n".getBytes(Util.US_ASCII)); // Last chunk + empty trailer + crlf.
+
+      this.body = bytesOut.toByteArray();
+      return this;
+    } catch (IOException e) {
+      throw new AssertionError(); // In-memory I/O doesn't throw IOExceptions.
+    }
+  }
+
+  /**
+   * Sets the response body to the UTF-8 encoded bytes of {@code body}, chunked
+   * every {@code maxChunkSize} bytes.
+   */
+  public MockResponse setChunkedBody(String body, int maxChunkSize) {
+    try {
+      return setChunkedBody(body.getBytes("UTF-8"), maxChunkSize);
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  public SocketPolicy getSocketPolicy() {
+    return socketPolicy;
+  }
+
+  public MockResponse setSocketPolicy(SocketPolicy socketPolicy) {
+    this.socketPolicy = socketPolicy;
+    return this;
+  }
+
+  public int getBytesPerSecond() {
+    return bytesPerSecond;
+  }
+
+  /**
+   * Set simulated network speed, in bytes per second. This applies to the
+   * response body only; response headers are not throttled.
+   */
+  public MockResponse setBytesPerSecond(int bytesPerSecond) {
+    this.bytesPerSecond = bytesPerSecond;
+    return this;
+  }
+
+  @Override public String toString() {
+    return status;
+  }
+}
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java
new file mode 100644
index 0000000..d036d53
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ * 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.mockwebserver;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import com.squareup.okhttp.internal.spdy.IncomingStreamHandler;
+import com.squareup.okhttp.internal.spdy.SpdyConnection;
+import com.squareup.okhttp.internal.spdy.SpdyStream;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
+
+/**
+ * A scriptable web server. Callers supply canned responses and the server
+ * replays them upon request in sequence.
+ */
+public final class MockWebServer {
+  private static final byte[] NPN_PROTOCOLS = {
+      // TODO: support HTTP/2.0.
+      // 17, 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '4', '/', '2', '.', '0',
+      6, 's', 'p', 'd', 'y', '/', '3',
+      8, 'h', 't', 't', 'p', '/', '1', '.', '1'
+  };
+  private static final byte[] SPDY3 = new byte[] {
+      's', 'p', 'd', 'y', '/', '3'
+  };
+  private static final byte[] HTTP_20_DRAFT_04 = new byte[] {
+      'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '4', '/', '2', '.', '0'
+  };
+  private static final byte[] HTTP_11 = new byte[] {
+      'h', 't', 't', 'p', '/', '1', '.', '1'
+  };
+
+  private static final X509TrustManager UNTRUSTED_TRUST_MANAGER = new X509TrustManager() {
+    @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
+        throws CertificateException {
+      throw new CertificateException();
+    }
+
+    @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {
+      throw new AssertionError();
+    }
+
+    @Override public X509Certificate[] getAcceptedIssuers() {
+      throw new AssertionError();
+    }
+  };
+
+  private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
+
+  private final BlockingQueue<RecordedRequest> requestQueue =
+      new LinkedBlockingQueue<RecordedRequest>();
+
+  /** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */
+  private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>();
+  private final Map<SpdyConnection, Boolean> openSpdyConnections
+      = new ConcurrentHashMap<SpdyConnection, Boolean>();
+  private final AtomicInteger requestCount = new AtomicInteger();
+  private int bodyLimit = Integer.MAX_VALUE;
+  private ServerSocket serverSocket;
+  private SSLSocketFactory sslSocketFactory;
+  private ExecutorService executor;
+  private boolean tunnelProxy;
+  private Dispatcher dispatcher = new QueueDispatcher();
+
+  private int port = -1;
+  private boolean npnEnabled = true;
+
+  public int getPort() {
+    if (port == -1) throw new IllegalStateException("Cannot retrieve port before calling play()");
+    return port;
+  }
+
+  public String getHostName() {
+    try {
+      return InetAddress.getLocalHost().getHostName();
+    } catch (UnknownHostException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  public Proxy toProxyAddress() {
+    return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
+  }
+
+  /**
+   * Returns a URL for connecting to this server.
+   * @param path the request path, such as "/".
+   */
+  public URL getUrl(String path) {
+    try {
+      return sslSocketFactory != null
+          ? new URL("https://" + getHostName() + ":" + getPort() + path)
+          : new URL("http://" + getHostName() + ":" + getPort() + path);
+    } catch (MalformedURLException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /**
+   * Returns a cookie domain for this server. This returns the server's
+   * non-loopback host name if it is known. Otherwise this returns ".local" for
+   * this server's loopback name.
+   */
+  public String getCookieDomain() {
+    String hostName = getHostName();
+    return hostName.contains(".") ? hostName : ".local";
+  }
+
+  /**
+   * Sets the number of bytes of the POST body to keep in memory to the given
+   * limit.
+   */
+  public void setBodyLimit(int maxBodyLength) {
+    this.bodyLimit = maxBodyLength;
+  }
+
+  /**
+   * Sets whether NPN is used on incoming HTTPS connections to negotiate a
+   * transport like HTTP/1.1 or SPDY/3. Call this method to disable NPN and
+   * SPDY.
+   */
+  public void setNpnEnabled(boolean npnEnabled) {
+    this.npnEnabled = npnEnabled;
+  }
+
+  /**
+   * Serve requests with HTTPS rather than otherwise.
+   * @param tunnelProxy true to expect the HTTP CONNECT method before
+   *     negotiating TLS.
+   */
+  public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
+    this.sslSocketFactory = sslSocketFactory;
+    this.tunnelProxy = tunnelProxy;
+  }
+
+  /**
+   * Awaits the next HTTP request, removes it, and returns it. Callers should
+   * use this to verify the request was sent as intended.
+   */
+  public RecordedRequest takeRequest() throws InterruptedException {
+    return requestQueue.take();
+  }
+
+  /**
+   * Returns the number of HTTP requests received thus far by this server. This
+   * may exceed the number of HTTP connections when connection reuse is in
+   * practice.
+   */
+  public int getRequestCount() {
+    return requestCount.get();
+  }
+
+  /**
+   * Scripts {@code response} to be returned to a request made in sequence. The
+   * first request is served by the first enqueued response; the second request
+   * by the second enqueued response; and so on.
+   *
+   * @throws ClassCastException if the default dispatcher has been replaced
+   *     with {@link #setDispatcher(Dispatcher)}.
+   */
+  public void enqueue(MockResponse response) {
+    ((QueueDispatcher) dispatcher).enqueueResponse(response.clone());
+  }
+
+  /** Equivalent to {@code play(0)}. */
+  public void play() throws IOException {
+    play(0);
+  }
+
+  /**
+   * Starts the server, serves all enqueued requests, and shuts the server down.
+   *
+   * @param port the port to listen to, or 0 for any available port. Automated
+   *     tests should always use port 0 to avoid flakiness when a specific port
+   *     is unavailable.
+   */
+  public void play(int port) throws IOException {
+    if (executor != null) throw new IllegalStateException("play() already called");
+    executor = Executors.newCachedThreadPool();
+    serverSocket = new ServerSocket(port);
+    serverSocket.setReuseAddress(true);
+
+    this.port = serverSocket.getLocalPort();
+    executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
+      public void run() {
+        try {
+          acceptConnections();
+        } catch (Throwable e) {
+          logger.log(Level.WARNING, "MockWebServer connection failed", e);
+        }
+
+        // This gnarly block of code will release all sockets and all thread,
+        // even if any close fails.
+        Util.closeQuietly(serverSocket);
+        for (Iterator<Socket> s = openClientSockets.keySet().iterator(); s.hasNext(); ) {
+          Util.closeQuietly(s.next());
+          s.remove();
+        }
+        for (Iterator<SpdyConnection> s = openSpdyConnections.keySet().iterator(); s.hasNext(); ) {
+          Util.closeQuietly(s.next());
+          s.remove();
+        }
+        executor.shutdown();
+      }
+
+      private void acceptConnections() throws Exception {
+        while (true) {
+          Socket socket;
+          try {
+            socket = serverSocket.accept();
+          } catch (SocketException e) {
+            return;
+          }
+          SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
+          if (socketPolicy == DISCONNECT_AT_START) {
+            dispatchBookkeepingRequest(0, socket);
+            socket.close();
+          } else {
+            openClientSockets.put(socket, true);
+            serveConnection(socket);
+          }
+        }
+      }
+    }));
+  }
+
+  public void shutdown() throws IOException {
+    if (serverSocket != null) {
+      serverSocket.close(); // Should cause acceptConnections() to break out.
+    }
+  }
+
+  private void serveConnection(final Socket raw) {
+    String name = "MockWebServer-" + raw.getRemoteSocketAddress();
+    executor.execute(namedRunnable(name, new Runnable() {
+      int sequenceNumber = 0;
+
+      public void run() {
+        try {
+          processConnection();
+        } catch (Exception e) {
+          logger.log(Level.WARNING, "MockWebServer connection failed", e);
+        }
+      }
+
+      public void processConnection() throws Exception {
+        Transport transport = Transport.HTTP_11;
+        Socket socket;
+        if (sslSocketFactory != null) {
+          if (tunnelProxy) {
+            createTunnel();
+          }
+          SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
+          if (socketPolicy == FAIL_HANDSHAKE) {
+            dispatchBookkeepingRequest(sequenceNumber, raw);
+            processHandshakeFailure(raw);
+            return;
+          }
+          socket = sslSocketFactory.createSocket(
+              raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
+          SSLSocket sslSocket = (SSLSocket) socket;
+          sslSocket.setUseClientMode(false);
+          openClientSockets.put(socket, true);
+
+          if (npnEnabled) {
+            Platform.get().setNpnProtocols(sslSocket, NPN_PROTOCOLS);
+          }
+
+          sslSocket.startHandshake();
+
+          if (npnEnabled) {
+            byte[] selectedProtocol = Platform.get().getNpnSelectedProtocol(sslSocket);
+            if (selectedProtocol == null || Arrays.equals(selectedProtocol, HTTP_11)) {
+              transport = Transport.HTTP_11;
+            } else if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_04)) {
+              transport = Transport.HTTP_20_DRAFT_04;
+            } else if (Arrays.equals(selectedProtocol, SPDY3)) {
+              transport = Transport.SPDY_3;
+            } else {
+              throw new IllegalStateException(
+                  "Unexpected transport: " + new String(selectedProtocol, Util.US_ASCII));
+            }
+          }
+          openClientSockets.remove(raw);
+        } else {
+          socket = raw;
+        }
+
+        if (transport == Transport.SPDY_3 || transport == Transport.HTTP_20_DRAFT_04) {
+          SpdySocketHandler spdySocketHandler = new SpdySocketHandler(socket, transport);
+          SpdyConnection.Builder builder = new SpdyConnection.Builder(false, socket)
+              .handler(spdySocketHandler);
+          if (transport == Transport.SPDY_3) {
+            builder.spdy3();
+          } else {
+            builder.http20Draft04();
+          }
+          SpdyConnection spdyConnection = builder.build();
+          openSpdyConnections.put(spdyConnection, Boolean.TRUE);
+          openClientSockets.remove(socket);
+          spdyConnection.readConnectionHeader();
+          return;
+        }
+
+        InputStream in = new BufferedInputStream(socket.getInputStream());
+        OutputStream out = new BufferedOutputStream(socket.getOutputStream());
+
+        while (processOneRequest(socket, in, out)) {
+        }
+
+        if (sequenceNumber == 0) {
+          logger.warning("MockWebServer connection didn't make a request");
+        }
+
+        in.close();
+        out.close();
+        socket.close();
+        openClientSockets.remove(socket);
+      }
+
+      /**
+       * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response is
+       * dispatched.
+       */
+      private void createTunnel() throws IOException, InterruptedException {
+        while (true) {
+          SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
+          if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) {
+            throw new IllegalStateException("Tunnel without any CONNECT!");
+          }
+          if (socketPolicy == SocketPolicy.UPGRADE_TO_SSL_AT_END) return;
+        }
+      }
+
+      /**
+       * Reads a request and writes its response. Returns true if a request was
+       * processed.
+       */
+      private boolean processOneRequest(Socket socket, InputStream in, OutputStream out)
+          throws IOException, InterruptedException {
+        RecordedRequest request = readRequest(socket, in, out, sequenceNumber);
+        if (request == null) return false;
+        requestCount.incrementAndGet();
+        requestQueue.add(request);
+        MockResponse response = dispatcher.dispatch(request);
+        writeResponse(out, response);
+        if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
+          in.close();
+          out.close();
+        } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
+          socket.shutdownInput();
+        } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
+          socket.shutdownOutput();
+        }
+        logger.info("Received request: " + request + " and responded: " + response);
+        sequenceNumber++;
+        return true;
+      }
+    }));
+  }
+
+  private void processHandshakeFailure(Socket raw) throws Exception {
+    SSLContext context = SSLContext.getInstance("TLS");
+    context.init(null, new TrustManager[] { UNTRUSTED_TRUST_MANAGER }, new SecureRandom());
+    SSLSocketFactory sslSocketFactory = context.getSocketFactory();
+    SSLSocket socket =
+        (SSLSocket) sslSocketFactory.createSocket(raw, raw.getInetAddress().getHostAddress(),
+            raw.getPort(), true);
+    try {
+      socket.startHandshake(); // we're testing a handshake failure
+      throw new AssertionError();
+    } catch (IOException expected) {
+    }
+    socket.close();
+  }
+
+  private void dispatchBookkeepingRequest(int sequenceNumber, Socket socket)
+      throws InterruptedException {
+    requestCount.incrementAndGet();
+    dispatcher.dispatch(new RecordedRequest(null, null, null, -1, null, sequenceNumber, socket));
+  }
+
+  /** @param sequenceNumber the index of this request on this connection. */
+  private RecordedRequest readRequest(Socket socket, InputStream in, OutputStream out,
+      int sequenceNumber) throws IOException {
+    String request;
+    try {
+      request = readAsciiUntilCrlf(in);
+    } catch (IOException streamIsClosed) {
+      return null; // no request because we closed the stream
+    }
+    if (request.length() == 0) {
+      return null; // no request because the stream is exhausted
+    }
+
+    List<String> headers = new ArrayList<String>();
+    long contentLength = -1;
+    boolean chunked = false;
+    boolean expectContinue = false;
+    String header;
+    while ((header = readAsciiUntilCrlf(in)).length() != 0) {
+      headers.add(header);
+      String lowercaseHeader = header.toLowerCase(Locale.US);
+      if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
+        contentLength = Long.parseLong(header.substring(15).trim());
+      }
+      if (lowercaseHeader.startsWith("transfer-encoding:")
+          && lowercaseHeader.substring(18).trim().equals("chunked")) {
+        chunked = true;
+      }
+      if (lowercaseHeader.startsWith("expect:")
+          && lowercaseHeader.substring(7).trim().equals("100-continue")) {
+        expectContinue = true;
+      }
+    }
+
+    if (expectContinue) {
+      out.write(("HTTP/1.1 100 Continue\r\n").getBytes(Util.US_ASCII));
+      out.write(("Content-Length: 0\r\n").getBytes(Util.US_ASCII));
+      out.write(("\r\n").getBytes(Util.US_ASCII));
+      out.flush();
+    }
+
+    boolean hasBody = false;
+    TruncatingOutputStream requestBody = new TruncatingOutputStream();
+    List<Integer> chunkSizes = new ArrayList<Integer>();
+    if (contentLength != -1) {
+      hasBody = true;
+      transfer(contentLength, in, requestBody);
+    } else if (chunked) {
+      hasBody = true;
+      while (true) {
+        int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
+        if (chunkSize == 0) {
+          readEmptyLine(in);
+          break;
+        }
+        chunkSizes.add(chunkSize);
+        transfer(chunkSize, in, requestBody);
+        readEmptyLine(in);
+      }
+    }
+
+    if (request.startsWith("OPTIONS ")
+        || request.startsWith("GET ")
+        || request.startsWith("HEAD ")
+        || request.startsWith("DELETE ")
+        || request.startsWith("TRACE ")
+        || request.startsWith("CONNECT ")) {
+      if (hasBody) {
+        throw new IllegalArgumentException("Request must not have a body: " + request);
+      }
+    } else if (!request.startsWith("POST ") && !request.startsWith("PUT ")) {
+      throw new UnsupportedOperationException("Unexpected method: " + request);
+    }
+
+    return new RecordedRequest(request, headers, chunkSizes, requestBody.numBytesReceived,
+        requestBody.toByteArray(), sequenceNumber, socket);
+  }
+
+  private void writeResponse(OutputStream out, MockResponse response) throws IOException {
+    out.write((response.getStatus() + "\r\n").getBytes(Util.US_ASCII));
+    for (String header : response.getHeaders()) {
+      out.write((header + "\r\n").getBytes(Util.US_ASCII));
+    }
+    out.write(("\r\n").getBytes(Util.US_ASCII));
+    out.flush();
+
+    InputStream in = response.getBodyStream();
+    if (in == null) return;
+    int bytesPerSecond = response.getBytesPerSecond();
+
+    // Stream data in MTU-sized increments, with a minimum of one packet per second.
+    byte[] buffer = bytesPerSecond >= 1452 ? new byte[1452] : new byte[bytesPerSecond];
+    long delayMs = bytesPerSecond == Integer.MAX_VALUE
+        ? 0
+        : (1000 * buffer.length) / bytesPerSecond;
+
+    int read;
+    long sinceDelay = 0;
+    while ((read = in.read(buffer)) != -1) {
+      out.write(buffer, 0, read);
+      out.flush();
+
+      sinceDelay += read;
+      if (sinceDelay >= buffer.length && delayMs > 0) {
+        sinceDelay %= buffer.length;
+        try {
+          Thread.sleep(delayMs);
+        } catch (InterruptedException e) {
+          throw new AssertionError();
+        }
+      }
+    }
+  }
+
+  /**
+   * Transfer bytes from {@code in} to {@code out} until either {@code length}
+   * bytes have been transferred or {@code in} is exhausted.
+   */
+  private void transfer(long length, InputStream in, OutputStream out) throws IOException {
+    byte[] buffer = new byte[1024];
+    while (length > 0) {
+      int count = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+      if (count == -1) return;
+      out.write(buffer, 0, count);
+      length -= count;
+    }
+  }
+
+  /**
+   * Returns the text from {@code in} until the next "\r\n", or null if {@code
+   * in} is exhausted.
+   */
+  private String readAsciiUntilCrlf(InputStream in) throws IOException {
+    StringBuilder builder = new StringBuilder();
+    while (true) {
+      int c = in.read();
+      if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
+        builder.deleteCharAt(builder.length() - 1);
+        return builder.toString();
+      } else if (c == -1) {
+        return builder.toString();
+      } else {
+        builder.append((char) c);
+      }
+    }
+  }
+
+  private void readEmptyLine(InputStream in) throws IOException {
+    String line = readAsciiUntilCrlf(in);
+    if (line.length() != 0) throw new IllegalStateException("Expected empty but was: " + line);
+  }
+
+  /**
+   * Sets the dispatcher used to match incoming requests to mock responses.
+   * The default dispatcher simply serves a fixed sequence of responses from
+   * a {@link #enqueue(MockResponse) queue}; custom dispatchers can vary the
+   * response based on timing or the content of the request.
+   */
+  public void setDispatcher(Dispatcher dispatcher) {
+    if (dispatcher == null) throw new NullPointerException();
+    this.dispatcher = dispatcher;
+  }
+
+  /** An output stream that drops data after bodyLimit bytes. */
+  private class TruncatingOutputStream extends ByteArrayOutputStream {
+    private long numBytesReceived = 0;
+
+    @Override public void write(byte[] buffer, int offset, int len) {
+      numBytesReceived += len;
+      super.write(buffer, offset, Math.min(len, bodyLimit - count));
+    }
+
+    @Override public void write(int oneByte) {
+      numBytesReceived++;
+      if (count < bodyLimit) {
+        super.write(oneByte);
+      }
+    }
+  }
+
+  private static Runnable namedRunnable(final String name, final Runnable runnable) {
+    return new Runnable() {
+      public void run() {
+        String originalName = Thread.currentThread().getName();
+        Thread.currentThread().setName(name);
+        try {
+          runnable.run();
+        } finally {
+          Thread.currentThread().setName(originalName);
+        }
+      }
+    };
+  }
+
+  /** Processes HTTP requests layered over SPDY/3. */
+  private class SpdySocketHandler implements IncomingStreamHandler {
+    private final Socket socket;
+    private final Transport transport;
+    private final AtomicInteger sequenceNumber = new AtomicInteger();
+
+    private SpdySocketHandler(Socket socket, Transport transport) {
+      this.socket = socket;
+      this.transport = transport;
+    }
+
+    @Override public void receive(SpdyStream stream) throws IOException {
+      RecordedRequest request = readRequest(stream);
+      requestQueue.add(request);
+      MockResponse response;
+      try {
+        response = dispatcher.dispatch(request);
+      } catch (InterruptedException e) {
+        throw new AssertionError(e);
+      }
+      writeResponse(stream, response);
+      logger.info("Received request: " + request + " and responded: " + response
+          + " transport is " + transport);
+    }
+
+    private RecordedRequest readRequest(SpdyStream stream) throws IOException {
+      List<String> spdyHeaders = stream.getRequestHeaders();
+      List<String> httpHeaders = new ArrayList<String>();
+      String method = "<:method omitted>";
+      String path = "<:path omitted>";
+      String version = "<:version omitted>";
+      for (Iterator<String> i = spdyHeaders.iterator(); i.hasNext(); ) {
+        String name = i.next();
+        String value = i.next();
+        if (":method".equals(name)) {
+          method = value;
+        } else if (":path".equals(name)) {
+          path = value;
+        } else if (":version".equals(name)) {
+          version = value;
+        } else {
+          httpHeaders.add(name + ": " + value);
+        }
+      }
+
+      InputStream bodyIn = stream.getInputStream();
+      ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
+      byte[] buffer = new byte[8192];
+      int count;
+      while ((count = bodyIn.read(buffer)) != -1) {
+        bodyOut.write(buffer, 0, count);
+      }
+      bodyIn.close();
+      String requestLine = method + ' ' + path + ' ' + version;
+      List<Integer> chunkSizes = Collections.emptyList(); // No chunked encoding for SPDY.
+      return new RecordedRequest(requestLine, httpHeaders, chunkSizes, bodyOut.size(),
+          bodyOut.toByteArray(), sequenceNumber.getAndIncrement(), socket);
+    }
+
+    private void writeResponse(SpdyStream stream, MockResponse response) throws IOException {
+      List<String> spdyHeaders = new ArrayList<String>();
+      String[] statusParts = response.getStatus().split(" ", 2);
+      if (statusParts.length != 2) {
+        throw new AssertionError("Unexpected status: " + response.getStatus());
+      }
+      spdyHeaders.add(":status");
+      spdyHeaders.add(statusParts[1]);
+      // TODO: no ":version" header for HTTP/2.0, only SPDY.
+      spdyHeaders.add(":version");
+      spdyHeaders.add(statusParts[0]);
+      for (String header : response.getHeaders()) {
+        String[] headerParts = header.split(":", 2);
+        if (headerParts.length != 2) {
+          throw new AssertionError("Unexpected header: " + header);
+        }
+        spdyHeaders.add(headerParts[0].toLowerCase(Locale.US).trim());
+        spdyHeaders.add(headerParts[1].trim());
+      }
+      byte[] body = response.getBody();
+      stream.reply(spdyHeaders, body.length > 0);
+      if (body.length > 0) {
+        stream.getOutputStream().write(body);
+        stream.getOutputStream().close();
+      }
+    }
+  }
+
+  enum Transport {
+    HTTP_11, SPDY_3, HTTP_20_DRAFT_04
+  }
+}
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/QueueDispatcher.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/QueueDispatcher.java
new file mode 100644
index 0000000..0f0cb28
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/QueueDispatcher.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 Google 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.mockwebserver;
+
+import java.net.HttpURLConnection;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Default dispatcher that processes a script of responses. Populate the script
+ * by calling {@link #enqueueResponse(MockResponse)}.
+ */
+public class QueueDispatcher extends Dispatcher {
+  protected final BlockingQueue<MockResponse> responseQueue
+      = new LinkedBlockingQueue<MockResponse>();
+  private MockResponse failFastResponse;
+
+  @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
+    // To permit interactive/browser testing, ignore requests for favicons.
+    final String requestLine = request.getRequestLine();
+    if (requestLine != null && requestLine.equals("GET /favicon.ico HTTP/1.1")) {
+      System.out.println("served " + requestLine);
+      return new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
+    }
+
+    if (failFastResponse != null && responseQueue.peek() == null) {
+      // Fail fast if there's no response queued up.
+      return failFastResponse;
+    }
+
+    return responseQueue.take();
+  }
+
+  @Override public SocketPolicy peekSocketPolicy() {
+    MockResponse peek = responseQueue.peek();
+    if (peek == null) {
+      return failFastResponse != null
+          ? failFastResponse.getSocketPolicy()
+          : SocketPolicy.KEEP_OPEN;
+    }
+    return peek.getSocketPolicy();
+  }
+
+  public void enqueueResponse(MockResponse response) {
+    responseQueue.add(response);
+  }
+
+  public void setFailFast(boolean failFast) {
+    MockResponse failFastResponse = failFast
+        ? new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
+        : null;
+    setFailFast(failFastResponse);
+  }
+
+  public void setFailFast(MockResponse failFastResponse) {
+    this.failFastResponse = failFastResponse;
+  }
+}
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/RecordedRequest.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/RecordedRequest.java
new file mode 100644
index 0000000..aceacd1
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/RecordedRequest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 Google 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.mockwebserver;
+
+import java.io.UnsupportedEncodingException;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import javax.net.ssl.SSLSocket;
+
+/** An HTTP request that came into the mock web server. */
+public final class RecordedRequest {
+  private final String requestLine;
+  private final String method;
+  private final String path;
+  private final List<String> headers;
+  private final List<Integer> chunkSizes;
+  private final long bodySize;
+  private final byte[] body;
+  private final int sequenceNumber;
+  private final String sslProtocol;
+
+  public RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
+      long bodySize, byte[] body, int sequenceNumber, Socket socket) {
+    this.requestLine = requestLine;
+    this.headers = headers;
+    this.chunkSizes = chunkSizes;
+    this.bodySize = bodySize;
+    this.body = body;
+    this.sequenceNumber = sequenceNumber;
+    this.sslProtocol = socket instanceof SSLSocket
+        ? ((SSLSocket) socket).getSession().getProtocol()
+        : null;
+
+    if (requestLine != null) {
+      int methodEnd = requestLine.indexOf(' ');
+      int pathEnd = requestLine.indexOf(' ', methodEnd + 1);
+      this.method = requestLine.substring(0, methodEnd);
+      this.path = requestLine.substring(methodEnd + 1, pathEnd);
+    } else {
+      this.method = null;
+      this.path = null;
+    }
+  }
+
+  public String getRequestLine() {
+    return requestLine;
+  }
+
+  public String getMethod() {
+    return method;
+  }
+
+  public String getPath() {
+    return path;
+  }
+
+  /** Returns all headers. */
+  public List<String> getHeaders() {
+    return headers;
+  }
+
+  /**
+   * Returns the first header named {@code name}, or null if no such header
+   * exists.
+   */
+  public String getHeader(String name) {
+    name += ":";
+    for (String header : headers) {
+      if (name.regionMatches(true, 0, header, 0, name.length())) {
+        return header.substring(name.length()).trim();
+      }
+    }
+    return null;
+  }
+
+  /** Returns the headers named {@code name}. */
+  public List<String> getHeaders(String name) {
+    List<String> result = new ArrayList<String>();
+    name += ":";
+    for (String header : headers) {
+      if (name.regionMatches(true, 0, header, 0, name.length())) {
+        result.add(header.substring(name.length()).trim());
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the sizes of the chunks of this request's body, or an empty list
+   * if the request's body was empty or unchunked.
+   */
+  public List<Integer> getChunkSizes() {
+    return chunkSizes;
+  }
+
+  /**
+   * Returns the total size of the body of this POST request (before
+   * truncation).
+   */
+  public long getBodySize() {
+    return bodySize;
+  }
+
+  /** Returns the body of this POST request. This may be truncated. */
+  public byte[] getBody() {
+    return body;
+  }
+
+  /** Returns the body of this POST request decoded as a UTF-8 string. */
+  public String getUtf8Body() {
+    try {
+      return new String(body, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /**
+   * Returns the index of this request on its HTTP connection. Since a single
+   * HTTP connection may serve multiple requests, each request is assigned its
+   * own sequence number.
+   */
+  public int getSequenceNumber() {
+    return sequenceNumber;
+  }
+
+  /**
+   * Returns the connection's SSL protocol like {@code TLSv1}, {@code SSLv3},
+   * {@code NONE} or null if the connection doesn't use SSL.
+   */
+  public String getSslProtocol() {
+    return sslProtocol;
+  }
+
+  @Override public String toString() {
+    return requestLine;
+  }
+}
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/SocketPolicy.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/SocketPolicy.java
new file mode 100644
index 0000000..7912f3a
--- /dev/null
+++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/SocketPolicy.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 Google 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.mockwebserver;
+
+/** What should be done with the incoming socket. */
+public enum SocketPolicy {
+
+  /**
+   * Keep the socket open after the response. This is the default HTTP/1.1
+   * behavior.
+   */
+  KEEP_OPEN,
+
+  /**
+   * Close the socket after the response. This is the default HTTP/1.0
+   * behavior.
+   */
+  DISCONNECT_AT_END,
+
+  /**
+   * Wrap the socket with SSL at the completion of this request/response pair.
+   * Used for CONNECT messages to tunnel SSL over an HTTP proxy.
+   */
+  UPGRADE_TO_SSL_AT_END,
+
+  /**
+   * Request immediate close of connection without even reading the request. Use
+   * to simulate buggy SSL servers closing connections in response to
+   * unrecognized TLS extensions.
+   */
+  DISCONNECT_AT_START,
+
+  /** Don't trust the client during the SSL handshake. */
+  FAIL_HANDSHAKE,
+
+  /**
+   * Shutdown the socket input after sending the response. For testing bad
+   * behavior.
+   */
+  SHUTDOWN_INPUT_AT_END,
+
+  /**
+   * Shutdown the socket output after sending the response. For testing bad
+   * behavior.
+   */
+  SHUTDOWN_OUTPUT_AT_END
+}
diff --git a/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/CustomDispatcherTest.java b/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/CustomDispatcherTest.java
new file mode 100644
index 0000000..22e6a95
--- /dev/null
+++ b/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/CustomDispatcherTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 Google 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.mockwebserver;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CustomDispatcherTest extends TestCase {
+
+    private MockWebServer mockWebServer = new MockWebServer();
+
+    @Override
+    public void tearDown() throws Exception {
+        mockWebServer.shutdown();
+    }
+
+    public void testSimpleDispatch() throws Exception {
+        mockWebServer.play();
+        final List<RecordedRequest> requestsMade = new ArrayList<RecordedRequest>();
+        final Dispatcher dispatcher = new Dispatcher() {
+            @Override
+            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
+                requestsMade.add(request);
+                return new MockResponse();
+            }
+        };
+        assertEquals(0, requestsMade.size());
+        mockWebServer.setDispatcher(dispatcher);
+        final URL url = mockWebServer.getUrl("/");
+        final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.getResponseCode(); // Force the connection to hit the "server".
+        // Make sure our dispatcher got the request.
+        assertEquals(1, requestsMade.size());
+    }
+
+    public void testOutOfOrderResponses() throws Exception {
+        AtomicInteger firstResponseCode = new AtomicInteger();
+        AtomicInteger secondResponseCode = new AtomicInteger();
+        mockWebServer.play();
+        final String secondRequest = "/bar";
+        final String firstRequest = "/foo";
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Dispatcher dispatcher = new Dispatcher() {
+            @Override
+            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
+                if (request.getPath().equals(firstRequest)) {
+                    latch.await();
+                }
+                return new MockResponse();
+            }
+        };
+        mockWebServer.setDispatcher(dispatcher);
+        final Thread startsFirst = buildRequestThread(firstRequest, firstResponseCode);
+        startsFirst.start();
+        final Thread endsFirst = buildRequestThread(secondRequest, secondResponseCode);
+        endsFirst.start();
+        endsFirst.join();
+        assertEquals(0, firstResponseCode.get()); // First response is still waiting.
+        assertEquals(200, secondResponseCode.get()); // Second response is done.
+        latch.countDown();
+        startsFirst.join();
+        assertEquals(200, firstResponseCode.get()); // And now it's done!
+        assertEquals(200, secondResponseCode.get()); // (Still done).
+    }
+
+    private Thread buildRequestThread(final String path, final AtomicInteger responseCode) {
+        return new Thread(new Runnable() {
+            @Override public void run() {
+                final URL url = mockWebServer.getUrl(path);
+                final HttpURLConnection conn;
+                try {
+                    conn = (HttpURLConnection) url.openConnection();
+                    responseCode.set(conn.getResponseCode()); // Force the connection to hit the "server".
+                } catch (IOException e) {
+                }
+            }
+        });
+    }
+
+}
diff --git a/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/MockWebServerTest.java b/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/MockWebServerTest.java
new file mode 100644
index 0000000..98efc44
--- /dev/null
+++ b/mockwebserver/src/test/java/com/squareup/okhttp/mockwebserver/MockWebServerTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 Google 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.mockwebserver;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import junit.framework.TestCase;
+
+public final class MockWebServerTest extends TestCase {
+
+    private MockWebServer server = new MockWebServer();
+
+    @Override protected void tearDown() throws Exception {
+        server.shutdown();
+        super.tearDown();
+    }
+
+    public void testRecordedRequestAccessors() {
+        List<String> headers = Arrays.asList(
+                "User-Agent: okhttp",
+                "Cookie: s=square",
+                "Cookie: a=android",
+                "X-Whitespace:  left",
+                "X-Whitespace:right  ",
+                "X-Whitespace:  both  "
+        );
+        List<Integer> chunkSizes = Collections.emptyList();
+        byte[] body = {'A', 'B', 'C'};
+        String requestLine = "GET / HTTP/1.1";
+        RecordedRequest request = new RecordedRequest(
+                requestLine, headers, chunkSizes, body.length, body, 0, null);
+        assertEquals("s=square", request.getHeader("cookie"));
+        assertEquals(Arrays.asList("s=square", "a=android"), request.getHeaders("cookie"));
+        assertEquals("left", request.getHeader("x-whitespace"));
+        assertEquals(Arrays.asList("left", "right", "both"), request.getHeaders("x-whitespace"));
+        assertEquals("ABC", request.getUtf8Body());
+    }
+
+    public void testDefaultMockResponse() {
+        MockResponse response = new MockResponse();
+        assertEquals(Arrays.asList("Content-Length: 0"), response.getHeaders());
+        assertEquals("HTTP/1.1 200 OK", response.getStatus());
+    }
+
+    public void testSetBodyAdjustsHeaders() throws IOException {
+        MockResponse response = new MockResponse().setBody("ABC");
+        assertEquals(Arrays.asList("Content-Length: 3"), response.getHeaders());
+        InputStream in = response.getBodyStream();
+        assertEquals('A', in.read());
+        assertEquals('B', in.read());
+        assertEquals('C', in.read());
+        assertEquals(-1, in.read());
+        assertEquals("HTTP/1.1 200 OK", response.getStatus());
+    }
+
+    public void testMockResponseAddHeader() {
+        MockResponse response = new MockResponse()
+                .clearHeaders()
+                .addHeader("Cookie: s=square")
+                .addHeader("Cookie", "a=android");
+        assertEquals(Arrays.asList("Cookie: s=square", "Cookie: a=android"),
+                response.getHeaders());
+    }
+
+    public void testMockResponseSetHeader() {
+        MockResponse response = new MockResponse()
+                .clearHeaders()
+                .addHeader("Cookie: s=square")
+                .addHeader("Cookie: a=android")
+                .addHeader("Cookies: delicious");
+        response.setHeader("cookie", "r=robot");
+        assertEquals(Arrays.asList("Cookies: delicious", "cookie: r=robot"),
+                response.getHeaders());
+    }
+
+    /**
+     * Clients who adhere to <a
+     * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3">100
+     * Status</a> expect the server to send an interim response with status code
+     * 100 before they send their payload.
+     * 
+     * <h4>Note</h4>
+     * 
+     * JRE 6 only passes this test if
+     * {@code -Dsun.net.http.allowRestrictedHeaders=true} is set.
+     */
+    public void testExpect100ContinueWithBody() throws Exception {
+        server.enqueue(new MockResponse());
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("PUT");
+        connection.setAllowUserInteraction(false);
+        connection.setRequestProperty("Expect", "100-continue");
+        connection.setDoOutput(true);
+        connection.getOutputStream().write("hello".getBytes());
+        assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
+
+        assertEquals(server.getRequestCount(), 1);
+        RecordedRequest request = server.takeRequest();
+        assertEquals(request.getRequestLine(), "PUT / HTTP/1.1");
+        assertEquals("5", request.getHeader("Content-Length"));
+        assertEquals(5, request.getBodySize());
+        assertEquals("hello", new String(request.getBody()));
+        // below fails on JRE 6 unless -Dsun.net.http.allowRestrictedHeaders=true is set
+        assertEquals("100-continue", request.getHeader("Expect"));
+    }
+
+    public void testExpect100ContinueWithNoBody() throws Exception {
+        server.enqueue(new MockResponse());
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("PUT");
+        connection.setAllowUserInteraction(false);
+        connection.setRequestProperty("Expect", "100-continue");
+        connection.setRequestProperty("Content-Length", "0");
+        connection.setDoOutput(true);
+        connection.setFixedLengthStreamingMode(0);
+        assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
+
+        assertEquals(server.getRequestCount(), 1);
+        RecordedRequest request = server.takeRequest();
+        assertEquals(request.getRequestLine(), "PUT / HTTP/1.1");
+        assertEquals("0", request.getHeader("Content-Length"));
+        assertEquals(0, request.getBodySize());
+        // below fails on JRE 6 unless -Dsun.net.http.allowRestrictedHeaders=true is set
+        assertEquals("100-continue", request.getHeader("Expect"));
+    }
+
+    public void testRegularResponse() throws Exception {
+        server.enqueue(new MockResponse().setBody("hello world"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestProperty("Accept-Language", "en-US");
+        InputStream in = connection.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
+        assertEquals("hello world", reader.readLine());
+
+        RecordedRequest request = server.takeRequest();
+        assertEquals("GET / HTTP/1.1", request.getRequestLine());
+        assertTrue(request.getHeaders().contains("Accept-Language: en-US"));
+    }
+
+    public void testRedirect() throws Exception {
+        server.play();
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+                .addHeader("Location: " + server.getUrl("/new-path"))
+                .setBody("This page has moved!"));
+        server.enqueue(new MockResponse().setBody("This is the new location!"));
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        InputStream in = connection.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        assertEquals("This is the new location!", reader.readLine());
+
+        RecordedRequest first = server.takeRequest();
+        assertEquals("GET / HTTP/1.1", first.getRequestLine());
+        RecordedRequest redirect = server.takeRequest();
+        assertEquals("GET /new-path HTTP/1.1", redirect.getRequestLine());
+    }
+
+    /**
+     * Test that MockWebServer blocks for a call to enqueue() if a request
+     * is made before a mock response is ready.
+     */
+    public void testDispatchBlocksWaitingForEnqueue() throws Exception {
+        server.play();
+
+        new Thread() {
+            @Override public void run() {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException ignored) {
+                }
+                server.enqueue(new MockResponse().setBody("enqueued in the background"));
+            }
+        }.start();
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        InputStream in = connection.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        assertEquals("enqueued in the background", reader.readLine());
+    }
+
+    public void testNonHexadecimalChunkSize() throws Exception {
+        server.enqueue(new MockResponse()
+                .setBody("G\r\nxxxxxxxxxxxxxxxx\r\n0\r\n\r\n")
+                .clearHeaders()
+                .addHeader("Transfer-encoding: chunked"));
+        server.play();
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        InputStream in = connection.getInputStream();
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void testResponseTimeout() throws Exception {
+        server.enqueue(new MockResponse()
+                .setBody("ABC")
+                .clearHeaders()
+                .addHeader("Content-Length: 4"));
+        server.enqueue(new MockResponse()
+                .setBody("DEF"));
+        server.play();
+
+        URLConnection urlConnection = server.getUrl("/").openConnection();
+        urlConnection.setReadTimeout(1000);
+        InputStream in = urlConnection.getInputStream();
+        assertEquals('A', in.read());
+        assertEquals('B', in.read());
+        assertEquals('C', in.read());
+        try {
+            in.read(); // if Content-Length was accurate, this would return -1 immediately
+            fail();
+        } catch (SocketTimeoutException expected) {
+        }
+
+        URLConnection urlConnection2 = server.getUrl("/").openConnection();
+        InputStream in2 = urlConnection2.getInputStream();
+        assertEquals('D', in2.read());
+        assertEquals('E', in2.read());
+        assertEquals('F', in2.read());
+        assertEquals(-1, in2.read());
+
+        assertEquals(0, server.takeRequest().getSequenceNumber());
+        assertEquals(0, server.takeRequest().getSequenceNumber());
+    }
+
+    public void testDisconnectAtStart() throws Exception {
+        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
+        server.enqueue(new MockResponse()); // The jdk's HttpUrlConnection is a bastard.
+        server.enqueue(new MockResponse());
+        server.play();
+        try {
+            server.getUrl("/a").openConnection().getInputStream();
+        } catch (IOException e) {
+            // Expected.
+        }
+        server.getUrl("/b").openConnection().getInputStream(); // Should succeed.
+    }
+
+    public void testStreamingResponseBody() throws Exception {
+        InputStream responseBody = new ByteArrayInputStream("ABC".getBytes("UTF-8"));
+        server.enqueue(new MockResponse().setBody(responseBody, 3));
+        server.play();
+
+        InputStream in = server.getUrl("/").openConnection().getInputStream();
+        assertEquals('A', in.read());
+        assertEquals('B', in.read());
+        assertEquals('C', in.read());
+
+        assertEquals(-1, responseBody.read()); // The body is exhausted.
+    }
+}
diff --git a/okhttp-apache/README.md b/okhttp-apache/README.md
new file mode 100644
index 0000000..ac71ec2
--- /dev/null
+++ b/okhttp-apache/README.md
@@ -0,0 +1,7 @@
+OkHttp Apache HttpClient Implementation
+=======================================
+
+This module is an implementation of the Apache `HttpClient` interface that is backed by OkHttp.
+
+**Warning**: Many core features of Apache HTTP client are not implemented by this API. This includes
+the keep-alive strategy, cookie store, credentials provider, route planner and others.
diff --git a/okhttp-apache/pom.xml b/okhttp-apache/pom.xml
new file mode 100644
index 0000000..f8fa6a4
--- /dev/null
+++ b/okhttp-apache/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>okhttp-apache</artifactId>
+  <name>OkHttp Apache HttpClient</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>okhttp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/okhttp-apache/src/main/java/com/squareup/okhttp/apache/OkApacheClient.java b/okhttp-apache/src/main/java/com/squareup/okhttp/apache/OkApacheClient.java
new file mode 100644
index 0000000..2c40f9a
--- /dev/null
+++ b/okhttp-apache/src/main/java/com/squareup/okhttp/apache/OkApacheClient.java
@@ -0,0 +1,210 @@
+// Copyright 2013 Square, Inc.
+package com.squareup.okhttp.apache;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnRouteParams;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.AbstractHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+import static java.net.Proxy.Type.HTTP;
+import static org.apache.http.HttpVersion.HTTP_1_1;
+
+/**
+ * Implements Apache's {@link HttpClient} API using {@link OkHttpClient}.
+ * <p>
+ * <strong>Warning:</strong> Many core features of Apache HTTP client are not implemented by this
+ * API. This includes the keep-alive strategy, cookie store, credentials provider, route planner
+ * and others.
+ */
+public class OkApacheClient implements HttpClient {
+  protected final OkHttpClient client;
+
+  private final HttpParams params = new AbstractHttpParams() {
+    @Override public Object getParameter(String name) {
+      if (name.equals(ConnRouteParams.DEFAULT_PROXY)) {
+        Proxy proxy = client.getProxy();
+        if (proxy == null) {
+          return null;
+        }
+        InetSocketAddress address = (InetSocketAddress) proxy.address();
+        return new HttpHost(address.getHostName(), address.getPort());
+      }
+      throw new IllegalArgumentException(name);
+    }
+
+    @Override public HttpParams setParameter(String name, Object value) {
+      if (name.equals(ConnRouteParams.DEFAULT_PROXY)) {
+        HttpHost host = (HttpHost) value;
+        Proxy proxy = null;
+        if (host != null) {
+          proxy = new Proxy(HTTP, new InetSocketAddress(host.getHostName(), host.getPort()));
+        }
+        client.setProxy(proxy);
+        return this;
+      }
+      throw new IllegalArgumentException(name);
+    }
+
+    @Override public HttpParams copy() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public boolean removeParameter(String name) {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  public OkApacheClient() {
+    this(new OkHttpClient());
+  }
+
+  public OkApacheClient(OkHttpClient client) {
+    this.client = client;
+  }
+
+  /**
+   * Returns a new HttpURLConnection customized for this application. Subclasses should override
+   * this to customize the connection.
+   */
+  protected HttpURLConnection openConnection(URL url) {
+    return client.open(url);
+  }
+
+  @Override public HttpParams getParams() {
+    return params;
+  }
+
+  @Override public ClientConnectionManager getConnectionManager() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override public HttpResponse execute(HttpUriRequest request) throws IOException {
+    return execute(null, request, (HttpContext) null);
+  }
+
+  @Override public HttpResponse execute(HttpUriRequest request, HttpContext context)
+      throws IOException {
+    return execute(null, request, context);
+  }
+
+  @Override public HttpResponse execute(HttpHost host, HttpRequest request) throws IOException {
+    return execute(host, request, (HttpContext) null);
+  }
+
+  @Override public HttpResponse execute(HttpHost host, HttpRequest request, HttpContext context)
+      throws IOException {
+    // Prepare the request headers.
+    RequestLine requestLine = request.getRequestLine();
+    URL url = new URL(requestLine.getUri());
+    HttpURLConnection connection = openConnection(url);
+    connection.setRequestMethod(requestLine.getMethod());
+    for (Header header : request.getAllHeaders()) {
+      connection.addRequestProperty(header.getName(), header.getValue());
+    }
+
+    // Stream the request body.
+    if (request instanceof HttpEntityEnclosingRequest) {
+      HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
+      if (entity != null) {
+        connection.setDoOutput(true);
+        Header type = entity.getContentType();
+        if (type != null) {
+          connection.addRequestProperty(type.getName(), type.getValue());
+        }
+        Header encoding = entity.getContentEncoding();
+        if (encoding != null) {
+          connection.addRequestProperty(encoding.getName(), encoding.getValue());
+        }
+        if (entity.isChunked() || entity.getContentLength() < 0) {
+          connection.setChunkedStreamingMode(0);
+        } else if (entity.getContentLength() <= 8192) {
+          // Buffer short, fixed-length request bodies. This costs memory, but permits the request
+          // to be transparently retried if there is a connection failure.
+          connection.addRequestProperty("Content-Length", Long.toString(entity.getContentLength()));
+        } else {
+          connection.setFixedLengthStreamingMode((int) entity.getContentLength());
+        }
+        entity.writeTo(connection.getOutputStream());
+      }
+    }
+
+    // Read the response headers.
+    int responseCode = connection.getResponseCode();
+    String message = connection.getResponseMessage();
+    BasicHttpResponse response = new BasicHttpResponse(HTTP_1_1, responseCode, message);
+    // Get the response body ready to stream.
+    InputStream responseBody =
+        responseCode < HttpURLConnection.HTTP_BAD_REQUEST ? connection.getInputStream()
+            : connection.getErrorStream();
+    InputStreamEntity entity = new InputStreamEntity(responseBody, connection.getContentLength());
+    for (int i = 0; true; i++) {
+      String name = connection.getHeaderFieldKey(i);
+      if (name == null) {
+        break;
+      }
+      BasicHeader header = new BasicHeader(name, connection.getHeaderField(i));
+      response.addHeader(header);
+      if (name.equalsIgnoreCase("Content-Type")) {
+          entity.setContentType(header);
+      } else if (name.equalsIgnoreCase("Content-Encoding")) {
+          entity.setContentEncoding(header);
+      }
+    }
+    response.setEntity(entity);
+
+    return response;
+  }
+
+  @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> handler)
+      throws IOException {
+    return execute(null, request, handler, null);
+  }
+
+  @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> handler,
+      HttpContext context) throws IOException {
+    return execute(null, request, handler, context);
+  }
+
+  @Override public <T> T execute(HttpHost host, HttpRequest request,
+      ResponseHandler<? extends T> handler) throws IOException {
+    return execute(host, request, handler, null);
+  }
+
+  @Override public <T> T execute(HttpHost host, HttpRequest request,
+      ResponseHandler<? extends T> handler, HttpContext context) throws IOException {
+    HttpResponse response = execute(host, request, context);
+    try {
+      return handler.handleResponse(response);
+    } finally {
+      consumeContentQuietly(response);
+    }
+  }
+
+  private static void consumeContentQuietly(HttpResponse response) {
+    try {
+      response.getEntity().consumeContent();
+    } catch (Throwable ignored) {
+    }
+  }
+}
diff --git a/okhttp-apache/src/test/java/com/squareup/okhttp/apache/OkApacheClientTest.java b/okhttp-apache/src/test/java/com/squareup/okhttp/apache/OkApacheClientTest.java
new file mode 100644
index 0000000..766e69c
--- /dev/null
+++ b/okhttp-apache/src/test/java/com/squareup/okhttp/apache/OkApacheClientTest.java
@@ -0,0 +1,253 @@
+package com.squareup.okhttp.apache;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.util.EntityUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class OkApacheClientTest {
+  private MockWebServer server;
+  private OkApacheClient client;
+
+  @Before public void setUp() throws IOException {
+    client = new OkApacheClient();
+    server = new MockWebServer();
+    server.play();
+  }
+
+  @After public void tearDown() throws IOException {
+    server.shutdown();
+  }
+
+  @Test public void success() throws Exception {
+    server.enqueue(new MockResponse().setBody("Hello, World!"));
+
+    HttpGet request = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response = client.execute(request);
+    String actual = EntityUtils.toString(response.getEntity());
+    assertEquals("Hello, World!", actual);
+  }
+
+  @Test public void redirect() throws Exception {
+    server.enqueue(new MockResponse().setResponseCode(302).addHeader("Location", "/foo"));
+    server.enqueue(new MockResponse().setBody("Hello, Redirect!"));
+
+    HttpGet request = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response = client.execute(request);
+    String actual = EntityUtils.toString(response.getEntity());
+    assertEquals("Hello, Redirect!", actual);
+  }
+
+  @Test public void sessionExpired() throws Exception {
+    server.enqueue(new MockResponse().setResponseCode(422));
+
+    HttpGet request = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response = client.execute(request);
+    assertEquals(422, response.getStatusLine().getStatusCode());
+  }
+
+  @Test public void headers() throws Exception {
+    server.enqueue(new MockResponse().addHeader("Foo", "Bar"));
+    server.enqueue(new MockResponse().addHeader("Foo", "Bar").addHeader("Foo", "Baz"));
+
+    HttpGet request1 = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response1 = client.execute(request1);
+    Header[] headers1 = response1.getHeaders("Foo");
+    assertEquals(1, headers1.length);
+    assertEquals("Bar", headers1[0].getValue());
+
+    HttpGet request2 = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response2 = client.execute(request2);
+    Header[] headers2 = response2.getHeaders("Foo");
+    assertEquals(2, headers2.length);
+    assertEquals("Bar", headers2[0].getValue());
+    assertEquals("Baz", headers2[1].getValue());
+  }
+
+  @Test public void noEntity() throws Exception {
+    server.enqueue(new MockResponse());
+
+    HttpPost post = new HttpPost(server.getUrl("/").toURI());
+    client.execute(post);
+  }
+
+  @Test public void postByteEntity() throws Exception {
+    server.enqueue(new MockResponse());
+
+    final HttpPost post = new HttpPost(server.getUrl("/").toURI());
+    byte[] body = "Hello, world!".getBytes("UTF-8");
+    post.setEntity(new ByteArrayEntity(body));
+    client.execute(post);
+
+    RecordedRequest request = server.takeRequest();
+    assertTrue(Arrays.equals(body, request.getBody()));
+    assertEquals(request.getHeader("Content-Length"), "13");
+  }
+
+  @Test public void postInputStreamEntity() throws Exception {
+    server.enqueue(new MockResponse());
+
+    final HttpPost post = new HttpPost(server.getUrl("/").toURI());
+    byte[] body = "Hello, world!".getBytes("UTF-8");
+    post.setEntity(new InputStreamEntity(new ByteArrayInputStream(body), body.length));
+    client.execute(post);
+
+    RecordedRequest request = server.takeRequest();
+    assertTrue(Arrays.equals(body, request.getBody()));
+    assertEquals(request.getHeader("Content-Length"), "13");
+  }
+
+  @Test public void contentType() throws Exception {
+    server.enqueue(new MockResponse().setBody("<html><body><h1>Hello, World!</h1></body></html>")
+        .setHeader("Content-Type", "text/html"));
+    server.enqueue(new MockResponse().setBody("{\"Message\": { \"text\": \"Hello, World!\" } }")
+        .setHeader("Content-Type", "application/json"));
+    server.enqueue(new MockResponse().setBody("Hello, World!"));
+
+    HttpGet request1 = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response1 = client.execute(request1);
+    Header[] headers1 = response1.getHeaders("Content-Type");
+    assertEquals(1, headers1.length);
+    assertEquals("text/html", headers1[0].getValue());
+    assertNotNull(response1.getEntity().getContentType());
+    assertEquals("text/html", response1.getEntity().getContentType().getValue());
+
+    HttpGet request2 = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response2 = client.execute(request2);
+    Header[] headers2 = response2.getHeaders("Content-Type");
+    assertEquals(1, headers2.length);
+    assertEquals("application/json", headers2[0].getValue());
+    assertNotNull(response2.getEntity().getContentType());
+    assertEquals("application/json", response2.getEntity().getContentType().getValue());
+
+    HttpGet request3 = new HttpGet(server.getUrl("/").toURI());
+    HttpResponse response3 = client.execute(request3);
+    Header[] headers3 = response3.getHeaders("Content-Type");
+    assertEquals(0, headers3.length);
+    assertNull(response3.getEntity().getContentType());
+  }
+
+  @Test public void contentEncoding() throws Exception {
+    String text = "{\"Message\": { \"text\": \"Hello, World!\" } }";
+    ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
+    OutputStreamWriter body = new OutputStreamWriter(new GZIPOutputStream(bodyBytes),
+        Charset.forName("UTF-8"));
+    body.write(text);
+    body.close();
+    server.enqueue(new MockResponse().setBody(bodyBytes.toByteArray())
+        .setHeader("Content-Encoding", "gzip"));
+
+    byte[] tmp = new byte[32];
+
+    HttpGet request1 = new HttpGet(server.getUrl("/").toURI());
+    request1.setHeader("Accept-encoding", "gzip"); // not transparent gzip
+    HttpResponse response1 = client.execute(request1);
+    Header[] headers1 = response1.getHeaders("Content-Encoding");
+    assertEquals(1, headers1.length);
+    assertEquals("gzip", headers1[0].getValue());
+    assertNotNull(response1.getEntity().getContentEncoding());
+    assertEquals("gzip", response1.getEntity().getContentEncoding().getValue());
+    InputStream content = new GZIPInputStream(response1.getEntity().getContent());
+    ByteArrayOutputStream rspBodyBytes = new ByteArrayOutputStream();
+    for (int len = content.read(tmp); len >= 0; len = content.read(tmp)) {
+      rspBodyBytes.write(tmp, 0, len);
+    }
+    String decodedContent = rspBodyBytes.toString("UTF-8");
+    assertEquals(text, decodedContent);
+  }
+
+  @Test public void jsonGzipResponse() throws Exception {
+    String text = "{\"Message\": { \"text\": \"Hello, World!\" } }";
+    ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
+    OutputStreamWriter body = new OutputStreamWriter(new GZIPOutputStream(bodyBytes),
+        Charset.forName("UTF-8"));
+    body.write(text);
+    body.close();
+    server.enqueue(new MockResponse().setBody(bodyBytes.toByteArray())
+        .setHeader("Content-Encoding", "gzip")
+        .setHeader("Content-Type", "application/json"));
+
+    byte[] tmp = new byte[32];
+
+    HttpGet request1 = new HttpGet(server.getUrl("/").toURI());
+    request1.setHeader("Accept-encoding", "gzip"); // not transparent gzip
+    HttpResponse response1 = client.execute(request1);
+    Header[] headers1a = response1.getHeaders("Content-Encoding");
+    assertEquals(1, headers1a.length);
+    assertEquals("gzip", headers1a[0].getValue());
+    assertNotNull(response1.getEntity().getContentEncoding());
+    assertEquals("gzip", response1.getEntity().getContentEncoding().getValue());
+    Header[] headers1b = response1.getHeaders("Content-Type");
+    assertEquals(1, headers1b.length);
+    assertEquals("application/json", headers1b[0].getValue());
+    assertNotNull(response1.getEntity().getContentType());
+    assertEquals("application/json", response1.getEntity().getContentType().getValue());
+    InputStream content = new GZIPInputStream(response1.getEntity().getContent());
+    ByteArrayOutputStream rspBodyBytes = new ByteArrayOutputStream();
+    for (int len = content.read(tmp); len >= 0; len = content.read(tmp)) {
+      rspBodyBytes.write(tmp, 0, len);
+    }
+    String decodedContent = rspBodyBytes.toString("UTF-8");
+    assertEquals(text, decodedContent);
+  }
+
+  @Test public void jsonTransparentGzipResponse() throws Exception {
+    String text = "{\"Message\": { \"text\": \"Hello, World!\" } }";
+    ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
+    OutputStreamWriter body = new OutputStreamWriter(new GZIPOutputStream(bodyBytes),
+        Charset.forName("UTF-8"));
+    body.write(text);
+    body.close();
+    server.enqueue(new MockResponse().setBody(bodyBytes.toByteArray())
+        .setHeader("Content-Encoding", "gzip")
+        .setHeader("Content-Type", "application/json"));
+
+    byte[] tmp = new byte[32];
+
+    HttpGet request1 = new HttpGet(server.getUrl("/").toURI());
+    // expecting transparent gzip response by not adding header "Accept-encoding: gzip"
+    HttpResponse response1 = client.execute(request1);
+    Header[] headers1a = response1.getHeaders("Content-Encoding");
+    assertEquals(0, headers1a.length);
+    assertNull(response1.getEntity().getContentEncoding());
+    // content length should also be absent
+    Header[] headers1b = response1.getHeaders("Content-Length");
+    assertEquals(0, headers1b.length);
+    assertTrue(response1.getEntity().getContentLength() < 0);
+    Header[] headers1c = response1.getHeaders("Content-Type");
+    assertEquals(1, headers1c.length);
+    assertEquals("application/json", headers1c[0].getValue());
+    assertNotNull(response1.getEntity().getContentType());
+    assertEquals("application/json", response1.getEntity().getContentType().getValue());
+    InputStream content = response1.getEntity().getContent();
+    ByteArrayOutputStream rspBodyBytes = new ByteArrayOutputStream();
+    for (int len = content.read(tmp); len >= 0; len = content.read(tmp)) {
+      rspBodyBytes.write(tmp, 0, len);
+    }
+    String decodedContent = rspBodyBytes.toString("UTF-8");
+    assertEquals(text, decodedContent);
+  }
+}
diff --git a/okhttp-protocols/pom.xml b/okhttp-protocols/pom.xml
new file mode 100644
index 0000000..0ecb915
--- /dev/null
+++ b/okhttp-protocols/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>okhttp-protocols</artifactId>
+  <name>OkHttp SPDY and HTTP/2.0 internals</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.mortbay.jetty.npn</groupId>
+      <artifactId>npn-boot</artifactId>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/src/main/java/com/squareup/okhttp/internal/Base64.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Base64.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/Base64.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Base64.java
diff --git a/src/main/java/com/squareup/okhttp/internal/NamedRunnable.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/NamedRunnable.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/NamedRunnable.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/NamedRunnable.java
diff --git a/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
similarity index 98%
rename from src/main/java/com/squareup/okhttp/internal/Platform.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
index 13ea4df..905641a 100644
--- a/src/main/java/com/squareup/okhttp/internal/Platform.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
@@ -16,7 +16,6 @@
  */
 package com.squareup.okhttp.internal;
 
-import com.squareup.okhttp.OkHttpClient;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
@@ -362,7 +361,7 @@
         JettyNpnProvider provider =
             (JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
         if (!provider.unsupported && provider.selected == null) {
-          Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+          Logger logger = Logger.getLogger("com.squareup.okhttp.OkHttpClient");
           logger.log(Level.INFO,
               "NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
           return null;
diff --git a/src/main/java/com/squareup/okhttp/internal/Util.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java
similarity index 87%
rename from src/main/java/com/squareup/okhttp/internal/Util.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java
index 0ce7f8a..9c5b008 100644
--- a/src/main/java/com/squareup/okhttp/internal/Util.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java
@@ -24,11 +24,15 @@
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
 import java.net.Socket;
+import java.net.ServerSocket;
 import java.net.URI;
 import java.net.URL;
 import java.nio.ByteOrder;
 import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -50,6 +54,9 @@
   public static final Charset UTF_8 = Charset.forName("UTF-8");
   private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
 
+  private static final char[] DIGITS =
+      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
   private Util() {
   }
 
@@ -131,6 +138,21 @@
   }
 
   /**
+   * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if
+   * {@code serverSocket} is null.
+   */
+  public static void closeQuietly(ServerSocket serverSocket) {
+    if (serverSocket != null) {
+      try {
+        serverSocket.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.
    */
@@ -262,6 +284,8 @@
    * buffer.
    */
   public static long skipByReading(InputStream in, long byteCount) throws IOException {
+    if (byteCount == 0) return 0L;
+
     // acquire the shared skip buffer.
     byte[] buffer = skipBuffer.getAndSet(null);
     if (buffer == null) {
@@ -329,6 +353,30 @@
     return result.toString();
   }
 
+  /** Returns a 32 character string containing a hash of {@code s}. */
+  public static String hash(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+      byte[] md5bytes = messageDigest.digest(s.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);
+  }
+
   /** Returns an immutable copy of {@code list}. */
   public static <T> List<T> immutableList(List<T> list) {
     return Collections.unmodifiableList(new ArrayList<T>(list));
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/ErrorCode.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/ErrorCode.java
new file mode 100644
index 0000000..d3a32e1
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/ErrorCode.java
@@ -0,0 +1,67 @@
+package com.squareup.okhttp.internal.spdy;
+
+public enum ErrorCode {
+  /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
+  NO_ERROR(0, -1, 0),
+
+  PROTOCOL_ERROR(1, 1, 1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  INVALID_STREAM(1, 2, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  UNSUPPORTED_VERSION(1, 4, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_IN_USE(1, 8, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_ALREADY_CLOSED(1, 9, -1),
+
+  INTERNAL_ERROR(2, 6, 2),
+
+  FLOW_CONTROL_ERROR(3, 7, -1),
+
+  STREAM_CLOSED(5, -1, -1),
+
+  FRAME_TOO_LARGE(6, 11, -1),
+
+  REFUSED_STREAM(7, 3, -1),
+
+  CANCEL(8, 5, -1),
+
+  COMPRESSION_ERROR(9, -1, -1),
+
+  INVALID_CREDENTIALS(-1, 10, -1);
+
+  public final int httpCode;
+  public final int spdyRstCode;
+  public final int spdyGoAwayCode;
+
+  private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) {
+    this.httpCode = httpCode;
+    this.spdyRstCode = spdyRstCode;
+    this.spdyGoAwayCode = spdyGoAwayCode;
+  }
+
+  public static ErrorCode fromSpdy3Rst(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyRstCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromHttp2(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.httpCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromSpdyGoAway(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyGoAwayCode == code) return errorCode;
+    }
+    return null;
+  }
+}
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java
new file mode 100644
index 0000000..1371262
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.spdy;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/** Reads transport frames for SPDY/3 or HTTP/2.0. */
+public interface FrameReader extends Closeable {
+  void readConnectionHeader() throws IOException;
+  boolean nextFrame(Handler handler) throws IOException;
+
+  public interface Handler {
+    void data(boolean inFinished, int streamId, InputStream in, int length) throws IOException;
+    /**
+     * Create or update incoming headers, creating the corresponding streams
+     * if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
+     * SYN_REPLY, and HTTP/2.0 HEADERS and PUSH_PROMISE.
+     *
+     * @param inFinished true if the sender will not send further frames.
+     * @param outFinished true if the receiver should not send further frames.
+     * @param streamId the stream owning these headers.
+     * @param associatedStreamId the stream that triggered the sender to create
+     *     this stream.
+     * @param priority or -1 for no priority. For SPDY, priorities range from 0
+     *     (highest) thru 7 (lowest). For HTTP/2.0, priorities range from 0
+     *     (highest) thru 2**31-1 (lowest).
+     */
+    void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+        int priority, List<String> nameValueBlock, HeadersMode headersMode);
+    void rstStream(int streamId, ErrorCode errorCode);
+    void settings(boolean clearPrevious, Settings settings);
+    void noop();
+    void ping(boolean reply, int payload1, int payload2);
+    void goAway(int lastGoodStreamId, ErrorCode errorCode);
+    void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl);
+    void priority(int streamId, int priority);
+  }
+}
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java
new file mode 100644
index 0000000..354f43d
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.spdy;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+/** Writes transport frames for SPDY/3 or HTTP/2.0. */
+public interface FrameWriter extends Closeable {
+  /** HTTP/2.0 only. */
+  void connectionHeader() throws IOException;
+
+  /** SPDY/3 only. */
+  void flush() throws IOException;
+  void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+      int priority, int slot, List<String> nameValueBlock) throws IOException;
+  void synReply(boolean outFinished, int streamId, List<String> nameValueBlock) throws IOException;
+  void headers(int streamId, List<String> nameValueBlock) throws IOException;
+  void rstStream(int streamId, ErrorCode errorCode) throws IOException;
+  void data(boolean outFinished, int streamId, byte[] data) throws IOException;
+  void data(boolean outFinished, int streamId, byte[] data, int offset, int byteCount)
+      throws IOException;
+  void settings(Settings settings) throws IOException;
+  void noop() throws IOException;
+  void ping(boolean reply, int payload1, int payload2) throws IOException;
+  void goAway(int lastGoodStreamId, ErrorCode errorCode) throws IOException;
+  void windowUpdate(int streamId, int deltaWindowSize) throws IOException;
+}
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HeadersMode.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HeadersMode.java
new file mode 100644
index 0000000..e16e176
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HeadersMode.java
@@ -0,0 +1,49 @@
+/*
+ * 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.spdy;
+
+enum HeadersMode {
+  SPDY_SYN_STREAM,
+  SPDY_REPLY,
+  SPDY_HEADERS,
+  HTTP_20_HEADERS;
+
+  /** Returns true if it is an error these headers to create a new stream. */
+  public boolean failIfStreamAbsent() {
+    return this == SPDY_REPLY || this == SPDY_HEADERS;
+  }
+
+  /** Returns true if it is an error these headers to update an existing stream. */
+  public boolean failIfStreamPresent() {
+    return this == SPDY_SYN_STREAM;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be the initial headers of a
+   * response.
+   */
+  public boolean failIfHeadersAbsent() {
+    return this == SPDY_HEADERS;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be update existing headers
+   * of a response.
+   */
+  public boolean failIfHeadersPresent() {
+    return this == SPDY_REPLY;
+  }
+}
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Hpack.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Hpack.java
new file mode 100644
index 0000000..1e799b4
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Hpack.java
@@ -0,0 +1,360 @@
+package com.squareup.okhttp.internal.spdy;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Read and write HPACK v01.
+ * http://http2.github.io/compression-spec/compression-spec.html#rfc.status
+ */
+final class Hpack {
+  static final int PREFIX_5_BITS = 0x1f;
+  static final int PREFIX_6_BITS = 0x3f;
+  static final int PREFIX_7_BITS = 0x7f;
+  static final int PREFIX_8_BITS = 0xff;
+
+  static final List<String> INITIAL_CLIENT_TO_SERVER_HEADER_TABLE = Arrays.asList(
+      ":scheme", "http",
+      ":scheme", "https",
+      ":host", "",
+      ":path", "/",
+      ":method", "GET",
+      "accept", "",
+      "accept-charset", "",
+      "accept-encoding", "",
+      "accept-language", "",
+      "cookie", "",
+      "if-modified-since", "",
+      "user-agent", "",
+      "referer", "",
+      "authorization", "",
+      "allow", "",
+      "cache-control", "",
+      "connection", "",
+      "content-length", "",
+      "content-type", "",
+      "date", "",
+      "expect", "",
+      "from", "",
+      "if-match", "",
+      "if-none-match", "",
+      "if-range", "",
+      "if-unmodified-since", "",
+      "max-forwards", "",
+      "proxy-authorization", "",
+      "range", "",
+      "via", ""
+  );
+
+  static final List<String> INITIAL_SERVER_TO_CLIENT_HEADER_TABLE = Arrays.asList(
+      ":status", "200",
+      "age", "",
+      "cache-control", "",
+      "content-length", "",
+      "content-type", "",
+      "date", "",
+      "etag", "",
+      "expires", "",
+      "last-modified", "",
+      "server", "",
+      "set-cookie", "",
+      "vary", "",
+      "via", "",
+      "access-control-allow-origin", "",
+      "accept-ranges", "",
+      "allow", "",
+      "connection", "",
+      "content-disposition", "",
+      "content-encoding", "",
+      "content-language", "",
+      "content-location", "",
+      "content-range", "",
+      "link", "",
+      "location", "",
+      "proxy-authenticate", "",
+      "refresh", "",
+      "retry-after", "",
+      "strict-transport-security", "",
+      "transfer-encoding", "",
+      "www-authenticate", ""
+  );
+
+  private Hpack() {
+  }
+
+  static class Reader {
+    private final long maxBufferSize = 4096; // TODO: needs to come from settings.
+    private final DataInputStream in;
+
+    private final BitSet referenceSet = new BitSet();
+    private final List<String> headerTable;
+    private final List<String> emittedHeaders = new ArrayList<String>();
+    private long bufferSize = 4096;
+    private long bytesLeft = 0;
+
+    Reader(DataInputStream in, boolean client) {
+      this.in = in;
+      this.headerTable = new ArrayList<String>(client
+          ? INITIAL_CLIENT_TO_SERVER_HEADER_TABLE
+          : INITIAL_SERVER_TO_CLIENT_HEADER_TABLE);
+    }
+
+    /**
+     * Read {@code byteCount} bytes of headers from the source stream into the
+     * set of emitted headers.
+     */
+    public void readHeaders(int byteCount) throws IOException {
+      bytesLeft += byteCount;
+      // TODO: limit to 'byteCount' bytes?
+
+      while (bytesLeft > 0) {
+        int b = readByte();
+
+        if ((b & 0x80) != 0) {
+          int index = readInt(b, PREFIX_7_BITS);
+          readIndexedHeader(index);
+        } else if (b == 0x60) {
+          readLiteralHeaderWithoutIndexingNewName();
+        } else if ((b & 0xe0) == 0x60) {
+          int index = readInt(b, PREFIX_5_BITS);
+          readLiteralHeaderWithoutIndexingIndexedName(index - 1);
+        } else if (b == 0x40) {
+          readLiteralHeaderWithIncrementalIndexingNewName();
+        } else if ((b & 0xe0) == 0x40) {
+          int index = readInt(b, PREFIX_5_BITS);
+          readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
+        } else if (b == 0) {
+          readLiteralHeaderWithSubstitutionIndexingNewName();
+        } else if ((b & 0xc0) == 0) {
+          int index = readInt(b, PREFIX_6_BITS);
+          readLiteralHeaderWithSubstitutionIndexingIndexedName(index - 1);
+        } else {
+          throw new AssertionError();
+        }
+      }
+    }
+
+    public void emitReferenceSet() {
+      for (int i = referenceSet.nextSetBit(0); i != -1; i = referenceSet.nextSetBit(i + 1)) {
+        emittedHeaders.add(getName(i));
+        emittedHeaders.add(getValue(i));
+      }
+    }
+
+    /**
+     * Returns all headers emitted since they were last cleared, then clears the
+     * emitted headers.
+     */
+    public List<String> getAndReset() {
+      List<String> result = new ArrayList<String>(emittedHeaders);
+      emittedHeaders.clear();
+      return result;
+    }
+
+    private void readIndexedHeader(int index) {
+      if (referenceSet.get(index)) {
+        referenceSet.clear(index);
+      } else {
+        referenceSet.set(index);
+        emittedHeaders.add(getName(index));
+        emittedHeaders.add(getValue(index));
+      }
+    }
+
+    private void readLiteralHeaderWithoutIndexingIndexedName(int index)
+        throws IOException {
+      String name = getName(index);
+      String value = readString();
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+    }
+
+    private void readLiteralHeaderWithoutIndexingNewName()
+        throws IOException {
+      String name = readString();
+      String value = readString();
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
+        throws IOException {
+      int index = headerTable.size();
+      String name = getName(nameIndex);
+      String value = readString();
+      appendToHeaderTable(name, value);
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+      referenceSet.set(index);
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
+      int index = headerTable.size();
+      String name = readString();
+      String value = readString();
+      appendToHeaderTable(name, value);
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+      referenceSet.set(index);
+    }
+
+    private void readLiteralHeaderWithSubstitutionIndexingIndexedName(int nameIndex)
+        throws IOException {
+      int index = readInt(readByte(), PREFIX_8_BITS);
+      String name = getName(nameIndex);
+      String value = readString();
+      replaceInHeaderTable(index, name, value);
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+      referenceSet.set(index);
+    }
+
+    private void readLiteralHeaderWithSubstitutionIndexingNewName() throws IOException {
+      String name = readString();
+      int index = readInt(readByte(), PREFIX_8_BITS);
+      String value = readString();
+      replaceInHeaderTable(index, name, value);
+      emittedHeaders.add(name);
+      emittedHeaders.add(value);
+      referenceSet.set(index);
+    }
+
+    private String getName(int index) {
+      return headerTable.get(index * 2);
+    }
+
+    private String getValue(int index) {
+      return headerTable.get(index * 2 + 1);
+    }
+
+    private void appendToHeaderTable(String name, String value) {
+      insertIntoHeaderTable(headerTable.size() * 2, name, value);
+    }
+
+    private void replaceInHeaderTable(int index, String name, String value) {
+      remove(index);
+      insertIntoHeaderTable(index, name, value);
+    }
+
+    private void insertIntoHeaderTable(int index, String name, String value) {
+      // TODO: This needs to be the length in UTF-8 bytes, not the length in chars.
+
+      int delta = 32 + name.length() + value.length();
+
+      // Prune headers to the required length.
+      while (bufferSize + delta > maxBufferSize) {
+        remove(0);
+        index--;
+      }
+
+      if (delta > maxBufferSize) {
+        return; // New values won't fit in the buffer; skip 'em.
+      }
+
+      if (index == 0) index = 0;
+
+      headerTable.add(index * 2, name);
+      headerTable.add(index * 2 + 1, value);
+      bufferSize += delta;
+    }
+
+    private void remove(int index) {
+      String name = headerTable.remove(index * 2);
+      String value = headerTable.remove(index * 2); // No +1 because it's shifted by remove() above.
+      // TODO: This needs to be the length in UTF-8 bytes, not the length in chars.
+      bufferSize -= (32 + name.length() + value.length());
+    }
+
+    private int readByte() throws IOException {
+      bytesLeft--;
+      return in.readByte() & 0xff;
+    }
+
+    int readInt(int firstByte, int prefixMask) throws IOException {
+      int prefix = firstByte & prefixMask;
+      if (prefix < prefixMask) {
+        return prefix; // This was a single byte value.
+      }
+
+      // This is a multibyte value. Read 7 bits at a time.
+      int result = prefixMask;
+      int shift = 0;
+      while (true) {
+        int b = readByte();
+        if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
+          result += (b & 0x7f) << shift;
+          shift += 7;
+        } else {
+          result += b << shift; // Last byte.
+          break;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Reads a UTF-8 encoded string. Since ASCII is a subset of UTF-8, this method
+     * may be used to read strings that are known to be ASCII-only.
+     */
+    public String readString() throws IOException {
+      int firstByte = readByte();
+      int length = readInt(firstByte, PREFIX_8_BITS);
+      byte[] encoded = new byte[length];
+      bytesLeft -= length;
+      in.readFully(encoded);
+      return new String(encoded, "UTF-8");
+    }
+  }
+
+  static class Writer {
+    private final OutputStream out;
+
+    Writer(OutputStream out) {
+      this.out = out;
+    }
+
+    public void writeHeaders(List<String> nameValueBlock) throws IOException {
+      // TODO: implement a compression strategy.
+      for (int i = 0, size = nameValueBlock.size(); i < size; i += 2) {
+        out.write(0x60); // Literal Header without Indexing - New Name.
+        writeString(nameValueBlock.get(i));
+        writeString(nameValueBlock.get(i + 1));
+      }
+    }
+
+    public void writeInt(int value, int prefixMask, int bits) throws IOException {
+      // Write the raw value for a single byte value.
+      if (value < prefixMask) {
+        out.write(bits | value);
+        return;
+      }
+
+      // Write the mask to start a multibyte value.
+      out.write(bits | prefixMask);
+      value -= prefixMask;
+
+      // Write 7 bits at a time 'til we're done.
+      while (value >= 0x80) {
+        int b = value & 0x7f;
+        out.write(b | 0x80);
+        value >>>= 7;
+      }
+      out.write(value);
+    }
+
+    /**
+     * Writes a UTF-8 encoded string. Since ASCII is a subset of UTF-8, this
+     * method can be used to write strings that are known to be ASCII-only.
+     */
+    public void writeString(String headerName) throws IOException {
+      byte[] bytes = headerName.getBytes("UTF-8");
+      writeInt(bytes.length, PREFIX_8_BITS, 0);
+      out.write(bytes);
+    }
+  }
+}
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft04.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft04.java
new file mode 100644
index 0000000..1d48def
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft04.java
@@ -0,0 +1,371 @@
+/*
+ * 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.spdy;
+
+import com.squareup.okhttp.internal.Util;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+
+final class Http20Draft04 implements Variant {
+  private static final byte[] CONNECTION_HEADER;
+  static {
+    try {
+      CONNECTION_HEADER = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes("UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  static final int TYPE_DATA = 0x0;
+  static final int TYPE_HEADERS = 0x1;
+  static final int TYPE_PRIORITY = 0x2;
+  static final int TYPE_RST_STREAM = 0x3;
+  static final int TYPE_SETTINGS = 0x4;
+  static final int TYPE_PUSH_PROMISE = 0x5;
+  static final int TYPE_PING = 0x6;
+  static final int TYPE_GOAWAY = 0x7;
+  static final int TYPE_WINDOW_UPDATE = 0x9;
+
+  static final int FLAG_END_STREAM = 0x1;
+  static final int FLAG_END_HEADERS = 0x4;
+  static final int FLAG_PRIORITY = 0x8;
+  static final int FLAG_PONG = 0x1;
+  static final int FLAG_END_FLOW_CONTROL = 0x1;
+
+  @Override public FrameReader newReader(InputStream in, boolean client) {
+    return new Reader(in, client);
+  }
+
+  @Override public FrameWriter newWriter(OutputStream out, boolean client) {
+    return new Writer(out, client);
+  }
+
+  static final class Reader implements FrameReader {
+    private final DataInputStream in;
+    private final boolean client;
+    private final Hpack.Reader hpackReader;
+
+    Reader(InputStream in, boolean client) {
+      this.in = new DataInputStream(in);
+      this.client = client;
+      this.hpackReader = new Hpack.Reader(this.in, client);
+    }
+
+    @Override public void readConnectionHeader() throws IOException {
+      if (client) return; // Nothing to read; servers don't send connection headers!
+      byte[] connectionHeader = new byte[CONNECTION_HEADER.length];
+      in.readFully(connectionHeader);
+      if (!Arrays.equals(connectionHeader, CONNECTION_HEADER)) {
+        throw ioException("Expected a connection header but was "
+            + Arrays.toString(connectionHeader));
+      }
+    }
+
+    @Override public boolean nextFrame(Handler handler) throws IOException {
+      int w1;
+      try {
+        w1 = in.readInt();
+      } catch (IOException e) {
+        return false; // This might be a normal socket close.
+      }
+      int w2 = in.readInt();
+
+      int length = (w1 & 0xffff0000) >> 16;
+      int type = (w1 & 0xff00) >> 8;
+      int flags = w1 & 0xff;
+      // boolean r = (w2 & 0x80000000) != 0; // Reserved.
+      int streamId = (w2 & 0x7fffffff);
+
+      switch (type) {
+        case TYPE_DATA:
+          readData(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_HEADERS:
+          readHeaders(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_PRIORITY:
+          readPriority(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_RST_STREAM:
+          readRstStream(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_SETTINGS:
+          readSettings(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_PUSH_PROMISE:
+          readPushPromise(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_PING:
+          readPing(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_GOAWAY:
+          readGoAway(handler, flags, length, streamId);
+          return true;
+
+        case TYPE_WINDOW_UPDATE:
+          readWindowUpdate(handler, flags, length, streamId);
+          return true;
+      }
+
+      throw new UnsupportedOperationException("TODO");
+    }
+
+    private void readHeaders(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      if (streamId == 0) throw ioException("TYPE_HEADERS streamId == 0");
+
+      while (true) {
+        hpackReader.readHeaders(length);
+
+        if ((flags & FLAG_END_HEADERS) != 0) {
+          hpackReader.emitReferenceSet();
+          List<String> namesAndValues = hpackReader.getAndReset();
+          boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+          int priority = -1; // TODO: priority
+          handler.headers(false, inFinished, streamId, -1, priority, namesAndValues,
+              HeadersMode.HTTP_20_HEADERS);
+          return;
+        }
+
+        // Read another frame of headers.
+        int w1 = in.readInt();
+        int w2 = in.readInt();
+
+        length = (w1 & 0xffff0000) >> 16;
+        int newType = (w1 & 0xff00) >> 8;
+        flags = w1 & 0xff;
+        // boolean r = (w2 & 0x80000000) != 0; // Reserved.
+        int newStreamId = (w2 & 0x7fffffff);
+
+        if (newType != TYPE_HEADERS) throw ioException("TYPE_HEADERS didn't have FLAG_END_HEADERS");
+        if (newStreamId != streamId) throw ioException("TYPE_HEADERS streamId changed");
+      }
+    }
+
+    private void readData(Handler handler, int flags, int length, int streamId) throws IOException {
+      boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+      handler.data(inFinished, streamId, in, length);
+    }
+
+    private void readPriority(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length);
+      if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
+      int w1 = in.readInt();
+      // boolean r = (w1 & 0x80000000) != 0; // Reserved.
+      int priority = (w1 & 0x7fffffff);
+      handler.priority(streamId, priority);
+    }
+
+    private void readRstStream(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
+      if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
+      int errorCodeInt = in.readInt();
+      ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      handler.rstStream(streamId, errorCode);
+    }
+
+    private void readSettings(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
+      if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
+      Settings settings = new Settings();
+      for (int i = 0; i < length; i += 8) {
+        int w1 = in.readInt();
+        int value = in.readInt();
+        // int r = (w1 & 0xff000000) >>> 24; // Reserved.
+        int id = w1 & 0xffffff;
+        settings.set(id, 0, value);
+      }
+      handler.settings(false, settings);
+    }
+
+    private void readPushPromise(Handler handler, int flags, int length, int streamId) {
+      // TODO:
+    }
+
+    private void readPing(Handler handler, int flags, int length, int streamId) throws IOException {
+      if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
+      if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
+      int payload1 = in.readInt();
+      int payload2 = in.readInt();
+      boolean reply = (flags & FLAG_PONG) != 0;
+      handler.ping(reply, payload1, payload2);
+    }
+
+    private void readGoAway(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
+      int lastStreamId = in.readInt();
+      int errorCodeInt = in.readInt();
+      int opaqueDataLength = length - 8;
+      ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      if (Util.skipByReading(in, opaqueDataLength) != opaqueDataLength) {
+        throw new IOException("TYPE_GOAWAY opaque data was truncated");
+      }
+      handler.goAway(lastStreamId, errorCode);
+    }
+
+    private void readWindowUpdate(Handler handler, int flags, int length, int streamId)
+        throws IOException {
+      int w1 = in.readInt();
+      // boolean r = (w1 & 0x80000000) != 0; // Reserved.
+      int windowSizeIncrement = (w1 & 0x7fffffff);
+      boolean endFlowControl = (flags & FLAG_END_FLOW_CONTROL) != 0;
+      handler.windowUpdate(streamId, windowSizeIncrement, endFlowControl);
+    }
+
+    private static IOException ioException(String message, Object... args) throws IOException {
+      throw new IOException(String.format(message, args));
+    }
+
+    @Override public void close() throws IOException {
+      in.close();
+    }
+  }
+
+  static final class Writer implements FrameWriter {
+    private final DataOutputStream out;
+    private final boolean client;
+    private final ByteArrayOutputStream hpackBuffer;
+    private final Hpack.Writer hpackWriter;
+
+    Writer(OutputStream out, boolean client) {
+      this.out = new DataOutputStream(out);
+      this.client = client;
+      this.hpackBuffer = new ByteArrayOutputStream();
+      this.hpackWriter = new Hpack.Writer(hpackBuffer);
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      out.flush();
+    }
+
+    @Override public synchronized void connectionHeader() throws IOException {
+      if (!client) return; // Nothing to write; servers don't send connection headers!
+      out.write(CONNECTION_HEADER);
+    }
+
+    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+        int streamId, int associatedStreamId, int priority, int slot, List<String> nameValueBlock)
+        throws IOException {
+      if (inFinished) throw new UnsupportedOperationException();
+      headers(outFinished, streamId, priority, nameValueBlock);
+    }
+
+    @Override public synchronized void synReply(boolean outFinished, int streamId,
+        List<String> nameValueBlock) throws IOException {
+      headers(outFinished, streamId, -1, nameValueBlock);
+    }
+
+    @Override public synchronized void headers(int streamId, List<String> nameValueBlock)
+        throws IOException {
+      headers(false, streamId, -1, nameValueBlock);
+    }
+
+    private void headers(boolean outFinished, int streamId, int priority,
+        List<String> nameValueBlock) throws IOException {
+      hpackBuffer.reset();
+      hpackWriter.writeHeaders(nameValueBlock);
+      int type = TYPE_HEADERS;
+      int length = hpackBuffer.size();
+      int flags = FLAG_END_HEADERS;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      if (priority != -1) flags |= FLAG_PRIORITY;
+      out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
+      out.writeInt(streamId & 0x7fffffff);
+      if (priority != -1) out.writeInt(priority & 0x7fffffff);
+      hpackBuffer.writeTo(out);
+    }
+
+    @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+        throws IOException {
+      throw new UnsupportedOperationException("TODO");
+    }
+
+    @Override public void data(boolean outFinished, int streamId, byte[] data) throws IOException {
+      data(outFinished, streamId, data, 0, data.length);
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, byte[] data,
+        int offset, int byteCount) throws IOException {
+      int type = TYPE_DATA;
+      int flags = 0;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      out.writeInt((byteCount & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
+      out.writeInt(streamId & 0x7fffffff);
+      out.write(data, offset, byteCount);
+    }
+
+    @Override public synchronized void settings(Settings settings) throws IOException {
+      int type = TYPE_SETTINGS;
+      int length = settings.size() * 8;
+      int flags = 0;
+      int streamId = 0;
+      out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
+      out.writeInt(streamId & 0x7fffffff);
+      for (int i = 0; i < Settings.COUNT; i++) {
+        if (!settings.isSet(i)) continue;
+        out.writeInt(i & 0xffffff);
+        out.writeInt(settings.get(i));
+      }
+    }
+
+    @Override public synchronized void noop() throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public synchronized void ping(boolean reply, int payload1, int payload2)
+        throws IOException {
+      // TODO
+    }
+
+    @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
+        throws IOException {
+      // TODO
+    }
+
+    @Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
+        throws IOException {
+      // TODO
+    }
+
+    @Override public void close() throws IOException {
+      out.close();
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
similarity index 95%
rename from src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
index 875fff0..44d4ea2 100644
--- a/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
@@ -22,7 +22,7 @@
 public interface IncomingStreamHandler {
   IncomingStreamHandler REFUSE_INCOMING_STREAMS = new IncomingStreamHandler() {
     @Override public void receive(SpdyStream stream) throws IOException {
-      stream.close(SpdyStream.RST_REFUSED_STREAM);
+      stream.close(ErrorCode.REFUSED_STREAM);
     }
   };
 
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java
new file mode 100644
index 0000000..b95d013
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java
@@ -0,0 +1,123 @@
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.Util;
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Reads a SPDY/3 Name/Value header block. This class is made complicated by the
+ * requirement that we're strict with which bytes we put in the compressed bytes
+ * buffer. We need to put all compressed bytes into that buffer -- but no other
+ * bytes.
+ */
+class NameValueBlockReader implements Closeable {
+  private final DataInputStream nameValueBlockIn;
+  private final FillableInflaterInputStream fillableInflaterInputStream;
+  private int compressedLimit;
+
+  NameValueBlockReader(final InputStream in) {
+    // Limit the inflater input stream to only those bytes in the Name/Value block. We cut the
+    // inflater off at its source because we can't predict the ratio of compressed bytes to
+    // uncompressed bytes.
+    InputStream throttleStream = new InputStream() {
+      @Override public int read() throws IOException {
+        return Util.readSingleByte(this);
+      }
+
+      @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException {
+        byteCount = Math.min(byteCount, compressedLimit);
+        int consumed = in.read(buffer, offset, byteCount);
+        compressedLimit -= consumed;
+        return consumed;
+      }
+
+      @Override public void close() throws IOException {
+        in.close();
+      }
+    };
+
+    // Subclass inflater to install a dictionary when it's needed.
+    Inflater inflater = new Inflater() {
+      @Override public int inflate(byte[] buffer, int offset, int count)
+          throws DataFormatException {
+        int result = super.inflate(buffer, offset, count);
+        if (result == 0 && needsDictionary()) {
+          setDictionary(Spdy3.DICTIONARY);
+          result = super.inflate(buffer, offset, count);
+        }
+        return result;
+      }
+    };
+
+    fillableInflaterInputStream = new FillableInflaterInputStream(throttleStream, inflater);
+    nameValueBlockIn = new DataInputStream(fillableInflaterInputStream);
+  }
+
+  /** Extend the inflater stream so we can eagerly fill the compressed bytes buffer if necessary. */
+  static class FillableInflaterInputStream extends InflaterInputStream {
+    public FillableInflaterInputStream(InputStream in, Inflater inf) {
+      super(in, inf);
+    }
+
+    @Override public void fill() throws IOException {
+      super.fill(); // This method is protected in the superclass.
+    }
+  }
+
+  public List<String> readNameValueBlock(int length) throws IOException {
+    this.compressedLimit += length;
+    try {
+      int numberOfPairs = nameValueBlockIn.readInt();
+      if (numberOfPairs < 0) {
+        throw new IOException("numberOfPairs < 0: " + numberOfPairs);
+      }
+      if (numberOfPairs > 1024) {
+        throw new IOException("numberOfPairs > 1024: " + numberOfPairs);
+      }
+      List<String> entries = new ArrayList<String>(numberOfPairs * 2);
+      for (int i = 0; i < numberOfPairs; i++) {
+        String name = readString();
+        String values = readString();
+        if (name.length() == 0) throw new IOException("name.length == 0");
+        entries.add(name);
+        entries.add(values);
+      }
+
+      doneReading();
+
+      return entries;
+    } catch (DataFormatException e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  private void doneReading() throws IOException {
+    if (compressedLimit == 0) return;
+
+    // Read any outstanding unread bytes. One side-effect of deflate compression is that sometimes
+    // there are bytes remaining in the stream after we've consumed all of the content.
+    fillableInflaterInputStream.fill();
+
+    if (compressedLimit != 0) {
+      throw new IOException("compressedLimit > 0: " + compressedLimit);
+    }
+  }
+
+  private String readString() throws DataFormatException, IOException {
+    int length = nameValueBlockIn.readInt();
+    byte[] bytes = new byte[length];
+    Util.readFully(nameValueBlockIn, bytes);
+    return new String(bytes, 0, length, "UTF-8");
+  }
+
+  @Override public void close() throws IOException {
+    nameValueBlockIn.close();
+  }
+}
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
similarity index 85%
rename from src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
index 774d791..05380e2 100644
--- a/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
@@ -31,23 +31,29 @@
   static final int PERSISTED = 0x2;
 
   /** Sender's estimate of max incoming kbps. */
-  static final int UPLOAD_BANDWIDTH = 0x1;
+  static final int UPLOAD_BANDWIDTH = 1;
   /** Sender's estimate of max outgoing kbps. */
-  static final int DOWNLOAD_BANDWIDTH = 0x2;
+  static final int DOWNLOAD_BANDWIDTH = 2;
   /** Sender's estimate of milliseconds between sending a request and receiving a response. */
-  static final int ROUND_TRIP_TIME = 0x3;
+  static final int ROUND_TRIP_TIME = 3;
   /** Sender's maximum number of concurrent streams. */
-  static final int MAX_CONCURRENT_STREAMS = 0x4;
+  static final int MAX_CONCURRENT_STREAMS = 4;
   /** Current CWND in Packets. */
-  static final int CURRENT_CWND = 0x5;
+  static final int CURRENT_CWND = 5;
   /** Retransmission rate. Percentage */
-  static final int DOWNLOAD_RETRANS_RATE = 0x6;
+  static final int DOWNLOAD_RETRANS_RATE = 6;
   /** Window size in bytes. */
-  static final int INITIAL_WINDOW_SIZE = 0x7;
+  static final int INITIAL_WINDOW_SIZE = 7;
   /** Window size in bytes. */
-  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 0x8;
+  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
+  /** Flow control options. */
+  static final int FLOW_CONTROL_OPTIONS = 9;
+
   /** Total number of settings. */
-  static final int COUNT = 0x9;
+  static final int COUNT = 10;
+
+  /** If set, flow control is disabled for streams directed to the sender of these settings. */
+  static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1;
 
   /** Bitfield of which flags that values. */
   private int set;
@@ -146,6 +152,13 @@
     return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
   }
 
+  // TODO: honor this setting.
+  boolean isFlowControlDisabled() {
+    int bit = 1 << FLOW_CONTROL_OPTIONS;
+    int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
+    return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0;
+  }
+
   /**
    * Returns true if this user agent should use this setting in future SPDY
    * connections to the same host.
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java
new file mode 100644
index 0000000..5d9a49b
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
+import java.util.List;
+import java.util.zip.Deflater;
+
+final class Spdy3 implements Variant {
+  static final int TYPE_DATA = 0x0;
+  static final int TYPE_SYN_STREAM = 0x1;
+  static final int TYPE_SYN_REPLY = 0x2;
+  static final int TYPE_RST_STREAM = 0x3;
+  static final int TYPE_SETTINGS = 0x4;
+  static final int TYPE_NOOP = 0x5;
+  static final int TYPE_PING = 0x6;
+  static final int TYPE_GOAWAY = 0x7;
+  static final int TYPE_HEADERS = 0x8;
+  static final int TYPE_WINDOW_UPDATE = 0x9;
+  static final int TYPE_CREDENTIAL = 0x10;
+
+  static final int FLAG_FIN = 0x1;
+  static final int FLAG_UNIDIRECTIONAL = 0x2;
+
+  static final int VERSION = 3;
+
+  static final byte[] DICTIONARY;
+  static {
+    try {
+      DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
+          + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
+          + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
+          + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
+          + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
+          + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
+          + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
+          + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
+          + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
+          + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
+          + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
+          + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
+          + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
+          + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
+          + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
+          + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
+          + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
+          + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
+          + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
+          + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
+          + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
+          + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
+          + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
+          + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
+          + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
+          + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
+          + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
+          + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
+          + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
+          + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
+          + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
+          + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
+          + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  @Override public FrameReader newReader(InputStream in, boolean client) {
+    return new Reader(in, client);
+  }
+
+  @Override public FrameWriter newWriter(OutputStream out, boolean client) {
+    return new Writer(out, client);
+  }
+
+  /** Read spdy/3 frames. */
+  static final class Reader implements FrameReader {
+    private final DataInputStream in;
+    private final boolean client;
+    private final NameValueBlockReader nameValueBlockReader;
+
+    Reader(InputStream in, boolean client) {
+      this.in = new DataInputStream(in);
+      this.nameValueBlockReader = new NameValueBlockReader(in);
+      this.client = client;
+    }
+
+    @Override public void readConnectionHeader() {
+    }
+
+    /**
+     * Send the next frame to {@code handler}. Returns true unless there are no
+     * more frames on the stream.
+     */
+    @Override public boolean nextFrame(Handler handler) throws IOException {
+      int w1;
+      try {
+        w1 = in.readInt();
+      } catch (IOException e) {
+        return false; // This might be a normal socket close.
+      }
+      int w2 = in.readInt();
+
+      boolean control = (w1 & 0x80000000) != 0;
+      int flags = (w2 & 0xff000000) >>> 24;
+      int length = (w2 & 0xffffff);
+
+      if (control) {
+        int version = (w1 & 0x7fff0000) >>> 16;
+        int type = (w1 & 0xffff);
+
+        if (version != 3) {
+          throw new ProtocolException("version != 3: " + version);
+        }
+
+        switch (type) {
+          case TYPE_SYN_STREAM:
+            readSynStream(handler, flags, length);
+            return true;
+
+          case TYPE_SYN_REPLY:
+            readSynReply(handler, flags, length);
+            return true;
+
+          case TYPE_RST_STREAM:
+            readRstStream(handler, flags, length);
+            return true;
+
+          case TYPE_SETTINGS:
+            readSettings(handler, flags, length);
+            return true;
+
+          case TYPE_NOOP:
+            if (length != 0) throw ioException("TYPE_NOOP length: %d != 0", length);
+            handler.noop();
+            return true;
+
+          case TYPE_PING:
+            readPing(handler, flags, length);
+            return true;
+
+          case TYPE_GOAWAY:
+            readGoAway(handler, flags, length);
+            return true;
+
+          case TYPE_HEADERS:
+            readHeaders(handler, flags, length);
+            return true;
+
+          case TYPE_WINDOW_UPDATE:
+            readWindowUpdate(handler, flags, length);
+            return true;
+
+          case TYPE_CREDENTIAL:
+            Util.skipByReading(in, length);
+            throw new UnsupportedOperationException("TODO"); // TODO: implement
+
+          default:
+            throw new IOException("Unexpected frame");
+        }
+      } else {
+        int streamId = w1 & 0x7fffffff;
+        boolean inFinished = (flags & FLAG_FIN) != 0;
+        handler.data(inFinished, streamId, in, length);
+        return true;
+      }
+    }
+
+    private void readSynStream(Handler handler, int flags, int length) throws IOException {
+      int w1 = in.readInt();
+      int w2 = in.readInt();
+      int s3 = in.readShort();
+      int streamId = w1 & 0x7fffffff;
+      int associatedStreamId = w2 & 0x7fffffff;
+      int priority = (s3 & 0xe000) >>> 13;
+      int slot = s3 & 0xff;
+      List<String> nameValueBlock = nameValueBlockReader.readNameValueBlock(length - 10);
+
+      boolean inFinished = (flags & FLAG_FIN) != 0;
+      boolean outFinished = (flags & FLAG_UNIDIRECTIONAL) != 0;
+      handler.headers(outFinished, inFinished, streamId, associatedStreamId, priority,
+          nameValueBlock, HeadersMode.SPDY_SYN_STREAM);
+    }
+
+    private void readSynReply(Handler handler, int flags, int length) throws IOException {
+      int w1 = in.readInt();
+      int streamId = w1 & 0x7fffffff;
+      List<String> nameValueBlock = nameValueBlockReader.readNameValueBlock(length - 4);
+      boolean inFinished = (flags & FLAG_FIN) != 0;
+      handler.headers(false, inFinished, streamId, -1, -1, nameValueBlock, HeadersMode.SPDY_REPLY);
+    }
+
+    private void readRstStream(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
+      int streamId = in.readInt() & 0x7fffffff;
+      int errorCodeInt = in.readInt();
+      ErrorCode errorCode = ErrorCode.fromSpdy3Rst(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      handler.rstStream(streamId, errorCode);
+    }
+
+    private void readHeaders(Handler handler, int flags, int length) throws IOException {
+      int w1 = in.readInt();
+      int streamId = w1 & 0x7fffffff;
+      List<String> nameValueBlock = nameValueBlockReader.readNameValueBlock(length - 4);
+      handler.headers(false, false, streamId, -1, -1, nameValueBlock, HeadersMode.SPDY_HEADERS);
+    }
+
+    private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
+      int w1 = in.readInt();
+      int w2 = in.readInt();
+      int streamId = w1 & 0x7fffffff;
+      int deltaWindowSize = w2 & 0x7fffffff;
+      handler.windowUpdate(streamId, deltaWindowSize, false);
+    }
+
+    private void readPing(Handler handler, int flags, int length) throws IOException {
+      if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
+      int id = in.readInt();
+      boolean reply = client == ((id % 2) == 1);
+      handler.ping(reply, id, 0);
+    }
+
+    private void readGoAway(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
+      int lastGoodStreamId = in.readInt() & 0x7fffffff;
+      int errorCodeInt = in.readInt();
+      ErrorCode errorCode = ErrorCode.fromSpdyGoAway(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+      }
+      handler.goAway(lastGoodStreamId, errorCode);
+    }
+
+    private void readSettings(Handler handler, int flags, int length) throws IOException {
+      int numberOfEntries = in.readInt();
+      if (length != 4 + 8 * numberOfEntries) {
+        throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
+      }
+      Settings settings = new Settings();
+      for (int i = 0; i < numberOfEntries; i++) {
+        int w1 = in.readInt();
+        int value = in.readInt();
+        int idFlags = (w1 & 0xff000000) >>> 24;
+        int id = w1 & 0xffffff;
+        settings.set(id, idFlags, value);
+      }
+      boolean clearPrevious = (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0;
+      handler.settings(clearPrevious, settings);
+    }
+
+    private static IOException ioException(String message, Object... args) throws IOException {
+      throw new IOException(String.format(message, args));
+    }
+
+    @Override public void close() throws IOException {
+      Util.closeAll(in, nameValueBlockReader);
+    }
+  }
+
+  /** Write spdy/3 frames. */
+  static final class Writer implements FrameWriter {
+    private final DataOutputStream out;
+    private final ByteArrayOutputStream nameValueBlockBuffer;
+    private final DataOutputStream nameValueBlockOut;
+    private final boolean client;
+
+    Writer(OutputStream out, boolean client) {
+      this.out = new DataOutputStream(out);
+      this.client = client;
+
+      Deflater deflater = new Deflater();
+      deflater.setDictionary(DICTIONARY);
+      nameValueBlockBuffer = new ByteArrayOutputStream();
+      nameValueBlockOut = new DataOutputStream(
+          Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
+    }
+
+    @Override public synchronized void connectionHeader() {
+      // Do nothing: no connection header for SPDY/3.
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      out.flush();
+    }
+
+    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+        int streamId, int associatedStreamId, int priority, int slot, List<String> nameValueBlock)
+        throws IOException {
+      writeNameValueBlockToBuffer(nameValueBlock);
+      int length = 10 + nameValueBlockBuffer.size();
+      int type = TYPE_SYN_STREAM;
+      int flags = (outFinished ? FLAG_FIN : 0) | (inFinished ? FLAG_UNIDIRECTIONAL : 0);
+
+      int unused = 0;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(streamId & 0x7fffffff);
+      out.writeInt(associatedStreamId & 0x7fffffff);
+      out.writeShort((priority & 0x7) << 13 | (unused & 0x1f) << 8 | (slot & 0xff));
+      nameValueBlockBuffer.writeTo(out);
+      out.flush();
+    }
+
+    @Override public synchronized void synReply(
+        boolean outFinished, int streamId, List<String> nameValueBlock) throws IOException {
+      writeNameValueBlockToBuffer(nameValueBlock);
+      int type = TYPE_SYN_REPLY;
+      int flags = (outFinished ? FLAG_FIN : 0);
+      int length = nameValueBlockBuffer.size() + 4;
+
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(streamId & 0x7fffffff);
+      nameValueBlockBuffer.writeTo(out);
+      out.flush();
+    }
+
+    @Override public synchronized void headers(int streamId, List<String> nameValueBlock)
+        throws IOException {
+      writeNameValueBlockToBuffer(nameValueBlock);
+      int flags = 0;
+      int type = TYPE_HEADERS;
+      int length = nameValueBlockBuffer.size() + 4;
+
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(streamId & 0x7fffffff);
+      nameValueBlockBuffer.writeTo(out);
+      out.flush();
+    }
+
+    @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+        throws IOException {
+      if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
+      int flags = 0;
+      int type = TYPE_RST_STREAM;
+      int length = 8;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(streamId & 0x7fffffff);
+      out.writeInt(errorCode.spdyRstCode);
+      out.flush();
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, byte[] data)
+        throws IOException {
+      data(outFinished, streamId, data, 0, data.length);
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, byte[] data,
+        int offset, int byteCount) throws IOException {
+      int flags = (outFinished ? FLAG_FIN : 0);
+      out.writeInt(streamId & 0x7fffffff);
+      out.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff);
+      out.write(data, offset, byteCount);
+    }
+
+    private void writeNameValueBlockToBuffer(List<String> nameValueBlock) throws IOException {
+      nameValueBlockBuffer.reset();
+      int numberOfPairs = nameValueBlock.size() / 2;
+      nameValueBlockOut.writeInt(numberOfPairs);
+      for (String s : nameValueBlock) {
+        nameValueBlockOut.writeInt(s.length());
+        nameValueBlockOut.write(s.getBytes("UTF-8"));
+      }
+      nameValueBlockOut.flush();
+    }
+
+    @Override public synchronized void settings(Settings settings) throws IOException {
+      int type = TYPE_SETTINGS;
+      int flags = 0;
+      int size = settings.size();
+      int length = 4 + size * 8;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(size);
+      for (int i = 0; i <= Settings.COUNT; i++) {
+        if (!settings.isSet(i)) continue;
+        int settingsFlags = settings.flags(i);
+        out.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
+        out.writeInt(settings.get(i));
+      }
+      out.flush();
+    }
+
+    @Override public synchronized void noop() throws IOException {
+      int type = TYPE_NOOP;
+      int length = 0;
+      int flags = 0;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.flush();
+    }
+
+    @Override public synchronized void ping(boolean reply, int payload1, int payload2)
+        throws IOException {
+      boolean payloadIsReply = client != ((payload1 % 2) == 1);
+      if (reply != payloadIsReply) throw new IllegalArgumentException("payload != reply");
+      int type = TYPE_PING;
+      int flags = 0;
+      int length = 4;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(payload1);
+      out.flush();
+    }
+
+    @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
+        throws IOException {
+      if (errorCode.spdyGoAwayCode == -1) throw new IllegalArgumentException();
+      int type = TYPE_GOAWAY;
+      int flags = 0;
+      int length = 8;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(lastGoodStreamId);
+      out.writeInt(errorCode.spdyGoAwayCode);
+      out.flush();
+    }
+
+    @Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
+        throws IOException {
+      int type = TYPE_WINDOW_UPDATE;
+      int flags = 0;
+      int length = 8;
+      out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      out.writeInt(streamId);
+      out.writeInt(deltaWindowSize);
+      out.flush();
+    }
+
+    @Override public void close() throws IOException {
+      Util.closeAll(out, nameValueBlockOut);
+    }
+  }
+}
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
similarity index 62%
rename from src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
index d3a3c9c..b19bd44 100644
--- a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
@@ -46,39 +46,22 @@
   // Internal state of this connection is guarded by 'this'. No blocking
   // operations may be performed while holding this lock!
   //
-  // Socket writes are guarded by spdyWriter.
+  // Socket writes are guarded by frameWriter.
   //
   // Socket reads are unguarded but are only made by the reader thread.
   //
   // Certain operations (like SYN_STREAM) need to synchronize on both the
-  // spdyWriter (to do blocking I/O) and this (to create streams). Such
+  // frameWriter (to do blocking I/O) and this (to create streams). Such
   // operations must synchronize on 'this' last. This ensures that we never
   // wait for a blocking operation while holding 'this'.
 
-  static final int FLAG_FIN = 0x1;
-  static final int FLAG_UNIDIRECTIONAL = 0x2;
-
-  static final int TYPE_DATA = 0x0;
-  static final int TYPE_SYN_STREAM = 0x1;
-  static final int TYPE_SYN_REPLY = 0x2;
-  static final int TYPE_RST_STREAM = 0x3;
-  static final int TYPE_SETTINGS = 0x4;
-  static final int TYPE_NOOP = 0x5;
-  static final int TYPE_PING = 0x6;
-  static final int TYPE_GOAWAY = 0x7;
-  static final int TYPE_HEADERS = 0x8;
-  static final int TYPE_WINDOW_UPDATE = 0x9;
-  static final int TYPE_CREDENTIAL = 0x10;
-  static final int VERSION = 3;
-
-  static final int GOAWAY_OK = 0;
-  static final int GOAWAY_PROTOCOL_ERROR = 1;
-  static final int GOAWAY_INTERNAL_ERROR = 2;
-
   private static final ExecutorService executor = new ThreadPoolExecutor(0,
       Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
       Util.daemonThreadFactory("OkHttp SpdyConnection"));
 
+  /** The protocol variant, like SPDY/3 or HTTP-draft-04/2.0. */
+  final Variant variant;
+
   /** True if this peer initiated the connection. */
   final boolean client;
 
@@ -87,8 +70,8 @@
    * run on the callback executor.
    */
   private final IncomingStreamHandler handler;
-  private final SpdyReader spdyReader;
-  private final SpdyWriter spdyWriter;
+  private final FrameReader frameReader;
+  private final FrameWriter frameWriter;
 
   private final Map<Integer, SpdyStream> streams = new HashMap<Integer, SpdyStream>();
   private final String hostName;
@@ -101,14 +84,15 @@
   private Map<Integer, Ping> pings;
   private int nextPingId;
 
-  /** Lazily-created settings for this connection. */
+  /** Lazily-created settings for the peer. */
   Settings settings;
 
   private SpdyConnection(Builder builder) {
+    variant = builder.variant;
     client = builder.client;
     handler = builder.handler;
-    spdyReader = new SpdyReader(builder.in);
-    spdyWriter = new SpdyWriter(builder.out);
+    frameReader = variant.newReader(builder.in, client);
+    frameWriter = variant.newWriter(builder.out, client);
     nextStreamId = builder.client ? 1 : 2;
     nextPingId = builder.client ? 1 : 2;
 
@@ -138,15 +122,18 @@
   }
 
   private synchronized void setIdle(boolean value) {
-    idleStartTimeNs = value ? System.nanoTime() : 0L;
+    idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE;
   }
 
   /** Returns true if this connection is idle. */
   public synchronized boolean isIdle() {
-    return idleStartTimeNs != 0L;
+    return idleStartTimeNs != Long.MAX_VALUE;
   }
 
-  /** Returns the time in ns when this connection became idle or 0L if connection is not idle. */
+  /**
+   * Returns the time in ns when this connection became idle or Long.MAX_VALUE
+   * if connection is not idle.
+   */
   public synchronized long getIdleStartTimeNs() {
     return idleStartTimeNs;
   }
@@ -155,63 +142,65 @@
    * Returns a new locally-initiated stream.
    *
    * @param out true to create an output stream that we can use to send data
-   * to the remote peer. Corresponds to {@code FLAG_FIN}.
+   *     to the remote peer. Corresponds to {@code FLAG_FIN}.
    * @param in true to create an input stream that the remote peer can use to
-   * send data to us. Corresponds to {@code FLAG_UNIDIRECTIONAL}.
+   *     send data to us. Corresponds to {@code FLAG_UNIDIRECTIONAL}.
    */
   public SpdyStream newStream(List<String> requestHeaders, boolean out, boolean in)
       throws IOException {
-    int flags = (out ? 0 : FLAG_FIN) | (in ? 0 : FLAG_UNIDIRECTIONAL);
+    boolean outFinished = !out;
+    boolean inFinished = !in;
     int associatedStreamId = 0;  // TODO: permit the caller to specify an associated stream?
     int priority = 0; // TODO: permit the caller to specify a priority?
     int slot = 0; // TODO: permit the caller to specify a slot?
     SpdyStream stream;
     int streamId;
 
-    synchronized (spdyWriter) {
+    synchronized (frameWriter) {
       synchronized (this) {
         if (shutdown) {
           throw new IOException("shutdown");
         }
         streamId = nextStreamId;
         nextStreamId += 2;
-        stream = new SpdyStream(streamId, this, flags, priority, slot, requestHeaders, settings);
+        stream = new SpdyStream(
+            streamId, this, outFinished, inFinished, priority, requestHeaders, settings);
         if (stream.isOpen()) {
           streams.put(streamId, stream);
           setIdle(false);
         }
       }
 
-      spdyWriter.synStream(flags, streamId, associatedStreamId, priority, slot, requestHeaders);
+      frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, priority, slot,
+          requestHeaders);
     }
 
     return stream;
   }
 
-  void writeSynReply(int streamId, int flags, List<String> alternating) throws IOException {
-    spdyWriter.synReply(flags, streamId, alternating);
+  void writeSynReply(int streamId, boolean outFinished, List<String> alternating)
+      throws IOException {
+    frameWriter.synReply(outFinished, streamId, alternating);
   }
 
-  /** Writes a complete data frame. */
-  void writeFrame(byte[] bytes, int offset, int length) throws IOException {
-    synchronized (spdyWriter) {
-      spdyWriter.out.write(bytes, offset, length);
-    }
+  public void writeData(int streamId, boolean outFinished, byte[] buffer, int offset, int byteCount)
+      throws IOException {
+    frameWriter.data(outFinished, streamId, buffer, offset, byteCount);
   }
 
-  void writeSynResetLater(final int streamId, final int statusCode) {
+  void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
     executor.submit(new NamedRunnable("OkHttp SPDY Writer %s stream %d", hostName, streamId) {
       @Override public void execute() {
         try {
-          writeSynReset(streamId, statusCode);
+          writeSynReset(streamId, errorCode);
         } catch (IOException ignored) {
         }
       }
     });
   }
 
-  void writeSynReset(int streamId, int statusCode) throws IOException {
-    spdyWriter.rstStream(streamId, statusCode);
+  void writeSynReset(int streamId, ErrorCode statusCode) throws IOException {
+    frameWriter.rstStream(streamId, statusCode);
   }
 
   void writeWindowUpdateLater(final int streamId, final int deltaWindowSize) {
@@ -226,7 +215,7 @@
   }
 
   void writeWindowUpdate(int streamId, int deltaWindowSize) throws IOException {
-    spdyWriter.windowUpdate(streamId, deltaWindowSize);
+    frameWriter.windowUpdate(streamId, deltaWindowSize);
   }
 
   /**
@@ -245,26 +234,28 @@
       if (pings == null) pings = new HashMap<Integer, Ping>();
       pings.put(pingId, ping);
     }
-    writePing(pingId, ping);
+    writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping);
     return ping;
   }
 
-  private void writePingLater(final int streamId, final Ping ping) {
-    executor.submit(new NamedRunnable("OkHttp SPDY Writer %s ping %d", hostName, streamId) {
+  private void writePingLater(
+      final boolean reply, final int payload1, final int payload2, final Ping ping) {
+    executor.submit(new NamedRunnable("OkHttp SPDY Writer %s ping %08x%08x",
+        hostName, payload1, payload2) {
       @Override public void execute() {
         try {
-          writePing(streamId, ping);
+          writePing(reply, payload1, payload2, ping);
         } catch (IOException ignored) {
         }
       }
     });
   }
 
-  private void writePing(int id, Ping ping) throws IOException {
-    synchronized (spdyWriter) {
+  private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException {
+    synchronized (frameWriter) {
       // Observe the sent time immediately before performing I/O.
       if (ping != null) ping.send();
-      spdyWriter.ping(0, id);
+      frameWriter.ping(reply, payload1, payload2);
     }
   }
 
@@ -274,13 +265,11 @@
 
   /** Sends a noop frame to the peer. */
   public void noop() throws IOException {
-    spdyWriter.noop();
+    frameWriter.noop();
   }
 
   public void flush() throws IOException {
-    synchronized (spdyWriter) {
-      spdyWriter.out.flush();
-    }
+    frameWriter.flush();
   }
 
   /**
@@ -288,12 +277,9 @@
    * locally, nor accepted from the remote peer. Existing streams are not
    * impacted. This is intended to permit an endpoint to gracefully stop
    * accepting new requests without harming previously established streams.
-   *
-   * @param statusCode one of {@link #GOAWAY_OK}, {@link
-   * #GOAWAY_INTERNAL_ERROR} or {@link #GOAWAY_PROTOCOL_ERROR}.
    */
-  public void shutdown(int statusCode) throws IOException {
-    synchronized (spdyWriter) {
+  public void shutdown(ErrorCode statusCode) throws IOException {
+    synchronized (frameWriter) {
       int lastGoodStreamId;
       synchronized (this) {
         if (shutdown) {
@@ -302,7 +288,7 @@
         shutdown = true;
         lastGoodStreamId = this.lastGoodStreamId;
       }
-      spdyWriter.goAway(0, lastGoodStreamId, statusCode);
+      frameWriter.goAway(lastGoodStreamId, statusCode);
     }
   }
 
@@ -312,14 +298,14 @@
    * internal executor services.
    */
   @Override public void close() throws IOException {
-    close(GOAWAY_OK, SpdyStream.RST_CANCEL);
+    close(ErrorCode.NO_ERROR, ErrorCode.CANCEL);
   }
 
-  private void close(int shutdownStatusCode, int rstStatusCode) throws IOException {
+  private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException {
     assert (!Thread.holdsLock(this));
     IOException thrown = null;
     try {
-      shutdown(shutdownStatusCode);
+      shutdown(connectionCode);
     } catch (IOException e) {
       thrown = e;
     }
@@ -341,7 +327,7 @@
     if (streamsToClose != null) {
       for (SpdyStream stream : streamsToClose) {
         try {
-          stream.close(rstStatusCode);
+          stream.close(streamCode);
         } catch (IOException e) {
           if (thrown != null) thrown = e;
         }
@@ -355,12 +341,12 @@
     }
 
     try {
-      spdyReader.close();
+      frameReader.close();
     } catch (IOException e) {
       thrown = e;
     }
     try {
-      spdyWriter.close();
+      frameWriter.close();
     } catch (IOException e) {
       if (thrown == null) thrown = e;
     }
@@ -368,12 +354,30 @@
     if (thrown != null) throw thrown;
   }
 
+  /**
+   * Sends a connection header if the current variant requires it. This should
+   * be called after {@link Builder#build} for all new connections.
+   */
+  public void sendConnectionHeader() throws IOException {
+    frameWriter.connectionHeader();
+    frameWriter.settings(new Settings());
+  }
+
+  /**
+   * Reads a connection header if the current variant requires it. This should
+   * be called after {@link Builder#build} for all new connections.
+   */
+  public void readConnectionHeader() throws IOException {
+    frameReader.readConnectionHeader();
+  }
+
   public static class Builder {
     private String hostName;
     private InputStream in;
     private OutputStream out;
     private IncomingStreamHandler handler = IncomingStreamHandler.REFUSE_INCOMING_STREAMS;
-    public boolean client;
+    private Variant variant = Variant.SPDY3;
+    private boolean client;
 
     public Builder(boolean client, Socket socket) throws IOException {
       this("", client, socket.getInputStream(), socket.getOutputStream());
@@ -407,108 +411,119 @@
       return this;
     }
 
+    public Builder spdy3() {
+      this.variant = Variant.SPDY3;
+      return this;
+    }
+
+    public Builder http20Draft04() {
+      this.variant = Variant.HTTP_20_DRAFT_04;
+      return this;
+    }
+
     public SpdyConnection build() {
       return new SpdyConnection(this);
     }
   }
 
-  private class Reader implements Runnable, SpdyReader.Handler {
+  private class Reader implements Runnable, FrameReader.Handler {
     @Override public void run() {
-      int shutdownStatusCode = GOAWAY_INTERNAL_ERROR;
-      int rstStatusCode = SpdyStream.RST_INTERNAL_ERROR;
+      ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
+      ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
       try {
-        while (spdyReader.nextFrame(this)) {
+        while (frameReader.nextFrame(this)) {
         }
-        shutdownStatusCode = GOAWAY_OK;
-        rstStatusCode = SpdyStream.RST_CANCEL;
+        connectionErrorCode = ErrorCode.NO_ERROR;
+        streamErrorCode = ErrorCode.CANCEL;
       } catch (IOException e) {
-        shutdownStatusCode = GOAWAY_PROTOCOL_ERROR;
-        rstStatusCode = SpdyStream.RST_PROTOCOL_ERROR;
+        connectionErrorCode = ErrorCode.PROTOCOL_ERROR;
+        streamErrorCode = ErrorCode.PROTOCOL_ERROR;
       } finally {
         try {
-          close(shutdownStatusCode, rstStatusCode);
+          close(connectionErrorCode, streamErrorCode);
         } catch (IOException ignored) {
         }
       }
     }
 
-    @Override public void data(int flags, int streamId, InputStream in, int length)
+    @Override public void data(boolean inFinished, int streamId, InputStream in, int length)
         throws IOException {
       SpdyStream dataStream = getStream(streamId);
       if (dataStream == null) {
-        writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM);
+        writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
         Util.skipByReading(in, length);
         return;
       }
       dataStream.receiveData(in, length);
-      if ((flags & SpdyConnection.FLAG_FIN) != 0) {
+      if (inFinished) {
         dataStream.receiveFin();
       }
     }
 
-    @Override public void synStream(int flags, int streamId, int associatedStreamId, int priority,
-        int slot, List<String> nameValueBlock) {
-      final SpdyStream synStream;
-      final SpdyStream previous;
+    @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
+        int associatedStreamId, int priority, List<String> nameValueBlock,
+        HeadersMode headersMode) {
+      SpdyStream stream;
       synchronized (SpdyConnection.this) {
-        synStream =
-            new SpdyStream(streamId, SpdyConnection.this, flags, priority, slot, nameValueBlock,
-                settings);
-        if (shutdown) {
+        // If we're shutdown, don't bother with this stream.
+        if (shutdown) return;
+
+        stream = getStream(streamId);
+
+        if (stream == null) {
+          // The headers claim to be for an existing stream, but we don't have one.
+          if (headersMode.failIfStreamAbsent()) {
+            writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
+            return;
+          }
+
+          // If the stream ID is less than the last created ID, assume it's already closed.
+          if (streamId <= lastGoodStreamId) return;
+
+          // If the stream ID is in the client's namespace, assume it's already closed.
+          if (streamId % 2 == nextStreamId % 2) return;
+
+          // Create a stream.
+          final SpdyStream newStream = new SpdyStream(streamId, SpdyConnection.this, outFinished,
+              inFinished, priority, nameValueBlock, settings);
+          lastGoodStreamId = streamId;
+          streams.put(streamId, newStream);
+          executor.submit(new NamedRunnable("OkHttp Callback %s stream %d", hostName, streamId) {
+            @Override public void execute() {
+              try {
+                handler.receive(newStream);
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
           return;
         }
-        lastGoodStreamId = streamId;
-        previous = streams.put(streamId, synStream);
       }
-      if (previous != null) {
-        previous.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
+
+      // The headers claim to be for a new stream, but we already have one.
+      if (headersMode.failIfStreamPresent()) {
+        stream.closeLater(ErrorCode.PROTOCOL_ERROR);
         removeStream(streamId);
         return;
       }
 
-      executor.submit(new NamedRunnable("OkHttp SPDY Callback %s stream %d", hostName, streamId) {
-        @Override public void execute() {
-          try {
-            handler.receive(synStream);
-          } catch (IOException e) {
-            throw new RuntimeException(e);
-          }
-        }
-      });
+      // Update an existing stream.
+      stream.receiveHeaders(nameValueBlock, headersMode);
+      if (inFinished) stream.receiveFin();
     }
 
-    @Override public void synReply(int flags, int streamId, List<String> nameValueBlock)
-        throws IOException {
-      SpdyStream replyStream = getStream(streamId);
-      if (replyStream == null) {
-        writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM);
-        return;
-      }
-      replyStream.receiveReply(nameValueBlock);
-      if ((flags & SpdyConnection.FLAG_FIN) != 0) {
-        replyStream.receiveFin();
-      }
-    }
-
-    @Override public void headers(int flags, int streamId, List<String> nameValueBlock)
-        throws IOException {
-      SpdyStream replyStream = getStream(streamId);
-      if (replyStream != null) {
-        replyStream.receiveHeaders(nameValueBlock);
-      }
-    }
-
-    @Override public void rstStream(int flags, int streamId, int statusCode) {
+    @Override public void rstStream(int streamId, ErrorCode errorCode) {
       SpdyStream rstStream = removeStream(streamId);
       if (rstStream != null) {
-        rstStream.receiveRstStream(statusCode);
+        rstStream.receiveRstStream(errorCode);
       }
     }
 
-    @Override public void settings(int flags, Settings newSettings) {
+    @Override public void settings(boolean clearPrevious, Settings newSettings) {
       SpdyStream[] streamsToNotify = null;
       synchronized (SpdyConnection.this) {
-        if (settings == null || (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0) {
+        if (settings == null || clearPrevious) {
           settings = newSettings;
         } else {
           settings.merge(newSettings);
@@ -522,6 +537,7 @@
           // The synchronization here is ugly. We need to synchronize on 'this' to guard
           // reads to 'settings'. We synchronize on 'stream' to guard the state change.
           // And we need to acquire the 'stream' lock first, since that may block.
+          // TODO: this can block the reader thread until a write completes. That's bad!
           synchronized (stream) {
             synchronized (SpdyConnection.this) {
               stream.receiveSettings(settings);
@@ -534,19 +550,19 @@
     @Override public void noop() {
     }
 
-    @Override public void ping(int flags, int streamId) {
-      if (client != (streamId % 2 == 1)) {
-        // Respond to a client ping if this is a server and vice versa.
-        writePingLater(streamId, null);
-      } else {
-        Ping ping = removePing(streamId);
+    @Override public void ping(boolean reply, int payload1, int payload2) {
+      if (reply) {
+        Ping ping = removePing(payload1);
         if (ping != null) {
           ping.receive();
         }
+      } else {
+        // Send a reply to a client ping if this is a server and vice versa.
+        writePingLater(true, payload1, payload2, null);
       }
     }
 
-    @Override public void goAway(int flags, int lastGoodStreamId, int statusCode) {
+    @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode) {
       synchronized (SpdyConnection.this) {
         shutdown = true;
 
@@ -556,18 +572,28 @@
           Map.Entry<Integer, SpdyStream> entry = i.next();
           int streamId = entry.getKey();
           if (streamId > lastGoodStreamId && entry.getValue().isLocallyInitiated()) {
-            entry.getValue().receiveRstStream(SpdyStream.RST_REFUSED_STREAM);
+            entry.getValue().receiveRstStream(ErrorCode.REFUSED_STREAM);
             i.remove();
           }
         }
       }
     }
 
-    @Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) {
+    @Override public void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl) {
+      if (streamId == 0) {
+        // TODO: honor whole-stream flow control
+        return;
+      }
+
+      // TODO: honor endFlowControl
       SpdyStream stream = getStream(streamId);
       if (stream != null) {
         stream.receiveWindowUpdate(deltaWindowSize);
       }
     }
+
+    @Override public void priority(int streamId, int priority) {
+      // TODO: honor priority.
+    }
   }
 }
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
similarity index 77%
rename from src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
index a6b39be..e550022 100644
--- a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
@@ -26,8 +26,6 @@
 import java.util.List;
 
 import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
-import static com.squareup.okhttp.internal.Util.pokeInt;
-import static java.nio.ByteOrder.BIG_ENDIAN;
 
 /** A logical bidirectional stream. */
 public final class SpdyStream {
@@ -35,35 +33,6 @@
   // Internal state is guarded by this. No long-running or potentially
   // blocking operations are performed while the lock is held.
 
-  private static final int DATA_FRAME_HEADER_LENGTH = 8;
-
-  private static final String[] STATUS_CODE_NAMES = {
-      null,
-      "PROTOCOL_ERROR",
-      "INVALID_STREAM",
-      "REFUSED_STREAM",
-      "UNSUPPORTED_VERSION",
-      "CANCEL",
-      "INTERNAL_ERROR",
-      "FLOW_CONTROL_ERROR",
-      "STREAM_IN_USE",
-      "STREAM_ALREADY_CLOSED",
-      "INVALID_CREDENTIALS",
-      "FRAME_TOO_LARGE"
-  };
-
-  public static final int RST_PROTOCOL_ERROR = 1;
-  public static final int RST_INVALID_STREAM = 2;
-  public static final int RST_REFUSED_STREAM = 3;
-  public static final int RST_UNSUPPORTED_VERSION = 4;
-  public static final int RST_CANCEL = 5;
-  public static final int RST_INTERNAL_ERROR = 6;
-  public static final int RST_FLOW_CONTROL_ERROR = 7;
-  public static final int RST_STREAM_IN_USE = 8;
-  public static final int RST_STREAM_ALREADY_CLOSED = 9;
-  public static final int RST_INVALID_CREDENTIALS = 10;
-  public static final int RST_FRAME_TOO_LARGE = 11;
-
   /**
    * The number of unacknowledged bytes at which the input stream will send
    * the peer a {@code WINDOW_UPDATE} frame. Must be less than this client's
@@ -75,7 +44,6 @@
   private final int id;
   private final SpdyConnection connection;
   private final int priority;
-  private final int slot;
   private long readTimeoutMillis = 0;
   private int writeWindowSize;
 
@@ -93,28 +61,19 @@
    * reasons to abnormally close this stream (such as both peers closing it
    * near-simultaneously) then this is the first reason known to this peer.
    */
-  private int rstStatusCode = -1;
+  private ErrorCode errorCode = null;
 
-  SpdyStream(int id, SpdyConnection connection, int flags, int priority, int slot,
-      List<String> requestHeaders, Settings settings) {
+  SpdyStream(int id, SpdyConnection connection, boolean outFinished, boolean inFinished,
+      int priority, List<String> requestHeaders, Settings settings) {
     if (connection == null) throw new NullPointerException("connection == null");
     if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
     this.id = id;
     this.connection = connection;
+    this.in.finished = inFinished;
+    this.out.finished = outFinished;
     this.priority = priority;
-    this.slot = slot;
     this.requestHeaders = requestHeaders;
 
-    if (isLocallyInitiated()) {
-      // I am the sender
-      in.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
-      out.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
-    } else {
-      // I am the receiver
-      in.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
-      out.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
-    }
-
     setSettings(settings);
   }
 
@@ -129,7 +88,7 @@
    * reports itself as not open. This is because input data is buffered.
    */
   public synchronized boolean isOpen() {
-    if (rstStatusCode != -1) {
+    if (errorCode != null) {
       return false;
     }
     if ((in.finished || in.closed) && (out.finished || out.closed) && responseHeaders != null) {
@@ -158,13 +117,13 @@
    */
   public synchronized List<String> getResponseHeaders() throws IOException {
     try {
-      while (responseHeaders == null && rstStatusCode == -1) {
+      while (responseHeaders == null && errorCode == null) {
         wait();
       }
       if (responseHeaders != null) {
         return responseHeaders;
       }
-      throw new IOException("stream was reset: " + rstStatusString());
+      throw new IOException("stream was reset: " + errorCode);
     } catch (InterruptedException e) {
       InterruptedIOException rethrow = new InterruptedIOException();
       rethrow.initCause(e);
@@ -173,15 +132,11 @@
   }
 
   /**
-   * Returns the reason why this stream was closed, or -1 if it closed
-   * normally or has not yet been closed. Valid reasons are {@link
-   * #RST_PROTOCOL_ERROR}, {@link #RST_INVALID_STREAM}, {@link
-   * #RST_REFUSED_STREAM}, {@link #RST_UNSUPPORTED_VERSION}, {@link
-   * #RST_CANCEL}, {@link #RST_INTERNAL_ERROR} and {@link
-   * #RST_FLOW_CONTROL_ERROR}.
+   * Returns the reason why this stream was closed, or null if it closed
+   * normally or has not yet been closed.
    */
-  public synchronized int getRstStatusCode() {
-    return rstStatusCode;
+  public synchronized ErrorCode getErrorCode() {
+    return errorCode;
   }
 
   /**
@@ -192,7 +147,7 @@
    */
   public void reply(List<String> responseHeaders, boolean out) throws IOException {
     assert (!Thread.holdsLock(SpdyStream.this));
-    int flags = 0;
+    boolean outFinished = false;
     synchronized (this) {
       if (responseHeaders == null) {
         throw new NullPointerException("responseHeaders == null");
@@ -206,10 +161,10 @@
       this.responseHeaders = responseHeaders;
       if (!out) {
         this.out.finished = true;
-        flags |= SpdyConnection.FLAG_FIN;
+        outFinished = true;
       }
     }
-    connection.writeSynReply(id, flags, responseHeaders);
+    connection.writeSynReply(id, outFinished, responseHeaders);
   }
 
   /**
@@ -248,7 +203,7 @@
    * Abnormally terminate this stream. This blocks until the {@code RST_STREAM}
    * frame has been transmitted.
    */
-  public void close(int rstStatusCode) throws IOException {
+  public void close(ErrorCode rstStatusCode) throws IOException {
     if (!closeInternal(rstStatusCode)) {
       return; // Already closed.
     }
@@ -259,68 +214,61 @@
    * Abnormally terminate this stream. This enqueues a {@code RST_STREAM}
    * frame and returns immediately.
    */
-  public void closeLater(int rstStatusCode) {
-    if (!closeInternal(rstStatusCode)) {
+  public void closeLater(ErrorCode errorCode) {
+    if (!closeInternal(errorCode)) {
       return; // Already closed.
     }
-    connection.writeSynResetLater(id, rstStatusCode);
+    connection.writeSynResetLater(id, errorCode);
   }
 
   /** Returns true if this stream was closed. */
-  private boolean closeInternal(int rstStatusCode) {
+  private boolean closeInternal(ErrorCode errorCode) {
     assert (!Thread.holdsLock(this));
     synchronized (this) {
-      if (this.rstStatusCode != -1) {
+      if (this.errorCode != null) {
         return false;
       }
       if (in.finished && out.finished) {
         return false;
       }
-      this.rstStatusCode = rstStatusCode;
+      this.errorCode = errorCode;
       notifyAll();
     }
     connection.removeStream(id);
     return true;
   }
 
-  void receiveReply(List<String> strings) throws IOException {
+  void receiveHeaders(List<String> headers, HeadersMode headersMode) {
     assert (!Thread.holdsLock(SpdyStream.this));
-    boolean streamInUseError = false;
+    ErrorCode errorCode = null;
     boolean open = true;
     synchronized (this) {
-      if (isLocallyInitiated() && responseHeaders == null) {
-        responseHeaders = strings;
-        open = isOpen();
-        notifyAll();
+      if (responseHeaders == null) {
+        if (headersMode.failIfHeadersAbsent()) {
+          errorCode = ErrorCode.PROTOCOL_ERROR;
+        } else {
+          responseHeaders = headers;
+          open = isOpen();
+          notifyAll();
+        }
       } else {
-        streamInUseError = true;
+        if (headersMode.failIfHeadersPresent()) {
+          errorCode = ErrorCode.STREAM_IN_USE;
+        } else {
+          List<String> newHeaders = new ArrayList<String>();
+          newHeaders.addAll(responseHeaders);
+          newHeaders.addAll(headers);
+          this.responseHeaders = newHeaders;
+        }
       }
     }
-    if (streamInUseError) {
-      closeLater(SpdyStream.RST_STREAM_IN_USE);
+    if (errorCode != null) {
+      closeLater(errorCode);
     } else if (!open) {
       connection.removeStream(id);
     }
   }
 
-  void receiveHeaders(List<String> headers) throws IOException {
-    assert (!Thread.holdsLock(SpdyStream.this));
-    boolean protocolError = false;
-    synchronized (this) {
-      if (responseHeaders != null) {
-        List<String> newHeaders = new ArrayList<String>();
-        newHeaders.addAll(responseHeaders);
-        newHeaders.addAll(headers);
-        this.responseHeaders = newHeaders;
-      } else {
-        protocolError = true;
-      }
-    }
-    if (protocolError) {
-      closeLater(SpdyStream.RST_PROTOCOL_ERROR);
-    }
-  }
-
   void receiveData(InputStream in, int length) throws IOException {
     assert (!Thread.holdsLock(SpdyStream.this));
     this.in.receive(in, length);
@@ -339,18 +287,20 @@
     }
   }
 
-  synchronized void receiveRstStream(int statusCode) {
-    if (rstStatusCode == -1) {
-      rstStatusCode = statusCode;
+  synchronized void receiveRstStream(ErrorCode errorCode) {
+    if (this.errorCode == null) {
+      this.errorCode = errorCode;
       notifyAll();
     }
   }
 
   private void setSettings(Settings settings) {
+    // TODO: For HTTP/2.0, also adjust the stream flow control window size
+    // by the difference between the new value and the old value.
     assert (Thread.holdsLock(connection)); // Because 'settings' is guarded by 'connection'.
-    this.writeWindowSize =
-        settings != null ? settings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE)
-            : Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+    this.writeWindowSize = settings != null
+        ? settings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE)
+        : Settings.DEFAULT_INITIAL_WINDOW_SIZE;
   }
 
   void receiveSettings(Settings settings) {
@@ -364,19 +314,10 @@
     notifyAll();
   }
 
-  private String rstStatusString() {
-    return rstStatusCode > 0 && rstStatusCode < STATUS_CODE_NAMES.length
-        ? STATUS_CODE_NAMES[rstStatusCode] : Integer.toString(rstStatusCode);
-  }
-
   int getPriority() {
     return priority;
   }
 
-  int getSlot() {
-    return slot;
-  }
-
   /**
    * An input stream that reads the incoming data frames of a stream. Although
    * this class uses synchronization to safely receive incoming data frames,
@@ -496,7 +437,7 @@
         remaining = readTimeoutMillis;
       }
       try {
-        while (pos == -1 && !finished && !closed && rstStatusCode == -1) {
+        while (pos == -1 && !finished && !closed && errorCode == null) {
           if (readTimeoutMillis == 0) {
             SpdyStream.this.wait();
           } else if (remaining > 0) {
@@ -534,7 +475,7 @@
       // If the peer sends more data than we can handle, discard it and close the connection.
       if (flowControlError) {
         Util.skipByReading(in, byteCount);
-        closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR);
+        closeLater(ErrorCode.FLOW_CONTROL_ERROR);
         return;
       }
 
@@ -583,8 +524,8 @@
       if (closed) {
         throw new IOException("stream closed");
       }
-      if (rstStatusCode != -1) {
-        throw new IOException("stream was reset: " + rstStatusString());
+      if (errorCode != null) {
+        throw new IOException("stream was reset: " + errorCode);
       }
     }
   }
@@ -602,7 +543,7 @@
       // is safe because the input stream is closed (we won't use any
       // further bytes) and the output stream is either finished or closed
       // (so RSTing both streams doesn't cause harm).
-      SpdyStream.this.close(RST_CANCEL);
+      SpdyStream.this.close(ErrorCode.CANCEL);
     } else if (!open) {
       connection.removeStream(id);
     }
@@ -614,7 +555,7 @@
    */
   private final class SpdyDataOutputStream extends OutputStream {
     private final byte[] buffer = new byte[8192];
-    private int pos = DATA_FRAME_HEADER_LENGTH;
+    private int pos = 0;
 
     /** True if the caller has closed this stream. */
     private boolean closed;
@@ -656,7 +597,7 @@
     @Override public void flush() throws IOException {
       assert (!Thread.holdsLock(SpdyStream.this));
       checkNotClosed();
-      if (pos > DATA_FRAME_HEADER_LENGTH) {
+      if (pos > 0) {
         writeFrame(false);
         connection.flush();
       }
@@ -677,22 +618,16 @@
       cancelStreamIfNecessary();
     }
 
-    private void writeFrame(boolean last) throws IOException {
+    private void writeFrame(boolean outFinished) throws IOException {
       assert (!Thread.holdsLock(SpdyStream.this));
 
-      int length = pos - DATA_FRAME_HEADER_LENGTH;
+      int length = pos;
       synchronized (SpdyStream.this) {
-        waitUntilWritable(length, last);
+        waitUntilWritable(length, outFinished);
         unacknowledgedBytes += length;
       }
-      int flags = 0;
-      if (last) {
-        flags |= SpdyConnection.FLAG_FIN;
-      }
-      pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN);
-      pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN);
-      connection.writeFrame(buffer, 0, pos);
-      pos = DATA_FRAME_HEADER_LENGTH;
+      connection.writeData(id, outFinished, buffer, 0, pos);
+      pos = 0;
     }
 
     /**
@@ -711,8 +646,8 @@
             throw new IOException("stream closed");
           } else if (finished) {
             throw new IOException("stream finished");
-          } else if (rstStatusCode != -1) {
-            throw new IOException("stream was reset: " + rstStatusString());
+          } else if (errorCode != null) {
+            throw new IOException("stream was reset: " + errorCode);
           }
         }
       } catch (InterruptedException e) {
@@ -726,8 +661,8 @@
           throw new IOException("stream closed");
         } else if (finished) {
           throw new IOException("stream finished");
-        } else if (rstStatusCode != -1) {
-          throw new IOException("stream was reset: " + rstStatusString());
+        } else if (errorCode != null) {
+          throw new IOException("stream was reset: " + errorCode);
         }
       }
     }
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java
new file mode 100644
index 0000000..06de317
--- /dev/null
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Variant.java
@@ -0,0 +1,37 @@
+/*
+ * 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.spdy;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** A version and dialect of the framed socket protocol. */
+interface Variant {
+  Variant SPDY3 = new Spdy3();
+  Variant HTTP_20_DRAFT_04 = new Http20Draft04();
+
+  /**
+   * @param client true if this is the HTTP client's reader, reading frames from
+   *     a peer SPDY or HTTP/2 server.
+   */
+  FrameReader newReader(InputStream in, boolean client);
+
+  /**
+   * @param client true if this is the HTTP client's writer, writing frames to a
+   *     peer SPDY or HTTP/2 server.
+   */
+  FrameWriter newWriter(OutputStream out, boolean client);
+}
diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackTest.java
new file mode 100644
index 0000000..0bcadce
--- /dev/null
+++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.spdy;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class HpackTest {
+  private ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+  private final Hpack.Writer hpackWriter = new Hpack.Writer(new DataOutputStream(bytesOut));
+
+  @Test public void readSingleByteInt() throws IOException {
+    assertEquals(10, new Hpack.Reader(byteStream(), true).readInt(10, 31));
+    assertEquals(10, new Hpack.Reader(byteStream(), true).readInt(0xe0 | 10, 31));
+  }
+
+  @Test public void readMultibyteInt() throws IOException {
+    assertEquals(1337, new Hpack.Reader(byteStream(154, 10), true).readInt(31, 31));
+  }
+
+  @Test public void writeSingleByteInt() throws IOException {
+    hpackWriter.writeInt(10, 31, 0);
+    assertBytes(10);
+    hpackWriter.writeInt(10, 31, 0xe0);
+    assertBytes(0xe0 | 10);
+  }
+
+  @Test public void writeMultibyteInt() throws IOException {
+    hpackWriter.writeInt(1337, 31, 0);
+    assertBytes(31, 154, 10);
+    hpackWriter.writeInt(1337, 31, 0xe0);
+    assertBytes(0xe0 | 31, 154, 10);
+  }
+
+  @Test public void max31BitValue() throws IOException {
+    hpackWriter.writeInt(0x7fffffff, 31, 0);
+    assertBytes(31, 224, 255, 255, 255, 7);
+    assertEquals(0x7fffffff,
+        new Hpack.Reader(byteStream(224, 255, 255, 255, 7), true).readInt(31, 31));
+  }
+
+  @Test public void prefixMask() throws IOException {
+    hpackWriter.writeInt(31, 31, 0);
+    assertBytes(31, 0);
+    assertEquals(31, new Hpack.Reader(byteStream(0), true).readInt(31, 31));
+  }
+
+  @Test public void prefixMaskMinusOne() throws IOException {
+    hpackWriter.writeInt(30, 31, 0);
+    assertBytes(30);
+    assertEquals(31, new Hpack.Reader(byteStream(0), true).readInt(31, 31));
+  }
+
+  @Test public void zero() throws IOException {
+    hpackWriter.writeInt(0, 31, 0);
+    assertBytes(0);
+    assertEquals(0, new Hpack.Reader(byteStream(), true).readInt(0, 31));
+  }
+
+  @Test public void headerName() throws IOException {
+    hpackWriter.writeString("foo");
+    assertBytes(3, 'f', 'o', 'o');
+    assertEquals("foo", new Hpack.Reader(byteStream(3, 'f', 'o', 'o'), true).readString());
+  }
+
+  @Test public void emptyHeaderName() throws IOException {
+    hpackWriter.writeString("");
+    assertBytes(0);
+    assertEquals("", new Hpack.Reader(byteStream(0), true).readString());
+  }
+
+  @Test public void headersRoundTrip() throws IOException {
+    List<String> sentHeaders = Arrays.asList("name", "value");
+    hpackWriter.writeHeaders(sentHeaders);
+    ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
+    Hpack.Reader reader = new Hpack.Reader(new DataInputStream(bytesIn), true);
+    reader.readHeaders(bytesOut.size());
+    reader.emitReferenceSet();
+    List<String> receivedHeaders = reader.getAndReset();
+    assertEquals(sentHeaders, receivedHeaders);
+  }
+
+  private DataInputStream byteStream(int... bytes) {
+    byte[] data = intArrayToByteArray(bytes);
+    return new DataInputStream(new ByteArrayInputStream(data));
+  }
+
+  private void assertBytes(int... bytes) {
+    byte[] expected = intArrayToByteArray(bytes);
+    byte[] actual = bytesOut.toByteArray();
+    assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+    bytesOut.reset(); // So the next test starts with a clean slate.
+  }
+
+  private byte[] intArrayToByteArray(int[] bytes) {
+    byte[] data = new byte[bytes.length];
+    for (int i = 0; i < bytes.length; i++) {
+      data[i] = (byte) bytes[i];
+    }
+    return data;
+  }
+}
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
similarity index 71%
rename from src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
rename to okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
index 088061b..99ddc6d 100644
--- a/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
+++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
@@ -37,8 +37,9 @@
 /** Replays prerecorded outgoing frames and records incoming frames. */
 public final class MockSpdyPeer implements Closeable {
   private int frameCount = 0;
+  private final boolean client;
   private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
-  private final SpdyWriter spdyWriter = new SpdyWriter(bytesOut);
+  private final FrameWriter frameWriter;
   private final List<OutFrame> outFrames = new ArrayList<OutFrame>();
   private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
   private int port;
@@ -46,13 +47,27 @@
   private ServerSocket serverSocket;
   private Socket socket;
 
+  public MockSpdyPeer(boolean client) {
+    this.client = client;
+    this.frameWriter = Variant.SPDY3.newWriter(bytesOut, client);
+  }
+
   public void acceptFrame() {
     frameCount++;
   }
 
-  public SpdyWriter sendFrame() {
+  public FrameWriter sendFrame() {
     outFrames.add(new OutFrame(frameCount++, bytesOut.size(), Integer.MAX_VALUE));
-    return spdyWriter;
+    return frameWriter;
+  }
+
+  /**
+   * Sends a manually-constructed frame. This is useful to test frames that
+   * won't be generated naturally.
+   */
+  public void sendFrame(byte[] frame) throws IOException {
+    outFrames.add(new OutFrame(frameCount++, bytesOut.size(), Integer.MAX_VALUE));
+    bytesOut.write(frame);
   }
 
   /**
@@ -60,9 +75,9 @@
    * useful for testing error handling as the truncated frame will be
    * malformed.
    */
-  public SpdyWriter sendTruncatedFrame(int truncateToLength) {
+  public FrameWriter sendTruncatedFrame(int truncateToLength) {
     outFrames.add(new OutFrame(frameCount++, bytesOut.size(), truncateToLength));
-    return spdyWriter;
+    return frameWriter;
   }
 
   public int getPort() {
@@ -94,7 +109,7 @@
     socket = serverSocket.accept();
     OutputStream out = socket.getOutputStream();
     InputStream in = socket.getInputStream();
-    SpdyReader reader = new SpdyReader(in);
+    FrameReader reader = Variant.SPDY3.newReader(in, client);
 
     Iterator<OutFrame> outFramesIterator = outFrames.iterator();
     byte[] outBytes = bytesOut.toByteArray();
@@ -158,105 +173,93 @@
     }
   }
 
-  public static class InFrame implements SpdyReader.Handler {
+  public static class InFrame implements FrameReader.Handler {
     public final int sequence;
-    public final SpdyReader reader;
+    public final FrameReader reader;
     public int type = -1;
-    public int flags;
+    public boolean clearPrevious;
+    public boolean outFinished;
+    public boolean inFinished;
     public int streamId;
     public int associatedStreamId;
     public int priority;
-    public int slot;
-    public int statusCode;
+    public ErrorCode errorCode;
     public int deltaWindowSize;
     public List<String> nameValueBlock;
     public byte[] data;
     public Settings settings;
+    public HeadersMode headersMode;
 
-    public InFrame(int sequence, SpdyReader reader) {
+    public InFrame(int sequence, FrameReader reader) {
       this.sequence = sequence;
       this.reader = reader;
     }
 
-    @Override public void settings(int flags, Settings settings) {
+    @Override public void settings(boolean clearPrevious, Settings settings) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_SETTINGS;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_SETTINGS;
+      this.clearPrevious = clearPrevious;
       this.settings = settings;
     }
 
-    @Override public void synStream(int flags, int streamId, int associatedStreamId, int priority,
-        int slot, List<String> nameValueBlock) {
+    @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
+        int associatedStreamId, int priority, List<String> nameValueBlock,
+        HeadersMode headersMode) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_SYN_STREAM;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_HEADERS;
+      this.outFinished = outFinished;
+      this.inFinished = inFinished;
       this.streamId = streamId;
       this.associatedStreamId = associatedStreamId;
       this.priority = priority;
-      this.slot = slot;
       this.nameValueBlock = nameValueBlock;
+      this.headersMode = headersMode;
     }
 
-    @Override public void synReply(int flags, int streamId, List<String> nameValueBlock) {
-      if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_SYN_REPLY;
-      this.streamId = streamId;
-      this.flags = flags;
-      this.nameValueBlock = nameValueBlock;
-    }
-
-    @Override public void headers(int flags, int streamId, List<String> nameValueBlock) {
-      if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_HEADERS;
-      this.streamId = streamId;
-      this.flags = flags;
-      this.nameValueBlock = nameValueBlock;
-    }
-
-    @Override public void data(int flags, int streamId, InputStream in, int length)
+    @Override public void data(boolean inFinished, int streamId, InputStream in, int length)
         throws IOException {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_DATA;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_DATA;
+      this.inFinished = inFinished;
       this.streamId = streamId;
       this.data = new byte[length];
       Util.readFully(in, this.data);
     }
 
-    @Override public void rstStream(int flags, int streamId, int statusCode) {
+    @Override public void rstStream(int streamId, ErrorCode errorCode) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_RST_STREAM;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_RST_STREAM;
       this.streamId = streamId;
-      this.statusCode = statusCode;
+      this.errorCode = errorCode;
     }
 
-    @Override public void ping(int flags, int streamId) {
+    @Override public void ping(boolean reply, int payload1, int payload2) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_PING;
-      this.flags = flags;
-      this.streamId = streamId;
+      this.type = Spdy3.TYPE_PING;
+      this.streamId = payload1;
     }
 
     @Override public void noop() {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_NOOP;
+      this.type = Spdy3.TYPE_NOOP;
     }
 
-    @Override public void goAway(int flags, int lastGoodStreamId, int statusCode) {
+    @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_GOAWAY;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_GOAWAY;
       this.streamId = lastGoodStreamId;
-      this.statusCode = statusCode;
+      this.errorCode = errorCode;
     }
 
-    @Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) {
+    @Override public void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl) {
       if (this.type != -1) throw new IllegalStateException();
-      this.type = SpdyConnection.TYPE_WINDOW_UPDATE;
-      this.flags = flags;
+      this.type = Spdy3.TYPE_WINDOW_UPDATE;
       this.streamId = streamId;
       this.deltaWindowSize = deltaWindowSize;
     }
+
+    @Override public void priority(int streamId, int priority) {
+      throw new UnsupportedOperationException();
+    }
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
rename to okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
similarity index 74%
rename from src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
rename to okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
index 7dd23f6..b0f23f4 100644
--- a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
+++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
@@ -16,6 +16,7 @@
 
 package com.squareup.okhttp.internal.spdy;
 
+import com.squareup.okhttp.internal.Base64;
 import com.squareup.okhttp.internal.Util;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -29,26 +30,24 @@
 import org.junit.Test;
 
 import static com.squareup.okhttp.internal.Util.UTF_8;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.CANCEL;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.FLOW_CONTROL_ERROR;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.INTERNAL_ERROR;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.INVALID_STREAM;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.PROTOCOL_ERROR;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.REFUSED_STREAM;
+import static com.squareup.okhttp.internal.spdy.ErrorCode.STREAM_IN_USE;
 import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_FIN;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_UNIDIRECTIONAL;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_INTERNAL_ERROR;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_PROTOCOL_ERROR;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_DATA;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_GOAWAY;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_NOOP;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_PING;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_RST_STREAM;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_REPLY;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_STREAM;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_WINDOW_UPDATE;
-import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_FLOW_CONTROL_ERROR;
-import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_INVALID_STREAM;
-import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_PROTOCOL_ERROR;
-import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_REFUSED_STREAM;
-import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_STREAM_IN_USE;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_DATA;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_GOAWAY;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_HEADERS;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_NOOP;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_PING;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_RST_STREAM;
+import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_WINDOW_UPDATE;
 import static com.squareup.okhttp.internal.spdy.SpdyStream.WINDOW_UPDATE_THRESHOLD;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -58,7 +57,7 @@
       throw new AssertionError();
     }
   };
-  private final MockSpdyPeer peer = new MockSpdyPeer();
+  private final MockSpdyPeer peer = new MockSpdyPeer(false);
 
   @After public void tearDown() throws Exception {
     peer.close();
@@ -67,8 +66,8 @@
   @Test public void clientCreatesStreamAndServerReplies() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
-    peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().data(true, 1, "robot".getBytes("UTF-8"));
     peer.acceptFrame(); // DATA
     peer.play();
 
@@ -82,8 +81,10 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(0, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertFalse(synStream.inFinished);
+    assertFalse(synStream.outFinished);
     assertEquals(1, synStream.streamId);
     assertEquals(0, synStream.associatedStreamId);
     assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock);
@@ -93,7 +94,7 @@
 
   @Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception {
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("b", "banana"));
     peer.play();
 
     SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
@@ -107,8 +108,8 @@
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(FLAG_FIN, 1, Arrays.asList("a", "android"));
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().synReply(true, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
@@ -120,14 +121,15 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
   }
 
   @Test public void serverCreatesStreamAndClientReplies() throws Exception {
     // write the mocking script
-    peer.sendFrame().synStream(0, 2, 0, 5, 129, Arrays.asList("a", "android"));
+    peer.sendFrame().synStream(false, false, 2, 0, 5, 129, Arrays.asList("a", "android"));
     peer.acceptFrame(); // SYN_REPLY
     peer.play();
 
@@ -137,9 +139,8 @@
       @Override public void receive(SpdyStream stream) throws IOException {
         receiveCount.incrementAndGet();
         assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
-        assertEquals(-1, stream.getRstStatusCode());
+        assertEquals(null, stream.getErrorCode());
         assertEquals(5, stream.getPriority());
-        assertEquals(129, stream.getSlot());
         stream.reply(Arrays.asList("b", "banana"), true);
       }
     };
@@ -147,8 +148,9 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame reply = peer.takeFrame();
-    assertEquals(TYPE_SYN_REPLY, reply.type);
-    assertEquals(0, reply.flags);
+    assertEquals(TYPE_HEADERS, reply.type);
+    assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode);
+    assertFalse(reply.inFinished);
     assertEquals(2, reply.streamId);
     assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
     assertEquals(1, receiveCount.get());
@@ -156,7 +158,7 @@
 
   @Test public void replyWithNoData() throws Exception {
     // write the mocking script
-    peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
+    peer.sendFrame().synStream(false, false, 2, 0, 0, 0, Arrays.asList("a", "android"));
     peer.acceptFrame(); // SYN_REPLY
     peer.play();
 
@@ -172,8 +174,9 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame reply = peer.takeFrame();
-    assertEquals(TYPE_SYN_REPLY, reply.type);
-    assertEquals(FLAG_FIN, reply.flags);
+    assertEquals(TYPE_HEADERS, reply.type);
+    assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode);
+    assertTrue(reply.inFinished);
     assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
     assertEquals(1, receiveCount.get());
   }
@@ -192,12 +195,11 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_NOOP, ping.type);
-    assertEquals(0, ping.flags);
   }
 
   @Test public void serverPingsClient() throws Exception {
     // write the mocking script
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -207,20 +209,19 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
-    assertEquals(0, ping.flags);
     assertEquals(2, ping.streamId);
   }
 
   @Test public void clientPingsServer() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     Ping ping = connection.ping();
     assertTrue(ping.roundTripTime() > 0);
     assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
@@ -228,16 +229,15 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
     assertEquals(TYPE_PING, pingFrame.type);
-    assertEquals(0, pingFrame.flags);
     assertEquals(1, pingFrame.streamId);
   }
 
   @Test public void unexpectedPingIsNotReturned() throws Exception {
     // write the mocking script
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 3); // This ping will not be returned.
-    peer.sendFrame().ping(0, 4);
+    peer.sendFrame().ping(true, 3, 0); // This ping will not be returned.
+    peer.sendFrame().ping(false, 4, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -255,15 +255,15 @@
     // write the mocking script
     Settings settings = new Settings();
     settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10);
-    peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings);
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().settings(settings);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
 
     peer.takeFrame(); // Guarantees that the Settings frame has been processed.
     synchronized (connection) {
@@ -277,20 +277,20 @@
     settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100);
     settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200);
     settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300);
-    peer.sendFrame().settings(0, settings1);
+    peer.sendFrame().settings(settings1);
     Settings settings2 = new Settings();
     settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400);
     settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500);
     settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600);
-    peer.sendFrame().settings(0, settings2);
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().settings(settings2);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame();
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
 
     peer.takeFrame(); // Guarantees that the Settings frame has been processed.
     synchronized (connection) {
@@ -307,9 +307,9 @@
 
   @Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception {
     // write the mocking script
-    peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8"));
+    peer.sendFrame().data(true, 42, "bogus".getBytes("UTF-8"));
     peer.acceptFrame(); // RST_STREAM
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -319,18 +319,17 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
-    assertEquals(0, rstStream.flags);
     assertEquals(42, rstStream.streamId);
-    assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
+    assertEquals(INVALID_STREAM, rstStream.errorCode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(2, ping.streamId);
   }
 
   @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception {
     // write the mocking script
-    peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android"));
+    peer.sendFrame().synReply(false, 42, Arrays.asList("a", "android"));
     peer.acceptFrame(); // RST_STREAM
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -340,9 +339,8 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
-    assertEquals(0, rstStream.flags);
     assertEquals(42, rstStream.streamId);
-    assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
+    assertEquals(INVALID_STREAM, rstStream.errorCode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(2, ping.streamId);
   }
@@ -350,17 +348,17 @@
   @Test public void clientClosesClientOutputStream() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("b", "banana"));
     peer.acceptFrame(); // TYPE_DATA
     peer.acceptFrame(); // TYPE_DATA with FLAG_FIN
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, false);
     OutputStream out = stream.getOutputStream();
     out.write("square".getBytes(UTF_8));
@@ -378,15 +376,17 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(FLAG_UNIDIRECTIONAL, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertFalse(synStream.inFinished);
+    assertTrue(synStream.outFinished);
     MockSpdyPeer.InFrame data = peer.takeFrame();
     assertEquals(TYPE_DATA, data.type);
-    assertEquals(0, data.flags);
+    assertFalse(data.inFinished);
     assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
     MockSpdyPeer.InFrame fin = peer.takeFrame();
     assertEquals(TYPE_DATA, fin.type);
-    assertEquals(FLAG_FIN, fin.flags);
+    assertTrue(fin.inFinished);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     assertEquals(1, ping.streamId);
@@ -395,16 +395,16 @@
   @Test public void serverClosesClientOutputStream() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().rstStream(1, SpdyStream.RST_CANCEL);
+    peer.sendFrame().rstStream(1, CANCEL);
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.acceptFrame(); // DATA
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
     OutputStream out = stream.getOutputStream();
     connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received.
@@ -419,15 +419,18 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(0, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertFalse(synStream.inFinished);
+    assertFalse(synStream.outFinished);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     assertEquals(1, ping.streamId);
     MockSpdyPeer.InFrame data = peer.takeFrame();
     assertEquals(TYPE_DATA, data.type);
     assertEquals(1, data.streamId);
-    assertEquals(FLAG_FIN, data.flags);
+    assertTrue(data.inFinished);
+    assertFalse(data.outFinished);
   }
 
   /**
@@ -441,9 +444,9 @@
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
     InputStream in = stream.getInputStream();
     OutputStream out = stream.getOutputStream();
@@ -464,11 +467,13 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertTrue(synStream.inFinished);
+    assertFalse(synStream.outFinished);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
-    assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
+    assertEquals(CANCEL, rstStream.errorCode);
   }
 
   /**
@@ -484,9 +489,9 @@
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
     InputStream in = stream.getInputStream();
     OutputStream out = stream.getOutputStream();
@@ -504,30 +509,33 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(0, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertFalse(synStream.inFinished);
+    assertFalse(synStream.outFinished);
     MockSpdyPeer.InFrame data = peer.takeFrame();
     assertEquals(TYPE_DATA, data.type);
     assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
     MockSpdyPeer.InFrame fin = peer.takeFrame();
     assertEquals(TYPE_DATA, fin.type);
-    assertEquals(FLAG_FIN, fin.flags);
+    assertTrue(fin.inFinished);
+    assertFalse(fin.outFinished);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
-    assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
+    assertEquals(CANCEL, rstStream.errorCode);
   }
 
   @Test public void serverClosesClientInputStream() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
-    peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("b", "banana"));
+    peer.sendFrame().data(true, 1, "square".getBytes(UTF_8));
     peer.play();
 
     // play it back
-    SpdyConnection connection =
-        new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
-            .build();
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+        .handler(REJECT_INCOMING_STREAMS)
+        .build();
     SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
     InputStream in = stream.getInputStream();
     assertStreamData("square", in);
@@ -535,17 +543,19 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
-    assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
+    assertTrue(synStream.inFinished);
+    assertFalse(synStream.outFinished);
   }
 
   @Test public void remoteDoubleSynReply() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().synReply(false, 1, Arrays.asList("b", "banana"));
+    peer.sendFrame().ping(true, 1, 0);
     peer.acceptFrame(); // RST_STREAM
     peer.play();
 
@@ -563,21 +573,21 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
     assertEquals(1, rstStream.streamId);
-    assertEquals(0, rstStream.flags);
-    assertEquals(RST_STREAM_IN_USE, rstStream.statusCode);
+    assertEquals(STREAM_IN_USE, rstStream.errorCode);
   }
 
   @Test public void remoteDoubleSynStream() throws Exception {
     // write the mocking script
-    peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
+    peer.sendFrame().synStream(false, false, 2, 0, 0, 0, Arrays.asList("a", "android"));
     peer.acceptFrame(); // SYN_REPLY
-    peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "banana"));
+    peer.sendFrame().synStream(false, false, 2, 0, 0, 0, Arrays.asList("b", "banana"));
     peer.acceptFrame(); // RST_STREAM
     peer.play();
 
@@ -587,7 +597,7 @@
       @Override public void receive(SpdyStream stream) throws IOException {
         receiveCount.incrementAndGet();
         assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
-        assertEquals(-1, stream.getRstStatusCode());
+        assertEquals(null, stream.getErrorCode());
         stream.reply(Arrays.asList("c", "cola"), true);
       }
     };
@@ -595,22 +605,22 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame reply = peer.takeFrame();
-    assertEquals(TYPE_SYN_REPLY, reply.type);
+    assertEquals(TYPE_HEADERS, reply.type);
+    assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
     assertEquals(2, rstStream.streamId);
-    assertEquals(0, rstStream.flags);
-    assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
+    assertEquals(PROTOCOL_ERROR, rstStream.errorCode);
     assertEquals(1, receiveCount.intValue());
   }
 
   @Test public void remoteSendsDataAfterInFinished() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
-    peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
-    peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "c3po".getBytes("UTF-8")); // Ignored.
-    peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().data(true, 1, "robot".getBytes("UTF-8"));
+    peer.sendFrame().data(true, 1, "c3po".getBytes("UTF-8")); // Ignored.
+    peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded.
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -622,20 +632,20 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     assertEquals(2, ping.streamId);
-    assertEquals(0, ping.flags);
   }
 
   @Test public void remoteSendsTooMuchData() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
-    peer.sendFrame().data(0, 1, new byte[64 * 1024 + 1]);
+    peer.sendFrame().synReply(false, 1, Arrays.asList("b", "banana"));
+    peer.sendFrame().data(false, 1, new byte[64 * 1024 + 1]);
     peer.acceptFrame(); // RST_STREAM
-    peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
+    peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded.
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -646,12 +656,12 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
     assertEquals(1, rstStream.streamId);
-    assertEquals(0, rstStream.flags);
-    assertEquals(RST_FLOW_CONTROL_ERROR, rstStream.statusCode);
+    assertEquals(FLOW_CONTROL_ERROR, rstStream.errorCode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     assertEquals(2, ping.streamId);
@@ -660,8 +670,8 @@
   @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().rstStream(1, RST_REFUSED_STREAM);
-    peer.sendFrame().ping(0, 2);
+    peer.sendFrame().rstStream(1, REFUSED_STREAM);
+    peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
 
@@ -678,20 +688,20 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     assertEquals(2, ping.streamId);
-    assertEquals(0, ping.flags);
   }
 
   @Test public void receiveGoAway() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM 1
     peer.acceptFrame(); // SYN_STREAM 3
-    peer.sendFrame().goAway(0, 1, GOAWAY_PROTOCOL_ERROR);
+    peer.sendFrame().goAway(1, PROTOCOL_ERROR);
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.acceptFrame(); // DATA STREAM 1
     peer.play();
 
@@ -719,9 +729,9 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream1.type);
+    assertEquals(TYPE_HEADERS, synStream1.type);
     MockSpdyPeer.InFrame synStream2 = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream2.type);
+    assertEquals(TYPE_HEADERS, synStream2.type);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     MockSpdyPeer.InFrame data1 = peer.takeFrame();
@@ -735,27 +745,27 @@
     peer.acceptFrame(); // SYN_STREAM 1
     peer.acceptFrame(); // GOAWAY
     peer.acceptFrame(); // PING
-    peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "b")); // Should be ignored!
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().synStream(false, false, 2, 0, 0, 0, Arrays.asList("b", "b")); // Should be ignored!
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
     SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
     connection.newStream(Arrays.asList("a", "android"), true, true);
     Ping ping = connection.ping();
-    connection.shutdown(GOAWAY_PROTOCOL_ERROR);
+    connection.shutdown(PROTOCOL_ERROR);
     assertEquals(1, connection.openStreamCount());
     ping.roundTripTime(); // Prevent the peer from exiting prematurely.
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream1.type);
+    assertEquals(TYPE_HEADERS, synStream1.type);
     MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
     assertEquals(TYPE_PING, pingFrame.type);
     MockSpdyPeer.InFrame goaway = peer.takeFrame();
     assertEquals(TYPE_GOAWAY, goaway.type);
     assertEquals(0, goaway.streamId);
-    assertEquals(GOAWAY_PROTOCOL_ERROR, goaway.statusCode);
+    assertEquals(PROTOCOL_ERROR, goaway.errorCode);
   }
 
   @Test public void noPingsAfterShutdown() throws Exception {
@@ -765,7 +775,7 @@
 
     // play it back
     SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
-    connection.shutdown(GOAWAY_INTERNAL_ERROR);
+    connection.shutdown(INTERNAL_ERROR);
     try {
       connection.ping();
       fail();
@@ -776,7 +786,7 @@
     // verify the peer received what was expected
     MockSpdyPeer.InFrame goaway = peer.takeFrame();
     assertEquals(TYPE_GOAWAY, goaway.type);
-    assertEquals(GOAWAY_INTERNAL_ERROR, goaway.statusCode);
+    assertEquals(INTERNAL_ERROR, goaway.errorCode);
   }
 
   @Test public void close() throws Exception {
@@ -813,7 +823,8 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame goaway = peer.takeFrame();
     assertEquals(TYPE_GOAWAY, goaway.type);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
@@ -837,9 +848,9 @@
   @Test public void readTimeoutExpires() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
@@ -860,16 +871,16 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
   }
 
   @Test public void headers() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
-    peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po"));
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().headers(1, Arrays.asList("c", "c3po"));
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
@@ -880,7 +891,8 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
   }
@@ -889,9 +901,9 @@
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // PING
-    peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po"));
+    peer.sendFrame().headers(1, Arrays.asList("c", "c3po"));
     peer.acceptFrame(); // RST_STREAM
-    peer.sendFrame().ping(0, 1);
+    peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
     // play it back
@@ -907,23 +919,24 @@
 
     // verify the peer received what was expected
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
+    assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode);
     MockSpdyPeer.InFrame ping = peer.takeFrame();
     assertEquals(TYPE_PING, ping.type);
     MockSpdyPeer.InFrame rstStream = peer.takeFrame();
     assertEquals(TYPE_RST_STREAM, rstStream.type);
-    assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
+    assertEquals(PROTOCOL_ERROR, rstStream.errorCode);
   }
 
   @Test public void readSendsWindowUpdate() throws Exception {
     // Write the mocking script.
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
     for (int i = 0; i < 3; i++) {
-      peer.sendFrame().data(0, 1, new byte[WINDOW_UPDATE_THRESHOLD]);
+      peer.sendFrame().data(false, 1, new byte[WINDOW_UPDATE_THRESHOLD]);
       peer.acceptFrame(); // WINDOW UPDATE
     }
-    peer.sendFrame().data(FLAG_FIN, 1, new byte[0]);
+    peer.sendFrame().data(true, 1, new byte[0]);
     peer.play();
 
     // Play it back.
@@ -942,7 +955,7 @@
 
     // Verify the peer received what was expected.
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
     for (int i = 0; i < 3; i++) {
       MockSpdyPeer.InFrame windowUpdate = peer.takeFrame();
       assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type);
@@ -974,7 +987,7 @@
 
     // Verify the peer received what was expected.
     MockSpdyPeer.InFrame synStream = peer.takeFrame();
-    assertEquals(TYPE_SYN_STREAM, synStream.type);
+    assertEquals(TYPE_HEADERS, synStream.type);
     MockSpdyPeer.InFrame data = peer.takeFrame();
     assertEquals(TYPE_DATA, data.type);
   }
@@ -982,8 +995,8 @@
   @Test public void testTruncatedDataFrame() throws Exception {
     // write the mocking script
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
-    peer.sendTruncatedFrame(8 + 100).data(0, 1, new byte[1024]);
+    peer.sendFrame().synReply(false, 1, Arrays.asList("a", "android"));
+    peer.sendTruncatedFrame(8 + 100).data(false, 1, new byte[1024]);
     peer.play();
 
     // play it back
@@ -999,6 +1012,33 @@
     }
   }
 
+  /** https://github.com/square/okhttp/issues/333 */
+  @Test public void nameValueBlockHasTrailingCompressedBytes() throws Exception {
+    // write the mocking script
+    peer.acceptFrame(); // SYN_STREAM
+    // This specially-formatted frame has trailing deflated bytes after the name value block.
+    String frame = "gAMAAgAAAgkAAAABeLvjxqfCYgAAAAD//2IAAAAA//9iAAAAAP//YgQAAAD//2IAAAAA//9iAAAAAP/"
+        + "/YgAAAAD//2IEAAAA//9KBAAAAP//YgAAAAD//2IAAAAA//9iAAAAAP//sgEAAAD//2IAAAAA\n//9iBAAAAP//Y"
+        + "gIAAAD//2IGAAAA//9iAQAAAP//YgUAAAD//2IDAAAA//9iBwAAAP//4gAAAAD//+IEAAAA///iAgAAAP//4gYAA"
+        + "AD//+IBAAAA///iBQAAAP//4gMAAAD//+IHAAAA//8SAAAAAP//EgQAAAD//xICAAAA//8SBgAAAP//EgEAAAD//"
+        + "xIFAAAA//8SAwAAAP//EgcAAAD//5IAAAAA//+SBAAAAP//kgIAAAD//5IGAAAA//+SAQAAAP//kgUAAAD//5IDA"
+        + "AAA//+SBwAAAP//UgAAAAD//1IEAAAA//9SAgAAAP//UgYAAAD//1IBAAAA//9SBQAAAP//UgMAAAD//1IHAAAA/"
+        + "//SAAAAAP//0gQAAAD//9ICAAAA///SBgAAAP//0gEAAAD//9IFAAAA///SAwAAAP//0gcAAAD//zIAAAAA//8yB"
+        + "AAAAP//MgIAAAD//zIGAAAA//8yAQAAAP//MgUAAAD//zIDAAAA//8yBwAAAP//sgAAAAD//7IEAAAA//+yAgAAA"
+        + "P//sgYAAAD//w==";
+    peer.sendFrame(Base64.decode(frame.getBytes(UTF_8)));
+    peer.sendFrame().data(true, 1, "robot".getBytes("UTF-8"));
+    peer.acceptFrame(); // DATA
+    peer.play();
+
+    // play it back
+    SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+    SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+    assertEquals("a", stream.getResponseHeaders().get(0));
+    assertEquals(60, stream.getResponseHeaders().get(1).length());
+    assertStreamData("robot", stream.getInputStream());
+  }
+
   private void writeAndClose(SpdyStream stream, String data) throws IOException {
     OutputStream out = stream.getOutputStream();
     out.write(data.getBytes("UTF-8"));
diff --git a/okhttp/pom.xml b/okhttp/pom.xml
new file mode 100644
index 0000000..9bad03d
--- /dev/null
+++ b/okhttp/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>okhttp</artifactId>
+  <name>OkHttp</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>okhttp-protocols</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mortbay.jetty.npn</groupId>
+      <artifactId>npn-boot</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <configuration>
+          <excludePackageNames>com.squareup.okhttp.internal:com.squareup.okhttp.internal.*</excludePackageNames>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptorRefs>
+            <descriptorRef>jar-with-dependencies</descriptorRef>
+          </descriptorRefs>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/com/squareup/okhttp/Address.java b/okhttp/src/main/java/com/squareup/okhttp/Address.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/Address.java
rename to okhttp/src/main/java/com/squareup/okhttp/Address.java
diff --git a/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/Connection.java
rename to okhttp/src/main/java/com/squareup/okhttp/Connection.java
index 5e9d72a..a6b798d 100644
--- a/src/main/java/com/squareup/okhttp/Connection.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java
@@ -159,6 +159,7 @@
         sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
         spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
             .build();
+        spdyConnection.sendConnectionHeader();
       } else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
         throw new IOException(
             "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
@@ -243,7 +244,7 @@
    * {@code keepAliveDurationNs}.
    */
   public boolean isExpired(long keepAliveDurationNs) {
-    return isIdle() && System.nanoTime() - getIdleStartTimeNs() > keepAliveDurationNs;
+    return getIdleStartTimeNs() < System.nanoTime() - keepAliveDurationNs;
   }
 
   /**
@@ -256,7 +257,8 @@
 
   /** Returns the transport appropriate for this connection. */
   public Object newTransport(HttpEngine httpEngine) throws IOException {
-    return (spdyConnection != null) ? new SpdyTransport(httpEngine, spdyConnection)
+    return (spdyConnection != null)
+        ? new SpdyTransport(httpEngine, spdyConnection)
         : new HttpTransport(httpEngine, out, in);
   }
 
@@ -294,7 +296,7 @@
     return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
   }
 
-  public final void updateReadTimeout(int newTimeout) throws IOException {
+  public void updateReadTimeout(int newTimeout) throws IOException {
     if (!connected) throw new IllegalStateException("updateReadTimeout - not connected");
     socket.setSoTimeout(newTimeout);
   }
diff --git a/src/main/java/com/squareup/okhttp/ConnectionPool.java b/okhttp/src/main/java/com/squareup/okhttp/ConnectionPool.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/ConnectionPool.java
rename to okhttp/src/main/java/com/squareup/okhttp/ConnectionPool.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/Dispatcher.java b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java
similarity index 94%
rename from src/main/java/com/squareup/okhttp/internal/http/Dispatcher.java
rename to okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java
index d5c5006..6a6c273 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/Dispatcher.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal.http;
+package com.squareup.okhttp;
 
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
@@ -28,7 +26,7 @@
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-public final class Dispatcher {
+final class Dispatcher {
   // TODO: thread pool size should be configurable; possibly configurable per host.
   private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
       8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
diff --git a/src/main/java/com/squareup/okhttp/Failure.java b/okhttp/src/main/java/com/squareup/okhttp/Failure.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/Failure.java
rename to okhttp/src/main/java/com/squareup/okhttp/Failure.java
index b40133b..a354700 100644
--- a/src/main/java/com/squareup/okhttp/Failure.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Failure.java
@@ -21,7 +21,7 @@
  * <h3>Warning: Experimental OkHttp 2.0 API</h3>
  * This class is in beta. APIs are subject to change!
  */
-public class Failure {
+/* OkHttp 2.0: public */ class Failure {
   private final Request request;
   private final Throwable exception;
 
diff --git a/src/main/java/com/squareup/okhttp/HttpResponseCache.java b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
similarity index 96%
rename from src/main/java/com/squareup/okhttp/HttpResponseCache.java
rename to okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
index a1c653c..8210318 100644
--- a/src/main/java/com/squareup/okhttp/HttpResponseCache.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
@@ -35,7 +35,6 @@
 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;
@@ -44,8 +43,6 @@
 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;
@@ -119,9 +116,6 @@
  * }</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;
@@ -176,26 +170,7 @@
   }
 
   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);
+    return Util.hash(uri.toString());
   }
 
   @Override public CacheResponse get(URI uri, String requestMethod,
diff --git a/src/main/java/com/squareup/okhttp/internal/http/Job.java b/okhttp/src/main/java/com/squareup/okhttp/Job.java
similarity index 93%
rename from src/main/java/com/squareup/okhttp/internal/http/Job.java
rename to okhttp/src/main/java/com/squareup/okhttp/Job.java
index 33c58e4..3a45384 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/Job.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Job.java
@@ -13,15 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal.http;
+package com.squareup.okhttp;
 
-import com.squareup.okhttp.Failure;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 
-public final class Job implements Runnable {
+final class Job implements Runnable {
   final HttpURLConnection connection;
   final Request request;
   final Response.Receiver responseReceiver;
diff --git a/src/main/java/com/squareup/okhttp/MediaType.java b/okhttp/src/main/java/com/squareup/okhttp/MediaType.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/MediaType.java
rename to okhttp/src/main/java/com/squareup/okhttp/MediaType.java
diff --git a/src/main/java/com/squareup/okhttp/OkAuthenticator.java b/okhttp/src/main/java/com/squareup/okhttp/OkAuthenticator.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/OkAuthenticator.java
rename to okhttp/src/main/java/com/squareup/okhttp/OkAuthenticator.java
diff --git a/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
similarity index 98%
rename from src/main/java/com/squareup/okhttp/OkHttpClient.java
rename to okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
index a86123d..945da1b 100644
--- a/src/main/java/com/squareup/okhttp/OkHttpClient.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
@@ -16,7 +16,6 @@
 package com.squareup.okhttp;
 
 import com.squareup.okhttp.internal.Util;
-import com.squareup.okhttp.internal.http.Dispatcher;
 import com.squareup.okhttp.internal.http.HttpAuthenticator;
 import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
 import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
@@ -321,7 +320,7 @@
   /**
    * Schedules {@code request} to be executed.
    */
-  public void enqueue(Request request, Response.Receiver responseReceiver) {
+  /* OkHttp 2.0: public */ void enqueue(Request request, Response.Receiver responseReceiver) {
     // Create the HttpURLConnection immediately so the enqueued job gets the current settings of
     // this client. Otherwise changes to this client (socket factory, redirect policy, etc.) may
     // incorrectly be reflected in the request when it is dispatched later.
@@ -332,7 +331,7 @@
    * Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
    * in flight might not be canceled.
    */
-  public void cancel(Object tag) {
+  /* OkHttp 2.0: public */ void cancel(Object tag) {
     dispatcher.cancel(tag);
   }
 
diff --git a/src/main/java/com/squareup/okhttp/OkResponseCache.java b/okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/OkResponseCache.java
rename to okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java
diff --git a/src/main/java/com/squareup/okhttp/Request.java b/okhttp/src/main/java/com/squareup/okhttp/Request.java
similarity index 99%
rename from src/main/java/com/squareup/okhttp/Request.java
rename to okhttp/src/main/java/com/squareup/okhttp/Request.java
index 6f3569b..a4e83f4 100644
--- a/src/main/java/com/squareup/okhttp/Request.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Request.java
@@ -35,7 +35,7 @@
  * <h3>Warning: Experimental OkHttp 2.0 API</h3>
  * This class is in beta. APIs are subject to change!
  */
-public final class Request {
+/* OkHttp 2.0: public */ final class Request {
   private final URL url;
   private final String method;
   private final RawHeaders headers;
diff --git a/src/main/java/com/squareup/okhttp/Response.java b/okhttp/src/main/java/com/squareup/okhttp/Response.java
similarity index 98%
rename from src/main/java/com/squareup/okhttp/Response.java
rename to okhttp/src/main/java/com/squareup/okhttp/Response.java
index 4896a38..4cef2cd 100644
--- a/src/main/java/com/squareup/okhttp/Response.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Response.java
@@ -33,7 +33,7 @@
  * <h3>Warning: Experimental OkHttp 2.0 API</h3>
  * This class is in beta. APIs are subject to change!
  */
-public final class Response {
+/* OkHttp 2.0: public */ final class Response {
   private final Request request;
   private final int code;
   private final RawHeaders headers;
diff --git a/src/main/java/com/squareup/okhttp/ResponseSource.java b/okhttp/src/main/java/com/squareup/okhttp/ResponseSource.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/ResponseSource.java
rename to okhttp/src/main/java/com/squareup/okhttp/ResponseSource.java
diff --git a/src/main/java/com/squareup/okhttp/Route.java b/okhttp/src/main/java/com/squareup/okhttp/Route.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/Route.java
rename to okhttp/src/main/java/com/squareup/okhttp/Route.java
diff --git a/src/main/java/com/squareup/okhttp/RouteDatabase.java b/okhttp/src/main/java/com/squareup/okhttp/RouteDatabase.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/RouteDatabase.java
rename to okhttp/src/main/java/com/squareup/okhttp/RouteDatabase.java
diff --git a/src/main/java/com/squareup/okhttp/TunnelRequest.java b/okhttp/src/main/java/com/squareup/okhttp/TunnelRequest.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/TunnelRequest.java
rename to okhttp/src/main/java/com/squareup/okhttp/TunnelRequest.java
diff --git a/src/main/java/com/squareup/okhttp/internal/AbstractOutputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/AbstractOutputStream.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/AbstractOutputStream.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/AbstractOutputStream.java
diff --git a/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java b/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
diff --git a/src/main/java/com/squareup/okhttp/internal/Dns.java b/okhttp/src/main/java/com/squareup/okhttp/internal/Dns.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/Dns.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/Dns.java
diff --git a/src/main/java/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
diff --git a/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java b/okhttp/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
similarity index 96%
rename from src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
index 187f3b6..a5d39b3 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -79,11 +79,11 @@
    * 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 {
+  protected final void endOfInput() throws IOException {
     if (cacheRequest != null) {
       cacheBody.close();
     }
-    httpEngine.release(streamCancelled);
+    httpEngine.release(false);
   }
 
   /**
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
similarity index 96%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
index 63f39e4..1ad3689 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
@@ -39,7 +39,7 @@
     @Override public Credential authenticate(
         Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
       for (Challenge challenge : challenges) {
-        if (!"Basic".equals(challenge.getScheme())) {
+        if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
           continue;
         }
 
@@ -56,7 +56,7 @@
     @Override public Credential authenticateProxy(
         Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
       for (Challenge challenge : challenges) {
-        if (!"Basic".equals(challenge.getScheme())) {
+        if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
           continue;
         }
 
@@ -146,7 +146,7 @@
         //       It needs to be fixed to handle any scheme and any parameters
         //       http://code.google.com/p/android/issues/detail?id=11140
 
-        if (!value.regionMatches(pos, "realm=\"", 0, "realm=\"".length())) {
+        if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
           break; // Unexpected challenge parameter; give up!
         }
 
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
index 8275958..b4d2c7c 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
@@ -36,7 +36,7 @@
       new ThreadLocal<DateFormat>() {
         @Override protected DateFormat initialValue() {
           DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
-          rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+          rfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
           return rfc1123;
         }
       };
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
similarity index 96%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
index eae10ee..fee0e01 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -110,7 +110,7 @@
   /** The time when the request headers were written, or -1 if they haven't been written yet. */
   long sentRequestMillis = -1;
 
-  /** Whether the connection has been established */
+  /** Whether the connection has been established. */
   boolean connected;
 
   /**
@@ -339,7 +339,7 @@
   }
 
   boolean hasRequestBody() {
-    return method.equals("POST") || method.equals("PUT");
+    return method.equals("POST") || method.equals("PUT") || method.equals("PATCH");
   }
 
   /** Returns the request body or null if this request doesn't have a body. */
@@ -433,7 +433,7 @@
    * closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
    * the connection will be used to follow a redirect.
    */
-  public final void release(boolean streamCancelled) {
+  public final void release(boolean streamCanceled) {
     // If the response body comes from the cache, close it.
     if (responseBodyIn == cachedResponseBody) {
       Util.closeQuietly(responseBodyIn);
@@ -442,8 +442,8 @@
     if (!connectionReleased && connection != null) {
       connectionReleased = true;
 
-      if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
-          responseTransferIn)) {
+      if (transport == null
+          || !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
         Util.closeQuietly(connection);
         connection = null;
       } else if (automaticallyReleaseConnectionToPool) {
@@ -649,10 +649,17 @@
       if (cachedResponseHeaders.validate(responseHeaders)) {
         release(false);
         ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
-        setResponse(combinedHeaders, cachedResponseBody);
+        this.responseHeaders = combinedHeaders;
+
+        // Update the cache after applying the combined headers but before initializing the content
+        // stream, otherwise the Content-Encoding header (if present) will be stripped from the
+        // combined headers and not end up in the cache file if transparent gzip compression is
+        // turned on.
         OkResponseCache responseCache = client.getOkResponseCache();
         responseCache.trackConditionalCacheHit();
         responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
+
+        initContentStream(cachedResponseBody);
         return;
       } else {
         Util.closeQuietly(cachedResponseBody);
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
index 9d64fa0..f8f9f17 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
@@ -141,9 +141,9 @@
     return headers;
   }
 
-  public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
+  public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
       InputStream responseBodyIn) {
-    if (streamCancelled) {
+    if (streamCanceled) {
       return false;
     }
 
@@ -177,6 +177,10 @@
    * Discards the response body so that the connection can be reused. This
    * needs to be done judiciously, since it delays the current request in
    * order to speed up a potential future request that may never occur.
+   *
+   * <p>A stream may be discarded to encourage response caching (a response
+   * cannot be cached unless it is consumed completely) or to enable connection
+   * reuse.
    */
   private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
     Connection connection = httpEngine.connection;
@@ -373,7 +377,7 @@
       super(is, httpEngine, cacheRequest);
       bytesRemaining = length;
       if (bytesRemaining == 0) {
-        endOfInput(false);
+        endOfInput();
       }
     }
 
@@ -391,7 +395,7 @@
       bytesRemaining -= read;
       cacheWrite(buffer, offset, read);
       if (bytesRemaining == 0) {
-        endOfInput(false);
+        endOfInput();
       }
       return read;
     }
@@ -468,7 +472,7 @@
         RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders();
         RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders);
         httpEngine.receiveHeaders(rawResponseHeaders);
-        endOfInput(false);
+        endOfInput();
       }
     }
 
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
index 5b5e91d..0ba0228 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
@@ -35,6 +35,7 @@
 import java.security.Permission;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -159,7 +160,7 @@
     try {
       return getResponse().getResponseHeaders().getHeaders().toMultimap(true);
     } catch (IOException e) {
-      return null;
+      return Collections.emptyMap();
     }
   }
 
@@ -253,8 +254,8 @@
         if (method.equals("GET")) {
           // they are requesting a stream to write to. This implies a POST method
           method = "POST";
-        } else if (!method.equals("POST") && !method.equals("PUT")) {
-          // If the request method is neither POST nor PUT, then you're not writing
+        } else if (!method.equals("POST") && !method.equals("PUT") && !method.equals("PATCH")) {
+          // If the request method is neither POST nor PUT nor PATCH, then you're not writing
           throw new ProtocolException(method + " does not support writing");
         }
       }
@@ -332,6 +333,11 @@
 
       httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(),
           (RetryableOutputStream) requestBody);
+
+      if (requestBody == null) {
+        // Drop the Content-Length header when redirected from POST to GET.
+        httpEngine.getRequestHeaders().removeContentLength();
+      }
     }
   }
 
@@ -491,7 +497,7 @@
     return isValidNonDirectProxy(client.getProxy());
   }
 
-  private static final boolean isValidNonDirectProxy(Proxy proxy) {
+  private static boolean isValidNonDirectProxy(Proxy proxy) {
     return proxy != null && proxy.type() != Proxy.Type.DIRECT;
   }
 
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/Policy.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/Policy.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
index e1fdcf4..8b45320 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
@@ -164,14 +164,17 @@
 
   /**
    * Add an HTTP header line containing a field name, a literal colon, and a
-   * value.
+   * value. This works around empty header names and header names that start
+   * with a colon (created by old broken SPDY versions of the response cache).
    */
   public void addLine(String line) {
-    int index = line.indexOf(":");
-    if (index == -1) {
-      addLenient("", line);
-    } else {
+    int index = line.indexOf(":", 1);
+    if (index != -1) {
       addLenient(line.substring(0, index), line.substring(index + 1));
+    } else if (line.startsWith(":")) {
+      addLenient("", line.substring(1)); // Empty header name.
+    } else {
+      addLenient("", line); // No header name.
     }
   }
 
diff --git a/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
similarity index 89%
rename from src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
index d5e3bd8..71c3cd0 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
@@ -213,6 +213,18 @@
     this.contentLength = contentLength;
   }
 
+  /**
+   * Remove the Content-Length headers. Call this when dropping the body on a
+   * request or response, such as when a redirect changes the method from POST
+   * to GET.
+   */
+  public void removeContentLength() {
+    if (contentLength != -1) {
+      headers.removeAll("Content-Length");
+      contentLength = -1;
+    }
+  }
+
   public void setUserAgent(String userAgent) {
     if (this.userAgent != null) {
       headers.removeAll("User-Agent");
@@ -282,9 +294,24 @@
   public void addCookies(Map<String, List<String>> allCookieHeaders) {
     for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
       String key = entry.getKey();
-      if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
-        headers.addAll(key, entry.getValue());
+      if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
+          && !entry.getValue().isEmpty()) {
+        headers.add(key, buildCookieHeader(entry.getValue()));
       }
     }
   }
+
+  /**
+   * Send all cookies in one big header, as recommended by
+   * <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
+   */
+  private String buildCookieHeader(List<String> cookies) {
+    if (cookies.size() == 1) return cookies.get(0);
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < cookies.size(); i++) {
+      if (i > 0) sb.append("; ");
+      sb.append(cookies.get(i));
+    }
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
diff --git a/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
similarity index 92%
rename from src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
index a37a91c..fce58f4 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
@@ -16,6 +16,7 @@
 
 package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.internal.spdy.ErrorCode;
 import com.squareup.okhttp.internal.spdy.SpdyConnection;
 import com.squareup.okhttp.internal.spdy.SpdyStream;
 import java.io.IOException;
@@ -80,15 +81,15 @@
     return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
   }
 
-  @Override public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
+  @Override public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
       InputStream responseBodyIn) {
-    if (streamCancelled) {
+    if (streamCanceled) {
       if (stream != null) {
-        stream.closeLater(SpdyStream.RST_CANCEL);
+        stream.closeLater(ErrorCode.CANCEL);
         return true;
       } else {
         // If stream is null, it either means that writeRequestHeaders wasn't called
-        // or that SpdyConnection#newStream threw an IOEXception. In both cases there's
+        // or that SpdyConnection#newStream threw an IOException. In both cases there's
         // nothing to do here and this stream can't be reused.
         return false;
       }
diff --git a/src/main/java/com/squareup/okhttp/internal/http/Transport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java
similarity index 97%
rename from src/main/java/com/squareup/okhttp/internal/http/Transport.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java
index 518827e..d408bfe 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/Transport.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java
@@ -59,6 +59,6 @@
   InputStream getTransferStream(CacheRequest cacheRequest) throws IOException;
 
   /** Returns true if the underlying connection can be recycled. */
-  boolean makeReusable(boolean streamReusable, OutputStream requestBodyOut,
+  boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
       InputStream responseBodyIn);
 }
diff --git a/src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
similarity index 92%
rename from src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
index 729e0b9..ca6bb59 100644
--- a/src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
@@ -25,9 +25,9 @@
 final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
   private boolean inputExhausted;
 
-  UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine)
+  UnknownLengthHttpInputStream(InputStream in, CacheRequest cacheRequest, HttpEngine httpEngine)
       throws IOException {
-    super(is, httpEngine, cacheRequest);
+    super(in, httpEngine, cacheRequest);
   }
 
   @Override public int read(byte[] buffer, int offset, int count) throws IOException {
@@ -39,7 +39,7 @@
     int read = in.read(buffer, offset, count);
     if (read == -1) {
       inputExhausted = true;
-      endOfInput(false);
+      endOfInput();
       return -1;
     }
     cacheWrite(buffer, offset, read);
diff --git a/src/main/java/com/squareup/okhttp/internal/tls/DistinguishedNameParser.java b/okhttp/src/main/java/com/squareup/okhttp/internal/tls/DistinguishedNameParser.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/tls/DistinguishedNameParser.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/tls/DistinguishedNameParser.java
diff --git a/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java b/okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
similarity index 100%
rename from src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
rename to okhttp/src/main/java/com/squareup/okhttp/internal/tls/OkHostnameVerifier.java
diff --git a/src/test/java/com/squareup/okhttp/internal/AsyncApiTest.java b/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
similarity index 87%
rename from src/test/java/com/squareup/okhttp/internal/AsyncApiTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
index 163b639..6636ca7 100644
--- a/src/test/java/com/squareup/okhttp/internal/AsyncApiTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
@@ -13,14 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal;
+package com.squareup.okhttp;
 
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.MockWebServer;
-import com.google.mockwebserver.RecordedRequest;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Request;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
 import org.junit.After;
 import org.junit.Test;
 
diff --git a/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java
similarity index 98%
rename from src/test/java/com/squareup/okhttp/ConnectionPoolTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java
index 6820b43..e243857 100644
--- a/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java
@@ -15,12 +15,11 @@
  */
 package com.squareup.okhttp;
 
-import com.google.mockwebserver.MockWebServer;
 import com.squareup.okhttp.internal.RecordingHostnameVerifier;
 import com.squareup.okhttp.internal.SslContextBuilder;
 import com.squareup.okhttp.internal.Util;
 import com.squareup.okhttp.internal.http.HttpAuthenticator;
-import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -53,7 +52,7 @@
     }
   }
 
-  private final MockSpdyServer spdyServer = new MockSpdyServer(sslContext.getSocketFactory());
+  private final MockWebServer spdyServer = new MockWebServer();
   private InetSocketAddress spdySocketAddress;
   private Address spdyAddress;
 
@@ -70,6 +69,8 @@
   private Connection spdyB;
 
   @Before public void setUp() throws Exception {
+    spdyServer.useHttps(sslContext.getSocketFactory(), false);
+
     httpServer.play();
     httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null,
         HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1"));
diff --git a/src/test/java/com/squareup/okhttp/MediaTypeTest.java b/okhttp/src/test/java/com/squareup/okhttp/MediaTypeTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/MediaTypeTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/MediaTypeTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/RecordedResponse.java b/okhttp/src/test/java/com/squareup/okhttp/RecordedResponse.java
similarity index 92%
rename from src/test/java/com/squareup/okhttp/internal/RecordedResponse.java
rename to okhttp/src/test/java/com/squareup/okhttp/RecordedResponse.java
index 388a27d..f30ae98 100644
--- a/src/test/java/com/squareup/okhttp/internal/RecordedResponse.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/RecordedResponse.java
@@ -13,11 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal;
+package com.squareup.okhttp;
 
-import com.squareup.okhttp.Failure;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
diff --git a/src/test/java/com/squareup/okhttp/internal/RecordingReceiver.java b/okhttp/src/test/java/com/squareup/okhttp/RecordingReceiver.java
similarity index 93%
rename from src/test/java/com/squareup/okhttp/internal/RecordingReceiver.java
rename to okhttp/src/test/java/com/squareup/okhttp/RecordingReceiver.java
index 58cd205..ee0db12 100644
--- a/src/test/java/com/squareup/okhttp/internal/RecordingReceiver.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/RecordingReceiver.java
@@ -13,11 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal;
+package com.squareup.okhttp;
 
-import com.squareup.okhttp.Failure;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/test/java/com/squareup/okhttp/RequestTest.java b/okhttp/src/test/java/com/squareup/okhttp/RequestTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/RequestTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/RequestTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/FaultRecoveringOutputStreamTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/FaultRecoveringOutputStreamTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/FaultRecoveringOutputStreamTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/FaultRecoveringOutputStreamTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/RecordingAuthenticator.java b/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingAuthenticator.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/RecordingAuthenticator.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/RecordingAuthenticator.java
diff --git a/src/test/java/com/squareup/okhttp/internal/RecordingHostnameVerifier.java b/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingHostnameVerifier.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/RecordingHostnameVerifier.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/RecordingHostnameVerifier.java
diff --git a/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java b/okhttp/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/RecordingOkAuthenticator.java
diff --git a/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/HttpOverSpdyTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java
similarity index 95%
rename from src/test/java/com/squareup/okhttp/internal/spdy/HttpOverSpdyTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java
index e17a120..7720c5b 100644
--- a/src/test/java/com/squareup/okhttp/internal/spdy/HttpOverSpdyTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpOverSpdyTest.java
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.squareup.okhttp.internal.spdy;
+package com.squareup.okhttp.internal.http;
 
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.RecordedRequest;
 import com.squareup.okhttp.HttpResponseCache;
 import com.squareup.okhttp.OkHttpClient;
 import com.squareup.okhttp.internal.RecordingAuthenticator;
 import com.squareup.okhttp.internal.SslContextBuilder;
 import com.squareup.okhttp.internal.Util;
-import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -72,12 +72,13 @@
       throw new RuntimeException(e);
     }
   }
-  private final MockSpdyServer server = new MockSpdyServer(sslContext.getSocketFactory());
+  private final MockWebServer server = new MockWebServer();
   private final String hostName = server.getHostName();
   private final OkHttpClient client = new OkHttpClient();
   private HttpResponseCache cache;
 
   @Before public void setUp() throws Exception {
+    server.useHttps(sslContext.getSocketFactory(), false);
     client.setSslSocketFactory(sslContext.getSocketFactory());
     client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
     String systemTmpDir = System.getProperty("java.io.tmpdir");
@@ -86,6 +87,7 @@
   }
 
   @After public void tearDown() throws Exception {
+    Authenticator.setDefault(null);
     server.shutdown();
   }
 
@@ -140,7 +142,7 @@
     assertEquals("DEF", readAscii(connection1.getInputStream(), 3));
     assertEquals("JKL", readAscii(connection2.getInputStream(), 3));
     assertEquals(0, server.takeRequest().getSequenceNumber());
-    assertEquals(0, server.takeRequest().getSequenceNumber());
+    assertEquals(1, server.takeRequest().getSequenceNumber());
   }
 
   @Test public void gzippedResponseBody() throws Exception {
diff --git a/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
similarity index 94%
rename from src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
index 9e39c34..89f31da 100644
--- a/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
@@ -16,17 +16,19 @@
 
 package com.squareup.okhttp.internal.http;
 
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.MockWebServer;
-import com.google.mockwebserver.RecordedRequest;
 import com.squareup.okhttp.HttpResponseCache;
 import com.squareup.okhttp.OkHttpClient;
 import com.squareup.okhttp.ResponseSource;
 import com.squareup.okhttp.internal.SslContextBuilder;
+import com.squareup.okhttp.internal.Util;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -72,7 +74,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -110,6 +112,7 @@
     cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
     ResponseCache.setDefault(cache);
     CookieHandler.setDefault(cookieManager);
+    server.setNpnEnabled(false);
   }
 
   @After public void tearDown() throws Exception {
@@ -437,15 +440,14 @@
     server.enqueue(new MockResponse().setBody("DEF"));
     server.play();
 
+    client.setSslSocketFactory(sslContext.getSocketFactory());
+    client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+
     HttpsURLConnection connection1 = (HttpsURLConnection) client.open(server.getUrl("/"));
-    connection1.setSSLSocketFactory(sslContext.getSocketFactory());
-    connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
     assertEquals("ABC", readAscii(connection1));
 
     // Cached!
     HttpsURLConnection connection2 = (HttpsURLConnection) client.open(server.getUrl("/"));
-    connection1.setSSLSocketFactory(sslContext.getSocketFactory());
-    connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
     assertEquals("ABC", readAscii(connection2));
 
     assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4
@@ -944,10 +946,34 @@
     server.enqueue(
         response.setBody(gzip("ABCABCABC".getBytes("UTF-8"))).addHeader("Content-Encoding: gzip"));
     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+    server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+
+    server.play();
+
+    // At least three request/response pairs are required because after the first request is cached
+    // a different execution path might be taken. Thus modifications to the cache applied during
+    // the second request might not be visible until another request is performed.
+    assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
+    assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
+    assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
+  }
+
+  @Test public void notModifiedSpecifiesEncoding() throws Exception {
+    server.enqueue(new MockResponse()
+        .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
+        .addHeader("Content-Encoding: gzip")
+        .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+        .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+    server.enqueue(new MockResponse()
+        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)
+        .addHeader("Content-Encoding: gzip"));
+    server.enqueue(new MockResponse()
+        .setBody("DEFDEFDEF"));
 
     server.play();
     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
+    assertEquals("DEFDEFDEF", readAscii(openConnection(server.getUrl("/"))));
   }
 
   @Test public void expiresDateBeforeModifiedDate() throws Exception {
@@ -1716,6 +1742,73 @@
   }
 
   /**
+   * Old implementations of OkHttp's response cache wrote header fields like
+   * ":status: 200 OK". This broke our cached response parser because it split
+   * on the first colon. This regression test exists to help us read these old
+   * bad cache entries.
+   *
+   * https://github.com/square/okhttp/issues/227
+   */
+  @Test public void testGoldenCacheResponse() throws Exception {
+    cache.close();
+    server.enqueue(new MockResponse()
+        .clearHeaders()
+        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+    server.play();
+
+    URL url = server.getUrl("/");
+    String urlKey = Util.hash(url.toString());
+    String entryMetadata = ""
+        + "" + url + "\n"
+        + "GET\n"
+        + "0\n"
+        + "HTTP/1.1 200 OK\n"
+        + "7\n"
+        + ":status: 200 OK\n"
+        + ":version: HTTP/1.1\n"
+        + "etag: foo\n"
+        + "content-length: 3\n"
+        + "OkHttp-Received-Millis: " + System.currentTimeMillis() + "\n"
+        + "X-Android-Response-Source: NETWORK 200\n"
+        + "OkHttp-Sent-Millis: " + System.currentTimeMillis() + "\n"
+        + "\n"
+        + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\n"
+        + "1\n"
+        + "MIIBpDCCAQ2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDEw1qd2lsc29uLmxvY2FsMB4XDTEzMDgy"
+        + "OTA1MDE1OVoXDTEzMDgzMDA1MDE1OVowGDEWMBQGA1UEAxMNandpbHNvbi5sb2NhbDCBnzANBgkqhkiG9w0BAQEF"
+        + "AAOBjQAwgYkCgYEAlFW+rGo/YikCcRghOyKkJanmVmJSce/p2/jH1QvNIFKizZdh8AKNwojt3ywRWaDULA/RlCUc"
+        + "ltF3HGNsCyjQI/+Lf40x7JpxXF8oim1E6EtDoYtGWAseelawus3IQ13nmo6nWzfyCA55KhAWf4VipelEy8DjcuFK"
+        + "v6L0xwXnI0ECAwEAATANBgkqhkiG9w0BAQsFAAOBgQAuluNyPo1HksU3+Mr/PyRQIQS4BI7pRXN8mcejXmqyscdP"
+        + "7S6J21FBFeRR8/XNjVOp4HT9uSc2hrRtTEHEZCmpyoxixbnM706ikTmC7SN/GgM+SmcoJ1ipJcNcl8N0X6zym4dm"
+        + "yFfXKHu2PkTo7QFdpOJFvP3lIigcSZXozfmEDg==\n"
+        + "-1\n";
+    String entryBody = "abc";
+    String journalBody = ""
+        + "libcore.io.DiskLruCache\n"
+        + "1\n"
+        + "201105\n"
+        + "2\n"
+        + "\n"
+        + "CLEAN " + urlKey + " " + entryMetadata.length() + " " + entryBody.length() + "\n";
+    writeFile(cache.getDirectory(), urlKey + ".0", entryMetadata);
+    writeFile(cache.getDirectory(), urlKey + ".1", entryBody);
+    writeFile(cache.getDirectory(), "journal", journalBody);
+    cache = new HttpResponseCache(cache.getDirectory(), Integer.MAX_VALUE);
+    client.setResponseCache(cache);
+
+    HttpURLConnection connection = client.open(url);
+    assertEquals(entryBody, readAscii(connection));
+    assertEquals("3", connection.getHeaderField("Content-Length"));
+    assertEquals("foo", connection.getHeaderField("etag"));
+  }
+
+  private void writeFile(File directory, String file, String content) throws IOException {
+    OutputStream out = new FileOutputStream(new File(directory, file));
+    out.write(content.getBytes(Util.UTF_8));
+    out.close();
+  }
+
+  /**
    * @param delta the offset from the current date to use. Negative
    * values yield dates in the past; positive values yield dates in the
    * future.
@@ -1726,7 +1819,7 @@
 
   private String formatDate(Date date) {
     DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
-    rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+    rfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
     return rfc1123.format(date);
   }
 
diff --git a/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
similarity index 89%
rename from src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
index 7d8ecf3..4ce80a5 100644
--- a/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
@@ -20,26 +20,29 @@
 import java.util.List;
 import org.junit.Test;
 
+import static junit.framework.Assert.assertNull;
 import static org.junit.Assert.assertEquals;
 
 public final class RawHeadersTest {
   @Test public void parseNameValueBlock() throws IOException {
-    List<String> nameValueBlock =
-        Arrays.asList("cache-control", "no-cache, no-store", "set-cookie", "Cookie1\u0000Cookie2",
-            ":status", "200 OK");
-    // TODO: fromNameValueBlock should synthesize a request status line
+    List<String> nameValueBlock = Arrays.asList(
+        "cache-control", "no-cache, no-store",
+        "set-cookie", "Cookie1\u0000Cookie2",
+        ":status", "200 OK",
+        ":version", "HTTP/1.1");
     RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
+    assertEquals(3, rawHeaders.length());
+    assertEquals("HTTP/1.1 200 OK", rawHeaders.getStatusLine());
     assertEquals("no-cache, no-store", rawHeaders.get("cache-control"));
     assertEquals("Cookie2", rawHeaders.get("set-cookie"));
-    assertEquals("200 OK", rawHeaders.get(":status"));
     assertEquals("cache-control", rawHeaders.getFieldName(0));
     assertEquals("no-cache, no-store", rawHeaders.getValue(0));
     assertEquals("set-cookie", rawHeaders.getFieldName(1));
     assertEquals("Cookie1", rawHeaders.getValue(1));
     assertEquals("set-cookie", rawHeaders.getFieldName(2));
     assertEquals("Cookie2", rawHeaders.getValue(2));
-    assertEquals(":status", rawHeaders.getFieldName(3));
-    assertEquals("200 OK", rawHeaders.getValue(3));
+    assertNull(rawHeaders.get(":status"));
+    assertNull(rawHeaders.get(":version"));
   }
 
   @Test public void toNameValueBlock() {
diff --git a/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
similarity index 97%
rename from src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
index 5abe477..7725f3d 100644
--- a/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
@@ -16,16 +16,16 @@
 
 package com.squareup.okhttp.internal.http;
 
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.MockWebServer;
-import com.google.mockwebserver.RecordedRequest;
-import com.google.mockwebserver.SocketPolicy;
 import com.squareup.okhttp.HttpResponseCache;
 import com.squareup.okhttp.OkHttpClient;
 import com.squareup.okhttp.internal.RecordingAuthenticator;
 import com.squareup.okhttp.internal.RecordingHostnameVerifier;
 import com.squareup.okhttp.internal.RecordingOkAuthenticator;
 import com.squareup.okhttp.internal.SslContextBuilder;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.SocketPolicy;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -76,11 +76,11 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
 import static com.squareup.okhttp.OkAuthenticator.Credential;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
+import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -109,9 +109,11 @@
 
   @Before public void setUp() throws Exception {
     hostName = server.getHostName();
+    server.setNpnEnabled(false);
   }
 
   @After public void tearDown() throws Exception {
+    Authenticator.setDefault(null);
     System.clearProperty("proxyHost");
     System.clearProperty("proxyPort");
     System.clearProperty("http.proxyHost");
@@ -1205,10 +1207,7 @@
 
   @Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception {
     List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\"");
-    assertEquals(1, calls.size());
-    String call = calls.get(0);
-    assertTrue(call, call.contains("scheme=Foo"));
-    assertTrue(call, call.contains("prompt=Bar"));
+    assertEquals(0, calls.size());
   }
 
   // Digest auth is currently unsupported. Test that digest requests should fail reasonably.
@@ -1218,10 +1217,7 @@
         + "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", "
         + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
         + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
-    assertEquals(1, calls.size());
-    String call = calls.get(0);
-    assertTrue(call, call.contains("scheme=Digest"));
-    assertTrue(call, call.contains("prompt=testrealm@host.com"));
+    assertEquals(0, calls.size());
   }
 
   @Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception {
@@ -1484,6 +1480,19 @@
     }
   }
 
+  /** https://github.com/square/okhttp/issues/342 */
+  @Test public void authenticateRealmUppercase() throws Exception {
+    server.enqueue(new MockResponse().setResponseCode(401)
+        .addHeader("wWw-aUtHeNtIcAtE: bAsIc rEaLm=\"pRoTeCtEd aReA\"")
+        .setBody("Please authenticate."));
+    server.enqueue(new MockResponse().setBody("Successful auth!"));
+    server.play();
+
+    Authenticator.setDefault(new RecordingAuthenticator());
+    HttpURLConnection connection = client.open(server.getUrl("/"));
+    assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+  }
+
   @Test public void redirectedWithChunkedEncoding() throws Exception {
     testRedirected(TransferKind.CHUNKED, true);
   }
@@ -1618,6 +1627,7 @@
     if (https) {
       server.useHttps(sslContext.getSocketFactory(), false);
       server2.useHttps(sslContext.getSocketFactory(), false);
+      server2.setNpnEnabled(false);
       client.setSslSocketFactory(sslContext.getSocketFactory());
       client.setHostnameVerifier(new RecordingHostnameVerifier());
     }
@@ -1719,6 +1729,32 @@
     assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
   }
 
+  @Test public void redirectedPostStripsRequestBodyHeaders() throws Exception {
+    server.enqueue(new MockResponse()
+        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+        .addHeader("Location: /page2"));
+    server.enqueue(new MockResponse().setBody("Page 2"));
+    server.play();
+
+    HttpURLConnection connection = client.open(server.getUrl("/page1"));
+    connection.setDoOutput(true);
+    connection.addRequestProperty("Content-Length", "4");
+    connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8");
+    connection.addRequestProperty("Transfer-Encoding", "identity");
+    OutputStream outputStream = connection.getOutputStream();
+    outputStream.write("ABCD".getBytes("UTF-8"));
+    outputStream.close();
+    assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+
+    assertEquals("POST /page1 HTTP/1.1", server.takeRequest().getRequestLine());
+
+    RecordedRequest page2 = server.takeRequest();
+    assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
+    assertContainsNoneMatching(page2.getHeaders(), "Content-Length");
+    assertContains(page2.getHeaders(), "Content-Type: text/plain; charset=utf-8");
+    assertContains(page2.getHeaders(), "Transfer-Encoding: identity");
+  }
+
   @Test public void response305UseProxy() throws Exception {
     server.play();
     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
diff --git a/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java
diff --git a/src/test/java/com/squareup/okhttp/internal/tls/FakeSSLSession.java b/okhttp/src/test/java/com/squareup/okhttp/internal/tls/FakeSSLSession.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/tls/FakeSSLSession.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/tls/FakeSSLSession.java
diff --git a/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
similarity index 100%
rename from src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
rename to okhttp/src/test/java/com/squareup/okhttp/internal/tls/HostnameVerifierTest.java
diff --git a/parent.iws b/parent.iws
new file mode 100644
index 0000000..54653fd
--- /dev/null
+++ b/parent.iws
@@ -0,0 +1,746 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CCaseConfig">
+    <option name="checkoutReserved" value="false" />
+    <option name="markExternalChangeAsUpToDate" value="true" />
+    <option name="checkInUseHijack" value="true" />
+    <option name="useUcmModel" value="true" />
+    <option name="synchOutside" value="false" />
+    <option name="isHistoryResticted" value="true" />
+    <option name="useIdenticalSwitch" value="true" />
+    <option name="synchActivitiesOnRefresh" value="true" />
+    <option name="lastScr" value="" />
+    <option name="scrTextFileName" value="" />
+    <option name="historyRevisionsNumber" value="4" />
+  </component>
+  <component name="ChangeListManager">
+    <list default="true" id="94459e15-aabd-4fe7-abb5-9cc51fb49e33" name="Default" comment="">
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.gitignore" afterPath="$PROJECT_DIR$/.gitignore" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java" afterPath="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java" />
+    </list>
+    <ignored path="parent.iws" />
+    <ignored path=".idea/workspace.xml" />
+    <option name="TRACKING_ENABLED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+  <component name="CreatePatchCommitExecutor">
+    <option name="PATCH_PATH" value="" />
+    <option name="REVERSE_PATCH" value="false" />
+  </component>
+  <component name="DaemonCodeAnalyzer">
+    <disable_hints />
+  </component>
+  <component name="DebuggerManager">
+    <breakpoint_any>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+    </breakpoint_any>
+    <breakpoint_rules />
+    <ui_properties />
+  </component>
+  <component name="FavoritesManager">
+    <favorites_list name="parent" />
+  </component>
+  <component name="FileEditorManager">
+    <leaf>
+      <file leaf-file-name="Util.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="332" column="28" selection-start="10444" selection-end="10444" vertical-scroll-proportion="-21.076923">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Address.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/Address.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="37" column="107" selection-start="1441" selection-end="1441" vertical-scroll-proportion="0.0">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Collections.class" pinned="false" current="false" current-in-tab="false">
+        <entry file="jar://$PROJECT_DIR$/../../../sdk/platforms/android-16/android.jar!/java/util/Collections.class">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="108" column="40" selection-start="6176" selection-end="6176" vertical-scroll-proportion="-29.48">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="OkHttpClient.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="244" column="30" selection-start="8360" selection-end="8360" vertical-scroll-proportion="-19.038462">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="HttpURLConnectionImpl.java" pinned="false" current="true" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="614" column="75" selection-start="21903" selection-end="21903" vertical-scroll-proportion="0.81121284">
+              <folding>
+                <element signature="imports" expanded="true" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Transport.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="42" column="72" selection-start="1758" selection-end="1758" vertical-scroll-proportion="0.0">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="ResponseHeaders.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="462" column="81" selection-start="15285" selection-end="15285" vertical-scroll-proportion="-4.923077">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="HttpEngine.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="651" column="106" selection-start="22531" selection-end="22531" vertical-scroll-proportion="-21.653847">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="SpdyTransport.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="77" column="19" selection-start="3014" selection-end="3014" vertical-scroll-proportion="0.0">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="HttpTransport.java" pinned="false" current="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java">
+          <provider selected="true" editor-type-id="text-editor">
+            <state line="141" column="94" selection-start="5240" selection-end="5240" vertical-scroll-proportion="0.0">
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="FindManager">
+    <FindUsagesManager>
+      <setting name="OPEN_NEW_TAB" value="false" />
+    </FindUsagesManager>
+  </component>
+  <component name="Git.Settings">
+    <option name="CHECKOUT_INCLUDE_TAGS" value="false" />
+    <option name="UPDATE_CHANGES_POLICY" value="STASH" />
+    <option name="LINE_SEPARATORS_CONVERSION" value="ASK" />
+  </component>
+  <component name="IdeDocumentHistory">
+    <option name="changedFiles">
+      <list>
+        <option value="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java" />
+        <option value="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java" />
+        <option value="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java" />
+        <option value="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java" />
+        <option value="$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java" />
+      </list>
+    </option>
+  </component>
+  <component name="MavenImportPreferences">
+    <option name="importingSettings">
+      <MavenImportingSettings>
+        <option name="keepSourceFolders" value="false" />
+      </MavenImportingSettings>
+    </option>
+  </component>
+  <component name="ModuleEditorState">
+    <option name="LAST_EDITED_MODULE_NAME" />
+    <option name="LAST_EDITED_TAB_NAME" />
+  </component>
+  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+    <OptionsSetting value="true" id="Add" />
+    <OptionsSetting value="true" id="Remove" />
+    <OptionsSetting value="true" id="Checkout" />
+    <OptionsSetting value="true" id="Update" />
+    <OptionsSetting value="true" id="Status" />
+    <OptionsSetting value="true" id="Edit" />
+    <OptionsSetting value="true" id="Undo Check Out" />
+    <OptionsSetting value="true" id="Get Latest Version" />
+    <ConfirmationsSetting value="0" id="Add" />
+    <ConfirmationsSetting value="0" id="Remove" />
+  </component>
+  <component name="ProjectReloadState">
+    <option name="STATE" value="0" />
+  </component>
+  <component name="ProjectView">
+    <navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
+      <flattenPackages />
+      <showMembers />
+      <showModules />
+      <showLibraryContents />
+      <hideEmptyPackages />
+      <abbreviatePackageNames />
+      <autoscrollToSource />
+      <autoscrollFromSource />
+      <sortByType />
+    </navigator>
+    <panes>
+      <pane id="Scope" />
+      <pane id="PackagesPane" />
+      <pane id="Favorites" />
+      <pane id="ProjectPane">
+        <subPane>
+          <PATH>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="parent" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+            </PATH_ELEMENT>
+          </PATH>
+          <PATH>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="parent" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+          </PATH>
+          <PATH>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="parent" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+          </PATH>
+          <PATH>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="parent" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="src" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+          </PATH>
+          <PATH>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="parent" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="src" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="main" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="java" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+            <PATH_ELEMENT>
+              <option name="myItemId" value="okhttp" />
+              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+            </PATH_ELEMENT>
+          </PATH>
+        </subPane>
+      </pane>
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="GoToFile.includeJavaFiles" value="false" />
+    <property name="project.structure.last.edited" value="Libraries" />
+    <property name="project.structure.proportion" value="0.15" />
+    <property name="options.splitter.main.proportions" value="0.3" />
+    <property name="MemberChooser.sorted" value="false" />
+    <property name="options.lastSelected" value="preferences.sourceCode" />
+    <property name="project.structure.side.proportion" value="0.34837964" />
+    <property name="MemberChooser.copyJavadoc" value="false" />
+    <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="MemberChooser.showClasses" value="true" />
+    <property name="GoToClass.includeLibraries" value="false" />
+    <property name="options.searchVisible" value="true" />
+    <property name="options.splitter.details.proportions" value="0.2" />
+  </component>
+  <component name="RunManager">
+    <configuration default="true" type="Remote" factoryName="Remote">
+      <option name="USE_SOCKET_TRANSPORT" value="true" />
+      <option name="SERVER_MODE" value="false" />
+      <option name="SHMEM_ADDRESS" value="javadebug" />
+      <option name="HOST" value="localhost" />
+      <option name="PORT" value="5005" />
+      <method>
+        <option name="AntTarget" enabled="false" />
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Maven.BeforeRunTask" enabled="false" />
+        <option name="PhingTarget" enabled="false" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Applet" factoryName="Applet">
+      <module name="" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="HTML_FILE_NAME" />
+      <option name="HTML_USED" value="false" />
+      <option name="WIDTH" value="400" />
+      <option name="HEIGHT" value="300" />
+      <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+      <option name="VM_PARAMETERS" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <method>
+        <option name="AntTarget" enabled="false" />
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+        <option name="Maven.BeforeRunTask" enabled="false" />
+        <option name="PhingTarget" enabled="false" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Application" factoryName="Application">
+      <extension name="coverage" enabled="false" merge="false" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="VM_PARAMETERS" />
+      <option name="PROGRAM_PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="ENABLE_SWING_INSPECTOR" value="false" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <module name="" />
+      <envs />
+      <method>
+        <option name="AntTarget" enabled="false" />
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+        <option name="Maven.BeforeRunTask" enabled="false" />
+        <option name="PhingTarget" enabled="false" />
+      </method>
+    </configuration>
+    <configuration default="true" type="JUnit" factoryName="JUnit">
+      <extension name="coverage" enabled="false" merge="false" />
+      <module name="" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="PACKAGE_NAME" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="METHOD_NAME" />
+      <option name="TEST_OBJECT" value="class" />
+      <option name="VM_PARAMETERS" />
+      <option name="PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <option name="TEST_SEARCH_SCOPE">
+        <value defaultName="moduleWithDependencies" />
+      </option>
+      <envs />
+      <patterns />
+      <method>
+        <option name="AntTarget" enabled="false" />
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+        <option name="Maven.BeforeRunTask" enabled="false" />
+        <option name="PhingTarget" enabled="false" />
+      </method>
+    </configuration>
+    <list size="0" />
+    <configuration name="&lt;template&gt;" type="WebApp" default="true" selected="false">
+      <Host>localhost</Host>
+      <Port>5050</Port>
+    </configuration>
+  </component>
+  <component name="ShelveChangesManager" show_recycled="false" />
+  <component name="SvnConfiguration" maxAnnotateRevisions="500">
+    <option name="USER" value="" />
+    <option name="PASSWORD" value="" />
+    <option name="mySSHConnectionTimeout" value="30000" />
+    <option name="mySSHReadTimeout" value="30000" />
+    <option name="LAST_MERGED_REVISION" />
+    <option name="MERGE_DRY_RUN" value="false" />
+    <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
+    <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
+    <option name="IGNORE_SPACES_IN_MERGE" value="false" />
+    <option name="DETECT_NESTED_COPIES" value="true" />
+    <option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
+    <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
+    <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
+    <option name="FORCE_UPDATE" value="false" />
+    <configuration useDefault="true">$USER_HOME$/.subversion_IDEA</configuration>
+    <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <created>1370025380149</created>
+      <updated>1370025380149</updated>
+    </task>
+    <servers />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="641" y="44" width="1352" height="1503" extended-state="1" />
+    <editor active="false" />
+    <layout>
+      <window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Phing Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32970226" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="IDEtalk Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="IDEtalk" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+      <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
+      <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Dependency Viewer" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+      <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.25" sideWeight="0.67029774" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+      <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+      <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+      <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+    </layout>
+  </component>
+  <component name="VcsManagerConfiguration">
+    <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
+    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
+    <option name="CHECK_NEW_TODO" value="true" />
+    <option name="myTodoPanelSettings">
+      <value>
+        <are-packages-shown value="false" />
+        <are-modules-shown value="false" />
+        <flatten-packages value="false" />
+        <is-autoscroll-to-source value="false" />
+      </value>
+    </option>
+    <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
+    <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
+    <option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
+    <option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
+    <option name="SHOW_ONLY_CHANGED_IN_SELECTION_DIFF" value="true" />
+    <option name="CHECK_COMMIT_MESSAGE_SPELLING" value="true" />
+    <option name="DEFAULT_PATCH_EXTENSION" value="patch" />
+    <option name="FORCE_NON_EMPTY_COMMENT" value="false" />
+    <option name="LAST_COMMIT_MESSAGE" />
+    <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
+    <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
+    <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
+    <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
+    <option name="ACTIVE_VCS_NAME" />
+    <option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
+    <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
+    <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
+    <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
+  </component>
+  <component name="VssConfiguration">
+    <option name="CLIENT_PATH" value="" />
+    <option name="SRCSAFEINI_PATH" value="" />
+    <option name="USER_NAME" value="" />
+    <option name="PWD" value="" />
+    <CheckoutOptions>
+      <option name="COMMENT" value="" />
+      <option name="DO_NOT_GET_LATEST_VERSION" value="false" />
+      <option name="REPLACE_WRITABLE" value="false" />
+      <option name="RECURSIVE" value="false" />
+    </CheckoutOptions>
+    <CheckinOptions>
+      <option name="COMMENT" value="" />
+      <option name="KEEP_CHECKED_OUT" value="false" />
+      <option name="RECURSIVE" value="false" />
+    </CheckinOptions>
+    <AddOptions>
+      <option name="STORE_ONLY_LATEST_VERSION" value="false" />
+      <option name="CHECK_OUT_IMMEDIATELY" value="false" />
+    </AddOptions>
+    <UndocheckoutOptions>
+      <option name="MAKE_WRITABLE" value="false" />
+      <option name="REPLACE_LOCAL_COPY" value="2" />
+      <option name="RECURSIVE" value="false" />
+    </UndocheckoutOptions>
+    <GetOptions>
+      <option name="REPLACE_WRITABLE" value="0" />
+      <option name="MAKE_WRITABLE" value="false" />
+      <option name="ANSWER_NEGATIVELY" value="false" />
+      <option name="ANSWER_POSITIVELY" value="false" />
+      <option name="RECURSIVE" value="false" />
+      <option name="VERSION" />
+    </GetOptions>
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager />
+  </component>
+  <component name="antWorkspaceConfiguration">
+    <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" />
+    <option name="FILTER_TARGETS" value="false" />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="79" column="18" selection-start="2756" selection-end="2756" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="42" column="61" selection-start="1690" selection-end="1690" vertical-scroll-proportion="-8.038462">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/Connection.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="203" column="57" selection-start="7474" selection-end="7474" vertical-scroll-proportion="-17.76923">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="399" column="14" selection-start="12625" selection-end="12627" vertical-scroll-proportion="157.88">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="414" column="55" selection-start="13008" selection-end="13008" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="301" column="42" selection-start="12286" selection-end="12286" vertical-scroll-proportion="-14.730769">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="42" column="72" selection-start="1758" selection-end="1758" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="77" column="19" selection-start="3014" selection-end="3014" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="jar://$PROJECT_DIR$/../../../sdk/platforms/android-16/android.jar!/java/util/Collections.class">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="108" column="40" selection-start="6176" selection-end="6176" vertical-scroll-proportion="-29.48">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/Address.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="37" column="107" selection-start="1441" selection-end="1441" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="244" column="30" selection-start="8360" selection-end="8360" vertical-scroll-proportion="-19.038462">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="332" column="28" selection-start="10444" selection-end="10444" vertical-scroll-proportion="-21.076923">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="141" column="94" selection-start="5240" selection-end="5240" vertical-scroll-proportion="0.0">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="462" column="81" selection-start="15285" selection-end="15285" vertical-scroll-proportion="-4.923077">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="651" column="106" selection-start="22531" selection-end="22531" vertical-scroll-proportion="-21.653847">
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java">
+      <provider selected="true" editor-type-id="text-editor">
+        <state line="614" column="75" selection-start="21903" selection-end="21903" vertical-scroll-proportion="0.81121284">
+          <folding>
+            <element signature="imports" expanded="true" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+  </component>
+  <component name="masterDetails">
+    <states>
+      <state key="ArtifactsStructureConfigurable.UI">
+        <settings>
+          <artifact-editor />
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+      <state key="FacetStructureConfigurable.UI">
+        <settings>
+          <last-edited>Android</last-edited>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+      <state key="GlobalLibrariesConfigurable.UI">
+        <settings>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+      <state key="JdkListConfigurable.UI">
+        <settings>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+      <state key="ModuleStructureConfigurable.UI">
+        <settings>
+          <last-edited>okhttp</last-edited>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.34837964" />
+                <option value="0.5" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+      <state key="ProjectLibrariesConfigurable.UI">
+        <settings>
+          <last-edited>Unnamed</last-edited>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.34837964" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+    </states>
+  </component>
+</project>
+
diff --git a/pom.xml b/pom.xml
index 853f0b9..a24a0ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,35 +4,170 @@
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
-    <groupId>com.squareup.okhttp</groupId>
-    <artifactId>parent</artifactId>
-    <version>1.1.2-SNAPSHOT</version>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
   </parent>
 
-  <artifactId>okhttp</artifactId>
-  <name>OkHttp</name>
+  <groupId>com.squareup.okhttp</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.2.2-SNAPSHOT</version>
+  <packaging>pom</packaging>
 
-  <dependencies>
-    <dependency>
-      <groupId>org.mortbay.jetty.npn</groupId>
-      <artifactId>npn-boot</artifactId>
-      <optional>true</optional>
-    </dependency>
+  <name>OkHttp (Parent)</name>
+  <description>An HTTP+SPDY client for Android and Java applications</description>
+  <url>https://github.com/square/okhttp</url>
 
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.bouncycastle</groupId>
-      <artifactId>bcprov-jdk15on</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.mockwebserver</groupId>
-      <artifactId>mockwebserver</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
+  <modules>
+    <module>okhttp</module>
+    <module>okhttp-apache</module>
+    <module>okhttp-protocols</module>
+    <module>mockwebserver</module>
+    <module>samples</module>
+  </modules>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+    <!-- Compilation -->
+    <java.version>1.6</java.version>
+    <npn.version>8.1.2.v20120308</npn.version>
+    <bouncycastle.version>1.48</bouncycastle.version>
+    <gson.version>2.2.3</gson.version>
+    <apache.http.version>4.2.2</apache.http.version>
+
+    <!-- Test Dependencies -->
+    <junit.version>4.10</junit.version>
+  </properties>
+
+  <scm>
+    <url>https://github.com/square/okhttp/</url>
+    <connection>scm:git:https://github.com/square/okhttp.git</connection>
+    <developerConnection>scm:git:git@github.com:square/okhttp.git</developerConnection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <issueManagement>
+    <system>GitHub Issues</system>
+    <url>https://github.com/square/okhttp/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>Apache 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.mortbay.jetty.npn</groupId>
+        <artifactId>npn-boot</artifactId>
+        <version>${npn.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>${junit.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bcprov-jdk15on</artifactId>
+        <version>${bouncycastle.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.code.gson</groupId>
+        <artifactId>gson</artifactId>
+        <version>${gson.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.httpcomponents</groupId>
+        <artifactId>httpclient</artifactId>
+        <version>${apache.http.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.0</version>
+          <configuration>
+            <source>${java.version}</source>
+            <target>${java.version}</target>
+          </configuration>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.9</version>
+          <configuration>
+            <argLine>-Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar</argLine>
+          </configuration>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>2.9</version>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-release-plugin</artifactId>
+          <version>2.3.2</version>
+          <configuration>
+            <autoVersionSubmodules>true</autoVersionSubmodules>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.10</version>
+        <configuration>
+          <failsOnError>true</failsOnError>
+          <configLocation>checkstyle.xml</configLocation>
+          <consoleOutput>true</consoleOutput>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>verify</phase>
+            <goals>
+              <goal>checkstyle</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>animal-sniffer-maven-plugin</artifactId>
+        <version>1.9</version>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <signature>
+            <groupId>org.codehaus.mojo.signature</groupId>
+            <artifactId>java15</artifactId>
+            <version>1.0</version>
+          </signature>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
 </project>
+
diff --git a/samples/guide/pom.xml b/samples/guide/pom.xml
new file mode 100644
index 0000000..dc0633c
--- /dev/null
+++ b/samples/guide/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp.sample</groupId>
+    <artifactId>sample-parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>guide</artifactId>
+  <name>Sample: Guide</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>okhttp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples/guide/src/main/java/com/squareup/okhttp/guide/GetExample.java b/samples/guide/src/main/java/com/squareup/okhttp/guide/GetExample.java
new file mode 100644
index 0000000..b5427c5
--- /dev/null
+++ b/samples/guide/src/main/java/com/squareup/okhttp/guide/GetExample.java
@@ -0,0 +1,43 @@
+package com.squareup.okhttp.guide;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class GetExample {
+  OkHttpClient client = new OkHttpClient();
+
+  void run() throws IOException {
+    String result = get(new URL("https://raw.github.com/square/okhttp/master/README.md"));
+    System.out.println(result);
+  }
+
+  String get(URL url) throws IOException {
+    HttpURLConnection connection = client.open(url);
+    InputStream in = null;
+    try {
+      // Read the response.
+      in = connection.getInputStream();
+      byte[] response = readFully(in);
+      return new String(response, "UTF-8");
+    } finally {
+      if (in != null) in.close();
+    }
+  }
+
+  byte[] readFully(InputStream in) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    byte[] buffer = new byte[1024];
+    for (int count; (count = in.read(buffer)) != -1; ) {
+      out.write(buffer, 0, count);
+    }
+    return out.toByteArray();
+  }
+
+  public static void main(String[] args) throws IOException {
+    new GetExample().run();
+  }
+}
diff --git a/samples/guide/src/main/java/com/squareup/okhttp/guide/PostExample.java b/samples/guide/src/main/java/com/squareup/okhttp/guide/PostExample.java
new file mode 100644
index 0000000..309ab70
--- /dev/null
+++ b/samples/guide/src/main/java/com/squareup/okhttp/guide/PostExample.java
@@ -0,0 +1,66 @@
+package com.squareup.okhttp.guide;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class PostExample {
+  OkHttpClient client = new OkHttpClient();
+
+  void run() throws IOException {
+    byte[] body = bowlingJson("Jesse", "Jake").getBytes("UTF-8");
+    String result = post(new URL("http://www.roundsapp.com/post"), body);
+    System.out.println(result);
+  }
+
+  String post(URL url, byte[] body) throws IOException {
+    HttpURLConnection connection = client.open(url);
+    OutputStream out = null;
+    InputStream in = null;
+    try {
+      // Write the request.
+      connection.setRequestMethod("POST");
+      out = connection.getOutputStream();
+      out.write(body);
+      out.close();
+
+      // Read the response.
+      if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+        throw new IOException("Unexpected HTTP response: "
+            + connection.getResponseCode() + " " + connection.getResponseMessage());
+      }
+      in = connection.getInputStream();
+      return readFirstLine(in);
+    } finally {
+      // Clean up.
+      if (out != null) out.close();
+      if (in != null) in.close();
+    }
+  }
+
+  String readFirstLine(InputStream in) throws IOException {
+    BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+    return reader.readLine();
+  }
+
+  String bowlingJson(String player1, String player2) {
+    return "{'winCondition':'HIGH_SCORE',"
+        + "'name':'Bowling',"
+        + "'round':4,"
+        + "'lastSaved':1367702411696,"
+        + "'dateStarted':1367702378785,"
+        + "'players':["
+        + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
+        + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
+        + "]}";
+  }
+
+  public static void main(String[] args) throws IOException {
+    new PostExample().run();
+  }
+}
diff --git a/samples/pom.xml b/samples/pom.xml
new file mode 100644
index 0000000..2462fd0
--- /dev/null
+++ b/samples/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <groupId>com.squareup.okhttp.sample</groupId>
+  <artifactId>sample-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Samples (Parent)</name>
+
+  <modules>
+    <module>guide</module>
+    <module>simple-client</module>
+    <module>static-server</module>
+  </modules>
+</project>
diff --git a/samples/simple-client/pom.xml b/samples/simple-client/pom.xml
new file mode 100644
index 0000000..990152b
--- /dev/null
+++ b/samples/simple-client/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp.sample</groupId>
+    <artifactId>sample-parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>simple-client</artifactId>
+  <name>Sample: Simple Client</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>okhttp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/samples/simple-client/src/main/java/com/squareup/okhttp/sample/OkHttpContributors.java b/samples/simple-client/src/main/java/com/squareup/okhttp/sample/OkHttpContributors.java
new file mode 100644
index 0000000..8969f47
--- /dev/null
+++ b/samples/simple-client/src/main/java/com/squareup/okhttp/sample/OkHttpContributors.java
@@ -0,0 +1,53 @@
+package com.squareup.okhttp.sample;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.squareup.okhttp.OkHttpClient;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class OkHttpContributors {
+  private static final String ENDPOINT = "https://api.github.com/repos/square/okhttp/contributors";
+  private static final Gson GSON = new Gson();
+  private static final TypeToken<List<Contributor>> CONTRIBUTORS =
+      new TypeToken<List<Contributor>>() {
+      };
+
+  class Contributor {
+    String login;
+    int contributions;
+  }
+
+  public static void main(String... args) throws Exception {
+    OkHttpClient client = new OkHttpClient();
+
+    // Create request for remote resource.
+    HttpURLConnection connection = client.open(new URL(ENDPOINT));
+    InputStream is = connection.getInputStream();
+    InputStreamReader isr = new InputStreamReader(is);
+
+    // Deserialize HTTP response to concrete type.
+    List<Contributor> contributors = GSON.fromJson(isr, CONTRIBUTORS.getType());
+
+    // Sort list by the most contributions.
+    Collections.sort(contributors, new Comparator<Contributor>() {
+      @Override public int compare(Contributor c1, Contributor c2) {
+        return c2.contributions - c1.contributions;
+      }
+    });
+
+    // Output list of contributors.
+    for (Contributor contributor : contributors) {
+      System.out.println(contributor.login + ": " + contributor.contributions);
+    }
+  }
+
+  private OkHttpContributors() {
+    // No instances.
+  }
+}
diff --git a/samples/static-server/pom.xml b/samples/static-server/pom.xml
new file mode 100644
index 0000000..f98e5b5
--- /dev/null
+++ b/samples/static-server/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.okhttp.sample</groupId>
+    <artifactId>sample-parent</artifactId>
+    <version>1.2.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>static-server</artifactId>
+  <name>Sample: Static Server</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>2.1</version>
+        <configuration>
+          <shadedArtifactAttached>true</shadedArtifactAttached>
+          <shadedClassifierName>shaded</shadedClassifierName>
+          <transformers>
+            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+              <manifestEntries>
+                <Main-Class>com.squareup.okhttp.sample.SampleServer</Main-Class>
+              </manifestEntries>
+            </transformer>
+          </transformers>
+          <filters>
+            <filter>
+              <artifact>*:*</artifact>
+              <excludes>
+                <exclude>META-INF/*.SF</exclude>
+                <exclude>META-INF/*.DSA</exclude>
+                <exclude>META-INF/*.RSA</exclude>
+              </excludes>
+            </filter>
+          </filters>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals><goal>shade</goal></goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/samples/static-server/src/main/java/com/squareup/okhttp/sample/SampleServer.java b/samples/static-server/src/main/java/com/squareup/okhttp/sample/SampleServer.java
new file mode 100644
index 0000000..274bf9d
--- /dev/null
+++ b/samples/static-server/src/main/java/com/squareup/okhttp/sample/SampleServer.java
@@ -0,0 +1,137 @@
+package com.squareup.okhttp.sample;
+
+import com.squareup.okhttp.internal.Util;
+import com.squareup.okhttp.mockwebserver.Dispatcher;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+public class SampleServer extends Dispatcher {
+  private final SSLContext sslContext;
+  private final String root;
+  private final int port;
+
+  public SampleServer(SSLContext sslContext, String root, int port) {
+    this.sslContext = sslContext;
+    this.root = root;
+    this.port = port;
+  }
+
+  public void run() throws IOException {
+    MockWebServer server = new MockWebServer();
+    server.useHttps(sslContext.getSocketFactory(), false);
+    server.setDispatcher(this);
+    server.play(port);
+  }
+
+  @Override public MockResponse dispatch(RecordedRequest request) {
+    String path = request.getPath();
+    try {
+      if (!path.startsWith("/") || path.contains("..")) throw new FileNotFoundException();
+
+      File file = new File(root + path);
+      return file.isDirectory()
+          ? directoryToResponse(path, file)
+          : fileToResponse(path, file);
+    } catch (FileNotFoundException e) {
+      return new MockResponse()
+          .setStatus("HTTP/1.1 404")
+          .addHeader("content-type: text/plain; charset=utf-8")
+          .setBody("NOT FOUND: " + path);
+    } catch (IOException e) {
+      return new MockResponse()
+          .setStatus("HTTP/1.1 500")
+          .addHeader("content-type: text/plain; charset=utf-8")
+          .setBody("SERVER ERROR: " + e);
+    }
+  }
+
+  private MockResponse directoryToResponse(String basePath, File directory) {
+    if (!basePath.endsWith("/")) basePath += "/";
+
+    StringBuilder response = new StringBuilder();
+    response.append(String.format("<html><head><title>%s</title></head><body>", basePath));
+    response.append(String.format("<h1>%s</h1>", basePath));
+    for (String file : directory.list()) {
+      response.append(String.format("<div class='file'><a href='%s'>%s</a></div>",
+          basePath + file, file));
+    }
+    response.append("</body></html>");
+
+    return new MockResponse()
+        .setStatus("HTTP/1.1 200")
+        .addHeader("content-type: text/html; charset=utf-8")
+        .setBody(response.toString());
+  }
+
+  private MockResponse fileToResponse(String path, File file) throws IOException {
+    return new MockResponse()
+        .setStatus("HTTP/1.1 200")
+        .setBody(fileToBytes(file))
+        .addHeader("content-type: " + contentType(path));
+  }
+
+  private byte[] fileToBytes(File file) throws IOException {
+    byte[] result = new byte[(int) file.length()];
+    Util.readFully(new FileInputStream(file), result);
+    return result;
+  }
+
+  private String contentType(String path) {
+    if (path.endsWith(".png")) return "image/png";
+    if (path.endsWith(".jpg")) return "image/jpeg";
+    if (path.endsWith(".jpeg")) return "image/jpeg";
+    if (path.endsWith(".gif")) return "image/gif";
+    if (path.endsWith(".html")) return "text/html; charset=utf-8";
+    if (path.endsWith(".txt")) return "text/plain; charset=utf-8";
+    return "application/octet-stream";
+  }
+
+  public static void main(String[] args) throws Exception {
+    if (args.length != 4) {
+      System.out.println("Usage: SampleServer <keystore> <password> <root file> <port>");
+      return;
+    }
+
+    String keystoreFile = args[0];
+    String password = args[1];
+    String root = args[2];
+    int port = Integer.parseInt(args[3]);
+
+    SSLContext sslContext = sslContext(keystoreFile, password);
+    SampleServer server = new SampleServer(sslContext, root, port);
+    server.run();
+  }
+
+  private static SSLContext sslContext(String keystoreFile, String password)
+      throws GeneralSecurityException, IOException {
+    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+    keystore.load(new FileInputStream(keystoreFile), password.toCharArray());
+
+    KeyManagerFactory keyManagerFactory =
+        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+    keyManagerFactory.init(keystore, password.toCharArray());
+
+    TrustManagerFactory trustManagerFactory =
+        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    trustManagerFactory.init(keystore);
+
+    SSLContext sslContext = SSLContext.getInstance("TLS");
+    sslContext.init(
+        keyManagerFactory.getKeyManagers(),
+        trustManagerFactory.getTrustManagers(),
+        new SecureRandom());
+
+    return sslContext;
+  }
+}
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java
deleted file mode 100644
index c4f60ab..0000000
--- a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.squareup.okhttp.internal.spdy;
-
-import com.squareup.okhttp.internal.Util;
-import java.io.Closeable;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.ProtocolException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-import java.util.zip.InflaterInputStream;
-
-/** Read spdy/3 frames. */
-final class SpdyReader implements Closeable {
-  static final byte[] DICTIONARY;
-  static {
-    try {
-      DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
-          + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
-          + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
-          + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
-          + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
-          + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
-          + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
-          + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
-          + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
-          + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
-          + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
-          + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
-          + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
-          + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
-          + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
-          + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
-          + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
-          + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
-          + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
-          + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
-          + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
-          + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
-          + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
-          + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
-          + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
-          + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
-          + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
-          + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
-          + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
-          + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
-          + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
-          + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
-          + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
-    } catch (UnsupportedEncodingException e) {
-      throw new AssertionError();
-    }
-  }
-
-  private final DataInputStream in;
-  private final DataInputStream nameValueBlockIn;
-  private int compressedLimit;
-
-  SpdyReader(InputStream in) {
-    this.in = new DataInputStream(in);
-    this.nameValueBlockIn = newNameValueBlockStream();
-  }
-
-  /**
-   * Send the next frame to {@code handler}. Returns true unless there are no
-   * more frames on the stream.
-   */
-  public boolean nextFrame(Handler handler) throws IOException {
-    int w1;
-    try {
-      w1 = in.readInt();
-    } catch (IOException e) {
-      return false; // This might be a normal socket close.
-    }
-    int w2 = in.readInt();
-
-    boolean control = (w1 & 0x80000000) != 0;
-    int flags = (w2 & 0xff000000) >>> 24;
-    int length = (w2 & 0xffffff);
-
-    if (control) {
-      int version = (w1 & 0x7fff0000) >>> 16;
-      int type = (w1 & 0xffff);
-
-      if (version != 3) {
-        throw new ProtocolException("version != 3: " + version);
-      }
-
-      switch (type) {
-        case SpdyConnection.TYPE_SYN_STREAM:
-          readSynStream(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_SYN_REPLY:
-          readSynReply(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_RST_STREAM:
-          readRstStream(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_SETTINGS:
-          readSettings(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_NOOP:
-          if (length != 0) throw ioException("TYPE_NOOP length: %d != 0", length);
-          handler.noop();
-          return true;
-
-        case SpdyConnection.TYPE_PING:
-          readPing(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_GOAWAY:
-          readGoAway(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_HEADERS:
-          readHeaders(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_WINDOW_UPDATE:
-          readWindowUpdate(handler, flags, length);
-          return true;
-
-        case SpdyConnection.TYPE_CREDENTIAL:
-          Util.skipByReading(in, length);
-          throw new UnsupportedOperationException("TODO"); // TODO: implement
-
-        default:
-          throw new IOException("Unexpected frame");
-      }
-    } else {
-      int streamId = w1 & 0x7fffffff;
-      handler.data(flags, streamId, in, length);
-      return true;
-    }
-  }
-
-  private void readSynStream(Handler handler, int flags, int length) throws IOException {
-    int w1 = in.readInt();
-    int w2 = in.readInt();
-    int s3 = in.readShort();
-    int streamId = w1 & 0x7fffffff;
-    int associatedStreamId = w2 & 0x7fffffff;
-    int priority = (s3 & 0xe000) >>> 13;
-    int slot = s3 & 0xff;
-    List<String> nameValueBlock = readNameValueBlock(length - 10);
-    handler.synStream(flags, streamId, associatedStreamId, priority, slot, nameValueBlock);
-  }
-
-  private void readSynReply(Handler handler, int flags, int length) throws IOException {
-    int w1 = in.readInt();
-    int streamId = w1 & 0x7fffffff;
-    List<String> nameValueBlock = readNameValueBlock(length - 4);
-    handler.synReply(flags, streamId, nameValueBlock);
-  }
-
-  private void readRstStream(Handler handler, int flags, int length) throws IOException {
-    if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
-    int streamId = in.readInt() & 0x7fffffff;
-    int statusCode = in.readInt();
-    handler.rstStream(flags, streamId, statusCode);
-  }
-
-  private void readHeaders(Handler handler, int flags, int length) throws IOException {
-    int w1 = in.readInt();
-    int streamId = w1 & 0x7fffffff;
-    List<String> nameValueBlock = readNameValueBlock(length - 4);
-    handler.headers(flags, streamId, nameValueBlock);
-  }
-
-  private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
-    if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
-    int w1 = in.readInt();
-    int w2 = in.readInt();
-    int streamId = w1 & 0x7fffffff;
-    int deltaWindowSize = w2 & 0x7fffffff;
-    handler.windowUpdate(flags, streamId, deltaWindowSize);
-  }
-
-  private DataInputStream newNameValueBlockStream() {
-    // Limit the inflater input stream to only those bytes in the Name/Value block.
-    final InputStream throttleStream = new InputStream() {
-      @Override public int read() throws IOException {
-        return Util.readSingleByte(this);
-      }
-
-      @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException {
-        byteCount = Math.min(byteCount, compressedLimit);
-        int consumed = in.read(buffer, offset, byteCount);
-        compressedLimit -= consumed;
-        return consumed;
-      }
-
-      @Override public void close() throws IOException {
-        in.close();
-      }
-    };
-
-    // Subclass inflater to install a dictionary when it's needed.
-    Inflater inflater = new Inflater() {
-      @Override public int inflate(byte[] buffer, int offset, int count)
-          throws DataFormatException {
-        int result = super.inflate(buffer, offset, count);
-        if (result == 0 && needsDictionary()) {
-          setDictionary(DICTIONARY);
-          result = super.inflate(buffer, offset, count);
-        }
-        return result;
-      }
-    };
-
-    return new DataInputStream(new InflaterInputStream(throttleStream, inflater));
-  }
-
-  private List<String> readNameValueBlock(int length) throws IOException {
-    this.compressedLimit += length;
-    try {
-      int numberOfPairs = nameValueBlockIn.readInt();
-      if (numberOfPairs < 0) {
-        Logger.getLogger(getClass().getName()).warning("numberOfPairs < 0: " + numberOfPairs);
-        throw ioException("numberOfPairs < 0");
-      }
-      List<String> entries = new ArrayList<String>(numberOfPairs * 2);
-      for (int i = 0; i < numberOfPairs; i++) {
-        String name = readString();
-        String values = readString();
-        if (name.length() == 0) throw ioException("name.length == 0");
-        if (values.length() == 0) throw ioException("values.length == 0");
-        entries.add(name);
-        entries.add(values);
-      }
-
-      if (compressedLimit != 0) {
-        Logger.getLogger(getClass().getName()).warning("compressedLimit > 0: " + compressedLimit);
-      }
-
-      return entries;
-    } catch (DataFormatException e) {
-      throw new IOException(e.getMessage());
-    }
-  }
-
-  private String readString() throws DataFormatException, IOException {
-    int length = nameValueBlockIn.readInt();
-    byte[] bytes = new byte[length];
-    Util.readFully(nameValueBlockIn, bytes);
-    return new String(bytes, 0, length, "UTF-8");
-  }
-
-  private void readPing(Handler handler, int flags, int length) throws IOException {
-    if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
-    int id = in.readInt();
-    handler.ping(flags, id);
-  }
-
-  private void readGoAway(Handler handler, int flags, int length) throws IOException {
-    if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
-    int lastGoodStreamId = in.readInt() & 0x7fffffff;
-    int statusCode = in.readInt();
-    handler.goAway(flags, lastGoodStreamId, statusCode);
-  }
-
-  private void readSettings(Handler handler, int flags, int length) throws IOException {
-    int numberOfEntries = in.readInt();
-    if (length != 4 + 8 * numberOfEntries) {
-      throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
-    }
-    Settings settings = new Settings();
-    for (int i = 0; i < numberOfEntries; i++) {
-      int w1 = in.readInt();
-      int value = in.readInt();
-      int idFlags = (w1 & 0xff000000) >>> 24;
-      int id = w1 & 0xffffff;
-      settings.set(id, idFlags, value);
-    }
-    handler.settings(flags, settings);
-  }
-
-  private static IOException ioException(String message, Object... args) throws IOException {
-    throw new IOException(String.format(message, args));
-  }
-
-  @Override public void close() throws IOException {
-    Util.closeAll(in, nameValueBlockIn);
-  }
-
-  public interface Handler {
-    void data(int flags, int streamId, InputStream in, int length) throws IOException;
-
-    void synStream(int flags, int streamId, int associatedStreamId, int priority, int slot,
-        List<String> nameValueBlock);
-
-    void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException;
-    void headers(int flags, int streamId, List<String> nameValueBlock) throws IOException;
-    void rstStream(int flags, int streamId, int statusCode);
-    void settings(int flags, Settings settings);
-    void noop();
-    void ping(int flags, int streamId);
-    void goAway(int flags, int lastGoodStreamId, int statusCode);
-    void windowUpdate(int flags, int streamId, int deltaWindowSize);
-  }
-}
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java
deleted file mode 100644
index b3d1d1f..0000000
--- a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.squareup.okhttp.internal.spdy;
-
-import com.squareup.okhttp.internal.Platform;
-import com.squareup.okhttp.internal.Util;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.zip.Deflater;
-
-/** Write spdy/3 frames. */
-final class SpdyWriter implements Closeable {
-  final DataOutputStream out;
-  private final ByteArrayOutputStream nameValueBlockBuffer;
-  private final DataOutputStream nameValueBlockOut;
-
-  SpdyWriter(OutputStream out) {
-    this.out = new DataOutputStream(out);
-
-    Deflater deflater = new Deflater();
-    deflater.setDictionary(SpdyReader.DICTIONARY);
-    nameValueBlockBuffer = new ByteArrayOutputStream();
-    nameValueBlockOut = new DataOutputStream(
-        Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
-  }
-
-  public synchronized void synStream(int flags, int streamId, int associatedStreamId, int priority,
-      int slot, List<String> nameValueBlock) throws IOException {
-    writeNameValueBlockToBuffer(nameValueBlock);
-    int length = 10 + nameValueBlockBuffer.size();
-    int type = SpdyConnection.TYPE_SYN_STREAM;
-
-    int unused = 0;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(streamId & 0x7fffffff);
-    out.writeInt(associatedStreamId & 0x7fffffff);
-    out.writeShort((priority & 0x7) << 13 | (unused & 0x1f) << 8 | (slot & 0xff));
-    nameValueBlockBuffer.writeTo(out);
-    out.flush();
-  }
-
-  public synchronized void synReply(int flags, int streamId, List<String> nameValueBlock)
-      throws IOException {
-    writeNameValueBlockToBuffer(nameValueBlock);
-    int type = SpdyConnection.TYPE_SYN_REPLY;
-    int length = nameValueBlockBuffer.size() + 4;
-
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(streamId & 0x7fffffff);
-    nameValueBlockBuffer.writeTo(out);
-    out.flush();
-  }
-
-  public synchronized void headers(int flags, int streamId, List<String> nameValueBlock)
-      throws IOException {
-    writeNameValueBlockToBuffer(nameValueBlock);
-    int type = SpdyConnection.TYPE_HEADERS;
-    int length = nameValueBlockBuffer.size() + 4;
-
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(streamId & 0x7fffffff);
-    nameValueBlockBuffer.writeTo(out);
-    out.flush();
-  }
-
-  public synchronized void rstStream(int streamId, int statusCode) throws IOException {
-    int flags = 0;
-    int type = SpdyConnection.TYPE_RST_STREAM;
-    int length = 8;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(streamId & 0x7fffffff);
-    out.writeInt(statusCode);
-    out.flush();
-  }
-
-  public synchronized void data(int flags, int streamId, byte[] data) throws IOException {
-    int length = data.length;
-    out.writeInt(streamId & 0x7fffffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.write(data);
-    out.flush();
-  }
-
-  private void writeNameValueBlockToBuffer(List<String> nameValueBlock) throws IOException {
-    nameValueBlockBuffer.reset();
-    int numberOfPairs = nameValueBlock.size() / 2;
-    nameValueBlockOut.writeInt(numberOfPairs);
-    for (String s : nameValueBlock) {
-      nameValueBlockOut.writeInt(s.length());
-      nameValueBlockOut.write(s.getBytes("UTF-8"));
-    }
-    nameValueBlockOut.flush();
-  }
-
-  public synchronized void settings(int flags, Settings settings) throws IOException {
-    int type = SpdyConnection.TYPE_SETTINGS;
-    int size = settings.size();
-    int length = 4 + size * 8;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(size);
-    for (int i = 0; i <= Settings.COUNT; i++) {
-      if (!settings.isSet(i)) continue;
-      int settingsFlags = settings.flags(i);
-      out.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
-      out.writeInt(settings.get(i));
-    }
-    out.flush();
-  }
-
-  public synchronized void noop() throws IOException {
-    int type = SpdyConnection.TYPE_NOOP;
-    int length = 0;
-    int flags = 0;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.flush();
-  }
-
-  public synchronized void ping(int flags, int id) throws IOException {
-    int type = SpdyConnection.TYPE_PING;
-    int length = 4;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(id);
-    out.flush();
-  }
-
-  public synchronized void goAway(int flags, int lastGoodStreamId, int statusCode)
-      throws IOException {
-    int type = SpdyConnection.TYPE_GOAWAY;
-    int length = 8;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(lastGoodStreamId);
-    out.writeInt(statusCode);
-    out.flush();
-  }
-
-  public synchronized void windowUpdate(int streamId, int deltaWindowSize) throws IOException {
-    int type = SpdyConnection.TYPE_WINDOW_UPDATE;
-    int flags = 0;
-    int length = 8;
-    out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
-    out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
-    out.writeInt(streamId);
-    out.writeInt(deltaWindowSize);
-    out.flush();
-  }
-
-  @Override public void close() throws IOException {
-    Util.closeAll(out, nameValueBlockOut);
-  }
-}
diff --git a/src/test/java/com/squareup/okhttp/internal/mockspdyserver/MockSpdyServer.java b/src/test/java/com/squareup/okhttp/internal/mockspdyserver/MockSpdyServer.java
deleted file mode 100644
index f7de138..0000000
--- a/src/test/java/com/squareup/okhttp/internal/mockspdyserver/MockSpdyServer.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2013 Square, Inc.
- * 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.mockspdyserver;
-
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.QueueDispatcher;
-import com.google.mockwebserver.RecordedRequest;
-import com.squareup.okhttp.internal.Platform;
-import com.squareup.okhttp.internal.spdy.IncomingStreamHandler;
-import com.squareup.okhttp.internal.spdy.SpdyConnection;
-import com.squareup.okhttp.internal.spdy.SpdyStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-
-/** A scriptable spdy/3 + HTTP server. */
-public final class MockSpdyServer {
-  private static final byte[] NPN_PROTOCOLS = new byte[] { 6, 's', 'p', 'd', 'y', '/', '3', };
-  private static final Logger logger = Logger.getLogger(MockSpdyServer.class.getName());
-  private SSLSocketFactory sslSocketFactory;
-  private QueueDispatcher dispatcher = new QueueDispatcher();
-  private ServerSocket serverSocket;
-  private final Set<Socket> openClientSockets =
-      Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
-  private int port = -1;
-  private final BlockingQueue<RecordedRequest> requestQueue =
-      new LinkedBlockingQueue<RecordedRequest>();
-
-  public MockSpdyServer(SSLSocketFactory sslSocketFactory) {
-    this.sslSocketFactory = sslSocketFactory;
-  }
-
-  public String getHostName() {
-    try {
-      return InetAddress.getLocalHost().getHostName();
-    } catch (UnknownHostException e) {
-      throw new AssertionError();
-    }
-  }
-
-  public int getPort() {
-    if (port == -1) {
-      throw new IllegalStateException("Cannot retrieve port before calling play()");
-    }
-    return port;
-  }
-
-  public URL getUrl(String path) {
-    try {
-      return new URL("https://" + getHostName() + ":" + getPort() + path);
-    } catch (MalformedURLException e) {
-      throw new AssertionError(e);
-    }
-  }
-
-  /**
-   * Returns a cookie domain for this server. This returns the server's
-   * non-loopback host name if it is known. Otherwise this returns ".local"
-   * for this server's loopback name.
-   */
-  public String getCookieDomain() {
-    String hostName = getHostName();
-    return hostName.contains(".") ? hostName : ".local";
-  }
-
-  /**
-   * Awaits the next HTTP request, removes it, and returns it. Callers should
-   * use this to verify the request sent was as intended.
-   */
-  public RecordedRequest takeRequest() throws InterruptedException {
-    return requestQueue.take();
-  }
-
-  public void play() throws IOException {
-    serverSocket = new ServerSocket(0);
-    serverSocket.setReuseAddress(true);
-    port = serverSocket.getLocalPort();
-
-    Thread acceptThread = new Thread("MockSpdyServer-accept-" + port) {
-      @Override public void run() {
-        int sequenceNumber = 0;
-        try {
-          acceptConnections(sequenceNumber);
-        } catch (Throwable e) {
-          logger.log(Level.WARNING, "MockWebServer connection failed", e);
-        }
-
-        // This gnarly block of code will release all sockets and
-        // all thread, even if any close fails.
-        try {
-          serverSocket.close();
-        } catch (Throwable e) {
-          logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
-        }
-        for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext(); ) {
-          try {
-            s.next().close();
-            s.remove();
-          } catch (Throwable e) {
-            logger.log(Level.WARNING, "MockWebServer socket close failed", e);
-          }
-        }
-      }
-    };
-    acceptThread.start();
-  }
-
-  public void enqueue(MockResponse response) {
-    dispatcher.enqueueResponse(response);
-  }
-
-  private void acceptConnections(int sequenceNumber) throws Exception {
-    while (true) {
-      Socket socket;
-      try {
-        socket = serverSocket.accept();
-      } catch (SocketException e) {
-        return;
-      }
-      openClientSockets.add(socket);
-      new SocketHandler(sequenceNumber++, socket).serve();
-    }
-  }
-
-  public void shutdown() throws IOException {
-    if (serverSocket != null) {
-      serverSocket.close(); // should cause acceptConnections() to break out
-    }
-  }
-
-  private class SocketHandler implements IncomingStreamHandler {
-    private final int sequenceNumber;
-    private Socket socket;
-
-    private SocketHandler(int sequenceNumber, Socket socket) throws IOException {
-      this.socket = socket;
-      this.sequenceNumber = sequenceNumber;
-    }
-
-    public void serve() throws IOException {
-      if (sslSocketFactory != null) {
-        socket = doSsl(socket);
-      }
-      new SpdyConnection.Builder(false, socket).handler(this).build();
-    }
-
-    private Socket doSsl(Socket socket) throws IOException {
-      SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket,
-          socket.getInetAddress().getHostAddress(), socket.getPort(), true);
-      sslSocket.setUseClientMode(false);
-      Platform.get().setNpnProtocols(sslSocket, NPN_PROTOCOLS);
-      return sslSocket;
-    }
-
-    @Override public void receive(final SpdyStream stream) throws IOException {
-      RecordedRequest request = readRequest(stream);
-      requestQueue.add(request);
-      MockResponse response;
-      try {
-        response = dispatcher.dispatch(request);
-      } catch (InterruptedException e) {
-        throw new AssertionError(e);
-      }
-      writeResponse(stream, response);
-      logger.info("Received request: " + request + " and responded: " + response);
-    }
-
-    private RecordedRequest readRequest(SpdyStream stream) throws IOException {
-      List<String> spdyHeaders = stream.getRequestHeaders();
-      List<String> httpHeaders = new ArrayList<String>();
-      String method = "<:method omitted>";
-      String path = "<:path omitted>";
-      String version = "<:version omitted>";
-      for (Iterator<String> i = spdyHeaders.iterator(); i.hasNext(); ) {
-        String name = i.next();
-        String value = i.next();
-        if (":method".equals(name)) {
-          method = value;
-        } else if (":path".equals(name)) {
-          path = value;
-        } else if (":version".equals(name)) {
-          version = value;
-        } else {
-          httpHeaders.add(name + ": " + value);
-        }
-      }
-
-      InputStream bodyIn = stream.getInputStream();
-      ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
-      byte[] buffer = new byte[8192];
-      int count;
-      while ((count = bodyIn.read(buffer)) != -1) {
-        bodyOut.write(buffer, 0, count);
-      }
-      bodyIn.close();
-      String requestLine = method + ' ' + path + ' ' + version;
-      List<Integer> chunkSizes = Collections.emptyList(); // No chunked encoding for SPDY.
-      return new RecordedRequest(requestLine, httpHeaders, chunkSizes, bodyOut.size(),
-          bodyOut.toByteArray(), sequenceNumber, socket);
-    }
-
-    private void writeResponse(SpdyStream stream, MockResponse response) throws IOException {
-      List<String> spdyHeaders = new ArrayList<String>();
-      String[] statusParts = response.getStatus().split(" ", 2);
-      if (statusParts.length != 2) {
-        throw new AssertionError("Unexpected status: " + response.getStatus());
-      }
-      spdyHeaders.add(":status");
-      spdyHeaders.add(statusParts[1]);
-      spdyHeaders.add(":version");
-      spdyHeaders.add(statusParts[0]);
-      for (String header : response.getHeaders()) {
-        String[] headerParts = header.split(":", 2);
-        if (headerParts.length != 2) {
-          throw new AssertionError("Unexpected header: " + header);
-        }
-        spdyHeaders.add(headerParts[0].toLowerCase(Locale.US).trim());
-        spdyHeaders.add(headerParts[1].trim());
-      }
-      byte[] body = response.getBody();
-      stream.reply(spdyHeaders, body.length > 0);
-      if (body.length > 0) {
-        stream.getOutputStream().write(body);
-        stream.getOutputStream().close();
-      }
-    }
-  }
-}
diff --git a/website/index.html b/website/index.html
new file mode 100644
index 0000000..34234ed
--- /dev/null
+++ b/website/index.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>OkHttp</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="An HTTP &amp; SPDY client for Android and Java applications">
+    <link href="static/bootstrap-combined.min.css" rel="stylesheet">
+    <link href="static/app.css" rel="stylesheet">
+    <link href="static/app-theme.css" rel="stylesheet">
+    <link href="http://fonts.googleapis.com/css?family=Roboto:400,300italic,100,100italic,300" rel="stylesheet" type="text/css">
+    <!--[if lt IE 9]><script src="static/html5shiv.min.js"></script><![endif]-->
+  </head>
+  <body data-target=".content-nav">
+    <header>
+      <div class="container">
+        <div class="row">
+          <div class="span5">
+            <h1>OkHttp</h1>
+          </div>
+          <div class="span7">
+            <menu>
+              <ul>
+                <li><a href="#download" class="menu download">Download <span class="version-tag">Latest</span></a></li>
+                <li><a href="http://github.com/square/okhttp" data-title="View GitHub Project" class="menu github"><img src="static/icon-github.png" alt="GitHub"/></a></li>
+                <li><a href="http://square.github.io/" data-title="Square Open Source Portal" class="menu square"><img src="static/icon-square.png" alt="Square"/></a></li>
+              </ul>
+            </menu>
+          </div>
+      </div>
+    </header>
+    <section id="subtitle">
+      <div class="container">
+        <div class="row">
+          <div class="span12">
+            <h2>An <strong>HTTP &amp; SPDY</strong> client for Android and Java applications</h2>
+          </div>
+        </div>
+      </div>
+    </section>
+    <section id="body">
+      <div class="container">
+        <div class="row">
+          <div class="span9">
+            <h3 id="overview">Overview</h3>
+            <p>HTTP is the way modern applications network. It’s how we exchange data & media.
+                Doing HTTP efficiently makes your stuff load faster and saves bandwidth.</p>
+
+            <p>OkHttp is an HTTP client that’s efficient by default:</p>
+            <ul>
+                <li>SPDY support allows all requests to the same host to share a socket.</li>
+                <li>Connection pooling reduces request latency (if SPDY isn’t available).</li>
+                <li>Transparent GZIP shrinks download sizes.</li>
+                <li>Response caching avoids the network completely for repeat requests.</li>
+            </ul>
+
+            <p>OkHttp perseveres when the network is troublesome: it will silently recover from
+                common connection problems. If your service has multiple IP addresses OkHttp will
+                attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6
+                and for services hosted in redundant data centers. OkHttp also recovers from problematic
+                proxy servers and failed SSL handshakes.</p>
+
+            <p>You can try OkHttp without rewriting your network code. The core module implements
+                the familiar <code>java.net.HttpURLConnection</code> API. And the optional
+                okhttp-apache module implements the Apache <code>HttpClient</code> API.</p>
+
+            <p>OkHttp supports Android 2.2 and above. For Java, the minimum requirement is 1.5.</p>
+
+            <h3 id="examples">Examples</h3>
+            <h4>Get a URL</h4>
+            <p>This program downloads a URL and print its contents as a string. <a href="https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/com/squareup/okhttp/guide/GetExample.java">Full source</a>.
+<pre class="prettyprint">
+    OkHttpClient client = new OkHttpClient();
+
+    String get(URL url) throws IOException {
+      HttpURLConnection connection = client.open(url);
+      InputStream in = null;
+      try {
+        // Read the response.
+        in = connection.getInputStream();
+        byte[] response = readFully(in);
+        return new String(response, "UTF-8");
+      } finally {
+        if (in != null) in.close();
+      }
+    }
+</pre>
+            <h4>Post to a Server</h4>
+            <p>This program posts data to a service. <a href="https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/com/squareup/okhttp/guide/PostExample.java">Full source</a>.
+
+<pre class="prettyprint">
+    OkHttpClient client = new OkHttpClient();
+
+    String post(URL url, byte[] body) throws IOException {
+      HttpURLConnection connection = client.open(url);
+      OutputStream out = null;
+      InputStream in = null;
+      try {
+        // Write the request.
+        connection.setRequestMethod("POST");
+        out = connection.getOutputStream();
+        out.write(body);
+        out.close();
+
+        // Read the response.
+        if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+          throw new IOException("Unexpected HTTP response: "
+              + connection.getResponseCode() + " " + connection.getResponseMessage());
+        }
+        in = connection.getInputStream();
+        return readFirstLine(in);
+      } finally {
+        // Clean up.
+        if (out != null) out.close();
+        if (in != null) in.close();
+      }
+    }
+</pre>
+
+                <!--
+                TODO
+                Error Handling
+                Authentication
+                Cookies
+                Response Caching
+                Captive Gateways
+                -->
+
+            <h3 id="download">Download</h3>
+            <p><a href="http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=okhttp&c=jar-with-dependencies&v=LATEST" class="dl version-href">&darr; <span class="version-tag">Latest</span> JAR</a></p>
+            <p>The source code to the OkHttp, its samples, and this website is <a href="http://github.com/square/okhttp">available on GitHub</a>.</p>
+
+            <h4>Maven</h4>
+            <pre class="prettyprint">&lt;dependency>
+  &lt;groupId>com.squareup.okhttp&lt;/groupId>
+  &lt;artifactId>okhttp&lt;/artifactId>
+  &lt;version><span class="version pln"><em>(insert latest version)</em></span>&lt;/version>
+&lt;/dependency></pre>
+
+            <h3 id="contributing">Contributing</h3>
+            <p>If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request.</p>
+            <p>When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. Please also make sure your code compiles by running <code>mvn clean verify</code>.</p>
+            <p>Before your code can be accepted into the project you must also sign the <a href="http://squ.re/sign-the-cla">Individual Contributor License Agreement (CLA)</a>.</p>
+
+            <h3 id="license">License</h3>
+            <pre>Copyright 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.</pre>
+          </div>
+          <div class="span3">
+            <div class="content-nav" data-spy="affix" data-offset-top="80">
+              <ul class="nav nav-tabs nav-stacked primary">
+                <li><a href="#overview">Overview</a></li>
+                <li><a href="#examples">Examples</a></li>
+                <li><a href="#download">Download</a></li>
+                <li><a href="#contributing">Contributing</a></li>
+                <li><a href="#license">License</a></li>
+              </ul>
+              <ul class="nav nav-pills nav-stacked secondary">
+                <li><a href="javadoc/index.html">Javadoc</a></li>
+                <li><a href="https://plus.google.com/communities/109244258569782858265/stream/b6d99838-775f-45a6-a259-af04d42d8639">Google+ Community</a></li>
+              </ul>
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <div class="span12 logo">
+            <a href="https://squareup.com"><img src="static/logo-square.png" alt="Square, Inc."/></a>
+          </div>
+        </div>
+      </div>
+    </section>
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
+    <script src="static/bootstrap.min.js"></script>
+    <script src="static/jquery.smooth-scroll.min.js"></script>
+    <script src="static/jquery-maven-artifact.min.js"></script>
+    <script src="static/prettify.js"></script>
+    <script type="text/javascript">
+      $(function() {
+        // Syntax highlight code blocks.
+        prettyPrint();
+
+        // Spy on scroll position for real-time updating of current section.
+        $('body').scrollspy();
+
+        // Use smooth-scroll for internal links.
+        $('a').smoothScroll();
+
+        // Enable tooltips on the header nav image items.
+        $('.menu').tooltip({
+          placement: 'bottom',
+          trigger: 'hover',
+          container: 'body',
+          delay: {
+            show: 500,
+            hide: 0
+          }
+        });
+
+        // Look up the latest version of the library.
+        $.fn.artifactVersion({
+          'groupId': 'com.squareup.okhttp',
+          'artifactId': 'okhttp',
+          'classifier': 'jar-with-dependencies'
+        }, function(version, url) {
+          $('.version').text(version);
+          $('.version-tag').text('v' + version);
+          $('.version-href').attr('href', url);
+        });
+      });
+
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-40704740-2']);
+      _gaq.push(['_trackPageview']);
+
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+  </body>
+</html>
diff --git a/website/static/app-theme.css b/website/static/app-theme.css
new file mode 100644
index 0000000..d7d00c4
--- /dev/null
+++ b/website/static/app-theme.css
@@ -0,0 +1,51 @@
+/* http://www.colorhexa.com/487fb9 */
+
+/*** Primary ***/
+
+header,
+#subtitle,
+a.dl {
+  background-color: #48b7b9;
+}
+
+.content-nav li.active a,
+.content-nav li.active a:hover {
+  border-left-color: #48b7b9;
+}
+
+/*** One step left on the monochromatic scale ***/
+
+header menu li a:hover,
+a.dl:hover {
+  background-color: #40a6a7;
+}
+a {
+  color: #40a6a7;
+}
+
+/*** Three steps left on the monochromatic scale ***/
+
+a:hover {
+  color: #328183;
+}
+
+
+/****************************************************************\
+ **** Syntax highlighting styles ********************************
+\****************************************************************/
+
+.pln { color: #000; }
+.str { color: #399395; }
+.kwd { color: #666; }
+.com { color: #399395; }
+.typ { color: #222; }
+.lit { color: #666; }
+.pun { color: #888; }
+.opn { color: #888; }
+.clo { color: #888; }
+.tag { color: #399395; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
+.var { color: #606; }
+.fun { color: #f00; }
diff --git a/website/static/app.css b/website/static/app.css
new file mode 100644
index 0000000..e3574b7
--- /dev/null
+++ b/website/static/app.css
@@ -0,0 +1,188 @@
+html, body {
+  font-family: 'Roboto', sans-serif;
+  font-size: 15px;
+}
+body {
+  background-color: #f6f6f6;
+  padding-bottom: 50px;
+  padding-top: 80px;
+}
+
+header {
+  min-height: 80px;
+  color: #f6f6f6;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 99;
+}
+header h1 {
+  margin: 10px 0;
+  font-size: 50px;
+  line-height: 60px;
+  font-weight: 100;
+  text-rendering: auto;
+}
+header menu {
+  margin: 20px 0 0;
+  padding: 0;
+  height: 40px;
+}
+header menu ul {
+  margin: 0;
+  padding: 0;
+  float: right;
+}
+header menu li {
+  list-style: none;
+  float: left;
+  margin: 0;
+  padding: 0;
+}
+header menu li a {
+  display: inline-block;
+  height: 40px;
+  font-size: 17px;
+  line-height: 40px;
+  padding: 0 20px;
+  color: #f6f6f6;
+}
+header menu li a:hover {
+  color: #f6f6f6;
+  text-decoration: none;
+}
+header menu li a img {
+  margin: 0;
+  padding: 5px 0;
+  vertical-align: bottom;
+  width: 30px;
+  height: 30px;
+}
+
+#subtitle {
+  position: absolute;
+  top: 80px;
+  left: 0;
+  width: 100%;
+}
+h2 {
+  font-weight: 200;
+  font-size: 26px;
+  line-height: 30px;
+  padding: 15px 0;
+  margin: 0;
+  color: #eee;
+}
+h2 strong {
+  font-weight: 300;
+}
+
+a.dl {
+  font-weight: 300;
+  font-size: 30px;
+  line-height: 40px;
+  padding: 3px 10px;
+  display: inline-block;
+  border-radius: 6px;
+  color: #f0f0f0;
+  margin: 5px 0;
+}
+a.dl:hover {
+  color: #f0f0f0;
+  text-decoration: none;
+}
+
+.content-nav {
+  margin-top: 130px;
+  width: 220px;
+}
+.content-nav.affix {
+  top: 0;
+}
+.content-nav li.active a, .content-nav li.active a:hover {
+  background-color: transparent;
+  color: #555;
+  border-left-width: 2px;
+}
+.content-nav .secondary a {
+  color: #aaa;
+}
+.content-nav .secondary a:hover {
+  color: #888;
+}
+
+h3 {
+  font-weight: 300;
+  font-style: italic;
+  color: #888;
+  font-size: 20px;
+  padding-top: 115px;
+  margin-top: 0;
+}
+
+h4 {
+  font-weight: 400;
+  text-transform: uppercase;
+  color: #888;
+  font-size: 15px;
+  padding-top: 20px;
+}
+
+p.license {
+  font-family: fixed-width;
+}
+
+.row .logo {
+  text-align: center;
+  margin-top: 150px;
+}
+.row .logo img {
+  height: 30px;
+}
+
+pre, code {
+  color: #666;
+}
+code {
+  border: 0;
+  background-color: transparent;
+}
+
+/* Widescreen desktop. */
+@media (min-width: 1200px) {
+  .content-nav {
+    width: 270px;
+  }
+}
+
+/* Smaller width browser, tablets. */
+@media (max-width: 979px) {
+  .content-nav {
+    width: 166px;
+  }
+}
+
+/* One-column mobile display. */
+@media (max-width: 767px) {
+  header {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    padding-left: 20px;
+  }
+  header menu {
+    display: none;
+  }
+  #subtitle {
+    position: absolute;
+    top: 80px;
+    left: 0;
+    width: 100%;
+    padding-left: 20px;
+  }
+  .content-nav {
+    display: none;
+  }
+}
\ No newline at end of file
diff --git a/website/static/bootstrap-combined.min.css b/website/static/bootstrap-combined.min.css
new file mode 100644
index 0000000..1334dfa
--- /dev/null
+++ b/website/static/bootstrap-combined.min.css
@@ -0,0 +1,18 @@
+/*!
+ * Bootstrap v2.3.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
+/*!
+ * Bootstrap Responsive v2.3.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
diff --git a/website/static/bootstrap.min.js b/website/static/bootstrap.min.js
new file mode 100644
index 0000000..95c5ac5
--- /dev/null
+++ b/website/static/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on("click.dropdown.data-api",r).on("click.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/website/static/html5shiv.min.js b/website/static/html5shiv.min.js
new file mode 100644
index 0000000..784f221
--- /dev/null
+++ b/website/static/html5shiv.min.js
@@ -0,0 +1,8 @@
+/*
+ HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
+a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
+c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
+"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
+for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
diff --git a/website/static/icon-github.png b/website/static/icon-github.png
new file mode 100644
index 0000000..a9c6940
--- /dev/null
+++ b/website/static/icon-github.png
Binary files differ
diff --git a/website/static/icon-square.png b/website/static/icon-square.png
new file mode 100644
index 0000000..bdc98d1
--- /dev/null
+++ b/website/static/icon-square.png
Binary files differ
diff --git a/website/static/jquery-maven-artifact.min.js b/website/static/jquery-maven-artifact.min.js
new file mode 100644
index 0000000..4b78fe2
--- /dev/null
+++ b/website/static/jquery-maven-artifact.min.js
@@ -0,0 +1,7 @@
+/**
+ * jQuery Maven Artifact Plugin
+ *
+ * Version: 2.0.0
+ * Author: Jake Wharton
+ * License: Apache 2.0
+ */(function(e){function n(e,t){var n=e.groupId.replace(/\./g,"/"),r="http://repo1.maven.org/maven2/"+n+"/"+e.artifactId+"/"+t+"/"+e.artifactId+"-"+t;return typeof e.classifier!="undefined"&&(r+="-"+e.classifier),r+="."+e.packaging,r}function r(e){var t={groupId:"g",artifactId:"a",packaging:"p",classifier:"l"},n="";for(var r in t)t.hasOwnProperty(r)&&e.hasOwnProperty(r)&&(n!==""&&(n+="+AND+"),n+=t[r]+':"'+e[r]+'"');return n}var t={packaging:"jar"};e.fn.artifactVersion=function(i,s){if(typeof i=="undefined"){alert("Error: config object is required.");return}if(typeof s=="undefined"){alert("Error: callback function required.");return}var i=e.extend({},t,i),o="http://search.maven.org/solrsearch/select/?q="+r(i)+"&wt=json&json.wrf=?";e.getJSON(o,function(e){var t=e.response.docs;if(t.length==0)return;var r=t[0].latestVersion||t[0].v,o=n(i,r);s(r,o)})},e.fn.artifactVersions=function(i,s){if(typeof i=="undefined"){alert("Error: config object is required.");return}if(typeof s=="undefined"){alert("Error: callback function required.");return}var i=e.extend({},t,i),o="http://search.maven.org/solrsearch/select/?q="+r(i)+"&wt=json&rows=10&core=gav&json.wrf=?";e.getJSON(o,function(e){var t=e.response.docs;if(t.length==0)return;t.sort(function(e,t){return e.v>t.v?-1:1});var r=[];for(var o=0;o<t.length;o++){var u=t[o].v;r.push({name:u,url:n(i,u)})}s(r)})}})(jQuery);
\ No newline at end of file
diff --git a/website/static/jquery.smooth-scroll.min.js b/website/static/jquery.smooth-scroll.min.js
new file mode 100644
index 0000000..2af596e
--- /dev/null
+++ b/website/static/jquery.smooth-scroll.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Smooth Scroll - v1.4.10 - 2013-03-02
+ * https://github.com/kswedberg/jquery-smooth-scroll
+ * Copyright (c) 2013 Karl Swedberg
+ * Licensed MIT (https://github.com/kswedberg/jquery-smooth-scroll/blob/master/LICENSE-MIT)
+ */
+(function(l){function t(l){return l.replace(/(:|\.)/g,"\\$1")}var e="1.4.10",o={exclude:[],excludeWithin:[],offset:0,direction:"top",scrollElement:null,scrollTarget:null,beforeScroll:function(){},afterScroll:function(){},easing:"swing",speed:400,autoCoefficent:2},r=function(t){var e=[],o=!1,r=t.dir&&"left"==t.dir?"scrollLeft":"scrollTop";return this.each(function(){if(this!=document&&this!=window){var t=l(this);t[r]()>0?e.push(this):(t[r](1),o=t[r]()>0,o&&e.push(this),t[r](0))}}),e.length||this.each(function(){"BODY"===this.nodeName&&(e=[this])}),"first"===t.el&&e.length>1&&(e=[e[0]]),e};l.fn.extend({scrollable:function(l){var t=r.call(this,{dir:l});return this.pushStack(t)},firstScrollable:function(l){var t=r.call(this,{el:"first",dir:l});return this.pushStack(t)},smoothScroll:function(e){e=e||{};var o=l.extend({},l.fn.smoothScroll.defaults,e),r=l.smoothScroll.filterPath(location.pathname);return this.unbind("click.smoothscroll").bind("click.smoothscroll",function(e){var n=this,s=l(this),c=o.exclude,i=o.excludeWithin,a=0,f=0,h=!0,u={},d=location.hostname===n.hostname||!n.hostname,m=o.scrollTarget||(l.smoothScroll.filterPath(n.pathname)||r)===r,p=t(n.hash);if(o.scrollTarget||d&&m&&p){for(;h&&c.length>a;)s.is(t(c[a++]))&&(h=!1);for(;h&&i.length>f;)s.closest(i[f++]).length&&(h=!1)}else h=!1;h&&(e.preventDefault(),l.extend(u,o,{scrollTarget:o.scrollTarget||p,link:n}),l.smoothScroll(u))}),this}}),l.smoothScroll=function(t,e){var o,r,n,s,c=0,i="offset",a="scrollTop",f={},h={};"number"==typeof t?(o=l.fn.smoothScroll.defaults,n=t):(o=l.extend({},l.fn.smoothScroll.defaults,t||{}),o.scrollElement&&(i="position","static"==o.scrollElement.css("position")&&o.scrollElement.css("position","relative"))),o=l.extend({link:null},o),a="left"==o.direction?"scrollLeft":a,o.scrollElement?(r=o.scrollElement,c=r[a]()):r=l("html, body").firstScrollable(),o.beforeScroll.call(r,o),n="number"==typeof t?t:e||l(o.scrollTarget)[i]()&&l(o.scrollTarget)[i]()[o.direction]||0,f[a]=n+c+o.offset,s=o.speed,"auto"===s&&(s=f[a]||r.scrollTop(),s/=o.autoCoefficent),h={duration:s,easing:o.easing,complete:function(){o.afterScroll.call(o.link,o)}},o.step&&(h.step=o.step),r.length?r.stop().animate(f,h):o.afterScroll.call(o.link,o)},l.smoothScroll.version=e,l.smoothScroll.filterPath=function(l){return l.replace(/^\//,"").replace(/(index|default).[a-zA-Z]{3,4}$/,"").replace(/\/$/,"")},l.fn.smoothScroll.defaults=o})(jQuery);
\ No newline at end of file
diff --git a/website/static/logo-square.png b/website/static/logo-square.png
new file mode 100644
index 0000000..788b301
--- /dev/null
+++ b/website/static/logo-square.png
Binary files differ
diff --git a/website/static/prettify.js b/website/static/prettify.js
new file mode 100644
index 0000000..7b99049
--- /dev/null
+++ b/website/static/prettify.js
@@ -0,0 +1,30 @@
+!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a=
+b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&"-"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),
+h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l==="("?++h:"\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l==="("?(++h,d[h]||(a[f]="(?:")):"\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&
+(a[f]="\\"+d[l]);for(f=0;f<c;++f)"^"===a[f]&&"^"!==a[f+1]&&(a[f]="");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,
+f:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(""+i);n.push("(?:"+s(i)+")")}return RegExp(n.join("|"),j?"gi":"g")}function T(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)s[j]="\n",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=
+a)}var b=/(?:^|\s)nocode(?:\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join("").replace(/\n$/,""),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function U(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?V.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function g(a){for(var j=a.e,k=[j,"pln"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w==="string")f=!1;else{var h=b[z.charAt(0)];
+if(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w="pln")}if((f=w.length>=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=
+g[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com",
+/^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+
+s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
+q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
+c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
+r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
+a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
+t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
+O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
+V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
+/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
+["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}),
+["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q,
+hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
+p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});
+return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\??prettify\b/.test(o):m!==3||/\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&
+o.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=" prettyprinted";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=U(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue("white-space"):0)&&"pre"===o.substring(0,3);u=j.linenums;if(!(u=u==="true"||+u))u=(u=k.match(/\blinenums\b(?::(\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=
+{h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):"function"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\blang(?:uage)?-([\w.]+)(?!\S)/,e=/\bprettyprint\b/,v=/\bprettyprinted\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,
+h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return Y})})();}()