entries = queryresult.getResultObjects();
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_35_includeSchema() {
+ query.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ JSONArray result;
+ if (error == null) {
+ result = queryresult.getSchema();
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_36_includeContentType() {
+ query.includeContentType();
+ query.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ if (error == null) {
+ JSONObject entries = queryresult.getContentType();
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_38_include_content_type() {
+ query.includeContentType();
+ query.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ JSONObject result;
+ if (error == null) {
+ result = queryresult.getContentType();
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_39_include_content_type() {
+ query.includeContentType();
+ query.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ if (error == null) {
+ JSONObject entries = queryresult.getContentType();
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_40_WithoutIncludeFallback() {
+ Query fallbackQuery = stack.contentType("categories").query();
+ fallbackQuery.locale("hi-in");
+ fallbackQuery.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ if (error == null) {
+ assertEquals(0, queryresult.getResultObjects().size());
+ fallbackQuery.includeFallback().locale("hi-in");
+ fallbackQuery.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ assertEquals(0, queryresult.getResultObjects().size());
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Test
+ public void test_40_WithIncludeFallback() {
+ Query fallbackQuery = stack.contentType("categories").query();
+ fallbackQuery.locale("hi-in");
+ fallbackQuery.find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ if (error == null) {
+ assertEquals(0, queryresult.getResultObjects().size());
+ }
+ }
+ });
+ }
+
+
+ @Test
+ public void test_41_entry_include_embedded_items_unit_test() throws InterruptedException {
+
+ final Query query = stack.contentType("user").query();
+ query.includeEmbeddedItems().find(new QueryResultsCallBack() {
+ @Override
+ public void onCompletion(ResponseType responseType, QueryResult queryresult, Error error) {
+ if (error == null) {
+ Entry checkResp = queryresult.getResultObjects().get(0);
+ Log.d(TAG, checkResp.toString());
+ }
+ boolean hasEmbeddedItemKey = query.mainJSON.has("include_embedded_items[]");
+ Assert.assertTrue(hasEmbeddedItemKey);
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/contentstack/src/main/AndroidManifest.xml b/contentstack/src/main/AndroidManifest.xml
new file mode 100755
index 00000000..ecbfcf7a
--- /dev/null
+++ b/contentstack/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Address.java b/contentstack/src/main/java/com/contentstack/okhttp/Address.java
new file mode 100755
index 00000000..ae8c9469
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Address.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+
+import java.net.Proxy;
+import java.util.List;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A specification for a connection to an origin server. For simple connections,
+ * this is the server's hostname and port. If an explicit proxy is requested (or
+ * {@linkplain Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
+ * that proxy information. For secure connections the address also includes the
+ * SSL socket factory and hostname verifier.
+ *
+ * HTTP requests that share the same {@code Address} may also share the same
+ * {@link Connection}.
+ */
+public final class Address {
+ final Proxy proxy;
+ final String uriHost;
+ final int uriPort;
+ final SocketFactory socketFactory;
+ final SSLSocketFactory sslSocketFactory;
+ final HostnameVerifier hostnameVerifier;
+ final Authenticator authenticator;
+ final List protocols;
+
+ public Address(String uriHost, int uriPort, SocketFactory socketFactory,
+ SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
+ Authenticator authenticator, Proxy proxy, List protocols) {
+ if (uriHost == null) throw new NullPointerException("uriHost == null");
+ if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
+ if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
+ if (protocols == null) throw new IllegalArgumentException("protocols == null");
+ this.proxy = proxy;
+ this.uriHost = uriHost;
+ this.uriPort = uriPort;
+ this.socketFactory = socketFactory;
+ this.sslSocketFactory = sslSocketFactory;
+ this.hostnameVerifier = hostnameVerifier;
+ this.authenticator = authenticator;
+ this.protocols = Util.immutableList(protocols);
+ }
+
+ /** Returns the hostname of the origin server. */
+ public String getUriHost() {
+ return uriHost;
+ }
+
+ /**
+ * Returns the port of the origin server; typically 80 or 443. Unlike
+ * may {@code getPort()} accessors, this method never returns -1.
+ */
+ public int getUriPort() {
+ return uriPort;
+ }
+
+ /** Returns the socket factory for new connections. */
+ public SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /**
+ * Returns the SSL socket factory, or null if this is not an HTTPS
+ * address.
+ */
+ public SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /**
+ * Returns the hostname verifier, or null if this is not an HTTPS
+ * address.
+ */
+ public HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ /**
+ * Returns the client's authenticator. This method never returns null.
+ */
+ public Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+ /**
+ * Returns the protocols the client supports. This method always returns a
+ * non-null list that contains minimally {@link Protocol#HTTP_1_1}.
+ */
+ public List getProtocols() {
+ return protocols;
+ }
+
+ /**
+ * Returns this address's explicitly-specified HTTP proxy, or null to
+ * delegate to the HTTP client's proxy selector.
+ */
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Address) {
+ Address that = (Address) other;
+ return Util.equal(this.proxy, that.proxy)
+ && this.uriHost.equals(that.uriHost)
+ && this.uriPort == that.uriPort
+ && Util.equal(this.sslSocketFactory, that.sslSocketFactory)
+ && Util.equal(this.hostnameVerifier, that.hostnameVerifier)
+ && Util.equal(this.authenticator, that.authenticator)
+ && Util.equal(this.protocols, that.protocols);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + uriHost.hashCode();
+ result = 31 * result + uriPort;
+ result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
+ result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
+ result = 31 * result + authenticator.hashCode();
+ result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
+ result = 31 * result + protocols.hashCode();
+ return result;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Authenticator.java b/contentstack/src/main/java/com/contentstack/okhttp/Authenticator.java
new file mode 100755
index 00000000..f204f805
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Authenticator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.contentstack.okhttp;
+
+import java.io.IOException;
+import java.net.Proxy;
+
+/**
+ * Responds to authentication challenges from the remote web or proxy server.
+ */
+public interface Authenticator {
+ /**
+ * Returns a request that includes a credential to satisfy an authentication
+ * challenge in {@code response}. Returns null if the challenge cannot be
+ * satisfied. This method is called in response to an HTTP 401 unauthorized
+ * status code sent by the origin server.
+ *
+ * Typical implementations will look up a credential and create a request
+ * derived from the initial request by setting the "Authorization" header.
+ *
{@code
+ *
+ * String credential = Credentials.basic(...)
+ * return response.request().newBuilder()
+ * .header("Authorization", credential)
+ * .build();
+ * }
+ */
+ Request authenticate(Proxy proxy, Response response) throws IOException;
+
+ /**
+ * Returns a request that includes a credential to satisfy an authentication
+ * challenge made by {@code response}. Returns null if the challenge cannot be
+ * satisfied. This method is called in response to an HTTP 407 unauthorized
+ * status code sent by the proxy server.
+ *
+ * Typical implementations will look up a credential and create a request
+ * derived from the initial request by setting the "Proxy-Authorization"
+ * header.
{@code
+ *
+ * String credential = Credentials.basic(...)
+ * return response.request().newBuilder()
+ * .header("Proxy-Authorization", credential)
+ * .build();
+ * }
+ */
+ Request authenticateProxy(Proxy proxy, Response response) throws IOException;
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Cache.java b/contentstack/src/main/java/com/contentstack/okhttp/Cache.java
new file mode 100755
index 00000000..00aa4494
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Cache.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.DiskLruCache;
+import com.contentstack.okhttp.internal.InternalCache;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.CacheRequest;
+import com.contentstack.okhttp.internal.http.CacheStrategy;
+import com.contentstack.okhttp.internal.http.HttpMethod;
+import com.contentstack.okhttp.internal.http.OkHeaders;
+import com.contentstack.okhttp.internal.http.StatusLine;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.ByteString;
+import com.contentstack.okio.ForwardingSink;
+import com.contentstack.okio.ForwardingSource;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
+ * saving time and bandwidth.
+ *
+ * Cache Optimization
+ * To measure cache effectiveness, this class tracks three statistics:
+ *
+ * - {@linkplain #getRequestCount() Request Count:} the
+ * number of HTTP requests issued since this cache was created.
+ *
- {@linkplain #getNetworkCount() Network Count:} the
+ * number of those requests that required network use.
+ *
- {@linkplain #getHitCount() Hit Count:} the number of
+ * those requests whose responses were served by the cache.
+ *
+ * Sometimes a request will result in a conditional cache hit. If the cache
+ * contains a stale copy of the response, the client will issue a conditional
+ * {@code GET}. The server will then send either the updated response if it has
+ * changed, or a short 'not modified' response if the client's copy is still
+ * valid. Such responses increment both the network count and hit count.
+ *
+ * The best way to improve the cache hit rate is by configuring the web
+ * server to return cacheable responses. Although this client honors all HTTP/1.1 (RFC 2068) cache
+ * headers, it doesn't cache partial responses.
+ *
+ *
Force a Network Response
+ * In some situations, such as after a user clicks a 'refresh' button, it may be
+ * necessary to skip the cache, and fetch data directly from the server. To force
+ * a full refresh, add the {@code no-cache} directive: {@code
+ * connection.addRequestProperty("Cache-Control", "no-cache");
+ * }
+ * If it is only necessary to force a cached response to be validated by the
+ * server, use the more efficient {@code max-age=0} instead: {@code
+ * connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }
+ *
+ * Force a Cache Response
+ * Sometimes you'll want to show resources if they are available immediately,
+ * but not otherwise. This can be used so your application can show
+ * something while waiting for the latest data to be downloaded. To
+ * restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: {@code
+ * try {
+ * connection.addRequestProperty("Cache-Control", "only-if-cached");
+ * InputStream cached = connection.getInputStream();
+ * // the resource was cached! show it
+ * } catch (FileNotFoundException e) {
+ * // the resource was not cached
+ * }
+ * }
+ * This technique works even better in situations where a stale response is
+ * better than no response. To permit stale cached responses, use the {@code
+ * max-stale} directive with the maximum staleness in seconds: {@code
+ * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }
+ */
+public final class Cache {
+ private static final int VERSION = 201105;
+ private static final int ENTRY_METADATA = 0;
+ private static final int ENTRY_BODY = 1;
+ private static final int ENTRY_COUNT = 2;
+
+ final InternalCache internalCache = new InternalCache() {
+ @Override public Response get(Request request) throws IOException {
+ return Cache.this.get(request);
+ }
+ @Override public CacheRequest put(Response response) throws IOException {
+ return Cache.this.put(response);
+ }
+ @Override public void remove(Request request) throws IOException {
+ Cache.this.remove(request);
+ }
+ @Override public void update(Response cached, Response network) throws IOException {
+ Cache.this.update(cached, network);
+ }
+ @Override public void trackConditionalCacheHit() {
+ Cache.this.trackConditionalCacheHit();
+ }
+ @Override public void trackResponse(CacheStrategy cacheStrategy) {
+ Cache.this.trackResponse(cacheStrategy);
+ }
+ };
+
+ private final DiskLruCache cache;
+
+ /* read and write statistics, all guarded by 'this' */
+ private int writeSuccessCount;
+ private int writeAbortCount;
+ private int networkCount;
+ private int hitCount;
+ private int requestCount;
+
+ public Cache(File directory, long maxSize) throws IOException {
+ cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
+ }
+
+ private static String urlToKey(Request request) {
+ return Util.hash(request.urlString());
+ }
+
+ Response get(Request request) {
+ String key = urlToKey(request);
+ DiskLruCache.Snapshot snapshot;
+ Entry entry;
+ try {
+ snapshot = cache.get(key);
+ if (snapshot == null) {
+ return null;
+ }
+ } catch (IOException e) {
+ // Give up because the cache cannot be read.
+ return null;
+ }
+
+ try {
+ entry = new Entry(snapshot.getSource(ENTRY_METADATA));
+ } catch (IOException e) {
+ Util.closeQuietly(snapshot);
+ return null;
+ }
+
+ Response response = entry.response(request, snapshot);
+
+ if (!entry.matches(request, response)) {
+ Util.closeQuietly(response.body());
+ return null;
+ }
+
+ return response;
+ }
+
+ private CacheRequest put(Response response) throws IOException {
+ String requestMethod = response.request().method();
+
+ if (HttpMethod.invalidatesCache(response.request().method())) {
+ try {
+ remove(response.request());
+ } catch (IOException ignored) {
+ // The cache cannot be written.
+ }
+ return null;
+ }
+ if (!requestMethod.equals("GET")) {
+ // Don't cache non-GET responses. We're technically allowed to cache
+ // HEAD requests and some POST requests, but the complexity of doing
+ // so is high and the benefit is low.
+ return null;
+ }
+
+ if (OkHeaders.hasVaryAll(response)) {
+ return null;
+ }
+
+ Entry entry = new Entry(response);
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = cache.edit(urlToKey(response.request()));
+ if (editor == null) {
+ return null;
+ }
+ entry.writeTo(editor);
+ return new CacheRequestImpl(editor);
+ } catch (IOException e) {
+ abortQuietly(editor);
+ return null;
+ }
+ }
+
+ private void remove(Request request) throws IOException {
+ cache.remove(urlToKey(request));
+ }
+
+ private void update(Response cached, Response network) {
+ Entry entry = new Entry(network);
+ DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = snapshot.edit(); // Returns null if snapshot is not current.
+ if (editor != null) {
+ entry.writeTo(editor);
+ editor.commit();
+ }
+ } catch (IOException e) {
+ abortQuietly(editor);
+ }
+ }
+
+ private void abortQuietly(DiskLruCache.Editor editor) {
+ // Give up because the cache cannot be written.
+ try {
+ if (editor != null) {
+ editor.abort();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ cache.delete();
+ }
+
+ public synchronized int getWriteAbortCount() {
+ return writeAbortCount;
+ }
+
+ public synchronized int getWriteSuccessCount() {
+ return writeSuccessCount;
+ }
+
+ public long getSize() {
+ return cache.size();
+ }
+
+ public long getMaxSize() {
+ return cache.getMaxSize();
+ }
+
+ public void flush() throws IOException {
+ cache.flush();
+ }
+
+ public void close() throws IOException {
+ cache.close();
+ }
+
+ public File getDirectory() {
+ return cache.getDirectory();
+ }
+
+ public boolean isClosed() {
+ return cache.isClosed();
+ }
+
+ private synchronized void trackResponse(CacheStrategy cacheStrategy) {
+ requestCount++;
+
+ if (cacheStrategy.networkRequest != null) {
+ // If this is a conditional request, we'll increment hitCount if/when it hits.
+ networkCount++;
+
+ } else if (cacheStrategy.cacheResponse != null) {
+ // This response uses the cache and not the network. That's a cache hit.
+ hitCount++;
+ }
+ }
+
+ private synchronized void trackConditionalCacheHit() {
+ hitCount++;
+ }
+
+ public synchronized int getNetworkCount() {
+ return networkCount;
+ }
+
+ public synchronized int getHitCount() {
+ return hitCount;
+ }
+
+ public synchronized int getRequestCount() {
+ return requestCount;
+ }
+
+ private final class CacheRequestImpl implements CacheRequest {
+ private final DiskLruCache.Editor editor;
+ private Sink cacheOut;
+ private boolean done;
+ private Sink body;
+
+ public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
+ this.editor = editor;
+ this.cacheOut = editor.newSink(ENTRY_BODY);
+ this.body = new ForwardingSink(cacheOut) {
+ @Override public void close() throws IOException {
+ synchronized (Cache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeSuccessCount++;
+ }
+ super.close();
+ editor.commit();
+ }
+ };
+ }
+
+ @Override public void abort() {
+ synchronized (Cache.this) {
+ if (done) {
+ return;
+ }
+ done = true;
+ writeAbortCount++;
+ }
+ Util.closeQuietly(cacheOut);
+ try {
+ editor.abort();
+ } catch (IOException ignored) {
+ }
+ }
+
+ @Override public Sink body() {
+ return body;
+ }
+ }
+
+ private static final class Entry {
+ private final String url;
+ private final Headers varyHeaders;
+ private final String requestMethod;
+ private final Protocol protocol;
+ private final int code;
+ private final String message;
+ private final Headers responseHeaders;
+ private final Handshake handshake;
+
+ /**
+ * Reads an entry from an input stream. A typical entry looks like this:
+ * {@code
+ * http://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ * }
+ *
+ * A typical HTTPS file looks like this:
+ *
{@code
+ * https://google.com/foo
+ * GET
+ * 2
+ * Accept-Language: fr-CA
+ * Accept-Charset: UTF-8
+ * HTTP/1.1 200 OK
+ * 3
+ * Content-Type: image/png
+ * Content-Length: 100
+ * Cache-Control: max-age=600
+ *
+ * AES_256_WITH_MD5
+ * 2
+ * base64-encoded peerCertificate[0]
+ * base64-encoded peerCertificate[1]
+ * -1
+ * }
+ * The file is newline separated. The first two lines are the URL and
+ * the request method. Next is the number of HTTP Vary request header
+ * lines, followed by those lines.
+ *
+ * Next is the response status line, followed by the number of HTTP
+ * response header lines, followed by those lines.
+ *
+ *
HTTPS responses also contain SSL session information. This begins
+ * with a blank line, and then a line containing the cipher suite. Next
+ * is the length of the peer certificate chain. These certificates are
+ * base64-encoded and appear each on their own line. The next line
+ * contains the length of the local certificate chain. These
+ * certificates are also base64-encoded and appear each on their own
+ * line. A length of -1 is used to encode a null array.
+ */
+ public Entry(Source in) throws IOException {
+ try {
+ BufferedSource source = Okio.buffer(in);
+ url = source.readUtf8LineStrict();
+ requestMethod = source.readUtf8LineStrict();
+ Headers.Builder varyHeadersBuilder = new Headers.Builder();
+ int varyRequestHeaderLineCount = readInt(source);
+ for (int i = 0; i < varyRequestHeaderLineCount; i++) {
+ varyHeadersBuilder.addLine(source.readUtf8LineStrict());
+ }
+ varyHeaders = varyHeadersBuilder.build();
+
+ StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+ protocol = statusLine.protocol;
+ code = statusLine.code;
+ message = statusLine.message;
+ Headers.Builder responseHeadersBuilder = new Headers.Builder();
+ int responseHeaderLineCount = readInt(source);
+ for (int i = 0; i < responseHeaderLineCount; i++) {
+ responseHeadersBuilder.addLine(source.readUtf8LineStrict());
+ }
+ responseHeaders = responseHeadersBuilder.build();
+
+ if (isHttps()) {
+ String blank = source.readUtf8LineStrict();
+ if (blank.length() > 0) {
+ throw new IOException("expected \"\" but was \"" + blank + "\"");
+ }
+ String cipherSuite = source.readUtf8LineStrict();
+ List peerCertificates = readCertificateList(source);
+ List localCertificates = readCertificateList(source);
+ handshake = Handshake.get(cipherSuite, peerCertificates, localCertificates);
+ } else {
+ handshake = null;
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ public Entry(Response response) {
+ this.url = response.request().urlString();
+ this.varyHeaders = OkHeaders.varyHeaders(response);
+ this.requestMethod = response.request().method();
+ this.protocol = response.protocol();
+ this.code = response.code();
+ this.message = response.message();
+ this.responseHeaders = response.headers();
+ this.handshake = response.handshake();
+ }
+
+ public void writeTo(DiskLruCache.Editor editor) throws IOException {
+ BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
+
+ sink.writeUtf8(url);
+ sink.writeByte('\n');
+ sink.writeUtf8(requestMethod);
+ sink.writeByte('\n');
+ sink.writeUtf8(Integer.toString(varyHeaders.size()));
+ sink.writeByte('\n');
+ for (int i = 0; i < varyHeaders.size(); i++) {
+ sink.writeUtf8(varyHeaders.name(i));
+ sink.writeUtf8(": ");
+ sink.writeUtf8(varyHeaders.value(i));
+ sink.writeByte('\n');
+ }
+
+ sink.writeUtf8(new StatusLine(protocol, code, message).toString());
+ sink.writeByte('\n');
+ sink.writeUtf8(Integer.toString(responseHeaders.size()));
+ sink.writeByte('\n');
+ for (int i = 0; i < responseHeaders.size(); i++) {
+ sink.writeUtf8(responseHeaders.name(i));
+ sink.writeUtf8(": ");
+ sink.writeUtf8(responseHeaders.value(i));
+ sink.writeByte('\n');
+ }
+
+ if (isHttps()) {
+ sink.writeByte('\n');
+ sink.writeUtf8(handshake.cipherSuite());
+ sink.writeByte('\n');
+ writeCertArray(sink, handshake.peerCertificates());
+ writeCertArray(sink, handshake.localCertificates());
+ }
+ sink.close();
+ }
+
+ private boolean isHttps() {
+ return url.startsWith("https://");
+ }
+
+ private List readCertificateList(BufferedSource source) throws IOException {
+ int length = readInt(source);
+ if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null.
+
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ List result = new ArrayList(length);
+ for (int i = 0; i < length; i++) {
+ String line = source.readUtf8LineStrict();
+ byte[] bytes = ByteString.decodeBase64(line).toByteArray();
+ result.add(certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)));
+ }
+ return result;
+ } catch (CertificateException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private void writeCertArray(BufferedSink sink, List certificates)
+ throws IOException {
+ try {
+ sink.writeUtf8(Integer.toString(certificates.size()));
+ sink.writeByte('\n');
+ for (int i = 0, size = certificates.size(); i < size; i++) {
+ byte[] bytes = certificates.get(i).getEncoded();
+ String line = ByteString.of(bytes).base64();
+ sink.writeUtf8(line);
+ sink.writeByte('\n');
+ }
+ } catch (CertificateEncodingException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public boolean matches(Request request, Response response) {
+ return url.equals(request.urlString())
+ && requestMethod.equals(request.method())
+ && OkHeaders.varyMatches(response, varyHeaders, request);
+ }
+
+ public Response response(Request request, DiskLruCache.Snapshot snapshot) {
+ String contentType = responseHeaders.get("Content-Type");
+ String contentLength = responseHeaders.get("Content-Length");
+ Request cacheRequest = new Request.Builder()
+ .url(url)
+ .method(requestMethod, null)
+ .headers(varyHeaders)
+ .build();
+ return new Response.Builder()
+ .request(cacheRequest)
+ .protocol(protocol)
+ .code(code)
+ .message(message)
+ .headers(responseHeaders)
+ .body(new CacheResponseBody(snapshot, contentType, contentLength))
+ .handshake(handshake)
+ .build();
+ }
+ }
+
+ private static int readInt(BufferedSource source) throws IOException {
+ String line = source.readUtf8LineStrict();
+ try {
+ return Integer.parseInt(line);
+ } catch (NumberFormatException e) {
+ throw new IOException("Expected an integer but was \"" + line + "\"");
+ }
+ }
+
+ private static class CacheResponseBody extends ResponseBody {
+ private final DiskLruCache.Snapshot snapshot;
+ private final BufferedSource bodySource;
+ private final String contentType;
+ private final String contentLength;
+
+ public CacheResponseBody(final DiskLruCache.Snapshot snapshot,
+ String contentType, String contentLength) {
+ this.snapshot = snapshot;
+ this.contentType = contentType;
+ this.contentLength = contentLength;
+
+ Source source = snapshot.getSource(ENTRY_BODY);
+ bodySource = Okio.buffer(new ForwardingSource(source) {
+ @Override public void close() throws IOException {
+ snapshot.close();
+ super.close();
+ }
+ });
+ }
+
+ @Override public MediaType contentType() {
+ return contentType != null ? MediaType.parse(contentType) : null;
+ }
+
+ @Override public long contentLength() {
+ try {
+ return contentLength != null ? Long.parseLong(contentLength) : -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ @Override public BufferedSource source() {
+ return bodySource;
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/CacheControl.java b/contentstack/src/main/java/com/contentstack/okhttp/CacheControl.java
new file mode 100755
index 00000000..fcbe6db3
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/CacheControl.java
@@ -0,0 +1,176 @@
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.http.HeaderParser;
+
+/**
+ * A Cache-Control header with cache directives from a server or client. These
+ * directives set policy on what responses can be stored, and which requests can
+ * be satisfied by those stored responses.
+ *
+ * See RFC
+ * 2616, 14.9.
+ */
+public final class CacheControl {
+ private final boolean noCache;
+ private final boolean noStore;
+ private final int maxAgeSeconds;
+ private final int sMaxAgeSeconds;
+ private final boolean isPublic;
+ private final boolean mustRevalidate;
+ private final int maxStaleSeconds;
+ private final int minFreshSeconds;
+ private final boolean onlyIfCached;
+
+ private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
+ boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds,
+ boolean onlyIfCached) {
+ this.noCache = noCache;
+ this.noStore = noStore;
+ this.maxAgeSeconds = maxAgeSeconds;
+ this.sMaxAgeSeconds = sMaxAgeSeconds;
+ this.isPublic = isPublic;
+ this.mustRevalidate = mustRevalidate;
+ this.maxStaleSeconds = maxStaleSeconds;
+ this.minFreshSeconds = minFreshSeconds;
+ this.onlyIfCached = onlyIfCached;
+ }
+
+ /**
+ * In a response, this field's name "no-cache" is misleading. It doesn't
+ * prevent us from caching the response; it only means we have to validate the
+ * response with the origin server before returning it. We can do this with a
+ * conditional GET.
+ *
+ *
In a request, it means do not use a cache to satisfy the request.
+ */
+ public boolean noCache() {
+ return noCache;
+ }
+
+ /** If true, this response should not be cached. */
+ public boolean noStore() {
+ return noStore;
+ }
+
+ /**
+ * The duration past the response's served date that it can be served without
+ * validation.
+ */
+ public int maxAgeSeconds() {
+ return maxAgeSeconds;
+ }
+
+ /**
+ * The "s-maxage" directive is the max age for shared caches. Not to be
+ * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
+ * this directive is not honored by this cache.
+ */
+ public int sMaxAgeSeconds() {
+ return sMaxAgeSeconds;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+ public boolean mustRevalidate() {
+ return mustRevalidate;
+ }
+
+ public int maxStaleSeconds() {
+ return maxStaleSeconds;
+ }
+
+ public int minFreshSeconds() {
+ return minFreshSeconds;
+ }
+
+ /**
+ * This field's name "only-if-cached" is misleading. It actually means "do
+ * not use the network". It is set by a client who only wants to make a
+ * request if it can be fully satisfied by the cache. Cached responses that
+ * would require validation (ie. conditional gets) are not permitted if this
+ * header is set.
+ */
+ public boolean onlyIfCached() {
+ return onlyIfCached;
+ }
+
+ /**
+ * Returns the cache directives of {@code headers}. This honors both
+ * Cache-Control and Pragma headers if they are present.
+ */
+ public static CacheControl parse(Headers headers) {
+ boolean noCache = false;
+ boolean noStore = false;
+ int maxAgeSeconds = -1;
+ int sMaxAgeSeconds = -1;
+ boolean isPublic = false;
+ boolean mustRevalidate = false;
+ int maxStaleSeconds = -1;
+ int minFreshSeconds = -1;
+ boolean onlyIfCached = false;
+
+ for (int i = 0; i < headers.size(); i++) {
+ if (!headers.name(i).equalsIgnoreCase("Cache-Control")
+ && !headers.name(i).equalsIgnoreCase("Pragma")) {
+ continue;
+ }
+
+ String string = headers.value(i);
+ int pos = 0;
+ while (pos < string.length()) {
+ int tokenStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, "=,;");
+ String directive = string.substring(tokenStart, pos).trim();
+ String parameter;
+
+ if (pos == string.length() || string.charAt(pos) == ',' || string.charAt(pos) == ';') {
+ pos++; // consume ',' or ';' (if necessary)
+ parameter = null;
+ } else {
+ pos++; // consume '='
+ pos = HeaderParser.skipWhitespace(string, pos);
+
+ // quoted string
+ if (pos < string.length() && string.charAt(pos) == '\"') {
+ pos++; // consume '"' open quote
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, "\"");
+ parameter = string.substring(parameterStart, pos);
+ pos++; // consume '"' close quote (if necessary)
+
+ // unquoted string
+ } else {
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, ",;");
+ parameter = string.substring(parameterStart, pos).trim();
+ }
+ }
+
+ if ("no-cache".equalsIgnoreCase(directive)) {
+ noCache = true;
+ } else if ("no-store".equalsIgnoreCase(directive)) {
+ noStore = true;
+ } else if ("max-age".equalsIgnoreCase(directive)) {
+ maxAgeSeconds = HeaderParser.parseSeconds(parameter);
+ } else if ("s-maxage".equalsIgnoreCase(directive)) {
+ sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
+ } else if ("public".equalsIgnoreCase(directive)) {
+ isPublic = true;
+ } else if ("must-revalidate".equalsIgnoreCase(directive)) {
+ mustRevalidate = true;
+ } else if ("max-stale".equalsIgnoreCase(directive)) {
+ maxStaleSeconds = HeaderParser.parseSeconds(parameter);
+ } else if ("min-fresh".equalsIgnoreCase(directive)) {
+ minFreshSeconds = HeaderParser.parseSeconds(parameter);
+ } else if ("only-if-cached".equalsIgnoreCase(directive)) {
+ onlyIfCached = true;
+ }
+ }
+ }
+
+ return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPublic,
+ mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Call.java b/contentstack/src/main/java/com/contentstack/okhttp/Call.java
new file mode 100755
index 00000000..16e98ae6
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Call.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.NamedRunnable;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+import com.contentstack.okhttp.internal.http.HttpMethod;
+import com.contentstack.okhttp.internal.http.OkHeaders;
+import com.contentstack.okhttp.internal.http.RetryableSink;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/**
+ * A call is a request that has been prepared for execution. A call can be
+ * canceled. As this object represents a single request/response pair (stream),
+ * it cannot be executed twice.
+ */
+public class Call {
+ private final OkHttpClient client;
+ private int redirectionCount;
+
+ // Guarded by this.
+ private boolean executed;
+ volatile boolean canceled;
+
+ /** The request; possibly a consequence of redirects or auth headers. */
+ private Request request;
+ HttpEngine engine;
+
+ protected Call(OkHttpClient client, Request request) {
+ // Copy the client. Otherwise changes (socket factory, redirect policy,
+ // etc.) may incorrectly be reflected in the request when it is executed.
+ this.client = client.copyWithDefaults();
+ this.request = request;
+ }
+
+ /**
+ * Invokes the request immediately, and blocks until the response can be
+ * processed or is in error.
+ *
+ *
The caller may read the response body with the response's
+ * {@link Response#body} method. To facilitate connection recycling, callers
+ * should always {@link ResponseBody#close() close the response body}.
+ *
+ *
Note that transport-layer success (receiving a HTTP response code,
+ * headers and body) does not necessarily indicate application-layer success:
+ * {@code response} may still indicate an unhappy HTTP response code like 404
+ * or 500.
+ *
+ * @throws IOException if the request could not be executed due to
+ * cancellation, a connectivity problem or timeout. Because networks can
+ * fail during an exchange, it is possible that the remote server
+ * accepted the request before the failure.
+ *
+ * @throws IllegalStateException when the call has already been executed.
+ */
+ public Response execute() throws IOException {
+ synchronized (this) {
+ if (executed) throw new IllegalStateException("Already Executed");
+ executed = true;
+ }
+ Response result = getResponse();
+ engine.releaseConnection(); // Transfer ownership of the body to the caller.
+ if (result == null) throw new IOException("Canceled");
+ return result;
+ }
+
+ /**
+ * Schedules the request to be executed at some point in the future.
+ *
+ *
The {@link OkHttpClient#getDispatcher dispatcher} defines when the
+ * request will run: usually immediately unless there are several other
+ * requests currently being executed.
+ *
+ *
This client will later call back {@code responseCallback} with either
+ * an HTTP response or a failure exception. If you {@link #cancel} a request
+ * before it completes the callback will not be invoked.
+ *
+ * @throws IllegalStateException when the call has already been executed.
+ */
+ public void enqueue(Callback responseCallback) {
+ synchronized (this) {
+ if (executed) throw new IllegalStateException("Already Executed");
+ executed = true;
+ }
+ client.getDispatcher().enqueue(new AsyncCall(responseCallback));
+ }
+
+ /**
+ * Cancels the request, if possible. Requests that are already complete
+ * cannot be canceled.
+ */
+ public void cancel() {
+ canceled = true;
+ if (engine != null) engine.disconnect();
+ }
+
+ final class AsyncCall extends NamedRunnable {
+ private final Callback responseCallback;
+
+ private AsyncCall(Callback responseCallback) {
+ super("OkHttp %s", request.urlString());
+ this.responseCallback = responseCallback;
+ }
+
+ String host() {
+ return request.url().getHost();
+ }
+
+ Request request() {
+ return request;
+ }
+
+ Object tag() {
+ return request.tag();
+ }
+
+ Call get() {
+ return Call.this;
+ }
+
+ @Override protected void execute() {
+ boolean signalledCallback = false;
+ try {
+ Response response = getResponse();
+ if (canceled) {
+ signalledCallback = true;
+ responseCallback.onFailure(request, new IOException("Canceled"));
+ } else {
+ signalledCallback = true;
+ engine.releaseConnection();
+ responseCallback.onResponse(response);
+ }
+ } catch (IOException e) {
+ if (signalledCallback) throw new RuntimeException(e); // Do not signal the callback twice!
+ responseCallback.onFailure(request, e);
+ } finally {
+ client.getDispatcher().finished(this);
+ }
+ }
+ }
+
+ /**
+ * Performs the request and returns the response. May return null if this
+ * call was canceled.
+ */
+ private Response getResponse() throws IOException {
+ // Copy body metadata to the appropriate request headers.
+ RequestBody body = request.body();
+ RetryableSink requestBodyOut = null;
+ if (body != null) {
+ Request.Builder requestBuilder = request.newBuilder();
+
+ MediaType contentType = body.contentType();
+ if (contentType != null) {
+ requestBuilder.header("Content-Type", contentType.toString());
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength != -1) {
+ requestBuilder.header("Content-Length", Long.toString(contentLength));
+ requestBuilder.removeHeader("Transfer-Encoding");
+ } else {
+ requestBuilder.header("Transfer-Encoding", "chunked");
+ requestBuilder.removeHeader("Content-Length");
+ }
+
+ request = requestBuilder.build();
+ } else if (HttpMethod.hasRequestBody(request.method())) {
+ requestBodyOut = Util.emptySink();
+ }
+
+ // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
+ engine = new HttpEngine(client, request, false, null, null, requestBodyOut, null);
+
+ while (true) {
+ if (canceled) return null;
+
+ try {
+ engine.sendRequest();
+
+ if (request.body() != null) {
+ BufferedSink sink = engine.getBufferedRequestBody();
+ request.body().writeTo(sink);
+ }
+
+ engine.readResponse();
+ } catch (IOException e) {
+ HttpEngine retryEngine = engine.recover(e, null);
+ if (retryEngine != null) {
+ engine = retryEngine;
+ continue;
+ }
+
+ // Give up; recovery is not possible.
+ throw e;
+ }
+
+ Response response = engine.getResponse();
+ Request followUp = engine.followUpRequest();
+
+ if (followUp == null) {
+ engine.releaseConnection();
+ return response.newBuilder()
+ .body(new RealResponseBody(response, engine.getResponseBody()))
+ .build();
+ }
+
+ if (engine.getResponse().isRedirect() && ++redirectionCount > HttpEngine.MAX_REDIRECTS) {
+ throw new ProtocolException("Too many redirects: " + redirectionCount);
+ }
+
+ if (!engine.sameConnection(followUp.url())) {
+ engine.releaseConnection();
+ }
+
+ Connection connection = engine.close();
+ request = followUp;
+ engine = new HttpEngine(client, request, false, connection, null, null, response);
+ }
+ }
+
+ private static class RealResponseBody extends ResponseBody {
+ private final Response response;
+ private final BufferedSource source;
+
+ RealResponseBody(Response response, BufferedSource source) {
+ this.response = response;
+ this.source = source;
+ }
+
+ @Override public MediaType contentType() {
+ String contentType = response.header("Content-Type");
+ return contentType != null ? MediaType.parse(contentType) : null;
+ }
+
+ @Override public long contentLength() {
+ return OkHeaders.contentLength(response);
+ }
+
+ @Override public BufferedSource source() {
+ return source;
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Callback.java b/contentstack/src/main/java/com/contentstack/okhttp/Callback.java
new file mode 100755
index 00000000..428040c6
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Callback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import java.io.IOException;
+
+public interface Callback {
+ /**
+ * Called when the request could not be executed due to cancellation, a
+ * connectivity problem or timeout. Because networks can fail during an
+ * exchange, it is possible that the remote server accepted the request
+ * before the failure.
+ */
+ void onFailure(Request request, IOException e);
+
+ /**
+ * Called when the HTTP response was successfully returned by the remote
+ * server. The callback may proceed to read the response body with {@link
+ * Response#body}. The response is still live until its response body is
+ * closed with {@code response.body().close()}. The recipient of the callback
+ * may even consume the response body on another thread.
+ *
+ *
Note that transport-layer success (receiving a HTTP response code,
+ * headers and body) does not necessarily indicate application-layer
+ * success: {@code response} may still indicate an unhappy HTTP response
+ * code like 404 or 500.
+ */
+ void onResponse(Response response) throws IOException;
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Challenge.java b/contentstack/src/main/java/com/contentstack/okhttp/Challenge.java
new file mode 100755
index 00000000..f76b5f47
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Challenge.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+
+/** An RFC 2617 challenge. */
+public final class Challenge {
+ private final String scheme;
+ private final String realm;
+
+ public Challenge(String scheme, String realm) {
+ this.scheme = scheme;
+ this.realm = realm;
+ }
+
+ /** Returns the authentication scheme, like {@code Basic}. */
+ public String getScheme() {
+ return scheme;
+ }
+
+ /** Returns the protection space. */
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Challenge
+ && Util.equal(scheme, ((Challenge) o).scheme)
+ && Util.equal(realm, ((Challenge) o).realm);
+ }
+
+ @Override public int hashCode() {
+ int result = 29;
+ result = 31 * result + (realm != null ? realm.hashCode() : 0);
+ result = 31 * result + (scheme != null ? scheme.hashCode() : 0);
+ return result;
+ }
+
+ @Override public String toString() {
+ return scheme + " realm=\"" + realm + "\"";
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Connection.java b/contentstack/src/main/java/com/contentstack/okhttp/Connection.java
new file mode 100755
index 00000000..26115125
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Connection.java
@@ -0,0 +1,419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Platform;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.HttpConnection;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+import com.contentstack.okhttp.internal.http.HttpTransport;
+import com.contentstack.okhttp.internal.http.OkHeaders;
+import com.contentstack.okhttp.internal.http.SpdyTransport;
+import com.contentstack.okhttp.internal.http.Transport;
+import com.contentstack.okhttp.internal.spdy.SpdyConnection;
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.URL;
+
+import javax.net.ssl.SSLSocket;
+
+import static com.contentstack.okhttp.internal.Util.getEffectivePort;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+
+/**
+ * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be
+ * used for multiple HTTP request/response exchanges. Connections may be direct
+ * to the origin server or via a proxy.
+ *
+ *
Typically instances of this class are created, connected and exercised
+ * automatically by the HTTP client. Applications may use this class to monitor
+ * HTTP connections as members of a {@linkplain ConnectionPool connection pool}.
+ *
+ *
Do not confuse this class with the misnamed {@code HttpURLConnection},
+ * which isn't so much a connection as a single request/response exchange.
+ *
+ *
Modern TLS
+ * There are tradeoffs when selecting which options to include when negotiating
+ * a secure connection to a remote host. Newer TLS options are quite useful:
+ *
+ * - Server Name Indication (SNI) enables one IP address to negotiate secure
+ * connections for multiple domain names.
+ *
- Application Layer Protocol Negotiation (ALPN) enables the HTTPS port
+ * (443) to be used for different HTTP and SPDY protocols.
+ *
+ * Unfortunately, older HTTPS servers refuse to connect when such options are
+ * presented. Rather than avoiding these options entirely, this class allows a
+ * connection to be attempted with modern options and then retried without them
+ * should the attempt fail.
+ */
+public final class Connection {
+ private final ConnectionPool pool;
+ private final Route route;
+
+ private Socket socket;
+ private boolean connected = false;
+ private HttpConnection httpConnection;
+ private SpdyConnection spdyConnection;
+ private Protocol protocol = Protocol.HTTP_1_1;
+ private long idleStartTimeNs;
+ private Handshake handshake;
+ private int recycleCount;
+
+ /**
+ * The object that owns this connection. Null if it is shared (for SPDY),
+ * belongs to a pool, or has been discarded. Guarded by {@code pool}, which
+ * clears the owner when an incoming connection is recycled.
+ */
+ private Object owner;
+
+ public Connection(ConnectionPool pool, Route route) {
+ this.pool = pool;
+ this.route = route;
+ }
+
+ Object getOwner() {
+ synchronized (pool) {
+ return owner;
+ }
+ }
+
+ void setOwner(Object owner) {
+ if (isSpdy()) return; // SPDY connections are shared.
+ synchronized (pool) {
+ if (this.owner != null) throw new IllegalStateException("Connection already has an owner!");
+ this.owner = owner;
+ }
+ }
+
+ /**
+ * Attempts to clears the owner of this connection. Returns true if the owner
+ * was cleared and the connection can be pooled or reused. This will return
+ * false if the connection cannot be pooled or reused, such as if it was
+ * closed with {@link #closeIfOwnedBy}.
+ */
+ boolean clearOwner() {
+ synchronized (pool) {
+ if (owner == null) {
+ // No owner? Don't reuse this connection.
+ return false;
+ }
+
+ owner = null;
+ return true;
+ }
+ }
+
+ /**
+ * Closes this connection if it is currently owned by {@code owner}. This also
+ * strips the ownership of the connection so it cannot be pooled or reused.
+ */
+ void closeIfOwnedBy(Object owner) throws IOException {
+ if (isSpdy()) throw new IllegalStateException();
+ synchronized (pool) {
+ if (this.owner != owner) {
+ return; // Wrong owner. Perhaps a late disconnect?
+ }
+
+ this.owner = null; // Drop the owner so the connection won't be reused.
+ }
+
+ // Don't close() inside the synchronized block.
+ socket.close();
+ }
+
+ void connect(int connectTimeout, int readTimeout, int writeTimeout, Request tunnelRequest)
+ throws IOException {
+ if (connected) throw new IllegalStateException("already connected");
+
+ if (route.proxy.type() == Proxy.Type.DIRECT || route.proxy.type() == Proxy.Type.HTTP) {
+ socket = route.address.socketFactory.createSocket();
+ } else {
+ socket = new Socket(route.proxy);
+ }
+
+ socket.setSoTimeout(readTimeout);
+ Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
+
+ if (route.address.sslSocketFactory != null) {
+ upgradeToTls(tunnelRequest, readTimeout, writeTimeout);
+ } else {
+ httpConnection = new HttpConnection(pool, this, socket);
+ }
+ connected = true;
+ }
+
+ /**
+ * Connects this connection if it isn't already. This creates tunnels, shares
+ * the connection with the connection pool, and configures timeouts.
+ */
+ void connectAndSetOwner(OkHttpClient client, Object owner, Request request) throws IOException {
+ setOwner(owner);
+
+ if (!isConnected()) {
+ Request tunnelRequest = tunnelRequest(request);
+ connect(client.getConnectTimeout(), client.getReadTimeout(),
+ client.getWriteTimeout(), tunnelRequest);
+ if (isSpdy()) {
+ client.getConnectionPool().share(this);
+ }
+ client.routeDatabase().connected(getRoute());
+ }
+
+ setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
+ }
+
+ /**
+ * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if
+ * no tunnel is necessary. Everything in the tunnel request is sent
+ * unencrypted to the proxy server, so tunnels include only the minimum set of
+ * headers. This avoids sending potentially sensitive data like HTTP cookies
+ * to the proxy unencrypted.
+ */
+ private Request tunnelRequest(Request request) throws IOException {
+ if (!route.requiresTunnel()) return null;
+
+ String host = request.url().getHost();
+ int port = getEffectivePort(request.url());
+ String authority = (port == Util.getDefaultPort("https")) ? host : (host + ":" + port);
+ Request.Builder result = new Request.Builder()
+ .url(new URL("https", host, port, "/"))
+ .header("Host", authority)
+ .header("Proxy-Connection", "Keep-Alive"); // For HTTP/1.0 proxies like Squid.
+
+ // Copy over the User-Agent header if it exists.
+ String userAgent = request.header("User-Agent");
+ if (userAgent != null) {
+ result.header("User-Agent", userAgent);
+ }
+
+ // Copy over the Proxy-Authorization header if it exists.
+ String proxyAuthorization = request.header("Proxy-Authorization");
+ if (proxyAuthorization != null) {
+ result.header("Proxy-Authorization", proxyAuthorization);
+ }
+
+ return result.build();
+ }
+
+ /**
+ * Create an {@code SSLSocket} and perform the TLS handshake and certificate
+ * validation.
+ */
+ private void upgradeToTls(Request tunnelRequest, int readTimeout, int writeTimeout)
+ throws IOException {
+ Platform platform = Platform.get();
+
+ // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
+ if (tunnelRequest != null) {
+ makeTunnel(tunnelRequest, readTimeout, writeTimeout);
+ }
+
+ // Create the wrapper over connected socket.
+ socket = route.address.sslSocketFactory
+ .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */);
+ SSLSocket sslSocket = (SSLSocket) socket;
+ platform.configureTls(sslSocket, route.address.uriHost, route.tlsVersion);
+
+ boolean useNpn = route.supportsNpn();
+ if (useNpn) {
+ platform.setProtocols(sslSocket, route.address.protocols);
+ }
+
+ // Force handshake. This can throw!
+ sslSocket.startHandshake();
+
+ // Verify that the socket's certificates are acceptable for the target host.
+ if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) {
+ throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
+ }
+
+ handshake = Handshake.get(sslSocket.getSession());
+
+ String maybeProtocol;
+ if (useNpn && (maybeProtocol = platform.getSelectedProtocol(sslSocket)) != null) {
+ protocol = Protocol.get(maybeProtocol); // Throws IOE on unknown.
+ }
+
+ if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
+ sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
+ spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, socket)
+ .protocol(protocol).build();
+ spdyConnection.sendConnectionPreface();
+ } else {
+ httpConnection = new HttpConnection(pool, this, socket);
+ }
+ }
+
+ /** Returns true if {@link #connect} has been attempted on this connection. */
+ boolean isConnected() {
+ return connected;
+ }
+
+ /** Returns the route used by this connection. */
+ public Route getRoute() {
+ return route;
+ }
+
+ /**
+ * Returns the socket that this connection uses, or null if the connection
+ * is not currently connected.
+ */
+ public Socket getSocket() {
+ return socket;
+ }
+
+ /** Returns true if this connection is alive. */
+ boolean isAlive() {
+ return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
+ }
+
+ /**
+ * Returns true if we are confident that we can read data from this
+ * connection. This is more expensive and more accurate than {@link
+ * #isAlive()}; callers should check {@link #isAlive()} first.
+ */
+ boolean isReadable() {
+ if (httpConnection != null) return httpConnection.isReadable();
+ return true; // SPDY connections, and connections before connect() are both optimistic.
+ }
+
+ void resetIdleStartTime() {
+ if (spdyConnection != null) throw new IllegalStateException("spdyConnection != null");
+ this.idleStartTimeNs = System.nanoTime();
+ }
+
+ /** Returns true if this connection is idle. */
+ boolean isIdle() {
+ return spdyConnection == null || spdyConnection.isIdle();
+ }
+
+ /**
+ * Returns true if this connection has been idle for longer than
+ * {@code keepAliveDurationNs}.
+ */
+ boolean isExpired(long keepAliveDurationNs) {
+ return getIdleStartTimeNs() < System.nanoTime() - keepAliveDurationNs;
+ }
+
+ /**
+ * Returns the time in ns when this connection became idle. Undefined if
+ * this connection is not idle.
+ */
+ long getIdleStartTimeNs() {
+ return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs();
+ }
+
+ public Handshake getHandshake() {
+ return handshake;
+ }
+
+ /** Returns the transport appropriate for this connection. */
+ Transport newTransport(HttpEngine httpEngine) throws IOException {
+ return (spdyConnection != null)
+ ? new SpdyTransport(httpEngine, spdyConnection)
+ : new HttpTransport(httpEngine, httpConnection);
+ }
+
+ /**
+ * Returns true if this is a SPDY connection. Such connections can be used
+ * in multiple HTTP requests simultaneously.
+ */
+ boolean isSpdy() {
+ return spdyConnection != null;
+ }
+
+ /**
+ * Returns the protocol negotiated by this connection, or {@link
+ * Protocol#HTTP_1_1} if no protocol has been negotiated.
+ */
+ public Protocol getProtocol() {
+ return protocol;
+ }
+
+ /**
+ * Sets the protocol negotiated by this connection. Typically this is used
+ * when an HTTP/1.1 request is sent and an HTTP/1.0 response is received.
+ */
+ void setProtocol(Protocol protocol) {
+ if (protocol == null) throw new IllegalArgumentException("protocol == null");
+ this.protocol = protocol;
+ }
+
+ void setTimeouts(int readTimeoutMillis, int writeTimeoutMillis) throws IOException {
+ if (!connected) throw new IllegalStateException("setTimeouts - not connected");
+
+ // Don't set timeouts on shared SPDY connections.
+ if (httpConnection != null) {
+ socket.setSoTimeout(readTimeoutMillis);
+ httpConnection.setTimeouts(readTimeoutMillis, writeTimeoutMillis);
+ }
+ }
+
+ void incrementRecycleCount() {
+ recycleCount++;
+ }
+
+ /**
+ * Returns the number of times this connection has been returned to the
+ * connection pool.
+ */
+ int recycleCount() {
+ return recycleCount;
+ }
+
+ /**
+ * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+ * CONNECT request to create the proxy connection. This may need to be
+ * retried if the proxy requires authorization.
+ */
+ private void makeTunnel(Request request, int readTimeout, int writeTimeout)
+ throws IOException {
+ HttpConnection tunnelConnection = new HttpConnection(pool, this, socket);
+ tunnelConnection.setTimeouts(readTimeout, writeTimeout);
+ URL url = request.url();
+ String requestLine = "CONNECT " + url.getHost() + ":" + url.getPort() + " HTTP/1.1";
+ while (true) {
+ tunnelConnection.writeRequest(request.headers(), requestLine);
+ tunnelConnection.flush();
+ Response response = tunnelConnection.readResponse().request(request).build();
+ tunnelConnection.emptyResponseBody();
+
+ switch (response.code()) {
+ case HTTP_OK:
+ // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If that
+ // happens, then we will have buffered bytes that are needed by the SSLSocket!
+ if (tunnelConnection.bufferSize() > 0) {
+ throw new IOException("TLS tunnel buffered too many bytes!");
+ }
+ return;
+
+ case HTTP_PROXY_AUTH:
+ request = OkHeaders.processAuthHeader(
+ route.address.authenticator, response, route.proxy);
+ if (request != null) continue;
+ throw new IOException("Failed to authenticate with proxy");
+
+ default:
+ throw new IOException(
+ "Unexpected response code for CONNECT: " + response.code());
+ }
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/ConnectionPool.java b/contentstack/src/main/java/com/contentstack/okhttp/ConnectionPool.java
new file mode 100755
index 00000000..23824ebe
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/ConnectionPool.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Platform;
+import com.contentstack.okhttp.internal.Util;
+
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
+ * requests that share the same {@link Address} may share a
+ * {@link Connection}. This class implements the policy of
+ * which connections to keep open for future use.
+ *
+ * The {@link #getDefault() system-wide default} uses system properties for
+ * tuning parameters:
+ *
+ * - {@code http.keepAlive} true if HTTP and SPDY connections should be
+ * pooled at all. Default is true.
+ *
- {@code http.maxConnections} maximum number of idle connections to
+ * each to keep in the pool. Default is 5.
+ *
- {@code http.keepAliveDuration} Time in milliseconds to keep the
+ * connection alive in the pool before closing it. Default is 5 minutes.
+ * This property isn't used by {@code HttpURLConnection}.
+ *
+ *
+ * The default instance doesn't adjust its configuration as system
+ * properties are changed. This assumes that the applications that set these
+ * parameters do so before making HTTP connections, and that this class is
+ * initialized lazily.
+ */
+public final class ConnectionPool {
+ private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;
+ private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+ private static final ConnectionPool systemDefault;
+
+ static {
+ String keepAlive = System.getProperty("http.keepAlive");
+ String keepAliveDuration = System.getProperty("http.keepAliveDuration");
+ String maxIdleConnections = System.getProperty("http.maxConnections");
+ long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration)
+ : DEFAULT_KEEP_ALIVE_DURATION_MS;
+ if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
+ systemDefault = new ConnectionPool(0, keepAliveDurationMs);
+ } else if (maxIdleConnections != null) {
+ systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);
+ } else {
+ systemDefault = new ConnectionPool(5, keepAliveDurationMs);
+ }
+ }
+
+ /** The maximum number of idle connections for each address. */
+ private final int maxIdleConnections;
+ private final long keepAliveDurationNs;
+
+ private final LinkedList connections = new LinkedList();
+
+ /** We use a single background thread to cleanup expired connections. */
+ private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
+ 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(),
+ Util.threadFactory("OkHttp ConnectionPool", true));
+ private final Runnable connectionsCleanupRunnable = new Runnable() {
+ @Override public void run() {
+ List expiredConnections = new ArrayList(MAX_CONNECTIONS_TO_CLEANUP);
+ int idleConnectionCount = 0;
+ synchronized (ConnectionPool.this) {
+ for (ListIterator i = connections.listIterator(connections.size());
+ i.hasPrevious(); ) {
+ Connection connection = i.previous();
+ if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {
+ i.remove();
+ expiredConnections.add(connection);
+ if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;
+ } else if (connection.isIdle()) {
+ idleConnectionCount++;
+ }
+ }
+
+ for (ListIterator i = connections.listIterator(connections.size());
+ i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {
+ Connection connection = i.previous();
+ if (connection.isIdle()) {
+ expiredConnections.add(connection);
+ i.remove();
+ --idleConnectionCount;
+ }
+ }
+ }
+ for (Connection expiredConnection : expiredConnections) {
+ Util.closeQuietly(expiredConnection.getSocket());
+ }
+ }
+ };
+
+ public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
+ this.maxIdleConnections = maxIdleConnections;
+ this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;
+ }
+
+ /**
+ * Returns a snapshot of the connections in this pool, ordered from newest to
+ * oldest. Waits for the cleanup callable to run if it is currently scheduled.
+ */
+ List getConnections() {
+ waitForCleanupCallableToRun();
+ synchronized (this) {
+ return new ArrayList(connections);
+ }
+ }
+
+ /**
+ * Blocks until the executor service has processed all currently enqueued
+ * jobs.
+ */
+ private void waitForCleanupCallableToRun() {
+ try {
+ executorService.submit(new Runnable() {
+ @Override public void run() {
+ }
+ }).get();
+ } catch (Exception e) {
+ throw new AssertionError();
+ }
+ }
+
+ public static ConnectionPool getDefault() {
+ return systemDefault;
+ }
+
+ /** Returns total number of connections in the pool. */
+ public synchronized int getConnectionCount() {
+ return connections.size();
+ }
+
+ /** Returns total number of spdy connections in the pool. */
+ public synchronized int getSpdyConnectionCount() {
+ int total = 0;
+ for (Connection connection : connections) {
+ if (connection.isSpdy()) total++;
+ }
+ return total;
+ }
+
+ /** Returns total number of http connections in the pool. */
+ public synchronized int getHttpConnectionCount() {
+ int total = 0;
+ for (Connection connection : connections) {
+ if (!connection.isSpdy()) total++;
+ }
+ return total;
+ }
+
+ /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
+ public synchronized Connection get(Address address) {
+ Connection foundConnection = null;
+ for (ListIterator i = connections.listIterator(connections.size());
+ i.hasPrevious(); ) {
+ Connection connection = i.previous();
+ if (!connection.getRoute().getAddress().equals(address)
+ || !connection.isAlive()
+ || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {
+ continue;
+ }
+ i.remove();
+ if (!connection.isSpdy()) {
+ try {
+ Platform.get().tagSocket(connection.getSocket());
+ } catch (SocketException e) {
+ Util.closeQuietly(connection.getSocket());
+ // When unable to tag, skip recycling and close
+ Platform.get().logW("Unable to tagSocket(): " + e);
+ continue;
+ }
+ }
+ foundConnection = connection;
+ break;
+ }
+
+ if (foundConnection != null && foundConnection.isSpdy()) {
+ connections.addFirst(foundConnection); // Add it back after iteration.
+ }
+
+ executorService.execute(connectionsCleanupRunnable);
+ return foundConnection;
+ }
+
+ /**
+ * Gives {@code connection} to the pool. The pool may store the connection,
+ * or close it, as its policy describes.
+ *
+ * It is an error to use {@code connection} after calling this method.
+ */
+ void recycle(Connection connection) {
+ if (connection.isSpdy()) {
+ return;
+ }
+
+ if (!connection.clearOwner()) {
+ return; // This connection isn't eligible for reuse.
+ }
+
+ if (!connection.isAlive()) {
+ Util.closeQuietly(connection.getSocket());
+ return;
+ }
+
+ try {
+ Platform.get().untagSocket(connection.getSocket());
+ } catch (SocketException e) {
+ // When unable to remove tagging, skip recycling and close.
+ Platform.get().logW("Unable to untagSocket(): " + e);
+ Util.closeQuietly(connection.getSocket());
+ return;
+ }
+
+ synchronized (this) {
+ connections.addFirst(connection);
+ connection.incrementRecycleCount();
+ connection.resetIdleStartTime();
+ }
+
+ executorService.execute(connectionsCleanupRunnable);
+ }
+
+ /**
+ * Shares the SPDY connection with the pool. Callers to this method may
+ * continue to use {@code connection}.
+ */
+ void share(Connection connection) {
+ if (!connection.isSpdy()) throw new IllegalArgumentException();
+ executorService.execute(connectionsCleanupRunnable);
+ if (connection.isAlive()) {
+ synchronized (this) {
+ connections.addFirst(connection);
+ }
+ }
+ }
+
+ /** Close and remove all connections in the pool. */
+ public void evictAll() {
+ List connections;
+ synchronized (this) {
+ connections = new ArrayList(this.connections);
+ this.connections.clear();
+ }
+
+ for (int i = 0, size = connections.size(); i < size; i++) {
+ Util.closeQuietly(connections.get(i).getSocket());
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Credentials.java b/contentstack/src/main/java/com/contentstack/okhttp/Credentials.java
new file mode 100755
index 00000000..3ef9fa87
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Credentials.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okio.ByteString;
+
+import java.io.UnsupportedEncodingException;
+
+/** Factory for HTTP authorization credentials. */
+public final class Credentials {
+ private Credentials() {
+ }
+
+ /** Returns an auth credential for the Basic scheme. */
+ public static String basic(String userName, String password) {
+ try {
+ String usernameAndPassword = userName + ":" + password;
+ byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
+ String encoded = ByteString.of(bytes).base64();
+ return "Basic " + encoded;
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Dispatcher.java b/contentstack/src/main/java/com/contentstack/okhttp/Dispatcher.java
new file mode 100755
index 00000000..7a4986cb
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Dispatcher.java
@@ -0,0 +1,160 @@
+/*
+ * 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.Call.AsyncCall;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Policy on when async requests are executed.
+ *
+ * Each dispatcher uses an {@link ExecutorService} to run calls internally. If you
+ * supply your own executor, it should be able to run {@linkplain #getMaxRequests the
+ * configured maximum} number of calls concurrently.
+ */
+public final class Dispatcher {
+ private int maxRequests = 64;
+ private int maxRequestsPerHost = 5;
+
+ /** Executes calls. Created lazily. */
+ private ExecutorService executorService;
+
+ /** Ready calls in the order they'll be run. */
+ private final Deque readyCalls = new ArrayDeque();
+
+ /** Running calls. Includes canceled calls that haven't finished yet. */
+ private final Deque runningCalls = new ArrayDeque();
+
+ public Dispatcher(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ public Dispatcher() {
+ }
+
+ public synchronized ExecutorService getExecutorService() {
+ if (executorService == null) {
+ executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
+ new LinkedBlockingQueue(), Util.threadFactory("OkHttp Dispatcher", false));
+ }
+ return executorService;
+ }
+
+ /**
+ * Set the maximum number of requests to execute concurrently. Above this
+ * requests queue in memory, waiting for the running calls to complete.
+ *
+ * If more than {@code maxRequests} requests are in flight when this is
+ * invoked, those requests will remain in flight.
+ */
+ public synchronized void setMaxRequests(int maxRequests) {
+ if (maxRequests < 1) {
+ throw new IllegalArgumentException("max < 1: " + maxRequests);
+ }
+ this.maxRequests = maxRequests;
+ promoteCalls();
+ }
+
+ public synchronized int getMaxRequests() {
+ return maxRequests;
+ }
+
+ /**
+ * Set the maximum number of requests for each host to execute concurrently.
+ * This limits requests by the URL's host name. Note that concurrent requests
+ * to a single IP address may still exceed this limit: multiple hostnames may
+ * share an IP address or be routed through the same HTTP proxy.
+ *
+ *
If more than {@code maxRequestsPerHost} requests are in flight when this
+ * is invoked, those requests will remain in flight.
+ */
+ public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
+ if (maxRequestsPerHost < 1) {
+ throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
+ }
+ this.maxRequestsPerHost = maxRequestsPerHost;
+ promoteCalls();
+ }
+
+ public synchronized int getMaxRequestsPerHost() {
+ return maxRequestsPerHost;
+ }
+
+ synchronized void enqueue(AsyncCall call) {
+ if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
+ runningCalls.add(call);
+ getExecutorService().execute(call);
+ } else {
+ readyCalls.add(call);
+ }
+ }
+
+ /** Cancel all calls with the tag {@code tag}. */
+ public synchronized void cancel(Object tag) {
+ for (Iterator i = readyCalls.iterator(); i.hasNext(); ) {
+ if (Util.equal(tag, i.next().tag())) i.remove();
+ }
+
+ for (AsyncCall call : runningCalls) {
+ if (Util.equal(tag, call.tag())) {
+ call.get().canceled = true;
+ HttpEngine engine = call.get().engine;
+ if (engine != null) engine.disconnect();
+ }
+ }
+ }
+
+ /** Used by {@code AsyncCall#run} to signal completion. */
+ synchronized void finished(AsyncCall call) {
+ if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
+ promoteCalls();
+ }
+
+ private void promoteCalls() {
+ if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
+ if (readyCalls.isEmpty()) return; // No ready calls to promote.
+
+ for (Iterator i = readyCalls.iterator(); i.hasNext(); ) {
+ AsyncCall call = i.next();
+
+ if (runningCallsForHost(call) < maxRequestsPerHost) {
+ i.remove();
+ runningCalls.add(call);
+ getExecutorService().execute(call);
+ }
+
+ if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
+ }
+ }
+
+ /** Returns the number of running calls that share a host with {@code call}. */
+ private int runningCallsForHost(AsyncCall call) {
+ int result = 0;
+ for (AsyncCall c : runningCalls) {
+ if (c.host().equals(call.host())) result++;
+ }
+ return result;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/FormEncodingBuilder.java b/contentstack/src/main/java/com/contentstack/okhttp/FormEncodingBuilder.java
new file mode 100755
index 00000000..6ec12709
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/FormEncodingBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * Fluent API to build HTML
+ * 2.0-compliant form data.
+ */
+public final class FormEncodingBuilder {
+ private static final MediaType CONTENT_TYPE
+ = MediaType.parse("application/x-www-form-urlencoded");
+
+ private final StringBuilder content = new StringBuilder();
+
+ /** Add new key-value pair. */
+ public FormEncodingBuilder add(String name, String value) {
+ if (content.length() > 0) {
+ content.append('&');
+ }
+ try {
+ content.append(URLEncoder.encode(name, "UTF-8"))
+ .append('=')
+ .append(URLEncoder.encode(value, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ return this;
+ }
+
+ public RequestBody build() {
+ if (content.length() == 0) {
+ throw new IllegalStateException("Form encoded body must have at least one part.");
+ }
+
+ // Convert to bytes so RequestBody.create() doesn't add a charset to the content-type.
+ byte[] contentBytes = content.toString().getBytes(Util.UTF_8);
+ return RequestBody.create(CONTENT_TYPE, contentBytes);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Handshake.java b/contentstack/src/main/java/com/contentstack/okhttp/Handshake.java
new file mode 100755
index 00000000..68e6a905
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Handshake.java
@@ -0,0 +1,122 @@
+/*
+ * 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A record of a TLS handshake. For HTTPS clients, the client is local
+ * and the remote server is its peer.
+ *
+ * This value object describes a completed handshake. Use {@link
+ * javax.net.ssl.SSLSocketFactory} to set policy for new handshakes.
+ */
+public final class Handshake {
+ private final String cipherSuite;
+ private final List peerCertificates;
+ private final List localCertificates;
+
+ private Handshake(
+ String cipherSuite, List peerCertificates, List localCertificates) {
+ this.cipherSuite = cipherSuite;
+ this.peerCertificates = peerCertificates;
+ this.localCertificates = localCertificates;
+ }
+
+ public static Handshake get(SSLSession session) {
+ String cipherSuite = session.getCipherSuite();
+ if (cipherSuite == null) throw new IllegalStateException("cipherSuite == null");
+
+ Certificate[] peerCertificates;
+ try {
+ peerCertificates = session.getPeerCertificates();
+ } catch (SSLPeerUnverifiedException ignored) {
+ peerCertificates = null;
+ }
+ List peerCertificatesList = peerCertificates != null
+ ? Util.immutableList(peerCertificates)
+ : Collections.emptyList();
+
+ Certificate[] localCertificates = session.getLocalCertificates();
+ List localCertificatesList = localCertificates != null
+ ? Util.immutableList(localCertificates)
+ : Collections.emptyList();
+
+ return new Handshake(cipherSuite, peerCertificatesList, localCertificatesList);
+ }
+
+ public static Handshake get(
+ String cipherSuite, List peerCertificates, List localCertificates) {
+ if (cipherSuite == null) throw new IllegalArgumentException("cipherSuite == null");
+ return new Handshake(cipherSuite, Util.immutableList(peerCertificates),
+ Util.immutableList(localCertificates));
+ }
+
+ /** Returns a cipher suite name like "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA". */
+ public String cipherSuite() {
+ return cipherSuite;
+ }
+
+ /** Returns a possibly-empty list of certificates that identify the remote peer. */
+ public List peerCertificates() {
+ return peerCertificates;
+ }
+
+ /** Returns the remote peer's principle, or null if that peer is anonymous. */
+ public Principal peerPrincipal() {
+ return !peerCertificates.isEmpty()
+ ? ((X509Certificate) peerCertificates.get(0)).getSubjectX500Principal()
+ : null;
+ }
+
+ /** Returns a possibly-empty list of certificates that identify this peer. */
+ public List localCertificates() {
+ return localCertificates;
+ }
+
+ /** Returns the local principle, or null if this peer is anonymous. */
+ public Principal localPrincipal() {
+ return !localCertificates.isEmpty()
+ ? ((X509Certificate) localCertificates.get(0)).getSubjectX500Principal()
+ : null;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (!(other instanceof Handshake)) return false;
+ Handshake that = (Handshake) other;
+ return cipherSuite.equals(that.cipherSuite)
+ && peerCertificates.equals(that.peerCertificates)
+ && localCertificates.equals(that.localCertificates);
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + cipherSuite.hashCode();
+ result = 31 * result + peerCertificates.hashCode();
+ result = 31 * result + localCertificates.hashCode();
+ return result;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Headers.java b/contentstack/src/main/java/com/contentstack/okhttp/Headers.java
new file mode 100755
index 00000000..34df08a9
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Headers.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.http.HttpDate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * The header fields of a single HTTP message. Values are uninterpreted strings;
+ * use {@code Request} and {@code Response} for interpreted headers. This class
+ * maintains the order of the header fields within the HTTP message.
+ *
+ * This class tracks header values line-by-line. A field with multiple comma-
+ * separated values on the same line will be treated as a field with a single
+ * value by this class. It is the caller's responsibility to detect and split
+ * on commas if their field permits multiple values. This simplifies use of
+ * single-valued fields whose values routinely contain commas, such as cookies
+ * or dates.
+ *
+ *
This class trims whitespace from values. It never returns values with
+ * leading or trailing whitespace.
+ *
+ *
Instances of this class are immutable. Use {@link Builder} to create
+ * instances.
+ */
+public final class Headers {
+ private final String[] namesAndValues;
+
+ private Headers(Builder builder) {
+ this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
+ }
+
+ private Headers(String[] namesAndValues) {
+ this.namesAndValues = namesAndValues;
+ }
+
+ /** Returns the last value corresponding to the specified field, or null. */
+ public String get(String name) {
+ return get(namesAndValues, name);
+ }
+
+ /**
+ * Returns the last value corresponding to the specified field parsed as an
+ * HTTP date, or null if either the field is absent or cannot be parsed as a
+ * date.
+ */
+ public Date getDate(String name) {
+ String value = get(name);
+ return value != null ? HttpDate.parse(value) : null;
+ }
+
+ /** Returns the number of field values. */
+ public int size() {
+ return namesAndValues.length / 2;
+ }
+
+ /** Returns the field at {@code position} or null if that is out of range. */
+ public String name(int index) {
+ int nameIndex = index * 2;
+ if (nameIndex < 0 || nameIndex >= namesAndValues.length) {
+ return null;
+ }
+ return namesAndValues[nameIndex];
+ }
+
+ /** Returns the value at {@code index} or null if that is out of range. */
+ public String value(int index) {
+ int valueIndex = index * 2 + 1;
+ if (valueIndex < 0 || valueIndex >= namesAndValues.length) {
+ return null;
+ }
+ return namesAndValues[valueIndex];
+ }
+
+ /** Returns an immutable case-insensitive set of header names. */
+ public Set names() {
+ TreeSet result = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < size(); i++) {
+ result.add(name(i));
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ /** Returns an immutable list of the header values for {@code name}. */
+ public List values(String name) {
+ List result = null;
+ for (int i = 0; i < size(); i++) {
+ if (name.equalsIgnoreCase(name(i))) {
+ if (result == null) result = new ArrayList(2);
+ result.add(value(i));
+ }
+ }
+ return result != null
+ ? Collections.unmodifiableList(result)
+ : Collections.emptyList();
+ }
+
+ public Builder newBuilder() {
+ Builder result = new Builder();
+ result.namesAndValues.addAll(Arrays.asList(namesAndValues));
+ return result;
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < size(); i++) {
+ result.append(name(i)).append(": ").append(value(i)).append("\n");
+ }
+ return result.toString();
+ }
+
+ private static String get(String[] namesAndValues, String name) {
+ for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
+ if (name.equalsIgnoreCase(namesAndValues[i])) {
+ return namesAndValues[i + 1];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns headers for the alternating header names and values. There must be
+ * an even number of arguments, and they must alternate between header names
+ * and values.
+ */
+ public static Headers of(String... namesAndValues) {
+ if (namesAndValues == null || namesAndValues.length % 2 != 0) {
+ throw new IllegalArgumentException("Expected alternating header names and values");
+ }
+
+ // Make a defensive copy and clean it up.
+ namesAndValues = namesAndValues.clone();
+ for (int i = 0; i < namesAndValues.length; i++) {
+ if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
+ namesAndValues[i] = namesAndValues[i].trim();
+ }
+
+ // Check for malformed headers.
+ for (int i = 0; i < namesAndValues.length; i += 2) {
+ String name = namesAndValues[i];
+ String value = namesAndValues[i + 1];
+ if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+ throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+ }
+ }
+
+ return new Headers(namesAndValues);
+ }
+
+ public static class Builder {
+ private final List namesAndValues = new ArrayList(20);
+
+ /** Add an header line containing a field name, a literal colon, and a value. */
+ Builder addLine(String line) {
+ int index = line.indexOf(":", 1);
+ if (index != -1) {
+ return addLenient(line.substring(0, index), line.substring(index + 1));
+ } else if (line.startsWith(":")) {
+ // Work around empty header names and header names that start with a
+ // colon (created by old broken SPDY versions of the response cache).
+ return addLenient("", line.substring(1)); // Empty header name.
+ } else {
+ return addLenient("", line); // No header name.
+ }
+ }
+
+ /** Add a field with the specified value. */
+ public Builder add(String name, String value) {
+ if (name == null) throw new IllegalArgumentException("name == null");
+ if (value == null) throw new IllegalArgumentException("value == null");
+ if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+ throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+ }
+ return addLenient(name, value);
+ }
+
+ /**
+ * Add a field with the specified value without any validation. Only
+ * appropriate for headers from the remote peer.
+ */
+ private Builder addLenient(String name, String value) {
+ namesAndValues.add(name);
+ namesAndValues.add(value.trim());
+ return this;
+ }
+
+ public Builder removeAll(String name) {
+ for (int i = 0; i < namesAndValues.size(); i += 2) {
+ if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+ namesAndValues.remove(i); // name
+ namesAndValues.remove(i); // value
+ i -= 2;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set a field with the specified value. If the field is not found, it is
+ * added. If the field is found, the existing values are replaced.
+ */
+ public Builder set(String name, String value) {
+ removeAll(name);
+ add(name, value);
+ return this;
+ }
+
+ /** Equivalent to {@code build().get(name)}, but potentially faster. */
+ public String get(String name) {
+ for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
+ if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+ return namesAndValues.get(i + 1);
+ }
+ }
+ return null;
+ }
+
+ public Headers build() {
+ return new Headers(this);
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/HostResolver.java b/contentstack/src/main/java/com/contentstack/okhttp/HostResolver.java
new file mode 100755
index 00000000..4c98a2fa
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/HostResolver.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Domain name service. Prefer this over {@link InetAddress#getAllByName} to
+ * make code more testable.
+ */
+public interface HostResolver {
+ HostResolver DEFAULT = new HostResolver() {
+ @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
+ if (host == null) throw new UnknownHostException("host == null");
+ return InetAddress.getAllByName(host);
+ }
+ };
+
+ InetAddress[] getAllByName(String host) throws UnknownHostException;
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/MediaType.java b/contentstack/src/main/java/com/contentstack/okhttp/MediaType.java
new file mode 100755
index 00000000..9c4d52b1
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/MediaType.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.contentstack.okhttp;
+
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An RFC 2045 Media Type,
+ * appropriate to describe the content type of an HTTP request or response body.
+ */
+public final class MediaType {
+ private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
+ private static final String QUOTED = "\"([^\"]*)\"";
+ private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
+ private static final Pattern PARAMETER = Pattern.compile(
+ ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?");
+
+ private final String mediaType;
+ private final String type;
+ private final String subtype;
+ private final String charset;
+
+ private MediaType(String mediaType, String type, String subtype, String charset) {
+ this.mediaType = mediaType;
+ this.type = type;
+ this.subtype = subtype;
+ this.charset = charset;
+ }
+
+ /**
+ * Returns a media type for {@code string}, or null if {@code string} is not a
+ * well-formed media type.
+ */
+ public static MediaType parse(String string) {
+ Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
+ if (!typeSubtype.lookingAt()) return null;
+ String type = typeSubtype.group(1).toLowerCase(Locale.US);
+ String subtype = typeSubtype.group(2).toLowerCase(Locale.US);
+
+ String charset = null;
+ Matcher parameter = PARAMETER.matcher(string);
+ for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
+ parameter.region(s, string.length());
+ if (!parameter.lookingAt()) return null; // This is not a well-formed media type.
+
+ String name = parameter.group(1);
+ if (name == null || !name.equalsIgnoreCase("charset")) continue;
+ String charsetParameter = parameter.group(2) != null
+ ? parameter.group(2) // Value is a token.
+ : parameter.group(3); // Value is a quoted string.
+ if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) {
+ throw new IllegalArgumentException("Multiple different charsets: " + string);
+ }
+ charset = charsetParameter;
+ }
+
+ return new MediaType(string, type, subtype, charset);
+ }
+
+ /**
+ * Returns the high-level media type, such as "text", "image", "audio",
+ * "video", or "application".
+ */
+ public String type() {
+ return type;
+ }
+
+ /**
+ * Returns a specific media subtype, such as "plain" or "png", "mpeg",
+ * "mp4" or "xml".
+ */
+ public String subtype() {
+ return subtype;
+ }
+
+ /**
+ * Returns the charset of this media type, or null if this media type doesn't
+ * specify a charset.
+ */
+ public Charset charset() {
+ return charset != null ? Charset.forName(charset) : null;
+ }
+
+ /**
+ * Returns the charset of this media type, or {@code defaultValue} if this
+ * media type doesn't specify a charset.
+ */
+ public Charset charset(Charset defaultValue) {
+ return charset != null ? Charset.forName(charset) : defaultValue;
+ }
+
+ /**
+ * Returns the encoded media type, like "text/plain; charset=utf-8",
+ * appropriate for use in a Content-Type header.
+ */
+ @Override public String toString() {
+ return mediaType;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
+ }
+
+ @Override public int hashCode() {
+ return mediaType.hashCode();
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/MultipartBuilder.java b/contentstack/src/main/java/com/contentstack/okhttp/MultipartBuilder.java
new file mode 100755
index 00000000..8aadd39a
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/MultipartBuilder.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.ByteString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Fluent API to build RFC
+ * 2387-compliant request bodies.
+ */
+public final class MultipartBuilder {
+ /**
+ * The "mixed" subtype of "multipart" is intended for use when the body
+ * parts are independent and need to be bundled in a particular order. Any
+ * "multipart" subtypes that an implementation does not recognize must be
+ * treated as being of subtype "mixed".
+ */
+ public static final MediaType MIXED = MediaType.parse("multipart/mixed");
+
+ /**
+ * The "multipart/alternative" type is syntactically identical to
+ * "multipart/mixed", but the semantics are different. In particular, each
+ * of the body parts is an "alternative" version of the same information.
+ */
+ public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
+
+ /**
+ * This type is syntactically identical to "multipart/mixed", but the
+ * semantics are different. In particular, in a digest, the default {@code
+ * Content-Type} value for a body part is changed from "text/plain" to
+ * "message/rfc822".
+ */
+ public static final MediaType DIGEST = MediaType.parse("multipart/digest");
+
+ /**
+ * This type is syntactically identical to "multipart/mixed", but the
+ * semantics are different. In particular, in a parallel entity, the order
+ * of body parts is not significant.
+ */
+ public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");
+
+ /**
+ * The media-type multipart/form-data follows the rules of all multipart
+ * MIME data streams as outlined in RFC 2046. In forms, there are a series
+ * of fields to be supplied by the user who fills out the form. Each field
+ * has a name. Within a given form, the names are unique.
+ */
+ public static final MediaType FORM = MediaType.parse("multipart/form-data");
+
+ private static final byte[] COLONSPACE = { ':', ' ' };
+ private static final byte[] CRLF = { '\r', '\n' };
+ private static final byte[] DASHDASH = { '-', '-' };
+
+ private final ByteString boundary;
+ private MediaType type = MIXED;
+ private long length = 0;
+
+ // Parallel lists of nullable headings (boundary + headers) and non-null bodies.
+ private final List partHeadings = new ArrayList();
+ private final List partBodies = new ArrayList();
+
+ /** Creates a new multipart builder that uses a random boundary token. */
+ public MultipartBuilder() {
+ this(UUID.randomUUID().toString());
+ }
+
+ /**
+ * Creates a new multipart builder that uses {@code boundary} to separate
+ * parts. Prefer the no-argument constructor to defend against injection
+ * attacks.
+ */
+ public MultipartBuilder(String boundary) {
+ this.boundary = ByteString.encodeUtf8(boundary);
+ }
+
+ /**
+ * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the
+ * default), {@link #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and
+ * {@link #FORM}.
+ */
+ public MultipartBuilder type(MediaType type) {
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+ if (!type.type().equals("multipart")) {
+ throw new IllegalArgumentException("multipart != " + type);
+ }
+ this.type = type;
+ return this;
+ }
+
+ /** Add a part to the body. */
+ public MultipartBuilder addPart(RequestBody body) {
+ return addPart(null, body);
+ }
+
+ /** Add a part to the body. */
+ public MultipartBuilder addPart(Headers headers, RequestBody body) {
+ if (body == null) {
+ throw new NullPointerException("body == null");
+ }
+ if (headers != null && headers.get("Content-Type") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Type");
+ }
+ if (headers != null && headers.get("Content-Length") != null) {
+ throw new IllegalArgumentException("Unexpected header: Content-Length");
+ }
+
+ Buffer heading = createPartHeading(headers, body, partHeadings.isEmpty());
+ partHeadings.add(heading);
+ partBodies.add(body);
+
+ long bodyContentLength = body.contentLength();
+ if (bodyContentLength == -1) {
+ length = -1;
+ } else if (length != -1) {
+ length += heading.size() + bodyContentLength;
+ }
+
+ return this;
+ }
+
+ /**
+ * Appends a quoted-string to a StringBuilder.
+ *
+ * RFC 2388 is rather vague about how one should escape special characters
+ * in form-data parameters, and as it turns out Firefox and Chrome actually
+ * do rather different things, and both say in their comments that they're
+ * not really sure what the right approach is. We go with Chrome's behavior
+ * (which also experimentally seems to match what IE does), but if you
+ * actually want to have a good chance of things working, please avoid
+ * double-quotes, newlines, percent signs, and the like in your field names.
+ */
+ private static StringBuilder appendQuotedString(StringBuilder target, String key) {
+ target.append('"');
+ for (int i = 0, len = key.length(); i < len; i++) {
+ char ch = key.charAt(i);
+ switch (ch) {
+ case '\n':
+ target.append("%0A");
+ break;
+ case '\r':
+ target.append("%0D");
+ break;
+ case '"':
+ target.append("%22");
+ break;
+ default:
+ target.append(ch);
+ break;
+ }
+ }
+ target.append('"');
+ return target;
+ }
+
+ /** Add a form data part to the body. */
+ public MultipartBuilder addFormDataPart(String name, String value) {
+ return addFormDataPart(name, null, RequestBody.create(null, value));
+ }
+
+ /** Add a form data part to the body. */
+ public MultipartBuilder addFormDataPart(String name, String filename, RequestBody value) {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ StringBuilder disposition = new StringBuilder("form-data; name=");
+ appendQuotedString(disposition, name);
+
+ if (filename != null) {
+ disposition.append("; filename=");
+ appendQuotedString(disposition, filename);
+ }
+
+ return addPart(Headers.of("Content-Disposition", disposition.toString()), value);
+ }
+
+ /** Creates a part "heading" from the boundary and any real or generated headers. */
+ private Buffer createPartHeading(Headers headers, RequestBody body, boolean isFirst) {
+ Buffer sink = new Buffer();
+
+ if (!isFirst) {
+ sink.write(CRLF);
+ }
+ sink.write(DASHDASH);
+ sink.write(boundary);
+ sink.write(CRLF);
+
+ if (headers != null) {
+ for (int i = 0; i < headers.size(); i++) {
+ sink.writeUtf8(headers.name(i))
+ .write(COLONSPACE)
+ .writeUtf8(headers.value(i))
+ .write(CRLF);
+ }
+ }
+
+ MediaType contentType = body.contentType();
+ if (contentType != null) {
+ sink.writeUtf8("Content-Type: ")
+ .writeUtf8(contentType.toString())
+ .write(CRLF);
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength != -1) {
+ sink.writeUtf8("Content-Length: ")
+ .writeUtf8(Long.toString(contentLength))
+ .write(CRLF);
+ }
+
+ sink.write(CRLF);
+
+ return sink;
+ }
+
+ /** Assemble the specified parts into a request body. */
+ public RequestBody build() {
+ if (partHeadings.isEmpty()) {
+ throw new IllegalStateException("Multipart body must have at least one part.");
+ }
+ return new MultipartRequestBody(type, boundary, partHeadings, partBodies, length);
+ }
+
+ private static final class MultipartRequestBody extends RequestBody {
+ private final ByteString boundary;
+ private final MediaType contentType;
+ private final List partHeadings;
+ private final List partBodies;
+ private final long length;
+
+ public MultipartRequestBody(MediaType type, ByteString boundary, List partHeadings,
+ List partBodies, long length) {
+ if (type == null) throw new NullPointerException("type == null");
+
+ this.boundary = boundary;
+ this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
+ this.partHeadings = Util.immutableList(partHeadings);
+ this.partBodies = Util.immutableList(partBodies);
+ if (length != -1) {
+ // Add the length of the final boundary.
+ length += CRLF.length + DASHDASH.length + boundary.size() + DASHDASH.length + CRLF.length;
+ }
+ this.length = length;
+ }
+
+ @Override public long contentLength() {
+ return length;
+ }
+
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ for (int i = 0, size = partHeadings.size(); i < size; i++) {
+ sink.writeAll(partHeadings.get(i).clone());
+ partBodies.get(i).writeTo(sink);
+ }
+
+ sink.write(CRLF);
+ sink.write(DASHDASH);
+ sink.write(boundary);
+ sink.write(DASHDASH);
+ sink.write(CRLF);
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/OkHttpClient.java b/contentstack/src/main/java/com/contentstack/okhttp/OkHttpClient.java
new file mode 100755
index 00000000..65c6b53f
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/OkHttpClient.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Internal;
+import com.contentstack.okhttp.internal.InternalCache;
+import com.contentstack.okhttp.internal.RouteDatabase;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.AuthenticatorAdapter;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+import com.contentstack.okhttp.internal.http.Transport;
+import com.contentstack.okhttp.internal.tls.OkHostnameVerifier;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URLConnection;
+import java.security.GeneralSecurityException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Configures and creates HTTP connections. Most applications can use a single
+ * OkHttpClient for all of their HTTP requests - benefiting from a shared
+ * response cache, thread pool, connection re-use, etc.
+ *
+ * Instances of OkHttpClient are intended to be fully configured before they're
+ * shared - once shared they should be treated as immutable and can safely be used
+ * to concurrently open new connections. If required, threads can call
+ * {@link #clone()} to make a shallow copy of the OkHttpClient that can be
+ * safely modified with further configuration changes.
+ */
+public class OkHttpClient implements Cloneable {
+ static {
+ Internal.instance = new Internal() {
+ @Override public Transport newTransport(
+ Connection connection, HttpEngine httpEngine) throws IOException {
+ return connection.newTransport(httpEngine);
+ }
+
+ @Override public boolean clearOwner(Connection connection) {
+ return connection.clearOwner();
+ }
+
+ @Override public void closeIfOwnedBy(Connection connection, Object owner) throws IOException {
+ connection.closeIfOwnedBy(owner);
+ }
+
+ @Override public int recycleCount(Connection connection) {
+ return connection.recycleCount();
+ }
+
+ @Override public void setProtocol(Connection connection, Protocol protocol) {
+ connection.setProtocol(protocol);
+ }
+
+ @Override public void setOwner(Connection connection, HttpEngine httpEngine) {
+ connection.setOwner(httpEngine);
+ }
+
+ @Override public boolean isReadable(Connection pooled) {
+ return pooled.isReadable();
+ }
+
+ @Override public void addLine(Headers.Builder builder, String line) {
+ builder.addLine(line);
+ }
+
+ @Override public void setCache(OkHttpClient client, InternalCache internalCache) {
+ client.setInternalCache(internalCache);
+ }
+
+ @Override public InternalCache internalCache(OkHttpClient client) {
+ return client.internalCache();
+ }
+
+ @Override public void recycle(ConnectionPool pool, Connection connection) {
+ pool.recycle(connection);
+ }
+
+ @Override public RouteDatabase routeDatabase(OkHttpClient client) {
+ return client.routeDatabase();
+ }
+
+ @Override public void connectAndSetOwner(OkHttpClient client, Connection connection,
+ HttpEngine owner, Request request) throws IOException {
+ connection.connectAndSetOwner(client, owner, request);
+ }
+ };
+ }
+
+ /** Lazily-initialized. */
+ private static SSLSocketFactory defaultSslSocketFactory;
+
+ private final RouteDatabase routeDatabase;
+ private Dispatcher dispatcher;
+ private Proxy proxy;
+ private List protocols;
+ private ProxySelector proxySelector;
+ private CookieHandler cookieHandler;
+
+ /** Non-null if this client is caching; possibly by {@code cache}. */
+ private InternalCache internalCache;
+ private Cache cache;
+
+ private SocketFactory socketFactory;
+ private SSLSocketFactory sslSocketFactory;
+ private HostnameVerifier hostnameVerifier;
+ private Authenticator authenticator;
+ private ConnectionPool connectionPool;
+ private HostResolver hostResolver;
+ private boolean followSslRedirects = true;
+ private boolean followRedirects = true;
+ private int connectTimeout;
+ private int readTimeout;
+ private int writeTimeout;
+
+ public OkHttpClient() {
+ routeDatabase = new RouteDatabase();
+ dispatcher = new Dispatcher();
+ }
+
+ private OkHttpClient(OkHttpClient okHttpClient) {
+ this.routeDatabase = okHttpClient.routeDatabase();
+ this.dispatcher = okHttpClient.getDispatcher();
+ this.proxy = okHttpClient.getProxy();
+ this.protocols = okHttpClient.getProtocols();
+ this.proxySelector = okHttpClient.getProxySelector();
+ this.cookieHandler = okHttpClient.getCookieHandler();
+ this.cache = okHttpClient.getCache();
+ this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache;
+ this.socketFactory = okHttpClient.getSocketFactory();
+ this.sslSocketFactory = okHttpClient.getSslSocketFactory();
+ this.hostnameVerifier = okHttpClient.getHostnameVerifier();
+ this.authenticator = okHttpClient.getAuthenticator();
+ this.connectionPool = okHttpClient.getConnectionPool();
+ this.followSslRedirects = okHttpClient.getFollowSslRedirects();
+ this.followRedirects = okHttpClient.getFollowRedirects();
+ this.connectTimeout = okHttpClient.getConnectTimeout();
+ this.readTimeout = okHttpClient.getReadTimeout();
+ this.writeTimeout = okHttpClient.getWriteTimeout();
+ }
+
+ /**
+ * Sets the default connect timeout for new connections. A value of 0 means no timeout.
+ *
+ * @see URLConnection#setConnectTimeout(int)
+ */
+ public final void setConnectTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ connectTimeout = (int) millis;
+ }
+
+ /** Default connect timeout (in milliseconds). */
+ public final int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /**
+ * Sets the default read timeout for new connections. A value of 0 means no timeout.
+ *
+ * @see URLConnection#setReadTimeout(int)
+ */
+ public final void setReadTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ readTimeout = (int) millis;
+ }
+
+ /** Default read timeout (in milliseconds). */
+ public final int getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * Sets the default write timeout for new connections. A value of 0 means no timeout.
+ */
+ public final void setWriteTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+ if (unit == null) throw new IllegalArgumentException("unit == null");
+ long millis = unit.toMillis(timeout);
+ if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+ writeTimeout = (int) millis;
+ }
+
+ /** Default write timeout (in milliseconds). */
+ public final int getWriteTimeout() {
+ return writeTimeout;
+ }
+
+ /**
+ * Sets the HTTP proxy that will be used by connections created by this
+ * client. This takes precedence over {@link #setProxySelector}, which is
+ * only honored when this proxy is null (which it is by default). To disable
+ * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+ */
+ public final OkHttpClient setProxy(Proxy proxy) {
+ this.proxy = proxy;
+ return this;
+ }
+
+ public final Proxy getProxy() {
+ return proxy;
+ }
+
+ /**
+ * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+ * is specified explicitly. The proxy selector may return multiple proxies;
+ * in that case they will be tried in sequence until a successful connection
+ * is established.
+ *
+ * If unset, the {@link ProxySelector#getDefault() system-wide default}
+ * proxy selector will be used.
+ */
+ public final OkHttpClient setProxySelector(ProxySelector proxySelector) {
+ this.proxySelector = proxySelector;
+ return this;
+ }
+
+ public final ProxySelector getProxySelector() {
+ return proxySelector;
+ }
+
+ /**
+ * Sets the cookie handler to be used to read outgoing cookies and write
+ * incoming cookies.
+ *
+ *
If unset, the {@link CookieHandler#getDefault() system-wide default}
+ * cookie handler will be used.
+ */
+ public final OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+ this.cookieHandler = cookieHandler;
+ return this;
+ }
+
+ public final CookieHandler getCookieHandler() {
+ return cookieHandler;
+ }
+
+ /** Sets the response cache to be used to read and write cached responses. */
+ final void setInternalCache(InternalCache internalCache) {
+ this.internalCache = internalCache;
+ this.cache = null;
+ }
+
+ final InternalCache internalCache() {
+ return internalCache;
+ }
+
+ public final OkHttpClient setCache(Cache cache) {
+ this.cache = cache;
+ this.internalCache = null;
+ return this;
+ }
+
+ public final Cache getCache() {
+ return cache;
+ }
+
+ /**
+ * Sets the socket factory used to create connections.
+ *
+ *
If unset, the {@link SocketFactory#getDefault() system-wide default}
+ * socket factory will be used.
+ */
+ public final OkHttpClient setSocketFactory(SocketFactory socketFactory) {
+ this.socketFactory = socketFactory;
+ return this;
+ }
+
+ public final SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /**
+ * Sets the socket factory used to secure HTTPS connections.
+ *
+ *
If unset, a lazily created SSL socket factory will be used.
+ */
+ public final OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
+ this.sslSocketFactory = sslSocketFactory;
+ return this;
+ }
+
+ public final SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /**
+ * Sets the verifier used to confirm that response certificates apply to
+ * requested hostnames for HTTPS connections.
+ *
+ *
If unset, the
+ * {@link javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()
+ * system-wide default} hostname verifier will be used.
+ */
+ public final OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ return this;
+ }
+
+ public final HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+ /**
+ * Sets the authenticator used to respond to challenges from the remote web
+ * server or proxy server.
+ *
+ *
If unset, the {@link java.net.Authenticator#setDefault system-wide default}
+ * authenticator will be used.
+ */
+ public final OkHttpClient setAuthenticator(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public final Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+ /**
+ * Sets the connection pool used to recycle HTTP and HTTPS connections.
+ *
+ *
If unset, the {@link ConnectionPool#getDefault() system-wide
+ * default} connection pool will be used.
+ */
+ public final OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+ this.connectionPool = connectionPool;
+ return this;
+ }
+
+ public final ConnectionPool getConnectionPool() {
+ return connectionPool;
+ }
+
+ /**
+ * Configure this client to follow redirects from HTTPS to HTTP and from HTTP
+ * to HTTPS.
+ *
+ *
If unset, protocol redirects will be followed. This is different than
+ * the built-in {@code HttpURLConnection}'s default.
+ */
+ public final OkHttpClient setFollowSslRedirects(boolean followProtocolRedirects) {
+ this.followSslRedirects = followProtocolRedirects;
+ return this;
+ }
+
+ public final boolean getFollowSslRedirects() {
+ return followSslRedirects;
+ }
+
+ /**
+ * Configure this client to follow redirects.
+ *
+ *
If unset, redirects will not be followed. This is the equivalent as the
+ * built-in {@code HttpURLConnection}'s default.
+ */
+ public final void setFollowRedirects(boolean followRedirects) {
+ this.followRedirects = followRedirects;
+ }
+
+ public final boolean getFollowRedirects() {
+ return followRedirects;
+ }
+
+ final RouteDatabase routeDatabase() {
+ return routeDatabase;
+ }
+
+ /**
+ * Sets the dispatcher used to set policy and execute asynchronous requests.
+ * Must not be null.
+ */
+ public final OkHttpClient setDispatcher(Dispatcher dispatcher) {
+ if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null");
+ this.dispatcher = dispatcher;
+ return this;
+ }
+
+ public final Dispatcher getDispatcher() {
+ return dispatcher;
+ }
+
+ /**
+ * Configure the protocols used by this client to communicate with remote
+ * servers. By default this client will prefer the most efficient transport
+ * available, falling back to more ubiquitous protocols. Applications should
+ * only call this method to avoid specific compatibility problems, such as web
+ * servers that behave incorrectly when SPDY is enabled.
+ *
+ *
The following protocols are currently supported:
+ *
+ *
+ * This is an evolving set. Future releases may drop
+ * support for transitional protocols (like h2-13), in favor of their
+ * successors (h2). The http/1.1 transport will never be dropped.
+ *
+ *
If multiple protocols are specified, NPN or
+ * ALPN
+ * will be used to negotiate a transport.
+ *
+ * @param protocols the protocols to use, in order of preference. The list
+ * must contain {@link Protocol#HTTP_1_1}. It must not contain null.
+ */
+ public final OkHttpClient setProtocols(List protocols) {
+ protocols = Util.immutableList(protocols);
+ if (!protocols.contains(Protocol.HTTP_1_1)) {
+ throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
+ }
+ if (protocols.contains(null)) {
+ throw new IllegalArgumentException("protocols must not contain null");
+ }
+ this.protocols = Util.immutableList(protocols);
+ return this;
+ }
+
+ public final List getProtocols() {
+ return protocols;
+ }
+
+ /*
+ * Sets the {@code HostResolver} that will be used by this client to resolve
+ * hostnames to IP addresses.
+ */
+ public OkHttpClient setHostResolver(HostResolver hostResolver) {
+ this.hostResolver = hostResolver;
+ return this;
+ }
+
+ public HostResolver getHostResolver() {
+ return hostResolver;
+ }
+
+ /**
+ * Prepares the {@code request} to be executed at some point in the future.
+ */
+ public Call newCall(Request request) {
+ return new Call(this, request);
+ }
+
+ /**
+ * Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
+ * complete cannot be canceled.
+ */
+ public OkHttpClient cancel(Object tag) {
+ getDispatcher().cancel(tag);
+ return this;
+ }
+
+ /**
+ * Returns a shallow copy of this OkHttpClient that uses the system-wide
+ * default for each field that hasn't been explicitly configured.
+ */
+ final OkHttpClient copyWithDefaults() {
+ OkHttpClient result = new OkHttpClient(this);
+ if (result.proxySelector == null) {
+ result.proxySelector = ProxySelector.getDefault();
+ }
+ if (result.cookieHandler == null) {
+ result.cookieHandler = CookieHandler.getDefault();
+ }
+ if (result.socketFactory == null) {
+ result.socketFactory = SocketFactory.getDefault();
+ }
+ if (result.sslSocketFactory == null) {
+ result.sslSocketFactory = getDefaultSSLSocketFactory();
+ }
+ if (result.hostnameVerifier == null) {
+ result.hostnameVerifier = OkHostnameVerifier.INSTANCE;
+ }
+ if (result.authenticator == null) {
+ result.authenticator = AuthenticatorAdapter.INSTANCE;
+ }
+ if (result.connectionPool == null) {
+ result.connectionPool = ConnectionPool.getDefault();
+ }
+ if (result.protocols == null) {
+ result.protocols = Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
+ }
+ if (result.hostResolver == null) {
+ result.hostResolver = HostResolver.DEFAULT;
+ }
+ return result;
+ }
+
+ /**
+ * Java and Android programs default to using a single global SSL context,
+ * accessible to HTTP clients as {@link SSLSocketFactory#getDefault()}. If we
+ * used the shared SSL context, when OkHttp enables NPN for its SPDY-related
+ * stuff, it would also enable NPN for other usages, which might crash them
+ * because NPN is enabled when it isn't expected to be.
+ *
+ * This code avoids that by defaulting to an OkHttp-created SSL context.
+ * The drawback of this approach is that apps that customize the global SSL
+ * context will lose these customizations.
+ */
+ private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
+ if (defaultSslSocketFactory == null) {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, null, null);
+ defaultSslSocketFactory = sslContext.getSocketFactory();
+ } catch (GeneralSecurityException e) {
+ throw new AssertionError(); // The system has no TLS. Just give up.
+ }
+ }
+ return defaultSslSocketFactory;
+ }
+
+ /** Returns a shallow copy of this OkHttpClient. */
+ @Override public final OkHttpClient clone() {
+ try {
+ return (OkHttpClient) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/OkUrlFactory.java b/contentstack/src/main/java/com/contentstack/okhttp/OkUrlFactory.java
new file mode 100755
index 00000000..278c1396
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/OkUrlFactory.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.InternalCache;
+import com.contentstack.okhttp.internal.huc.CacheAdapter;
+import com.contentstack.okhttp.internal.huc.HttpURLConnectionImpl;
+import com.contentstack.okhttp.internal.huc.HttpsURLConnectionImpl;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ResponseCache;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable {
+ private final OkHttpClient client;
+
+ public OkUrlFactory(OkHttpClient client) {
+ this.client = client;
+ }
+
+ public OkHttpClient client() {
+ return client;
+ }
+
+ /** Sets the response cache to be used to read and write cached responses. */
+ OkUrlFactory setResponseCache(ResponseCache responseCache) {
+ client.setInternalCache(responseCache != null ? new CacheAdapter(responseCache) : null);
+ return this;
+ }
+
+ ResponseCache getResponseCache() {
+ InternalCache cache = client.internalCache();
+ return cache instanceof CacheAdapter ? ((CacheAdapter) cache).getDelegate() : null;
+ }
+
+ /**
+ * Returns a copy of this stream handler factory that includes a shallow copy
+ * of the internal {@linkplain OkHttpClient HTTP client}.
+ */
+ @Override public OkUrlFactory clone() {
+ return new OkUrlFactory(client.clone());
+ }
+
+ public HttpURLConnection open(URL url) {
+ return open(url, client.getProxy());
+ }
+
+ HttpURLConnection open(URL url, Proxy proxy) {
+ String protocol = url.getProtocol();
+ OkHttpClient copy = client.copyWithDefaults();
+ copy.setProxy(proxy);
+
+ if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
+ if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
+ throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+ }
+
+ /**
+ * Creates a URLStreamHandler as a {@link java.net.URL#setURLStreamHandlerFactory}.
+ *
+ *
This code configures OkHttp to handle all HTTP and HTTPS connections
+ * created with {@link java.net.URL#openConnection()}:
{@code
+ *
+ * OkHttpClient okHttpClient = new OkHttpClient();
+ * URL.setURLStreamHandlerFactory(new OkUrlFactory(okHttpClient));
+ * }
+ */
+ @Override public URLStreamHandler createURLStreamHandler(final String protocol) {
+ if (!protocol.equals("http") && !protocol.equals("https")) return null;
+
+ return new URLStreamHandler() {
+ @Override protected URLConnection openConnection(URL url) {
+ return open(url);
+ }
+
+ @Override protected URLConnection openConnection(URL url, Proxy proxy) {
+ return open(url, proxy);
+ }
+
+ @Override protected int getDefaultPort() {
+ if (protocol.equals("http")) return 80;
+ if (protocol.equals("https")) return 443;
+ throw new AssertionError();
+ }
+ };
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Protocol.java b/contentstack/src/main/java/com/contentstack/okhttp/Protocol.java
new file mode 100755
index 00000000..986768ba
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Protocol.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import java.io.IOException;
+
+/**
+ * Protocols that OkHttp implements for NPN and
+ * ALPN
+ * selection.
+ *
+ * Protocol vs Scheme
+ * Despite its name, {@link java.net.URL#getProtocol()} returns the
+ * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
+ * the protocol (http/1.1, spdy/3.1, etc.). OkHttp uses the word protocol
+ * to identify how HTTP messages are framed.
+ */
+public enum Protocol {
+ /**
+ * An obsolete plaintext framing that does not use persistent sockets by
+ * default.
+ */
+ HTTP_1_0("http/1.0"),
+
+ /**
+ * A plaintext framing that includes persistent connections.
+ *
+ * This version of OkHttp implements RFC 2616, and tracks
+ * revisions to that spec.
+ */
+ HTTP_1_1("http/1.1"),
+
+ /**
+ * Chromium's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on SPDY/3.
+ *
+ *
This version of OkHttp implements SPDY 3 draft
+ * 3.1. Future releases of OkHttp may use this identifier for a newer draft
+ * of the SPDY spec.
+ */
+ SPDY_3("spdy/3.1"),
+
+ /**
+ * The IETF's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on HTTP/2.
+ *
+ *
This version of OkHttp implements HTTP/2 draft 12
+ * with HPACK draft
+ * 6. Future releases of OkHttp may use this identifier for a newer draft
+ * of these specs.
+ */
+ HTTP_2("h2-13");
+
+ private final String protocol;
+
+ Protocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ /**
+ * Returns the protocol identified by {@code protocol}.
+ * @throws IOException if {@code protocol} is unknown.
+ */
+ public static Protocol get(String protocol) throws IOException {
+ // Unroll the loop over values() to save an allocation.
+ if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
+ if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
+ if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
+ if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
+ throw new IOException("Unexpected protocol: " + protocol);
+ }
+
+ /**
+ * Returns the string used to identify this protocol for ALPN and NPN, like
+ * "http/1.1", "spdy/3.1" or "h2-13".
+ */
+ @Override public String toString() {
+ return protocol;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Request.java b/contentstack/src/main/java/com/contentstack/okhttp/Request.java
new file mode 100755
index 00000000..545620d6
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Request.java
@@ -0,0 +1,240 @@
+/*
+ * 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Platform;
+import com.contentstack.okhttp.internal.http.HttpMethod;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * An HTTP request. Instances of this class are immutable if their {@link #body}
+ * is null or itself immutable.
+ */
+public final class Request {
+ private final String urlString;
+ private final String method;
+ private final Headers headers;
+ private final RequestBody body;
+ private final Object tag;
+
+ private volatile URL url; // Lazily initialized.
+ private volatile URI uri; // Lazily initialized.
+ private volatile CacheControl cacheControl; // Lazily initialized.
+
+ private Request(Builder builder) {
+ this.urlString = builder.urlString;
+ this.method = builder.method;
+ this.headers = builder.headers.build();
+ this.body = builder.body;
+ this.tag = builder.tag != null ? builder.tag : this;
+ this.url = builder.url;
+ }
+
+ public URL url() {
+ try {
+ URL result = url;
+ return result != null ? result : (url = new URL(urlString));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Malformed URL: " + urlString, e);
+ }
+ }
+
+ public URI uri() throws IOException {
+ try {
+ URI result = uri;
+ return result != null ? result : (uri = Platform.get().toUriLenient(url));
+ } catch (URISyntaxException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ public String urlString() {
+ return urlString;
+ }
+
+ public String method() {
+ return method;
+ }
+
+ public Headers headers() {
+ return headers;
+ }
+
+ public String header(String name) {
+ return headers.get(name);
+ }
+
+ public List headers(String name) {
+ return headers.values(name);
+ }
+
+ public RequestBody body() {
+ return body;
+ }
+
+ public Object tag() {
+ return tag;
+ }
+
+ public Builder newBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
+ public boolean isHttps() {
+ return url().getProtocol().equals("https");
+ }
+
+ @Override public String toString() {
+ return "Request{method="
+ + method
+ + ", url="
+ + urlString
+ + ", tag="
+ + (tag != this ? tag : null)
+ + '}';
+ }
+
+ public static class Builder {
+ private String urlString;
+ private URL url;
+ private String method;
+ private Headers.Builder headers;
+ private RequestBody body;
+ private Object tag;
+
+ public Builder() {
+ this.method = "GET";
+ this.headers = new Headers.Builder();
+ }
+
+ private Builder(Request request) {
+ this.urlString = request.urlString;
+ this.url = request.url;
+ this.method = request.method;
+ this.body = request.body;
+ this.tag = request.tag;
+ this.headers = request.headers.newBuilder();
+ }
+
+ public Builder url(String url) {
+ if (url == null) throw new IllegalArgumentException("url == null");
+ urlString = url;
+ return this;
+ }
+
+ public Builder url(URL url) {
+ if (url == null) throw new IllegalArgumentException("url == null");
+ this.url = url;
+ this.urlString = url.toString();
+ return this;
+ }
+
+ /**
+ * Sets the header named {@code name} to {@code value}. If this request
+ * already has any headers with that name, they are all replaced.
+ */
+ public Builder header(String name, String value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ /**
+ * Adds a header with {@code name} and {@code value}. Prefer this method for
+ * multiply-valued headers like "Cookie".
+ */
+ public Builder addHeader(String name, String value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public Builder removeHeader(String name) {
+ headers.removeAll(name);
+ return this;
+ }
+
+ /** Removes all headers on this builder and adds {@code headers}. */
+ public Builder headers(Headers headers) {
+ this.headers = headers.newBuilder();
+ return this;
+ }
+
+ public Builder get() {
+ return method("GET", null);
+ }
+
+ public Builder head() {
+ return method("HEAD", null);
+ }
+
+ public Builder post(RequestBody body) {
+ return method("POST", body);
+ }
+
+ public Builder delete() {
+ return method("DELETE", null);
+ }
+
+ public Builder put(RequestBody body) {
+ return method("PUT", body);
+ }
+
+ public Builder patch(RequestBody body) {
+ return method("PATCH", body);
+ }
+
+ public Builder method(String method, RequestBody body) {
+ if (method == null || method.length() == 0) {
+ throw new IllegalArgumentException("method == null || method.length() == 0");
+ }
+ if (body != null && !HttpMethod.hasRequestBody(method)) {
+ throw new IllegalArgumentException("method " + method + " must not have a request body.");
+ }
+ this.method = method;
+ this.body = body;
+ return this;
+ }
+
+ /**
+ * Attaches {@code tag} to the request. It can be used later to cancel the
+ * request. If the tag is unspecified or null, the request is canceled by
+ * using the request itself as the tag.
+ */
+ public Builder tag(Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ public Request build() {
+ if (urlString == null) throw new IllegalStateException("url == null");
+ return new Request(this);
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/RequestBody.java b/contentstack/src/main/java/com/contentstack/okhttp/RequestBody.java
new file mode 100755
index 00000000..111cae92
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/RequestBody.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Source;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+public abstract class RequestBody {
+ /** Returns the Content-Type header for this body. */
+ public abstract MediaType contentType();
+
+ /**
+ * Returns the number of bytes that will be written to {@code out} in a call
+ * to {@link #writeTo}, or -1 if that count is unknown.
+ */
+ public long contentLength() {
+ return -1;
+ }
+
+ /** Writes the content of this request to {@code out}. */
+ public abstract void writeTo(BufferedSink sink) throws IOException;
+
+ /**
+ * Returns a new request body that transmits {@code content}. If {@code
+ * contentType} is non-null and lacks a charset, this will use UTF-8.
+ */
+ public static RequestBody create(MediaType contentType, String content) {
+ Charset charset = Util.UTF_8;
+ if (contentType != null) {
+ charset = contentType.charset();
+ if (charset == null) {
+ charset = Util.UTF_8;
+ contentType = MediaType.parse(contentType + "; charset=utf-8");
+ }
+ }
+ byte[] bytes = content.getBytes(charset);
+ return create(contentType, bytes);
+ }
+
+ /** Returns a new request body that transmits {@code content}. */
+ public static RequestBody create(final MediaType contentType, final byte[] content) {
+ if (content == null) throw new NullPointerException("content == null");
+
+ return new RequestBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() {
+ return content.length;
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ sink.write(content);
+ }
+ };
+ }
+
+ /** Returns a new request body that transmits the content of {@code file}. */
+ public static RequestBody create(final MediaType contentType, final File file) {
+ if (file == null) throw new NullPointerException("content == null");
+
+ return new RequestBody() {
+ @Override public MediaType contentType() {
+ return contentType;
+ }
+
+ @Override public long contentLength() {
+ return file.length();
+ }
+
+ @Override public void writeTo(BufferedSink sink) throws IOException {
+ Source source = null;
+ try {
+ source = Okio.source(file);
+ sink.writeAll(source);
+ } finally {
+ Util.closeQuietly(source);
+ }
+ }
+ };
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Response.java b/contentstack/src/main/java/com/contentstack/okhttp/Response.java
new file mode 100755
index 00000000..f751f012
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Response.java
@@ -0,0 +1,351 @@
+/*
+ * 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.http.OkHeaders;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.contentstack.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+/**
+ * An HTTP response. Instances of this class are not immutable: the response
+ * body is a one-shot value that may be consumed only once. All other properties
+ * are immutable.
+ */
+public final class Response {
+ private final Request request;
+ private final Protocol protocol;
+ private final int code;
+ private final String message;
+ private final Handshake handshake;
+ private final Headers headers;
+ private final ResponseBody body;
+ private Response networkResponse;
+ private Response cacheResponse;
+ private final Response priorResponse;
+
+ private volatile CacheControl cacheControl; // Lazily initialized.
+
+ private Response(Builder builder) {
+ this.request = builder.request;
+ this.protocol = builder.protocol;
+ this.code = builder.code;
+ this.message = builder.message;
+ this.handshake = builder.handshake;
+ this.headers = builder.headers.build();
+ this.body = builder.body;
+ this.networkResponse = builder.networkResponse;
+ this.cacheResponse = builder.cacheResponse;
+ this.priorResponse = builder.priorResponse;
+ }
+
+ /**
+ * The wire-level request that initiated this HTTP response. This is not
+ * necessarily the same request issued by the application:
+ *
+ * - It may be transformed by the HTTP client. For example, the client
+ * may copy headers like {@code Content-Length} from the request body.
+ *
- It may be the request generated in response to an HTTP redirect or
+ * authentication challenge. In this case the request URL may be
+ * different than the initial request URL.
+ *
+ */
+ public Request request() {
+ return request;
+ }
+
+ /**
+ * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link
+ * Protocol#HTTP_1_0}.
+ */
+ public Protocol protocol() {
+ return protocol;
+ }
+
+ /** Returns the HTTP status code. */
+ public int code() {
+ return code;
+ }
+
+ /**
+ * Returns true if the code is in [200..300), which means the request was
+ * successfully received, understood, and accepted.
+ */
+ public boolean isSuccessful() {
+ return code >= 200 && code < 300;
+ }
+
+ /** Returns the HTTP status message or null if it is unknown. */
+ public String message() {
+ return message;
+ }
+
+ /**
+ * Returns the TLS handshake of the connection that carried this response, or
+ * null if the response was received without TLS.
+ */
+ public Handshake handshake() {
+ return handshake;
+ }
+
+ public List headers(String name) {
+ return headers.values(name);
+ }
+
+ public String header(String name) {
+ return header(name, null);
+ }
+
+ public String header(String name, String defaultValue) {
+ String result = headers.get(name);
+ return result != null ? result : defaultValue;
+ }
+
+ public Headers headers() {
+ return headers;
+ }
+
+ public ResponseBody body() {
+ return body;
+ }
+
+ public Builder newBuilder() {
+ return new Builder(this);
+ }
+
+ /** Returns true if this response redirects to another resource. */
+ public boolean isRedirect() {
+ switch (code) {
+ case HTTP_TEMP_REDIRECT:
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_MOVED_TEMP:
+ case HTTP_SEE_OTHER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns the raw response received from the network. Will be null if this
+ * response didn't use the network, such as when the response is fully cached.
+ * The body of the returned response should not be read.
+ */
+ public Response networkResponse() {
+ return networkResponse;
+ }
+
+ /**
+ * Returns the raw response received from the cache. Will be null if this
+ * response didn't use the cache. For conditional get requests the cache
+ * response and network response may both be non-null. The body of the
+ * returned response should not be read.
+ */
+ public Response cacheResponse() {
+ return cacheResponse;
+ }
+
+ /**
+ * Returns the response for the HTTP redirect or authorization challenge that
+ * triggered this response, or null if this response wasn't triggered by an
+ * automatic retry. The body of the returned response should not be read
+ * because it has already been consumed by the redirecting client.
+ */
+ public Response priorResponse() {
+ return priorResponse;
+ }
+
+ /**
+ * Returns the authorization challenges appropriate for this response's code.
+ * If the response code is 401 unauthorized, this returns the
+ * "WWW-Authenticate" challenges. If the response code is 407 proxy
+ * unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise
+ * this returns an empty list of challenges.
+ */
+ public List challenges() {
+ String responseField;
+ if (code == HTTP_UNAUTHORIZED) {
+ responseField = "WWW-Authenticate";
+ } else if (code == HTTP_PROXY_AUTH) {
+ responseField = "Proxy-Authenticate";
+ } else {
+ return Collections.emptyList();
+ }
+ return OkHeaders.parseChallenges(headers(), responseField);
+ }
+
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
+ @Override public String toString() {
+ return "Response{protocol="
+ + protocol
+ + ", code="
+ + code
+ + ", message="
+ + message
+ + ", url="
+ + request.urlString()
+ + '}';
+ }
+
+ public static class Builder {
+ private Request request;
+ private Protocol protocol;
+ private int code = -1;
+ private String message;
+ private Handshake handshake;
+ private Headers.Builder headers;
+ private ResponseBody body;
+ private Response networkResponse;
+ private Response cacheResponse;
+ private Response priorResponse;
+
+ public Builder() {
+ headers = new Headers.Builder();
+ }
+
+ private Builder(Response response) {
+ this.request = response.request;
+ this.protocol = response.protocol;
+ this.code = response.code;
+ this.message = response.message;
+ this.handshake = response.handshake;
+ this.headers = response.headers.newBuilder();
+ this.body = response.body;
+ this.networkResponse = response.networkResponse;
+ this.cacheResponse = response.cacheResponse;
+ this.priorResponse = response.priorResponse;
+ }
+
+ public Builder request(Request request) {
+ this.request = request;
+ return this;
+ }
+
+ public Builder protocol(Protocol protocol) {
+ this.protocol = protocol;
+ return this;
+ }
+
+ public Builder code(int code) {
+ this.code = code;
+ return this;
+ }
+
+ public Builder message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public Builder handshake(Handshake handshake) {
+ this.handshake = handshake;
+ return this;
+ }
+
+ /**
+ * Sets the header named {@code name} to {@code value}. If this request
+ * already has any headers with that name, they are all replaced.
+ */
+ public Builder header(String name, String value) {
+ headers.set(name, value);
+ return this;
+ }
+
+ /**
+ * Adds a header with {@code name} and {@code value}. Prefer this method for
+ * multiply-valued headers like "Set-Cookie".
+ */
+ public Builder addHeader(String name, String value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public Builder removeHeader(String name) {
+ headers.removeAll(name);
+ return this;
+ }
+
+ /** Removes all headers on this builder and adds {@code headers}. */
+ public Builder headers(Headers headers) {
+ this.headers = headers.newBuilder();
+ return this;
+ }
+
+ public Builder body(ResponseBody body) {
+ this.body = body;
+ return this;
+ }
+
+ public Builder networkResponse(Response networkResponse) {
+ if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse);
+ this.networkResponse = networkResponse;
+ return this;
+ }
+
+ public Builder cacheResponse(Response cacheResponse) {
+ if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse);
+ this.cacheResponse = cacheResponse;
+ return this;
+ }
+
+ private void checkSupportResponse(String name, Response response) {
+ if (response.body != null) {
+ throw new IllegalArgumentException(name + ".body != null");
+ } else if (response.networkResponse != null) {
+ throw new IllegalArgumentException(name + ".networkResponse != null");
+ } else if (response.cacheResponse != null) {
+ throw new IllegalArgumentException(name + ".cacheResponse != null");
+ } else if (response.priorResponse != null) {
+ throw new IllegalArgumentException(name + ".priorResponse != null");
+ }
+ }
+
+ public Builder priorResponse(Response priorResponse) {
+ if (priorResponse != null) checkPriorResponse(priorResponse);
+ this.priorResponse = priorResponse;
+ return this;
+ }
+
+ private void checkPriorResponse(Response response) {
+ if (response.body != null) {
+ throw new IllegalArgumentException("priorResponse.body != null");
+ }
+ }
+
+ public Response build() {
+ if (request == null) throw new IllegalStateException("request == null");
+ if (protocol == null) throw new IllegalStateException("protocol == null");
+ if (code < 0) throw new IllegalStateException("code < 0: " + code);
+ return new Response(this);
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/ResponseBody.java b/contentstack/src/main/java/com/contentstack/okhttp/ResponseBody.java
new file mode 100755
index 00000000..6c610a4a
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/ResponseBody.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.BufferedSource;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import static com.contentstack.okhttp.internal.Util.UTF_8;
+
+public abstract class ResponseBody implements Closeable {
+ /** Multiple calls to {@link #charStream()} must return the same instance. */
+ private Reader reader;
+
+ public abstract MediaType contentType();
+
+ /**
+ * Returns the number of bytes in that will returned by {@link #bytes}, or
+ * {@link #byteStream}, or -1 if unknown.
+ */
+ public abstract long contentLength();
+
+ public final InputStream byteStream() {
+ return source().inputStream();
+ }
+
+ public abstract BufferedSource source();
+
+ public final byte[] bytes() throws IOException {
+ long contentLength = contentLength();
+ if (contentLength > Integer.MAX_VALUE) {
+ throw new IOException("Cannot buffer entire body for content length: " + contentLength);
+ }
+
+ BufferedSource source = source();
+ byte[] bytes;
+ try {
+ bytes = source.readByteArray();
+ } finally {
+ Util.closeQuietly(source);
+ }
+ if (contentLength != -1 && contentLength != bytes.length) {
+ throw new IOException("Content-Length and stream length disagree");
+ }
+ return bytes;
+ }
+
+ /**
+ * Returns the response as a character stream decoded with the charset
+ * of the Content-Type header. If that header is either absent or lacks a
+ * charset, this will attempt to decode the response body as UTF-8.
+ */
+ public final Reader charStream() {
+ Reader r = reader;
+ return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
+ }
+
+ /**
+ * Returns the response as a string decoded with the charset of the
+ * Content-Type header. If that header is either absent or lacks a charset,
+ * this will attempt to decode the response body as UTF-8.
+ */
+ public final String string() throws IOException {
+ return new String(bytes(), charset().name());
+ }
+
+ private Charset charset() {
+ MediaType contentType = contentType();
+ return contentType != null ? contentType.charset(UTF_8) : UTF_8;
+ }
+
+ @Override public void close() throws IOException {
+ source().close();
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/Route.java b/contentstack/src/main/java/com/contentstack/okhttp/Route.java
new file mode 100755
index 00000000..a3ef8ba9
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/Route.java
@@ -0,0 +1,110 @@
+/*
+ * 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.contentstack.okhttp;
+
+import com.contentstack.okhttp.internal.http.RouteSelector;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/**
+ * The concrete route used by a connection to reach an abstract origin server.
+ * When creating a connection the client has many options:
+ *
+ * - HTTP proxy: a proxy server may be explicitly
+ * configured for the client. Otherwise the {@linkplain java.net.ProxySelector
+ * proxy selector} is used. It may return multiple proxies to attempt.
+ *
- IP address: whether connecting directly to an origin
+ * server or a proxy, opening a socket requires an IP address. The DNS
+ * server may return multiple IP addresses to attempt.
+ *
- TLS version: which TLS version to attempt with the
+ * HTTPS connection.
+ *
+ * Each route is a specific selection of these options.
+ */
+public final class Route {
+ final Address address;
+ final Proxy proxy;
+ final InetSocketAddress inetSocketAddress;
+ final String tlsVersion;
+
+ public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
+ String tlsVersion) {
+ if (address == null) throw new NullPointerException("address == null");
+ if (proxy == null) throw new NullPointerException("proxy == null");
+ if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
+ if (tlsVersion == null) throw new NullPointerException("tlsVersion == null");
+ this.address = address;
+ this.proxy = proxy;
+ this.inetSocketAddress = inetSocketAddress;
+ this.tlsVersion = tlsVersion;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns the {@link Proxy} of this route.
+ *
+ * Warning: This may disagree with {@link Address#getProxy}
+ * when it is null. When the address's proxy is null, the proxy selector is
+ * used.
+ */
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ public InetSocketAddress getSocketAddress() {
+ return inetSocketAddress;
+ }
+
+ public String getTlsVersion() {
+ return tlsVersion;
+ }
+
+ boolean supportsNpn() {
+ return !tlsVersion.equals(RouteSelector.SSL_V3);
+ }
+
+ /**
+ * Returns true if this route tunnels HTTPS through an HTTP proxy. See RFC 2817, Section 5.2.
+ */
+ public boolean requiresTunnel() {
+ return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
+ }
+
+ @Override public boolean equals(Object obj) {
+ if (obj instanceof Route) {
+ Route other = (Route) obj;
+ return address.equals(other.address)
+ && proxy.equals(other.proxy)
+ && inetSocketAddress.equals(other.inetSocketAddress)
+ && tlsVersion.equals(other.tlsVersion);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + address.hashCode();
+ result = 31 * result + proxy.hashCode();
+ result = 31 * result + inetSocketAddress.hashCode();
+ result = 31 * result + tlsVersion.hashCode();
+ return result;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/BitArray.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/BitArray.java
new file mode 100755
index 00000000..5cb35520
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/BitArray.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014 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.contentstack.okhttp.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.lang.String.format;
+
+/** A simple bitset which supports left shifting. */
+public interface BitArray {
+
+ void clear();
+
+ void set(int index);
+
+ void toggle(int index);
+
+ boolean get(int index);
+
+ void shiftLeft(int count);
+
+ /** Bit set that only supports settings bits 0 - 63. */
+ public final class FixedCapacity implements BitArray {
+ long data = 0x0000000000000000L;
+
+ @Override public void clear() {
+ data = 0x0000000000000000L;
+ }
+
+ @Override public void set(int index) {
+ data |= (1L << checkInput(index));
+ }
+
+ @Override public void toggle(int index) {
+ data ^= (1L << checkInput(index));
+ }
+
+ @Override public boolean get(int index) {
+ return ((data >> checkInput(index)) & 1L) == 1;
+ }
+
+ @Override public void shiftLeft(int count) {
+ data = data << checkInput(count);
+ }
+
+ @Override public String toString() {
+ return Long.toBinaryString(data);
+ }
+
+ public BitArray toVariableCapacity() {
+ return new VariableCapacity(this);
+ }
+
+ private static int checkInput(int index) {
+ if (index < 0 || index > 63) {
+ throw new IllegalArgumentException(format("input must be between 0 and 63: %s", index));
+ }
+ return index;
+ }
+ }
+
+ /** Bit set that grows as needed. */
+ public final class VariableCapacity implements BitArray {
+
+ long[] data;
+
+ // Start offset which allows for cheap shifting. Data is always kept on 64-bit bounds but we
+ // offset the outward facing index to support shifts without having to move the underlying bits.
+ private int start; // Valid values are [0..63]
+
+ public VariableCapacity() {
+ data = new long[1];
+ }
+
+ private VariableCapacity(FixedCapacity small) {
+ data = new long[] {small.data, 0};
+ }
+
+ private void growToSize(int size) {
+ long[] newData = new long[size];
+ if (data != null) {
+ System.arraycopy(data, 0, newData, 0, data.length);
+ }
+ data = newData;
+ }
+
+ private int offsetOf(int index) {
+ index += start;
+ int offset = index / 64;
+ if (offset > data.length - 1) {
+ growToSize(offset + 1);
+ }
+ return offset;
+ }
+
+ private int shiftOf(int index) {
+ return (index + start) % 64;
+ }
+
+ @Override public void clear() {
+ Arrays.fill(data, 0);
+ }
+
+ @Override public void set(int index) {
+ checkInput(index);
+ int offset = offsetOf(index);
+ data[offset] |= 1L << shiftOf(index);
+ }
+
+ @Override public void toggle(int index) {
+ checkInput(index);
+ int offset = offsetOf(index);
+ data[offset] ^= 1L << shiftOf(index);
+ }
+
+ @Override public boolean get(int index) {
+ checkInput(index);
+ int offset = offsetOf(index);
+ return (data[offset] & (1L << shiftOf(index))) != 0;
+ }
+
+ @Override public void shiftLeft(int count) {
+ start -= checkInput(count);
+ if (start < 0) {
+ int arrayShift = (start / -64) + 1;
+ long[] newData = new long[data.length + arrayShift];
+ System.arraycopy(data, 0, newData, arrayShift, data.length);
+ data = newData;
+ start = 64 + (start % 64);
+ }
+ }
+
+ @Override public String toString() {
+ StringBuilder builder = new StringBuilder("{");
+ List ints = toIntegerList();
+ for (int i = 0, count = ints.size(); i < count; i++) {
+ if (i > 0) {
+ builder.append(',');
+ }
+ builder.append(ints.get(i));
+ }
+ return builder.append('}').toString();
+ }
+
+ List toIntegerList() {
+ List ints = new ArrayList();
+ for (int i = 0, count = data.length * 64 - start; i < count; i++) {
+ if (get(i)) {
+ ints.add(i);
+ }
+ }
+ return ints;
+ }
+
+ private static int checkInput(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException(format("input must be a positive number: %s", index));
+ }
+ return index;
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/DiskLruCache.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/DiskLruCache.java
new file mode 100755
index 00000000..40928c8c
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/DiskLruCache.java
@@ -0,0 +1,940 @@
+/*
+ * 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.contentstack.okhttp.internal;
+
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.ForwardingSink;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+import com.contentstack.okio.Timeout;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex [a-z0-9_-]{1,64}. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ *
This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ *
Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ *
+ * - When an entry is being created it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ *
- When an entry is being edited, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ *
This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TEMP = "journal.tmp";
+ static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN ***REMOVED***
+ * DIRTY ***REMOVED***
+ * CLEAN ***REMOVED*** ***REMOVED******REMOVED***
+ * REMOVE ***REMOVED***
+ * DIRTY ***REMOVED***
+ * CLEAN ***REMOVED*** ***REMOVED******REMOVED***
+ * READ ***REMOVED***
+ * READ ***REMOVED***
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final File journalFileBackup;
+ private final int appVersion;
+ private long maxSize;
+ private final int valueCount;
+ private long size = 0;
+ private BufferedSink journalWriter;
+ private final LinkedHashMap lruEntries = new LinkedHashMap(0, 0.75f, true);
+ private int redundantOpCount;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /** This cache uses a single background thread to evict entries. */
+ final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue(), Util.threadFactory("OkHttp DiskLruCache", true));
+ private final Runnable cleanupRunnable = new Runnable() {
+ public void run() {
+ synchronized (DiskLruCache.this) {
+ if (journalWriter == null) {
+ return; // Closed.
+ }
+ try {
+ trimToSize();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ };
+
+ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+ this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Opens the cache in {@code directory}, creating a cache if none exists
+ * there.
+ *
+ * @param directory a writable directory
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ * @throws IOException if reading or writing the cache directory fails
+ */
+ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+ throws IOException {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // If a bkp file exists, use it instead.
+ File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+ if (backupFile.exists()) {
+ File journalFile = new File(directory, JOURNAL_FILE);
+ // If journal file also exists just delete backup file.
+ if (journalFile.exists()) {
+ backupFile.delete();
+ } else {
+ renameTo(backupFile, journalFile, false);
+ }
+ }
+
+ // Prefer to pick up where we left off.
+ DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ if (cache.journalFile.exists()) {
+ try {
+ cache.readJournal();
+ cache.processJournal();
+ cache.journalWriter = Okio.buffer(Okio.appendingSink(cache.journalFile));
+ return cache;
+ } catch (IOException journalIsCorrupt) {
+ Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
+ + journalIsCorrupt.getMessage() + ", removing");
+ cache.delete();
+ }
+ }
+
+ // Create a new empty cache.
+ directory.mkdirs();
+ cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ cache.rebuildJournal();
+ return cache;
+ }
+
+ private void readJournal() throws IOException {
+ BufferedSource source = Okio.buffer(Okio.source(journalFile));
+ try {
+ String magic = source.readUtf8LineStrict();
+ String version = source.readUtf8LineStrict();
+ String appVersionString = source.readUtf8LineStrict();
+ String valueCountString = source.readUtf8LineStrict();
+ String blank = source.readUtf8LineStrict();
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ + valueCountString + ", " + blank + "]");
+ }
+
+ int lineCount = 0;
+ while (true) {
+ try {
+ readJournalLine(source.readUtf8LineStrict());
+ lineCount++;
+ } catch (EOFException endOfJournal) {
+ break;
+ }
+ }
+ redundantOpCount = lineCount - lruEntries.size();
+ } finally {
+ Util.closeQuietly(source);
+ }
+ }
+
+ private void readJournalLine(String line) throws IOException {
+ int firstSpace = line.indexOf(' ');
+ if (firstSpace == -1) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ int keyBegin = firstSpace + 1;
+ int secondSpace = line.indexOf(' ', keyBegin);
+ final String key;
+ if (secondSpace == -1) {
+ key = line.substring(keyBegin);
+ if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+ lruEntries.remove(key);
+ return;
+ }
+ } else {
+ key = line.substring(keyBegin, secondSpace);
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+ String[] parts = line.substring(secondSpace + 1).split(" ");
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(parts);
+ } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+ entry.currentEditor = new Editor(entry);
+ } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+ // This work was already done by calling lruEntries.get().
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ deleteIfExists(journalFileTmp);
+ for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) {
+ Entry entry = i.next();
+ if (entry.currentEditor == null) {
+ for (int t = 0; t < valueCount; t++) {
+ size += entry.lengths[t];
+ }
+ } else {
+ entry.currentEditor = null;
+ for (int t = 0; t < valueCount; t++) {
+ deleteIfExists(entry.cleanFiles[t]);
+ deleteIfExists(entry.dirtyFiles[t]);
+ }
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Creates a new journal that omits redundant information. This replaces the
+ * current journal if it exists.
+ */
+ private synchronized void rebuildJournal() throws IOException {
+ if (journalWriter != null) {
+ journalWriter.close();
+ }
+
+ BufferedSink writer = Okio.buffer(Okio.sink(journalFileTmp));
+ try {
+ writer.writeUtf8(MAGIC).writeByte('\n');
+ writer.writeUtf8(VERSION_1).writeByte('\n');
+ writer.writeUtf8(Integer.toString(appVersion)).writeByte('\n');
+ writer.writeUtf8(Integer.toString(valueCount)).writeByte('\n');
+ writer.writeByte('\n');
+
+ for (Entry entry : lruEntries.values()) {
+ if (entry.currentEditor != null) {
+ writer.writeUtf8(DIRTY).writeByte(' ');
+ writer.writeUtf8(entry.key);
+ writer.writeByte('\n');
+ } else {
+ writer.writeUtf8(CLEAN).writeByte(' ');
+ writer.writeUtf8(entry.key);
+ writer.writeUtf8(entry.getLengths());
+ writer.writeByte('\n');
+ }
+ }
+ } finally {
+ writer.close();
+ }
+
+ if (journalFile.exists()) {
+ renameTo(journalFile, journalFileBackup, true);
+ }
+ renameTo(journalFileTmp, journalFile, false);
+ journalFileBackup.delete();
+
+ journalWriter = Okio.buffer(Okio.appendingSink(journalFile));
+ }
+
+ private static void deleteIfExists(File file) throws IOException {
+ // If delete() fails, make sure it's because the file didn't exist!
+ if (!file.delete() && file.exists()) {
+ throw new IOException("failed to delete " + file);
+ }
+ }
+
+ private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
+ if (deleteDestination) {
+ deleteIfExists(to);
+ }
+ if (!from.renameTo(to)) {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+ * exist is not currently readable. If a value is returned, it is moved to
+ * the head of the LRU queue.
+ */
+ public synchronized Snapshot get(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ return null;
+ }
+
+ if (!entry.readable) {
+ return null;
+ }
+
+ // Open all streams eagerly to guarantee that we see a single published
+ // snapshot. If we opened streams lazily then the streams could come
+ // from different edits.
+ Source[] sources = new Source[valueCount];
+ try {
+ for (int i = 0; i < valueCount; i++) {
+ sources[i] = Okio.source(entry.cleanFiles[i]);
+ }
+ } catch (FileNotFoundException e) {
+ // A file must have been deleted manually!
+ for (int i = 0; i < valueCount; i++) {
+ if (sources[i] != null) {
+ Util.closeQuietly(sources[i]);
+ } else {
+ break;
+ }
+ }
+ return null;
+ }
+
+ redundantOpCount++;
+ journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
+ if (journalRebuildRequired()) {
+ executorService.execute(cleanupRunnable);
+ }
+
+ return new Snapshot(key, entry.sequenceNumber, sources, entry.lengths);
+ }
+
+ /**
+ * Returns an editor for the entry named {@code key}, or null if another
+ * edit is in progress.
+ */
+ public Editor edit(String key) throws IOException {
+ return edit(key, ANY_SEQUENCE_NUMBER);
+ }
+
+ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+ || entry.sequenceNumber != expectedSequenceNumber)) {
+ return null; // Snapshot is stale.
+ }
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ } else if (entry.currentEditor != null) {
+ return null; // Another edit is in progress.
+ }
+
+ Editor editor = new Editor(entry);
+ entry.currentEditor = editor;
+
+ // Flush the journal before creating files to prevent file leaks.
+ journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
+ journalWriter.flush();
+ return editor;
+ }
+
+ /** Returns the directory where this cache stores its data. */
+ public File getDirectory() {
+ return directory;
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public synchronized long getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Changes the maximum number of bytes the cache can store and queues a job
+ * to trim the existing store, if necessary.
+ */
+ public synchronized void setMaxSize(long maxSize) {
+ this.maxSize = maxSize;
+ executorService.execute(cleanupRunnable);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the max size if a background
+ * deletion is pending.
+ */
+ public synchronized long size() {
+ return size;
+ }
+
+ private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+ Entry entry = editor.entry;
+ if (entry.currentEditor != editor) {
+ throw new IllegalStateException();
+ }
+
+ // If this edit is creating the entry for the first time, every index must have a value.
+ if (success && !entry.readable) {
+ for (int i = 0; i < valueCount; i++) {
+ if (!editor.written[i]) {
+ editor.abort();
+ throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+ }
+ if (!entry.dirtyFiles[i].exists()) {
+ editor.abort();
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File dirty = entry.dirtyFiles[i];
+ if (success) {
+ if (dirty.exists()) {
+ File clean = entry.cleanFiles[i];
+ dirty.renameTo(clean);
+ long oldLength = entry.lengths[i];
+ long newLength = clean.length();
+ entry.lengths[i] = newLength;
+ size = size - oldLength + newLength;
+ }
+ } else {
+ deleteIfExists(dirty);
+ }
+ }
+
+ redundantOpCount++;
+ entry.currentEditor = null;
+ if (entry.readable | success) {
+ entry.readable = true;
+ journalWriter.writeUtf8(CLEAN).writeByte(' ');
+ journalWriter.writeUtf8(entry.key);
+ journalWriter.writeUtf8(entry.getLengths());
+ journalWriter.writeByte('\n');
+ if (success) {
+ entry.sequenceNumber = nextSequenceNumber++;
+ }
+ } else {
+ lruEntries.remove(entry.key);
+ journalWriter.writeUtf8(REMOVE).writeByte(' ');
+ journalWriter.writeUtf8(entry.key);
+ journalWriter.writeByte('\n');
+ }
+ journalWriter.flush();
+
+ if (size > maxSize || journalRebuildRequired()) {
+ executorService.execute(cleanupRunnable);
+ }
+ }
+
+ /**
+ * We only rebuild the journal when it will halve the size of the journal
+ * and eliminate at least 2000 ops.
+ */
+ private boolean journalRebuildRequired() {
+ final int redundantOpCompactThreshold = 2000;
+ return redundantOpCount >= redundantOpCompactThreshold
+ && redundantOpCount >= lruEntries.size();
+ }
+
+ /**
+ * Drops the entry for {@code key} if it exists and can be removed. Entries
+ * actively being edited cannot be removed.
+ *
+ * @return true if an entry was removed.
+ */
+ public synchronized boolean remove(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null || entry.currentEditor != null) {
+ return false;
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File file = entry.cleanFiles[i];
+ deleteIfExists(file);
+ size -= entry.lengths[i];
+ entry.lengths[i] = 0;
+ }
+
+ redundantOpCount++;
+ journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(key).writeByte('\n');
+ lruEntries.remove(key);
+
+ if (journalRebuildRequired()) {
+ executorService.execute(cleanupRunnable);
+ }
+
+ return true;
+ }
+
+ /** Returns true if this cache has been closed. */
+ public boolean isClosed() {
+ return journalWriter == null;
+ }
+
+ private void checkNotClosed() {
+ if (journalWriter == null) {
+ throw new IllegalStateException("cache is closed");
+ }
+ }
+
+ /** Force buffered operations to the filesystem. */
+ public synchronized void flush() throws IOException {
+ checkNotClosed();
+ trimToSize();
+ journalWriter.flush();
+ }
+
+ /** Closes this cache. Stored values will remain on the filesystem. */
+ public synchronized void close() throws IOException {
+ if (journalWriter == null) {
+ return; // Already closed.
+ }
+ // Copying for safe iteration.
+ for (Object next : lruEntries.values().toArray()) {
+ Entry entry = (Entry) next;
+ if (entry.currentEditor != null) {
+ entry.currentEditor.abort();
+ }
+ }
+ trimToSize();
+ journalWriter.close();
+ journalWriter = null;
+ }
+
+ private void trimToSize() throws IOException {
+ while (size > maxSize) {
+ Map.Entry toEvict = lruEntries.entrySet().iterator().next();
+ remove(toEvict.getKey());
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ close();
+ Util.deleteContents(directory);
+ }
+
+ private void validateKey(String key) {
+ Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
+ }
+ }
+
+ private static String sourceToString(Source in) throws IOException {
+ try {
+ return Okio.buffer(in).readUtf8();
+ } finally {
+ Util.closeQuietly(in);
+ }
+ }
+
+ /** A snapshot of the values for an entry. */
+ public final class Snapshot implements Closeable {
+ private final String key;
+ private final long sequenceNumber;
+ private final Source[] sources;
+ private final long[] lengths;
+
+ private Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) {
+ this.key = key;
+ this.sequenceNumber = sequenceNumber;
+ this.sources = sources;
+ this.lengths = lengths;
+ }
+
+ /**
+ * Returns an editor for this snapshot's entry, or null if either the
+ * entry has changed since this snapshot was created or if another edit
+ * is in progress.
+ */
+ public Editor edit() throws IOException {
+ return DiskLruCache.this.edit(key, sequenceNumber);
+ }
+
+ /** Returns the unbuffered stream with the value for {@code index}. */
+ public Source getSource(int index) {
+ return sources[index];
+ }
+
+ /** Returns the string value for {@code index}. */
+ public String getString(int index) throws IOException {
+ return sourceToString(getSource(index));
+ }
+
+ /** Returns the byte length of the value for {@code index}. */
+ public long getLength(int index) {
+ return lengths[index];
+ }
+
+ public void close() {
+ for (Source in : sources) {
+ Util.closeQuietly(in);
+ }
+ }
+ }
+
+ private static final Sink NULL_SINK = new Sink() {
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ // Eat all writes silently. Nom nom.
+ }
+
+ @Override public void flush() throws IOException {
+ }
+
+ @Override public Timeout timeout() {
+ return Timeout.NONE;
+ }
+
+ @Override public void close() throws IOException {
+ }
+ };
+
+ /** Edits the values for an entry. */
+ public final class Editor {
+ private final Entry entry;
+ private final boolean[] written;
+ private boolean hasErrors;
+ private boolean committed;
+
+ private Editor(Entry entry) {
+ this.entry = entry;
+ this.written = (entry.readable) ? null : new boolean[valueCount];
+ }
+
+ /**
+ * Returns an unbuffered input stream to read the last committed value,
+ * or null if no value has been committed.
+ */
+ public Source newSource(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ return null;
+ }
+ try {
+ return Okio.source(entry.cleanFiles[index]);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns the last committed value as a string, or null if no value
+ * has been committed.
+ */
+ public String getString(int index) throws IOException {
+ Source source = newSource(index);
+ return source != null ? sourceToString(source) : null;
+ }
+
+ /**
+ * Returns a new unbuffered output stream to write the value at
+ * {@code index}. If the underlying output stream encounters errors
+ * when writing to the filesystem, this edit will be aborted when
+ * {@link #commit} is called. The returned output stream does not throw
+ * IOExceptions.
+ */
+ public Sink newSink(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ written[index] = true;
+ }
+ File dirtyFile = entry.dirtyFiles[index];
+ Sink sink;
+ try {
+ sink = Okio.sink(dirtyFile);
+ } catch (FileNotFoundException e) {
+ // Attempt to recreate the cache directory.
+ directory.mkdirs();
+ try {
+ sink = Okio.sink(dirtyFile);
+ } catch (FileNotFoundException e2) {
+ // We are unable to recover. Silently eat the writes.
+ return NULL_SINK;
+ }
+ }
+ return new FaultHidingSink(sink);
+ }
+ }
+
+ /** Sets the value at {@code index} to {@code value}. */
+ public void set(int index, String value) throws IOException {
+ BufferedSink writer = Okio.buffer(newSink(index));
+ writer.writeUtf8(value);
+ writer.close();
+ }
+
+ /**
+ * Commits this edit so it is visible to readers. This releases the
+ * edit lock so another edit may be started on the same key.
+ */
+ public void commit() throws IOException {
+ if (hasErrors) {
+ completeEdit(this, false);
+ remove(entry.key); // The previous entry is stale.
+ } else {
+ completeEdit(this, true);
+ }
+ committed = true;
+ }
+
+ /**
+ * Aborts this edit. This releases the edit lock so another edit may be
+ * started on the same key.
+ */
+ public void abort() throws IOException {
+ completeEdit(this, false);
+ }
+
+ public void abortUnlessCommitted() {
+ if (!committed) {
+ try {
+ abort();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ private class FaultHidingSink extends ForwardingSink {
+ public FaultHidingSink(Sink delegate) {
+ super(delegate);
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ try {
+ super.write(source, byteCount);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void flush() throws IOException {
+ try {
+ super.flush();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void close() throws IOException {
+ try {
+ super.close();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ private final class Entry {
+ private final String key;
+
+ /** Lengths of this entry's files. */
+ private final long[] lengths;
+ private final File[] cleanFiles;
+ private final File[] dirtyFiles;
+
+ /** True if this entry has ever been published. */
+ private boolean readable;
+
+ /** The ongoing edit or null if this entry is not being edited. */
+ private Editor currentEditor;
+
+ /** The sequence number of the most recently committed edit to this entry. */
+ private long sequenceNumber;
+
+ private Entry(String key) {
+ this.key = key;
+
+ lengths = new long[valueCount];
+ cleanFiles = new File[valueCount];
+ dirtyFiles = new File[valueCount];
+
+ // The names are repetitive so re-use the same builder to avoid allocations.
+ StringBuilder fileBuilder = new StringBuilder(key).append('.');
+ int truncateTo = fileBuilder.length();
+ for (int i = 0; i < valueCount; i++) {
+ fileBuilder.append(i);
+ cleanFiles[i] = new File(directory, fileBuilder.toString());
+ fileBuilder.append(".tmp");
+ dirtyFiles[i] = new File(directory, fileBuilder.toString());
+ fileBuilder.setLength(truncateTo);
+ }
+ }
+
+ public String getLengths() throws IOException {
+ StringBuilder result = new StringBuilder();
+ for (long size : lengths) {
+ result.append(' ').append(size);
+ }
+ return result.toString();
+ }
+
+ /** Set lengths using decimal numbers like "10123". */
+ private void setLengths(String[] strings) throws IOException {
+ if (strings.length != valueCount) {
+ throw invalidLengths(strings);
+ }
+
+ try {
+ for (int i = 0; i < strings.length; i++) {
+ lengths[i] = Long.parseLong(strings[i]);
+ }
+ } catch (NumberFormatException e) {
+ throw invalidLengths(strings);
+ }
+ }
+
+ private IOException invalidLengths(String[] strings) throws IOException {
+ throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/Internal.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/Internal.java
new file mode 100755
index 00000000..d5ecbf18
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/Internal.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp.internal;
+
+import com.contentstack.okhttp.Connection;
+import com.contentstack.okhttp.ConnectionPool;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.OkHttpClient;
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+import com.contentstack.okhttp.internal.http.Transport;
+
+import java.io.IOException;
+
+/**
+ * Escalate internal APIs in {@code com.squareup.okhttp} so they can be used
+ * from OkHttp's implementation packages. The only implementation of this
+ * interface is in {@link OkHttpClient}.
+ */
+public abstract class Internal {
+ public static Internal instance;
+
+ public abstract Transport newTransport(Connection connection, HttpEngine httpEngine)
+ throws IOException;
+
+ public abstract boolean clearOwner(Connection connection);
+
+ public abstract void closeIfOwnedBy(Connection connection, Object owner) throws IOException;
+
+ public abstract int recycleCount(Connection connection);
+
+ public abstract void setProtocol(Connection connection, Protocol protocol);
+
+ public abstract void setOwner(Connection connection, HttpEngine httpEngine);
+
+ public abstract boolean isReadable(Connection pooled);
+
+ public abstract void addLine(Headers.Builder builder, String line);
+
+ public abstract void setCache(OkHttpClient client, InternalCache internalCache);
+
+ public abstract InternalCache internalCache(OkHttpClient client);
+
+ public abstract void recycle(ConnectionPool pool, Connection connection);
+
+ public abstract RouteDatabase routeDatabase(OkHttpClient client);
+
+ public abstract void connectAndSetOwner(OkHttpClient client, Connection connection,
+ HttpEngine owner, Request request) throws IOException;
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/InternalCache.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/InternalCache.java
new file mode 100755
index 00000000..6ab9ac18
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/InternalCache.java
@@ -0,0 +1,54 @@
+/*
+ * 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.contentstack.okhttp.internal;
+
+import com.contentstack.okhttp.Cache;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.internal.http.CacheRequest;
+import com.contentstack.okhttp.internal.http.CacheStrategy;
+
+import java.io.IOException;
+
+/**
+ * OkHttp's internal cache interface. Applications shouldn't implement this:
+ * instead use {@link Cache}.
+ */
+public interface InternalCache {
+ Response get(Request request) throws IOException;
+
+ CacheRequest put(Response response) throws IOException;
+
+ /**
+ * Remove any cache entries for the supplied {@code request}. This is invoked
+ * when the client invalidates the cache, such as when making POST requests.
+ */
+ void remove(Request request) throws IOException;
+
+ /**
+ * Handles a conditional request hit by updating the stored cache response
+ * with the headers from {@code network}. The cached response body is not
+ * updated. If the stored response has changed since {@code cached} was
+ * returned, this does nothing.
+ */
+ void update(Response cached, Response network) throws IOException;
+
+ /** Track an conditional GET that was satisfied by this cache. */
+ void trackConditionalCacheHit();
+
+ /** Track an HTTP response being satisfied with {@code cacheStrategy}. */
+ void trackResponse(CacheStrategy cacheStrategy);
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/NamedRunnable.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/NamedRunnable.java
new file mode 100755
index 00000000..718a8401
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/NamedRunnable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal;
+
+/**
+ * Runnable implementation which always sets its thread name.
+ */
+public abstract class NamedRunnable implements Runnable {
+ private final String name;
+
+ public NamedRunnable(String format, Object... args) {
+ this.name = String.format(format, args);
+ }
+
+ @Override public final void run() {
+ String oldName = Thread.currentThread().getName();
+ Thread.currentThread().setName(name);
+ try {
+ execute();
+ } finally {
+ Thread.currentThread().setName(oldName);
+ }
+ }
+
+ protected abstract void execute();
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/Platform.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/Platform.java
new file mode 100755
index 00000000..d9a268c0
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/Platform.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp.internal;
+
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okio.Buffer;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Access to Platform-specific features necessary for SPDY and advanced TLS.
+ *
+ * ALPN and NPN
+ * This class uses TLS extensions ALPN and NPN to negotiate the upgrade from
+ * HTTP/1.1 (the default protocol to use with TLS on port 443) to either SPDY
+ * or HTTP/2.
+ *
+ * NPN (Next Protocol Negotiation) was developed for SPDY. It is widely
+ * available and we support it on both Android (4.1+) and OpenJDK 7 (via the
+ * Jetty Alpn-boot library). NPN is not yet available on OpenJDK 8.
+ *
+ *
ALPN (Application Layer Protocol Negotiation) is the successor to NPN. It
+ * has some technical advantages over NPN. ALPN first arrived in Android 4.4,
+ * but that release suffers a concurrency bug
+ * so we don't use it. ALPN is supported on OpenJDK 7 and 8 (via the Jetty
+ * ALPN-boot library).
+ *
+ *
On platforms that support both extensions, OkHttp will use both,
+ * preferring ALPN's result. Future versions of OkHttp will drop support for
+ * NPN.
+ */
+public class Platform {
+ private static final Platform PLATFORM = findPlatform();
+
+ public static Platform get() {
+ return PLATFORM;
+ }
+
+ /** Prefix used on custom headers. */
+ public String getPrefix() {
+ return "OkHttp";
+ }
+
+ public void logW(String warning) {
+ System.out.println(warning);
+ }
+
+ public void tagSocket(Socket socket) throws SocketException {
+ }
+
+ public void untagSocket(Socket socket) throws SocketException {
+ }
+
+ public URI toUriLenient(URL url) throws URISyntaxException {
+ return url.toURI(); // this isn't as good as the built-in toUriLenient
+ }
+
+ /**
+ * Configure the TLS connection to use {@code tlsVersion}. We also bundle
+ * certain extensions with certain versions. In particular, we enable Server
+ * Name Indication (SNI) and Next Protocol Negotiation (NPN) with TLSv1 on
+ * platforms that support them.
+ */
+ public void configureTls(SSLSocket socket, String uriHost, String tlsVersion) {
+ // We don't call setEnabledProtocols("TLSv1") on the assumption that that's
+ if (tlsVersion.equals("SSLv3")) {
+ socket.setEnabledProtocols(new String[] {"SSLv3"});
+ }
+ }
+
+ /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+ public String getSelectedProtocol(SSLSocket socket) {
+ return null;
+ }
+
+ /**
+ * Sets client-supported protocols on a socket to send to a server. The
+ * protocols are only sent if the socket implementation supports ALPN or NPN.
+ */
+ public void setProtocols(SSLSocket socket, List protocols) {
+ }
+
+ public void connectSocket(Socket socket, InetSocketAddress address,
+ int connectTimeout) throws IOException {
+ socket.connect(address, connectTimeout);
+ }
+
+ /** Attempt to match the host runtime to a capable Platform implementation. */
+ private static Platform findPlatform() {
+ // Attempt to find Android 2.3+ APIs.
+ Class> openSslSocketClass;
+ Method setUseSessionTickets;
+ Method setHostname;
+ try {
+ try {
+ openSslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
+ } catch (ClassNotFoundException ignored) {
+ // Older platform before being unbundled.
+ openSslSocketClass = Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+ }
+
+ setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
+ setHostname = openSslSocketClass.getMethod("setHostname", String.class);
+
+ // Attempt to find Android 4.0+ APIs.
+ Method trafficStatsTagSocket = null;
+ Method trafficStatsUntagSocket = null;
+ try {
+ Class> trafficStats = Class.forName("android.net.TrafficStats");
+ trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
+ trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);
+ } catch (ClassNotFoundException ignored) {
+ } catch (NoSuchMethodException ignored) {
+ }
+
+ // Attempt to find Android 4.1+ APIs.
+ Method setNpnProtocols = null;
+ Method getNpnSelectedProtocol = null;
+ try {
+ setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
+ getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
+ } catch (NoSuchMethodException ignored) {
+ }
+
+ return new Android(openSslSocketClass, setUseSessionTickets, setHostname,
+ trafficStatsTagSocket, trafficStatsUntagSocket, setNpnProtocols,
+ getNpnSelectedProtocol);
+ } catch (ClassNotFoundException ignored) {
+ // This isn't an Android runtime.
+ } catch (NoSuchMethodException ignored) {
+ // This isn't Android 2.3 or better.
+ }
+
+ try { // to find the Jetty's ALPN or NPN extension for OpenJDK.
+ String negoClassName = "org.eclipse.jetty.alpn.ALPN";
+ Class> negoClass;
+ try {
+ negoClass = Class.forName(negoClassName);
+ } catch (ClassNotFoundException ignored) { // ALPN isn't on the classpath.
+ negoClassName = "org.eclipse.jetty.npn.NextProtoNego";
+ negoClass = Class.forName(negoClassName);
+ }
+ Class> providerClass = Class.forName(negoClassName + "$Provider");
+ Class> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
+ Class> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
+ Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
+ Method getMethod = negoClass.getMethod("get", SSLSocket.class);
+ return new JdkWithJettyBootPlatform(
+ putMethod, getMethod, clientProviderClass, serverProviderClass);
+ } catch (ClassNotFoundException ignored) { // NPN isn't on the classpath.
+ } catch (NoSuchMethodException ignored) { // The ALPN or NPN version isn't what we expect.
+ }
+
+ return new Platform();
+ }
+
+ /**
+ * Android 2.3 or better. Version 2.3 supports TLS session tickets and server
+ * name indication (SNI). Versions 4.1 supports NPN.
+ */
+ private static class Android extends Platform {
+ // Non-null.
+ protected final Class> openSslSocketClass;
+ private final Method setUseSessionTickets;
+ private final Method setHostname;
+
+ // Non-null on Android 4.0+.
+ private final Method trafficStatsTagSocket;
+ private final Method trafficStatsUntagSocket;
+
+ // Non-null on Android 4.1+.
+ private final Method setNpnProtocols;
+ private final Method getNpnSelectedProtocol;
+
+ private Android(Class> openSslSocketClass, Method setUseSessionTickets, Method setHostname,
+ Method trafficStatsTagSocket, Method trafficStatsUntagSocket, Method setNpnProtocols,
+ Method getNpnSelectedProtocol) {
+ this.openSslSocketClass = openSslSocketClass;
+ this.setUseSessionTickets = setUseSessionTickets;
+ this.setHostname = setHostname;
+ this.trafficStatsTagSocket = trafficStatsTagSocket;
+ this.trafficStatsUntagSocket = trafficStatsUntagSocket;
+ this.setNpnProtocols = setNpnProtocols;
+ this.getNpnSelectedProtocol = getNpnSelectedProtocol;
+ }
+
+ @Override public void connectSocket(Socket socket, InetSocketAddress address,
+ int connectTimeout) throws IOException {
+ try {
+ socket.connect(address, connectTimeout);
+ } catch (SecurityException se) {
+ // Before android 4.3, socket.connect could throw a SecurityException
+ // if opening a socket resulted in an EACCES error.
+ IOException ioException = new IOException("Exception in connect");
+ ioException.initCause(se);
+ throw ioException;
+ }
+ }
+
+ @Override public void configureTls(SSLSocket socket, String uriHost, String tlsVersion) {
+ super.configureTls(socket, uriHost, tlsVersion);
+
+ if (tlsVersion.equals("TLSv1") && openSslSocketClass.isInstance(socket)) {
+ try {
+ setUseSessionTickets.invoke(socket, true);
+ setHostname.invoke(socket, uriHost);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ @Override public void setProtocols(SSLSocket socket, List protocols) {
+ if (setNpnProtocols == null) return;
+ if (!openSslSocketClass.isInstance(socket)) return;
+ try {
+ Object[] parameters = { concatLengthPrefixed(protocols) };
+ setNpnProtocols.invoke(socket, parameters);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public String getSelectedProtocol(SSLSocket socket) {
+ if (getNpnSelectedProtocol == null) return null;
+ if (!openSslSocketClass.isInstance(socket)) return null;
+ try {
+ byte[] npnResult = (byte[]) getNpnSelectedProtocol.invoke(socket);
+ if (npnResult == null) return null;
+ return new String(npnResult, Util.UTF_8);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public void tagSocket(Socket socket) throws SocketException {
+ if (trafficStatsTagSocket == null) return;
+
+ try {
+ trafficStatsTagSocket.invoke(null, socket);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public void untagSocket(Socket socket) throws SocketException {
+ if (trafficStatsUntagSocket == null) return;
+
+ try {
+ trafficStatsUntagSocket.invoke(null, socket);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * OpenJDK 7+ with {@code org.mortbay.jetty.npn/npn-boot} or
+ * {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
+ */
+ private static class JdkWithJettyBootPlatform extends Platform {
+ private final Method getMethod;
+ private final Method putMethod;
+ private final Class> clientProviderClass;
+ private final Class> serverProviderClass;
+
+ public JdkWithJettyBootPlatform(Method putMethod, Method getMethod,
+ Class> clientProviderClass, Class> serverProviderClass) {
+ this.putMethod = putMethod;
+ this.getMethod = getMethod;
+ this.clientProviderClass = clientProviderClass;
+ this.serverProviderClass = serverProviderClass;
+ }
+
+ @Override public void setProtocols(SSLSocket socket, List protocols) {
+ try {
+ List names = new ArrayList(protocols.size());
+ for (int i = 0, size = protocols.size(); i < size; i++) {
+ Protocol protocol = protocols.get(i);
+ if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for NPN or ALPN.
+ names.add(protocol.toString());
+ }
+ Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+ new Class[] { clientProviderClass, serverProviderClass }, new JettyNegoProvider(names));
+ putMethod.invoke(null, socket, provider);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public String getSelectedProtocol(SSLSocket socket) {
+ try {
+ JettyNegoProvider provider =
+ (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
+ if (!provider.unsupported && provider.selected == null) {
+ Logger logger = Logger.getLogger("com.squareup.okhttp.OkHttpClient");
+ logger.log(Level.INFO, "NPN/ALPN callback dropped: SPDY and HTTP/2 are disabled. "
+ + "Is npn-boot or alpn-boot on the boot class path?");
+ return null;
+ }
+ return provider.unsupported ? null : provider.selected;
+ } catch (InvocationTargetException e) {
+ throw new AssertionError();
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /**
+ * Handle the methods of NPN or ALPN's ClientProvider and ServerProvider
+ * without a compile-time dependency on those interfaces.
+ */
+ private static class JettyNegoProvider implements InvocationHandler {
+ /** This peer's supported protocols. */
+ private final List protocols;
+ /** Set when remote peer notifies NPN or ALPN is unsupported. */
+ private boolean unsupported;
+ /** The protocol the client (NPN) or server (ALPN) selected. */
+ private String selected;
+
+ public JettyNegoProvider(List protocols) {
+ this.protocols = protocols;
+ }
+
+ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ if (args == null) {
+ args = Util.EMPTY_STRING_ARRAY;
+ }
+ if (methodName.equals("supports") && boolean.class == returnType) {
+ return true; // NPN or ALPN is supported.
+ } else if (methodName.equals("unsupported") && void.class == returnType) {
+ this.unsupported = true; // Peer doesn't support NPN or ALPN.
+ return null;
+ } else if (methodName.equals("protocols") && args.length == 0) {
+ return protocols; // Server (NPN) or Client (ALPN) advertises these protocols.
+ } else if ((methodName.equals("selectProtocol") || methodName.equals("select"))
+ && String.class == returnType && args.length == 1 && args[0] instanceof List) {
+ List peerProtocols = (List) args[0];
+ // Pick the first known protocol the peer advertises.
+ for (int i = 0, size = peerProtocols.size(); i < size; i++) {
+ if (protocols.contains(peerProtocols.get(i))) {
+ return selected = peerProtocols.get(i);
+ }
+ }
+ return selected = protocols.get(0); // On no intersection, try peer's first protocol.
+ } else if ((methodName.equals("protocolSelected") || methodName.equals("selected"))
+ && args.length == 1) {
+ this.selected = (String) args[0]; // Client (NPN) or Server (ALPN) selected this protocol.
+ return null;
+ } else {
+ return method.invoke(this, args);
+ }
+ }
+ }
+
+ /**
+ * Returns the concatenation of 8-bit, length prefixed protocol names.
+ * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+ */
+ static byte[] concatLengthPrefixed(List protocols) {
+ Buffer result = new Buffer();
+ for (int i = 0, size = protocols.size(); i < size; i++) {
+ Protocol protocol = protocols.get(i);
+ if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for NPN.
+ result.writeByte(protocol.toString().length());
+ result.writeUtf8(protocol.toString());
+ }
+ return result.readByteArray();
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/RouteDatabase.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/RouteDatabase.java
new file mode 100755
index 00000000..c298daac
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/RouteDatabase.java
@@ -0,0 +1,51 @@
+/*
+ * 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.contentstack.okhttp.internal;
+
+import com.contentstack.okhttp.Route;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A blacklist of failed routes to avoid when creating a new connection to a
+ * target address. This is used so that OkHttp can learn from its mistakes: if
+ * there was a failure attempting to connect to a specific IP address, proxy
+ * server or TLS mode, that failure is remembered and alternate routes are
+ * preferred.
+ */
+public final class RouteDatabase {
+ private final Set failedRoutes = new LinkedHashSet();
+
+ /** Records a failure connecting to {@code failedRoute}. */
+ public synchronized void failed(Route failedRoute) {
+ failedRoutes.add(failedRoute);
+ }
+
+ /** Records success connecting to {@code failedRoute}. */
+ public synchronized void connected(Route route) {
+ failedRoutes.remove(route);
+ }
+
+ /** Returns true if {@code route} has failed recently and should be avoided. */
+ public synchronized boolean shouldPostpone(Route route) {
+ return failedRoutes.contains(route);
+ }
+
+ public synchronized int failedRoutesCount() {
+ return failedRoutes.size();
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/Util.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/Util.java
new file mode 100755
index 00000000..72c1237d
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/Util.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal;
+
+import com.contentstack.okhttp.internal.http.RetryableSink;
+import com.contentstack.okhttp.internal.spdy.Header;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.ByteString;
+import com.contentstack.okio.Source;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ThreadFactory;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+/** Junk drawer of utility methods. */
+public final class Util {
+ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ /** A cheap and type-safe constant for the US-ASCII Charset. */
+ public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ /** A cheap and type-safe constant for the UTF-8 Charset. */
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private Util() {
+ }
+
+ public static int getEffectivePort(URI uri) {
+ return getEffectivePort(uri.getScheme(), uri.getPort());
+ }
+
+ public static int getEffectivePort(URL url) {
+ return getEffectivePort(url.getProtocol(), url.getPort());
+ }
+
+ private static int getEffectivePort(String scheme, int specifiedPort) {
+ return specifiedPort != -1 ? specifiedPort : getDefaultPort(scheme);
+ }
+
+ public static int getDefaultPort(String protocol) {
+ if ("http".equals(protocol)) return 80;
+ if ("https".equals(protocol)) return 443;
+ return -1;
+ }
+
+ public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
+ if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ /** Returns true if two possibly-null objects are equal. */
+ public static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ /**
+ * Closes {@code closeable}, ignoring any checked exceptions. Does nothing
+ * if {@code closeable} is null.
+ */
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Closes {@code socket}, ignoring any checked exceptions. Does nothing if
+ * {@code socket} is null.
+ */
+ public static void closeQuietly(Socket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Closes {@code 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.
+ */
+ public static void closeAll(Closeable a, Closeable b) throws IOException {
+ Throwable thrown = null;
+ try {
+ a.close();
+ } catch (Throwable e) {
+ thrown = e;
+ }
+ try {
+ b.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ if (thrown == null) return;
+ if (thrown instanceof IOException) throw (IOException) thrown;
+ if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
+ if (thrown instanceof Error) throw (Error) thrown;
+ throw new AssertionError(thrown);
+ }
+
+ /**
+ * Deletes the contents of {@code dir}. Throws an IOException if any file
+ * could not be deleted, or if {@code dir} is not a readable directory.
+ */
+ public static void deleteContents(File dir) throws IOException {
+ File[] files = dir.listFiles();
+ if (files == null) {
+ throw new IOException("not a readable directory: " + dir);
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ if (!file.delete()) {
+ throw new IOException("failed to delete file: " + file);
+ }
+ }
+ }
+
+ /** Reads until {@code in} is exhausted or the timeout has elapsed. */
+ public static boolean skipAll(Source in, int timeoutMillis) throws IOException {
+ long startNanos = System.nanoTime();
+ Buffer skipBuffer = new Buffer();
+ while (NANOSECONDS.toMillis(System.nanoTime() - startNanos) < timeoutMillis) {
+ long read = in.read(skipBuffer, 2048);
+ if (read == -1) return true; // Successfully exhausted the stream.
+ skipBuffer.clear();
+ }
+ return false; // Ran out of time.
+ }
+
+ /** 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 ByteString.of(md5bytes).hex();
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Returns an immutable copy of {@code list}. */
+ public static List immutableList(List list) {
+ return Collections.unmodifiableList(new ArrayList(list));
+ }
+
+ /** Returns an immutable list containing {@code elements}. */
+ public static List immutableList(T... elements) {
+ return Collections.unmodifiableList(Arrays.asList(elements.clone()));
+ }
+
+ public static ThreadFactory threadFactory(final String name, final boolean daemon) {
+ return new ThreadFactory() {
+ @Override public Thread newThread(Runnable runnable) {
+ Thread result = new Thread(runnable, name);
+ result.setDaemon(daemon);
+ return result;
+ }
+ };
+ }
+
+ public static List headerEntries(String... elements) {
+ List result = new ArrayList(elements.length / 2);
+ for (int i = 0; i < elements.length; i += 2) {
+ result.add(new Header(elements[i], elements[i + 1]));
+ }
+ return result;
+ }
+
+ public static RetryableSink emptySink() {
+ return EMPTY_SINK;
+ }
+
+ private static final RetryableSink EMPTY_SINK = new RetryableSink(0);
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/AuthenticatorAdapter.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/AuthenticatorAdapter.java
new file mode 100755
index 00000000..816f7c65
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/AuthenticatorAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * 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.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Authenticator;
+import com.contentstack.okhttp.Challenge;
+import com.contentstack.okhttp.Credentials;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+
+import java.io.IOException;
+import java.net.Authenticator.RequestorType;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.List;
+
+/** Adapts {@link java.net.Authenticator} to {@link Authenticator}. */
+public final class AuthenticatorAdapter implements Authenticator {
+ /** Uses the global authenticator to get the password. */
+ public static final Authenticator INSTANCE = new AuthenticatorAdapter();
+
+ @Override public Request authenticate(Proxy proxy, Response response) throws IOException {
+ List challenges = response.challenges();
+ Request request = response.request();
+ URL url = request.url();
+ for (int i = 0, size = challenges.size(); i < size; i++) {
+ Challenge challenge = challenges.get(i);
+ if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+ PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+ url.getHost(), getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(),
+ challenge.getRealm(), challenge.getScheme(), url, RequestorType.SERVER);
+ if (auth == null) continue;
+
+ String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+ return request.newBuilder()
+ .header("Authorization", credential)
+ .build();
+ }
+ return null;
+
+ }
+
+ @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
+ List challenges = response.challenges();
+ Request request = response.request();
+ URL url = request.url();
+ for (int i = 0, size = challenges.size(); i < size; i++) {
+ Challenge challenge = challenges.get(i);
+ if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+ InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
+ PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+ proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
+ url.getProtocol(), challenge.getRealm(), challenge.getScheme(), url,
+ RequestorType.PROXY);
+ if (auth == null) continue;
+
+ String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+ return request.newBuilder()
+ .header("Proxy-Authorization", credential)
+ .build();
+ }
+ return null;
+ }
+
+ private InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
+ return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
+ ? ((InetSocketAddress) proxy.address()).getAddress()
+ : InetAddress.getByName(url.getHost());
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheRequest.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheRequest.java
new file mode 100755
index 00000000..e6333f6e
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheRequest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp.internal.http;
+
+import com.contentstack.okio.Sink;
+
+import java.io.IOException;
+
+public interface CacheRequest {
+ Sink body() throws IOException;
+ void abort();
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheStrategy.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheStrategy.java
new file mode 100755
index 00000000..4baddf2a
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/CacheStrategy.java
@@ -0,0 +1,282 @@
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.CacheControl;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+
+import java.net.HttpURLConnection;
+import java.util.Date;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * Given a request and cached response, this figures out whether to use the
+ * network, the cache, or both.
+ *
+ * Selecting a cache strategy may add conditions to the request (like the
+ * "If-Modified-Since" header for conditional GETs) or warnings to the cached
+ * response (if the cached data is potentially stale).
+ */
+public final class CacheStrategy {
+ /** The request to send on the network, or null if this call doesn't use the network. */
+ public final Request networkRequest;
+
+ /** The cached response to return or validate; or null if this call doesn't use a cache. */
+ public final Response cacheResponse;
+
+ private CacheStrategy(Request networkRequest, Response cacheResponse) {
+ this.networkRequest = networkRequest;
+ this.cacheResponse = cacheResponse;
+ }
+
+ /**
+ * Returns true if {@code response} can be stored to later serve another
+ * request.
+ */
+ public static boolean isCacheable(Response response, Request request) {
+ // Always go to network for uncacheable response codes (RFC 2616, 13.4),
+ // This implementation doesn't support caching partial content.
+ int responseCode = response.code();
+ if (responseCode != HttpURLConnection.HTTP_OK
+ && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE
+ && responseCode != HttpURLConnection.HTTP_MULT_CHOICE
+ && responseCode != HttpURLConnection.HTTP_MOVED_PERM
+ && responseCode != HttpURLConnection.HTTP_GONE) {
+ return false;
+ }
+
+ // Responses to authorized requests aren't cacheable unless they include
+ // a 'public', 'must-revalidate' or 's-maxage' directive.
+ CacheControl responseCaching = response.cacheControl();
+ if (request.header("Authorization") != null
+ && !responseCaching.isPublic()
+ && !responseCaching.mustRevalidate()
+ && responseCaching.sMaxAgeSeconds() == -1) {
+ return false;
+ }
+
+ if (responseCaching.noStore()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static class Factory {
+ final long nowMillis;
+ final Request request;
+ final Response cacheResponse;
+
+ /** The server's time when the cached response was served, if known. */
+ private Date servedDate;
+ private String servedDateString;
+
+ /** The last modified date of the cached response, if known. */
+ private Date lastModified;
+ private String lastModifiedString;
+
+ /**
+ * The expiration date of the cached response, if known. If both this field
+ * and the max age are set, the max age is preferred.
+ */
+ private Date expires;
+
+ /**
+ * Extension header set by OkHttp specifying the timestamp when the cached
+ * HTTP request was first initiated.
+ */
+ private long sentRequestMillis;
+
+ /**
+ * Extension header set by OkHttp specifying the timestamp when the cached
+ * HTTP response was first received.
+ */
+ private long receivedResponseMillis;
+
+ /** Etag of the cached response. */
+ private String etag;
+
+ /** Age of the cached response. */
+ private int ageSeconds = -1;
+
+ public Factory(long nowMillis, Request request, Response cacheResponse) {
+ this.nowMillis = nowMillis;
+ this.request = request;
+ this.cacheResponse = cacheResponse;
+
+ if (cacheResponse != null) {
+ for (int i = 0; i < cacheResponse.headers().size(); i++) {
+ String fieldName = cacheResponse.headers().name(i);
+ String value = cacheResponse.headers().value(i);
+ if ("Date".equalsIgnoreCase(fieldName)) {
+ servedDate = HttpDate.parse(value);
+ servedDateString = value;
+ } else if ("Expires".equalsIgnoreCase(fieldName)) {
+ expires = HttpDate.parse(value);
+ } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
+ lastModified = HttpDate.parse(value);
+ lastModifiedString = value;
+ } else if ("ETag".equalsIgnoreCase(fieldName)) {
+ etag = value;
+ } else if ("Age".equalsIgnoreCase(fieldName)) {
+ ageSeconds = HeaderParser.parseSeconds(value);
+ } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+ sentRequestMillis = Long.parseLong(value);
+ } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+ receivedResponseMillis = Long.parseLong(value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a strategy to satisfy {@code request} using the a cached response
+ * {@code response}.
+ */
+ public CacheStrategy get() {
+ CacheStrategy candidate = getCandidate();
+
+ if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
+ // We're forbidden from using the network and the cache is insufficient.
+ return new CacheStrategy(null, null);
+ }
+
+ return candidate;
+ }
+
+ /** Returns a strategy to use assuming the request can use the network. */
+ private CacheStrategy getCandidate() {
+ // No cached response.
+ if (cacheResponse == null) {
+ return new CacheStrategy(request, null);
+ }
+
+ // Drop the cached response if it's missing a required handshake.
+ if (request.isHttps() && cacheResponse.handshake() == null) {
+ return new CacheStrategy(request, null);
+ }
+
+ // If this response shouldn't have been stored, it should never be used
+ // as a response source. This check should be redundant as long as the
+ // persistence store is well-behaved and the rules are constant.
+ if (!isCacheable(cacheResponse, request)) {
+ return new CacheStrategy(request, null);
+ }
+
+ CacheControl requestCaching = request.cacheControl();
+ if (requestCaching.noCache() || hasConditions(request)) {
+ return new CacheStrategy(request, null);
+ }
+
+ long ageMillis = cacheResponseAge();
+ long freshMillis = computeFreshnessLifetime();
+
+ if (requestCaching.maxAgeSeconds() != -1) {
+ freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
+ }
+
+ long minFreshMillis = 0;
+ if (requestCaching.minFreshSeconds() != -1) {
+ minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
+ }
+
+ long maxStaleMillis = 0;
+ CacheControl responseCaching = cacheResponse.cacheControl();
+ if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
+ maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
+ }
+
+ if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+ Response.Builder builder = cacheResponse.newBuilder();
+ if (ageMillis + minFreshMillis >= freshMillis) {
+ builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
+ }
+ long oneDayMillis = 24 * 60 * 60 * 1000L;
+ if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
+ builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+ }
+ return new CacheStrategy(null, builder.build());
+ }
+
+ Request.Builder conditionalRequestBuilder = request.newBuilder();
+
+ if (lastModified != null) {
+ conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
+ } else if (servedDate != null) {
+ conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
+ }
+
+ if (etag != null) {
+ conditionalRequestBuilder.header("If-None-Match", etag);
+ }
+
+ Request conditionalRequest = conditionalRequestBuilder.build();
+ return hasConditions(conditionalRequest)
+ ? new CacheStrategy(conditionalRequest, cacheResponse)
+ : new CacheStrategy(conditionalRequest, null);
+ }
+
+ /**
+ * Returns the number of milliseconds that the response was fresh for,
+ * starting from the served date.
+ */
+ private long computeFreshnessLifetime() {
+ CacheControl responseCaching = cacheResponse.cacheControl();
+ if (responseCaching.maxAgeSeconds() != -1) {
+ return SECONDS.toMillis(responseCaching.maxAgeSeconds());
+ } else if (expires != null) {
+ long servedMillis = servedDate != null
+ ? servedDate.getTime()
+ : receivedResponseMillis;
+ long delta = expires.getTime() - servedMillis;
+ return delta > 0 ? delta : 0;
+ } else if (lastModified != null
+ && cacheResponse.request().url().getQuery() == null) {
+ // As recommended by the HTTP RFC and implemented in Firefox, the
+ // max age of a document should be defaulted to 10% of the
+ // document's age at the time it was served. Default expiration
+ // dates aren't used for URIs containing a query.
+ long servedMillis = servedDate != null
+ ? servedDate.getTime()
+ : sentRequestMillis;
+ long delta = servedMillis - lastModified.getTime();
+ return delta > 0 ? (delta / 10) : 0;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the current age of the response, in milliseconds. The calculation
+ * is specified by RFC 2616, 13.2.3 Age Calculations.
+ */
+ private long cacheResponseAge() {
+ long apparentReceivedAge = servedDate != null
+ ? Math.max(0, receivedResponseMillis - servedDate.getTime())
+ : 0;
+ long receivedAge = ageSeconds != -1
+ ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
+ : apparentReceivedAge;
+ long responseDuration = receivedResponseMillis - sentRequestMillis;
+ long residentDuration = nowMillis - receivedResponseMillis;
+ return receivedAge + responseDuration + residentDuration;
+ }
+
+ /**
+ * Returns true if computeFreshnessLifetime used a heuristic. If we used a
+ * heuristic to serve a cached response older than 24 hours, we are required
+ * to attach a warning.
+ */
+ private boolean isFreshnessLifetimeHeuristic() {
+ return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
+ }
+
+ /**
+ * Returns true if the request contains conditions that save the server from
+ * sending a response that the client has locally. When a request is enqueued
+ * with its own conditions, the built-in response cache won't be used.
+ */
+ private static boolean hasConditions(Request request) {
+ return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HeaderParser.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HeaderParser.java
new file mode 100755
index 00000000..e12ee2e1
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HeaderParser.java
@@ -0,0 +1,69 @@
+/*
+ * 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.contentstack.okhttp.internal.http;
+
+public final class HeaderParser {
+ /**
+ * Returns the next index in {@code input} at or after {@code pos} that
+ * contains a character from {@code characters}. Returns the input length if
+ * none of the requested characters can be found.
+ */
+ public static int skipUntil(String input, int pos, String characters) {
+ for (; pos < input.length(); pos++) {
+ if (characters.indexOf(input.charAt(pos)) != -1) {
+ break;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Returns the next non-whitespace character in {@code input} that is white
+ * space. Result is undefined if input contains newline characters.
+ */
+ public static int skipWhitespace(String input, int pos) {
+ for (; pos < input.length(); pos++) {
+ char c = input.charAt(pos);
+ if (c != ' ' && c != '\t') {
+ break;
+ }
+ }
+ return pos;
+ }
+
+ /**
+ * Returns {@code value} as a positive integer, or 0 if it is negative, or
+ * -1 if it cannot be parsed.
+ */
+ public static int parseSeconds(String value) {
+ try {
+ long seconds = Long.parseLong(value);
+ if (seconds > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ } else if (seconds < 0) {
+ return 0;
+ } else {
+ return (int) seconds;
+ }
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ private HeaderParser() {
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpConnection.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpConnection.java
new file mode 100755
index 00000000..8ebc25d3
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpConnection.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Connection;
+import com.contentstack.okhttp.ConnectionPool;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.internal.Internal;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+import com.contentstack.okio.Timeout;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A socket connection that can be used to send HTTP/1.1 messages. This class
+ * strictly enforces the following lifecycle:
+ *
+ * - {@link #writeRequest Send request headers}.
+ *
- Open a sink to write the request body. Either {@link
+ * #newFixedLengthSink fixed-length} or {@link #newChunkedSink chunked}.
+ *
- Write to and then close that stream.
+ *
- {@link #readResponse Read response headers}.
+ *
- Open the HTTP response body input stream. Either {@link
+ * #newFixedLengthSource fixed-length}, {@link #newChunkedSource chunked}
+ * or {@link #newUnknownLengthSource unknown length}.
+ *
- Read from and close that stream.
+ *
+ * Exchanges that do not have a request body may skip creating and closing
+ * the request body. Exchanges that do not have a response body must call {@link
+ * #emptyResponseBody}.
+ */
+public final class HttpConnection {
+ private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers.
+ private static final int STATE_OPEN_REQUEST_BODY = 1;
+ private static final int STATE_WRITING_REQUEST_BODY = 2;
+ private static final int STATE_READ_RESPONSE_HEADERS = 3;
+ private static final int STATE_OPEN_RESPONSE_BODY = 4;
+ private static final int STATE_READING_RESPONSE_BODY = 5;
+ private static final int STATE_CLOSED = 6;
+
+ private static final int ON_IDLE_HOLD = 0;
+ private static final int ON_IDLE_POOL = 1;
+ private static final int ON_IDLE_CLOSE = 2;
+
+ private final ConnectionPool pool;
+ private final Connection connection;
+ private final Socket socket;
+ private final BufferedSource source;
+ private final BufferedSink sink;
+
+ private int state = STATE_IDLE;
+ private int onIdle = ON_IDLE_HOLD;
+
+ public HttpConnection(ConnectionPool pool, Connection connection, Socket socket)
+ throws IOException {
+ this.pool = pool;
+ this.connection = connection;
+ this.socket = socket;
+ this.source = Okio.buffer(Okio.source(socket));
+ this.sink = Okio.buffer(Okio.sink(socket));
+ }
+
+ public void setTimeouts(int readTimeoutMillis, int writeTimeoutMillis) {
+ if (readTimeoutMillis != 0) {
+ source.timeout().timeout(readTimeoutMillis, TimeUnit.MILLISECONDS);
+ }
+ if (writeTimeoutMillis != 0) {
+ sink.timeout().timeout(writeTimeoutMillis, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Configure this connection to put itself back into the connection pool when
+ * the HTTP response body is exhausted.
+ */
+ public void poolOnIdle() {
+ onIdle = ON_IDLE_POOL;
+
+ // If we're already idle, go to the pool immediately.
+ if (state == STATE_IDLE) {
+ onIdle = ON_IDLE_HOLD; // Set the on idle policy back to the default.
+ Internal.instance.recycle(pool, connection);
+ }
+ }
+
+ /**
+ * Configure this connection to close itself when the HTTP response body is
+ * exhausted.
+ */
+ public void closeOnIdle() throws IOException {
+ onIdle = ON_IDLE_CLOSE;
+
+ // If we're already idle, close immediately.
+ if (state == STATE_IDLE) {
+ state = STATE_CLOSED;
+ connection.getSocket().close();
+ }
+ }
+
+ /** Returns true if this connection is closed. */
+ public boolean isClosed() {
+ return state == STATE_CLOSED;
+ }
+
+ public void closeIfOwnedBy(Object owner) throws IOException {
+ Internal.instance.closeIfOwnedBy(connection, owner);
+ }
+
+ public void flush() throws IOException {
+ sink.flush();
+ }
+
+ /** Returns the number of buffered bytes immediately readable. */
+ public long bufferSize() {
+ return source.buffer().size();
+ }
+
+ /** Test for a stale socket. */
+ public boolean isReadable() {
+ try {
+ int readTimeout = socket.getSoTimeout();
+ try {
+ socket.setSoTimeout(1);
+ if (source.exhausted()) {
+ return false; // Stream is exhausted; socket is closed.
+ }
+ return true;
+ } finally {
+ socket.setSoTimeout(readTimeout);
+ }
+ } catch (SocketTimeoutException ignored) {
+ return true; // Read timed out; socket is good.
+ } catch (IOException e) {
+ return false; // Couldn't read; socket is closed.
+ }
+ }
+
+ /** Returns bytes of a request header for sending on an HTTP transport. */
+ public void writeRequest(Headers headers, String requestLine) throws IOException {
+ if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
+ sink.writeUtf8(requestLine).writeUtf8("\r\n");
+ for (int i = 0; i < headers.size(); i ++) {
+ sink.writeUtf8(headers.name(i))
+ .writeUtf8(": ")
+ .writeUtf8(headers.value(i))
+ .writeUtf8("\r\n");
+ }
+ sink.writeUtf8("\r\n");
+ state = STATE_OPEN_REQUEST_BODY;
+ }
+
+ /** Parses bytes of a response header from an HTTP transport. */
+ public Response.Builder readResponse() throws IOException {
+ if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
+ throw new IllegalStateException("state: " + state);
+ }
+
+ while (true) {
+ StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+
+ Response.Builder responseBuilder = new Response.Builder()
+ .protocol(statusLine.protocol)
+ .code(statusLine.code)
+ .message(statusLine.message);
+
+ Headers.Builder headersBuilder = new Headers.Builder();
+ readHeaders(headersBuilder);
+ headersBuilder.add(OkHeaders.SELECTED_PROTOCOL, statusLine.protocol.toString());
+ responseBuilder.headers(headersBuilder.build());
+
+ if (statusLine.code != StatusLine.HTTP_CONTINUE) {
+ state = STATE_OPEN_RESPONSE_BODY;
+ return responseBuilder;
+ }
+ }
+ }
+
+ /** Reads headers or trailers into {@code builder}. */
+ public void readHeaders(Headers.Builder builder) throws IOException {
+ // parse the result headers until the first blank line
+ for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
+ Internal.instance.addLine(builder, line);
+ }
+ }
+
+ /**
+ * Discards the response body so that the connection can be reused and the
+ * cache entry can be completed. 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.
+ */
+ public boolean discard(Source in, int timeoutMillis) {
+ try {
+ int socketTimeout = socket.getSoTimeout();
+ socket.setSoTimeout(timeoutMillis);
+ try {
+ return Util.skipAll(in, timeoutMillis);
+ } finally {
+ socket.setSoTimeout(socketTimeout);
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ public Sink newChunkedSink() {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_WRITING_REQUEST_BODY;
+ return new ChunkedSink();
+ }
+
+ public Sink newFixedLengthSink(long contentLength) {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_WRITING_REQUEST_BODY;
+ return new FixedLengthSink(contentLength);
+ }
+
+ public void writeRequestBody(RetryableSink requestBody) throws IOException {
+ if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READ_RESPONSE_HEADERS;
+ requestBody.writeToSocket(sink);
+ }
+
+ public Source newFixedLengthSource(CacheRequest cacheRequest, long length)
+ throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READING_RESPONSE_BODY;
+ return new FixedLengthSource(cacheRequest, length);
+ }
+
+ /**
+ * Call this to advance past a response body for HTTP responses that do not
+ * have a response body.
+ */
+ public void emptyResponseBody() throws IOException {
+ newFixedLengthSource(null, 0L); // Transition to STATE_IDLE.
+ }
+
+ public Source newChunkedSource(CacheRequest cacheRequest, HttpEngine httpEngine)
+ throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READING_RESPONSE_BODY;
+ return new ChunkedSource(cacheRequest, httpEngine);
+ }
+
+ public Source newUnknownLengthSource(CacheRequest cacheRequest) throws IOException {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ state = STATE_READING_RESPONSE_BODY;
+ return new UnknownLengthSource(cacheRequest);
+ }
+
+ /** An HTTP body with a fixed length known in advance. */
+ private final class FixedLengthSink implements Sink {
+ private boolean closed;
+ private long bytesRemaining;
+
+ private FixedLengthSink(long bytesRemaining) {
+ this.bytesRemaining = bytesRemaining;
+ }
+
+ @Override public Timeout timeout() {
+ return sink.timeout();
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ Util.checkOffsetAndCount(source.size(), 0, byteCount);
+ if (byteCount > bytesRemaining) {
+ throw new ProtocolException("expected " + bytesRemaining
+ + " bytes but received " + byteCount);
+ }
+ sink.write(source, byteCount);
+ bytesRemaining -= byteCount;
+ }
+
+ @Override public void flush() throws IOException {
+ if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ if (bytesRemaining > 0) throw new ProtocolException("unexpected end of stream");
+ state = STATE_READ_RESPONSE_HEADERS;
+ }
+ }
+
+ private static final String CRLF = "\r\n";
+ private static final byte[] HEX_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+ private static final byte[] FINAL_CHUNK = new byte[] { '0', '\r', '\n', '\r', '\n' };
+
+ /**
+ * An HTTP body with alternating chunk sizes and chunk bodies. It is the
+ * caller's responsibility to buffer chunks; typically by using a buffered
+ * sink with this sink.
+ */
+ private final class ChunkedSink implements Sink {
+ /** Scratch space for up to 16 hex digits, and then a constant CRLF. */
+ private final byte[] hex = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\r', '\n' };
+
+ private boolean closed;
+
+ @Override public Timeout timeout() {
+ return sink.timeout();
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ if (byteCount == 0) return;
+
+ writeHex(byteCount);
+ sink.write(source, byteCount);
+ sink.writeUtf8(CRLF);
+ }
+
+ @Override public synchronized void flush() throws IOException {
+ if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush();
+ }
+
+ @Override public synchronized void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ sink.write(FINAL_CHUNK);
+ state = STATE_READ_RESPONSE_HEADERS;
+ }
+
+ /**
+ * Equivalent to, but cheaper than writing Long.toHexString().getBytes()
+ * followed by CRLF.
+ */
+ private void writeHex(long i) throws IOException {
+ int cursor = 16;
+ do {
+ hex[--cursor] = HEX_DIGITS[((int) (i & 0xf))];
+ } while ((i >>>= 4) != 0);
+ sink.write(hex, cursor, hex.length - cursor);
+ }
+ }
+
+ private class AbstractSource {
+ private final CacheRequest cacheRequest;
+ protected final Sink cacheBody;
+ protected boolean closed;
+
+ AbstractSource(CacheRequest cacheRequest) throws IOException {
+ // Some apps return a null body; for compatibility we treat that like a null cache request.
+ Sink cacheBody = cacheRequest != null ? cacheRequest.body() : null;
+ if (cacheBody == null) {
+ cacheRequest = null;
+ }
+
+ this.cacheBody = cacheBody;
+ this.cacheRequest = cacheRequest;
+ }
+
+ /** Copy the last {@code byteCount} bytes of {@code source} to the cache body. */
+ protected final void cacheWrite(Buffer source, long byteCount) throws IOException {
+ if (cacheBody != null) {
+ cacheBody.write(source.clone(), byteCount);
+ }
+ }
+
+ /**
+ * 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 recyclable) throws IOException {
+ if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+
+ if (cacheRequest != null) {
+ cacheBody.close();
+ }
+
+ state = STATE_IDLE;
+ if (recyclable && onIdle == ON_IDLE_POOL) {
+ onIdle = ON_IDLE_HOLD; // Set the on idle policy back to the default.
+ Internal.instance.recycle(pool, connection);
+ } else if (onIdle == ON_IDLE_CLOSE) {
+ state = STATE_CLOSED;
+ connection.getSocket().close();
+ }
+ }
+
+ /**
+ * Calls abort on the cache entry and disconnects the socket. This
+ * should be invoked when the connection is closed unexpectedly to
+ * invalidate the cache entry and to prevent the HTTP connection from
+ * being reused. HTTP messages are sent in serial so whenever a message
+ * cannot be read to completion, subsequent messages cannot be read
+ * either and the connection must be discarded.
+ *
+ *
An earlier implementation skipped the remaining bytes, but this
+ * requires that the entire transfer be completed. If the intention was
+ * to cancel the transfer, closing the connection is the only solution.
+ */
+ protected final void unexpectedEndOfInput() {
+ if (cacheRequest != null) {
+ cacheRequest.abort();
+ }
+ Util.closeQuietly(connection.getSocket());
+ state = STATE_CLOSED;
+ }
+ }
+
+ /** An HTTP body with a fixed length specified in advance. */
+ private class FixedLengthSource extends AbstractSource implements Source {
+ private long bytesRemaining;
+
+ public FixedLengthSource(CacheRequest cacheRequest, long length) throws IOException {
+ super(cacheRequest);
+ bytesRemaining = length;
+ if (bytesRemaining == 0) {
+ endOfInput(true);
+ }
+ }
+
+ @Override public long read(Buffer sink, long byteCount)
+ throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (bytesRemaining == 0) return -1;
+
+ long read = source.read(sink, Math.min(bytesRemaining, byteCount));
+ if (read == -1) {
+ unexpectedEndOfInput(); // the server didn't supply the promised content length
+ throw new ProtocolException("unexpected end of stream");
+ }
+
+ bytesRemaining -= read;
+ cacheWrite(sink, read);
+ if (bytesRemaining == 0) {
+ endOfInput(true);
+ }
+ return read;
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ if (bytesRemaining != 0 && !discard(this, Transport.DISCARD_STREAM_TIMEOUT_MILLIS)) {
+ unexpectedEndOfInput();
+ }
+
+ closed = true;
+ }
+ }
+
+ /** An HTTP body with alternating chunk sizes and chunk bodies. */
+ private class ChunkedSource extends AbstractSource implements Source {
+ private static final int NO_CHUNK_YET = -1;
+ private int bytesRemainingInChunk = NO_CHUNK_YET;
+ private boolean hasMoreChunks = true;
+ private final HttpEngine httpEngine;
+
+ ChunkedSource(CacheRequest cacheRequest, HttpEngine httpEngine) throws IOException {
+ super(cacheRequest);
+ this.httpEngine = httpEngine;
+ }
+
+ @Override public long read(
+ Buffer sink, long byteCount) throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (!hasMoreChunks) return -1;
+
+ if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
+ readChunkSize();
+ if (!hasMoreChunks) return -1;
+ }
+
+ long read = source.read(sink, Math.min(byteCount, bytesRemainingInChunk));
+ if (read == -1) {
+ unexpectedEndOfInput(); // the server didn't supply the promised chunk length
+ throw new IOException("unexpected end of stream");
+ }
+ bytesRemainingInChunk -= read;
+ cacheWrite(sink, read);
+ return read;
+ }
+
+ private void readChunkSize() throws IOException {
+ // read the suffix of the previous chunk
+ if (bytesRemainingInChunk != NO_CHUNK_YET) {
+ source.readUtf8LineStrict();
+ }
+ String chunkSizeString = source.readUtf8LineStrict();
+ int index = chunkSizeString.indexOf(";");
+ if (index != -1) {
+ chunkSizeString = chunkSizeString.substring(0, index);
+ }
+ try {
+ bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16);
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("Expected a hex chunk size but was " + chunkSizeString);
+ }
+ if (bytesRemainingInChunk == 0) {
+ hasMoreChunks = false;
+ Headers.Builder trailersBuilder = new Headers.Builder();
+ readHeaders(trailersBuilder);
+ httpEngine.receiveHeaders(trailersBuilder.build());
+ endOfInput(true);
+ }
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ if (hasMoreChunks && !discard(this, Transport.DISCARD_STREAM_TIMEOUT_MILLIS)) {
+ unexpectedEndOfInput();
+ }
+ closed = true;
+ }
+ }
+
+ /** An HTTP message body terminated by the end of the underlying stream. */
+ class UnknownLengthSource extends AbstractSource implements Source {
+ private boolean inputExhausted;
+
+ UnknownLengthSource(CacheRequest cacheRequest) throws IOException {
+ super(cacheRequest);
+ }
+
+ @Override public long read(Buffer sink, long byteCount)
+ throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (inputExhausted) return -1;
+
+ long read = source.read(sink, byteCount);
+ if (read == -1) {
+ inputExhausted = true;
+ endOfInput(false);
+ return -1;
+ }
+ cacheWrite(sink, read);
+ return read;
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ if (!inputExhausted) {
+ unexpectedEndOfInput();
+ }
+ closed = true;
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpDate.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpDate.java
new file mode 100755
index 00000000..3a3e62c3
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpDate.java
@@ -0,0 +1,119 @@
+/*
+ * 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.contentstack.okhttp.internal.http;
+
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ */
+public final class HttpDate {
+
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+ /**
+ * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
+ * cookies are on the fast path.
+ */
+ private static final ThreadLocal STANDARD_DATE_FORMAT =
+ new ThreadLocal() {
+ @Override protected DateFormat initialValue() {
+ // RFC 2616 specified: RFC 822, updated by RFC 1123 format with fixed GMT.
+ DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+ rfc1123.setLenient(false);
+ rfc1123.setTimeZone(GMT);
+ return rfc1123;
+ }
+ };
+
+ /** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */
+ private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] {
+ // HTTP formats required by RFC2616 but with any timezone.
+ "EEE, dd MMM yyyy HH:mm:ss zzz", // RFC 822, updated by RFC 1123 with any TZ
+ "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 850, obsoleted by RFC 1036 with any TZ.
+ "EEE MMM d HH:mm:ss yyyy", // ANSI C's asctime() format
+ // Alternative formats.
+ "EEE, dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MMM-yyyy HH-mm-ss z",
+ "EEE, dd MMM yy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH:mm:ss z",
+ "EEE dd MMM yyyy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH-mm-ss z",
+ "EEE dd-MMM-yy HH:mm:ss z",
+ "EEE dd MMM yy HH:mm:ss z",
+ "EEE,dd-MMM-yy HH:mm:ss z",
+ "EEE,dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MM-yyyy HH:mm:ss z",
+
+ /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
+ "EEE MMM d yyyy HH:mm:ss z",
+ };
+
+ private static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS =
+ new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length];
+
+ /** Returns the date for {@code value}. Returns null if the value couldn't be parsed. */
+ public static Date parse(String value) {
+ if (value.length() == 0) {
+ return null;
+ }
+
+ ParsePosition position = new ParsePosition(0);
+ Date result = STANDARD_DATE_FORMAT.get().parse(value, position);
+ if (position.getIndex() == value.length()) {
+ // STANDARD_DATE_FORMAT must match exactly; all text must be consumed, e.g. no ignored
+ // non-standard trailing "+01:00". Those cases are covered below.
+ return result;
+ }
+ synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) {
+ for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) {
+ DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i];
+ if (format == null) {
+ format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US);
+ // Set the timezone to use when interpreting formats that don't have a timezone. GMT is
+ // specified by RFC 2616.
+ format.setTimeZone(GMT);
+ BROWSER_COMPATIBLE_DATE_FORMATS[i] = format;
+ }
+ position.setIndex(0);
+ result = format.parse(value, position);
+ if (position.getIndex() != 0) {
+ // Something was parsed. It's possible the entire string was not consumed but we ignore
+ // that. If any of the BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS ended in "'GMT'" we'd have
+ // to also check that position.getIndex() == value.length() otherwise parsing might have
+ // terminated early, ignoring things like "+01:00". Leaving this as != 0 means that any
+ // trailing junk is ignored.
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Returns the string for {@code value}. */
+ public static String format(Date value) {
+ return STANDARD_DATE_FORMAT.get().format(value);
+ }
+
+ private HttpDate() {
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpEngine.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpEngine.java
new file mode 100755
index 00000000..605817dd
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpEngine.java
@@ -0,0 +1,831 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Connection;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.MediaType;
+import com.contentstack.okhttp.OkHttpClient;
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.ResponseBody;
+import com.contentstack.okhttp.Route;
+import com.contentstack.okhttp.internal.Internal;
+import com.contentstack.okhttp.internal.InternalCache;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.GzipSource;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.CookieHandler;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import static com.contentstack.okhttp.internal.Util.closeQuietly;
+import static com.contentstack.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
+import static com.contentstack.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+/**
+ * Handles a single HTTP request/response pair. Each HTTP engine follows this
+ * lifecycle:
+ *
+ * - It is created.
+ *
- The HTTP request message is sent with sendRequest(). Once the request
+ * is sent it is an error to modify the request headers. After
+ * sendRequest() has been called the request body can be written to if
+ * it exists.
+ *
- The HTTP response message is read with readResponse(). After the
+ * response has been read the response headers and body can be read.
+ * All responses have a response body input stream, though in some
+ * instances this stream is empty.
+ *
+ *
+ * The request and response may be served by the HTTP response cache, by the
+ * network, or by both in the event of a conditional GET.
+ */
+public final class HttpEngine {
+ /**
+ * How many redirects should we follow? Chrome follows 21; Firefox, curl,
+ * and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
+ */
+ public static final int MAX_REDIRECTS = 20;
+
+ private static final ResponseBody EMPTY_BODY = new ResponseBody() {
+ @Override public MediaType contentType() {
+ return null;
+ }
+ @Override public long contentLength() {
+ return 0;
+ }
+ @Override public BufferedSource source() {
+ return new Buffer();
+ }
+ };
+
+ final OkHttpClient client;
+
+ private Connection connection;
+ private RouteSelector routeSelector;
+ private Route route;
+ private final Response priorResponse;
+
+ private Transport transport;
+
+ /** The time when the request headers were written, or -1 if they haven't been written yet. */
+ long sentRequestMillis = -1;
+
+ /**
+ * True if this client added an "Accept-Encoding: gzip" header field and is
+ * therefore responsible for also decompressing the transfer stream.
+ */
+ private boolean transparentGzip;
+
+ /**
+ * True if the request body must be completely buffered before transmission;
+ * false if it can be streamed. Buffering has two advantages: we don't need
+ * the content-length in advance and we can retransmit if necessary. The
+ * upside of streaming is that we can save memory.
+ */
+ public final boolean bufferRequestBody;
+
+ /**
+ * The original application-provided request. Never modified by OkHttp. When
+ * follow-up requests are necessary, they are derived from this request.
+ */
+ private final Request userRequest;
+
+ /**
+ * The request to send on the network, or null for no network request. This is
+ * derived from the user request, and customized to support OkHttp features
+ * like compression and caching.
+ */
+ private Request networkRequest;
+
+ /**
+ * The cached response, or null if the cache doesn't exist or cannot be used
+ * for this request. Conditional caching means this may be non-null even when
+ * the network request is non-null. Never modified by OkHttp.
+ */
+ private Response cacheResponse;
+
+ /**
+ * The response read from the network. Null if the network response hasn't
+ * been read yet, or if the network is not used. Never modified by OkHttp.
+ */
+ private Response networkResponse;
+
+ /**
+ * The user-visible response. This is derived from either the network
+ * response, cache response, or both. It is customized to support OkHttp
+ * features like compression and caching.
+ */
+ private Response userResponse;
+
+ private Sink requestBodyOut;
+ private BufferedSink bufferedRequestBody;
+
+ /** Null until a response is received from the network or the cache. */
+ private Source responseTransferSource;
+ private BufferedSource responseBody;
+ private InputStream responseBodyBytes;
+
+ /** The cache request currently being populated from a network response. */
+ private CacheRequest storeRequest;
+ private CacheStrategy cacheStrategy;
+
+ /**
+ * @param request the HTTP request without a body. The body must be
+ * written via the engine's request body stream.
+ * @param connection the connection used for an intermediate response
+ * immediately prior to this request/response pair, such as a same-host
+ * redirect. This engine assumes ownership of the connection and must
+ * release it when it is unneeded.
+ * @param routeSelector the route selector used for a failed attempt
+ * immediately preceding this attempt, or null if this request doesn't
+ * recover from a failure.
+ */
+ public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
+ Connection connection, RouteSelector routeSelector, RetryableSink requestBodyOut,
+ Response priorResponse) {
+ this.client = client;
+ this.userRequest = request;
+ this.bufferRequestBody = bufferRequestBody;
+ this.connection = connection;
+ this.routeSelector = routeSelector;
+ this.requestBodyOut = requestBodyOut;
+ this.priorResponse = priorResponse;
+
+ if (connection != null) {
+ Internal.instance.setOwner(connection, this);
+ this.route = connection.getRoute();
+ } else {
+ this.route = null;
+ }
+ }
+
+ /**
+ * Figures out what the response source will be, and opens a socket to that
+ * source if necessary. Prepares the request headers and gets ready to start
+ * writing the request body if it exists.
+ */
+ public void sendRequest() throws IOException {
+ if (cacheStrategy != null) return; // Already sent.
+ if (transport != null) throw new IllegalStateException();
+
+ Request request = networkRequest(userRequest);
+
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ Response cacheCandidate = responseCache != null
+ ? responseCache.get(request)
+ : null;
+
+ long now = System.currentTimeMillis();
+ cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
+ networkRequest = cacheStrategy.networkRequest;
+ cacheResponse = cacheStrategy.cacheResponse;
+
+ if (responseCache != null) {
+ responseCache.trackResponse(cacheStrategy);
+ }
+
+ if (cacheCandidate != null && cacheResponse == null) {
+ Util.closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
+ }
+
+ if (networkRequest != null) {
+ // Open a connection unless we inherited one from a redirect.
+ if (connection == null) {
+ connect(networkRequest);
+ }
+
+ transport = Internal.instance.newTransport(connection, this);
+
+ // Create a request body if we don't have one already. We'll already have
+ // one if we're retrying a failed POST.
+ if (hasRequestBody() && requestBodyOut == null) {
+ requestBodyOut = transport.createRequestBody(request);
+ }
+
+ } else {
+ // We aren't using the network. Recycle a connection we may have inherited from a redirect.
+ if (connection != null) {
+ Internal.instance.recycle(client.getConnectionPool(), connection);
+ connection = null;
+ }
+
+ if (cacheResponse != null) {
+ // We have a valid cached response. Promote it to the user response immediately.
+ this.userResponse = cacheResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .cacheResponse(stripBody(cacheResponse))
+ .build();
+ } else {
+ // We're forbidden from using the network, and the cache is insufficient.
+ this.userResponse = new Response.Builder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .protocol(Protocol.HTTP_1_1)
+ .code(504)
+ .message("Unsatisfiable Request (only-if-cached)")
+ .body(EMPTY_BODY)
+ .build();
+ }
+
+ if (userResponse.body() != null) {
+ initContentStream(userResponse.body().source());
+ }
+ }
+ }
+
+ private static Response stripBody(Response response) {
+ return response != null && response.body() != null
+ ? response.newBuilder().body(null).build()
+ : response;
+ }
+
+ /** Connect to the origin server either directly or via a proxy. */
+ private void connect(Request request) throws IOException {
+ if (connection != null) throw new IllegalStateException();
+
+ if (routeSelector == null) {
+ routeSelector = RouteSelector.get(request, client);
+ }
+
+ connection = routeSelector.next(this);
+ route = connection.getRoute();
+ }
+
+ /**
+ * Called immediately before the transport transmits HTTP request headers.
+ * This is used to observe the sent time should the request be cached.
+ */
+ public void writingRequestHeaders() {
+ if (sentRequestMillis != -1) throw new IllegalStateException();
+ sentRequestMillis = System.currentTimeMillis();
+ }
+
+ boolean hasRequestBody() {
+ return HttpMethod.hasRequestBody(userRequest.method())
+ && !Util.emptySink().equals(requestBodyOut);
+ }
+
+ /** Returns the request body or null if this request doesn't have a body. */
+ public Sink getRequestBody() {
+ if (cacheStrategy == null) throw new IllegalStateException();
+ return requestBodyOut;
+ }
+
+ public BufferedSink getBufferedRequestBody() {
+ BufferedSink result = bufferedRequestBody;
+ if (result != null) return result;
+ Sink requestBody = getRequestBody();
+ return requestBody != null
+ ? (bufferedRequestBody = Okio.buffer(requestBody))
+ : null;
+ }
+
+ public boolean hasResponse() {
+ return userResponse != null;
+ }
+
+ public Request getRequest() {
+ return userRequest;
+ }
+
+ public Response getResponse() {
+ if (userResponse == null) throw new IllegalStateException();
+ return userResponse;
+ }
+
+ public BufferedSource getResponseBody() {
+ if (userResponse == null) throw new IllegalStateException();
+ return responseBody;
+ }
+
+ public InputStream getResponseBodyBytes() {
+ InputStream result = responseBodyBytes;
+ return result != null
+ ? result
+ : (responseBodyBytes = Okio.buffer(getResponseBody()).inputStream());
+ }
+
+ public Connection getConnection() {
+ return connection;
+ }
+
+ /**
+ * Report and attempt to recover from {@code e}. Returns a new HTTP engine
+ * that should be used for the retry if {@code e} is recoverable, or null if
+ * the failure is permanent. Requests with a body can only be recovered if the
+ * body is buffered.
+ */
+ public HttpEngine recover(IOException e, Sink requestBodyOut) {
+ if (routeSelector != null && connection != null) {
+ routeSelector.connectFailed(connection, e);
+ }
+
+ boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
+ if (routeSelector == null && connection == null // No connection.
+ || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
+ || !isRecoverable(e)
+ || !canRetryRequestBody) {
+ return null;
+ }
+
+ Connection connection = close();
+
+ // For failure recovery, use the same route selector with a new connection.
+ return new HttpEngine(client, userRequest, bufferRequestBody, connection, routeSelector,
+ (RetryableSink) requestBodyOut, priorResponse);
+ }
+
+ public HttpEngine recover(IOException e) {
+ return recover(e, requestBodyOut);
+ }
+
+ private boolean isRecoverable(IOException e) {
+ // If the problem was a CertificateException from the X509TrustManager,
+ // do not retry, we didn't have an abrupt server-initiated exception.
+ boolean sslFailure =
+ e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException;
+ boolean protocolFailure = e instanceof ProtocolException;
+ return !sslFailure && !protocolFailure;
+ }
+
+ /**
+ * Returns the route used to retrieve the response. Null if we haven't
+ * connected yet, or if no connection was necessary.
+ */
+ public Route getRoute() {
+ return route;
+ }
+
+ private void maybeCache() throws IOException {
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ if (responseCache == null) return;
+
+ // Should we cache this response for this request?
+ if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
+ if (HttpMethod.invalidatesCache(networkRequest.method())) {
+ try {
+ responseCache.remove(networkRequest);
+ } catch (IOException ignored) {
+ // The cache cannot be written.
+ }
+ }
+ return;
+ }
+
+ // Offer this request to the cache.
+ storeRequest = responseCache.put(stripBody(userResponse));
+ }
+
+ /**
+ * Configure the socket connection to be either pooled or closed when it is
+ * either exhausted or closed. If it is unneeded when this is called, it will
+ * be released immediately.
+ */
+ public void releaseConnection() throws IOException {
+ if (transport != null && connection != null) {
+ transport.releaseConnectionOnIdle();
+ }
+ connection = null;
+ }
+
+ /**
+ * Immediately closes the socket connection if it's currently held by this
+ * engine. Use this to interrupt an in-flight request from any thread. It's
+ * the caller's responsibility to close the request body and response body
+ * streams; otherwise resources may be leaked.
+ */
+ public void disconnect() {
+ if (transport != null) {
+ try {
+ transport.disconnect(this);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Release any resources held by this engine. If a connection is still held by
+ * this engine, it is returned.
+ */
+ public Connection close() {
+ if (bufferedRequestBody != null) {
+ // This also closes the wrapped requestBodyOut.
+ closeQuietly(bufferedRequestBody);
+ } else if (requestBodyOut != null) {
+ Util.closeQuietly(requestBodyOut);
+ }
+
+ // If this engine never achieved a response body, its connection cannot be reused.
+ if (responseBody == null) {
+ if (connection != null) closeQuietly(connection.getSocket());
+ connection = null;
+ return null;
+ }
+
+ // Close the response body. This will recycle the connection if it is eligible.
+ closeQuietly(responseBody);
+
+ // Clear the buffer held by the response body input stream adapter.
+ Util.closeQuietly(responseBodyBytes);
+
+ // Close the connection if it cannot be reused.
+ if (transport != null && connection != null && !transport.canReuseConnection()) {
+ closeQuietly(connection.getSocket());
+ connection = null;
+ return null;
+ }
+
+ // Prevent this engine from disconnecting a connection it no longer owns.
+ if (connection != null && !Internal.instance.clearOwner(connection)) {
+ connection = null;
+ }
+
+ Connection result = connection;
+ connection = null;
+ return result;
+ }
+
+ /**
+ * Initialize the response content stream from the response transfer source.
+ * These two sources are the same unless we're doing transparent gzip, in
+ * which case the content source is decompressed.
+ *
+ *
Whenever we do transparent gzip we also strip the corresponding headers.
+ * We strip the Content-Encoding header to prevent the application from
+ * attempting to double decompress. We strip the Content-Length header because
+ * it is the length of the compressed content, but the application is only
+ * interested in the length of the uncompressed content.
+ *
+ *
This method should only be used for non-empty response bodies. Response
+ * codes like "304 Not Modified" can include "Content-Encoding: gzip" without
+ * a response body and we will crash if we attempt to decompress the zero-byte
+ * source.
+ */
+ private void initContentStream(Source transferSource) throws IOException {
+ responseTransferSource = transferSource;
+ if (transparentGzip && "gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) {
+ userResponse = userResponse.newBuilder()
+ .removeHeader("Content-Encoding")
+ .removeHeader("Content-Length")
+ .build();
+ responseBody = Okio.buffer(new GzipSource(transferSource));
+ } else {
+ responseBody = Okio.buffer(transferSource);
+ }
+ }
+
+ /**
+ * Returns true if the response must have a (possibly 0-length) body.
+ * See RFC 2616 section 4.3.
+ */
+ public boolean hasResponseBody() {
+ // HEAD requests never yield a body regardless of the response headers.
+ if (userRequest.method().equals("HEAD")) {
+ return false;
+ }
+
+ int responseCode = userResponse.code();
+ if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
+ && responseCode != HTTP_NO_CONTENT
+ && responseCode != HTTP_NOT_MODIFIED) {
+ return true;
+ }
+
+ // If the Content-Length or Transfer-Encoding headers disagree with the
+ // response code, the response is malformed. For best compatibility, we
+ // honor the headers.
+ if (OkHeaders.contentLength(networkResponse) != -1
+ || "chunked".equalsIgnoreCase(networkResponse.header("Transfer-Encoding"))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Populates request with defaults and cookies.
+ *
+ *
This client doesn't specify a default {@code Accept} header because it
+ * doesn't know what content publishType the application is interested in.
+ */
+ private Request networkRequest(Request request) throws IOException {
+ Request.Builder result = request.newBuilder();
+
+ if (request.header("Host") == null) {
+ result.header("Host", hostHeader(request.url()));
+ }
+
+ if ((connection == null || connection.getProtocol() != Protocol.HTTP_1_0)
+ && request.header("Connection") == null) {
+ result.header("Connection", "Keep-Alive");
+ }
+
+ if (request.header("Accept-Encoding") == null) {
+ transparentGzip = true;
+ result.header("Accept-Encoding", "gzip");
+ }
+
+ CookieHandler cookieHandler = client.getCookieHandler();
+ if (cookieHandler != null) {
+ // Capture the request headers added so far so that they can be offered to the CookieHandler.
+ // This is mostly to stay close to the RI; it is unlikely any of the headers above would
+ // affect cookie choice besides "Host".
+ Map> headers = OkHeaders.toMultimap(result.build().headers(), null);
+
+ Map> cookies = cookieHandler.get(request.uri(), headers);
+
+ // Add any new cookies to the request.
+ OkHeaders.addCookies(result, cookies);
+ }
+
+ return result.build();
+ }
+
+ public static String hostHeader(URL url) {
+ return Util.getEffectivePort(url) != Util.getDefaultPort(url.getProtocol())
+ ? url.getHost() + ":" + url.getPort()
+ : url.getHost();
+ }
+
+ /**
+ * Flushes the remaining request header and body, parses the HTTP response
+ * headers and starts reading the HTTP response body if it exists.
+ */
+ public void readResponse() throws IOException {
+ if (userResponse != null) {
+ return; // Already ready.
+ }
+ if (networkRequest == null && cacheResponse == null) {
+ throw new IllegalStateException("call sendRequest() first!");
+ }
+ if (networkRequest == null) {
+ return; // No network response to read.
+ }
+
+ // Flush the request body if there's data outstanding.
+ if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
+ bufferedRequestBody.flush();
+ }
+
+ if (sentRequestMillis == -1) {
+ if (OkHeaders.contentLength(networkRequest) == -1
+ && requestBodyOut instanceof RetryableSink) {
+ // We might not learn the Content-Length until the request body has been buffered.
+ long contentLength = ((RetryableSink) requestBodyOut).contentLength();
+ networkRequest = networkRequest.newBuilder()
+ .header("Content-Length", Long.toString(contentLength))
+ .build();
+ }
+ transport.writeRequestHeaders(networkRequest);
+ }
+
+ if (requestBodyOut != null) {
+ if (bufferedRequestBody != null) {
+ // This also closes the wrapped requestBodyOut.
+ bufferedRequestBody.close();
+ } else {
+ requestBodyOut.close();
+ }
+ if (requestBodyOut instanceof RetryableSink && !Util.emptySink().equals(requestBodyOut)) {
+ transport.writeRequestBody((RetryableSink) requestBodyOut);
+ }
+ }
+
+ transport.flushRequest();
+
+ networkResponse = transport.readResponseHeaders()
+ .request(networkRequest)
+ .handshake(connection.getHandshake())
+ .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
+ .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
+ .build();
+ Internal.instance.setProtocol(connection, networkResponse.protocol());
+ receiveHeaders(networkResponse.headers());
+
+ // If we have a cache response too, then we're doing a conditional get.
+ if (cacheResponse != null) {
+ if (validate(cacheResponse, networkResponse)) {
+ userResponse = cacheResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .headers(combine(cacheResponse.headers(), networkResponse.headers()))
+ .cacheResponse(stripBody(cacheResponse))
+ .networkResponse(stripBody(networkResponse))
+ .build();
+ transport.emptyTransferStream();
+ releaseConnection();
+
+ // Update the cache after combining headers but before stripping the
+ // Content-Encoding header (as performed by initContentStream()).
+ InternalCache responseCache = Internal.instance.internalCache(client);
+ responseCache.trackConditionalCacheHit();
+ responseCache.update(cacheResponse, stripBody(userResponse));
+
+ if (cacheResponse.body() != null) {
+ initContentStream(cacheResponse.body().source());
+ }
+ return;
+ } else {
+ Util.closeQuietly(cacheResponse.body());
+ }
+ }
+
+ userResponse = networkResponse.newBuilder()
+ .request(userRequest)
+ .priorResponse(stripBody(priorResponse))
+ .cacheResponse(stripBody(cacheResponse))
+ .networkResponse(stripBody(networkResponse))
+ .build();
+
+ if (!hasResponseBody()) {
+ // Don't call initContentStream() when the response doesn't have any content.
+ responseTransferSource = transport.getTransferStream(storeRequest);
+ responseBody = Okio.buffer(responseTransferSource);
+ return;
+ }
+
+ maybeCache();
+ initContentStream(transport.getTransferStream(storeRequest));
+ }
+
+ /**
+ * Returns true if {@code cached} should be used; false if {@code network}
+ * response should be used.
+ */
+ private static boolean validate(Response cached, Response network) {
+ if (network.code() == HTTP_NOT_MODIFIED) {
+ return true;
+ }
+
+ // The HTTP spec says that if the network's response is older than our
+ // cached response, we may return the cache's response. Like Chrome (but
+ // unlike Firefox), this client prefers to return the newer response.
+ Date lastModified = cached.headers().getDate("Last-Modified");
+ if (lastModified != null) {
+ Date networkLastModified = network.headers().getDate("Last-Modified");
+ if (networkLastModified != null
+ && networkLastModified.getTime() < lastModified.getTime()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Combines cached headers with a network headers as defined by RFC 2616,
+ * 13.5.3.
+ */
+ private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException {
+ Headers.Builder result = new Headers.Builder();
+
+ for (int i = 0; i < cachedHeaders.size(); i++) {
+ String fieldName = cachedHeaders.name(i);
+ String value = cachedHeaders.value(i);
+ if ("Warning".equals(fieldName) && value.startsWith("1")) {
+ continue; // drop 100-level freshness warnings
+ }
+ if (!OkHeaders.isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
+ result.add(fieldName, value);
+ }
+ }
+
+ for (int i = 0; i < networkHeaders.size(); i++) {
+ String fieldName = networkHeaders.name(i);
+ if (OkHeaders.isEndToEnd(fieldName)) {
+ result.add(fieldName, networkHeaders.value(i));
+ }
+ }
+
+ return result.build();
+ }
+
+ public void receiveHeaders(Headers headers) throws IOException {
+ CookieHandler cookieHandler = client.getCookieHandler();
+ if (cookieHandler != null) {
+ cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
+ }
+ }
+
+ /**
+ * Figures out the HTTP request to make in response to receiving this engine's
+ * response. This will either add authentication headers or follow redirects.
+ * If a follow-up is either unnecessary or not applicable, this returns null.
+ */
+ public Request followUpRequest() throws IOException {
+ if (userResponse == null) throw new IllegalStateException();
+ Proxy selectedProxy = getRoute() != null
+ ? getRoute().getProxy()
+ : client.getProxy();
+ int responseCode = userResponse.code();
+
+ switch (responseCode) {
+ case HTTP_PROXY_AUTH:
+ if (selectedProxy.type() != Proxy.Type.HTTP) {
+ throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
+ }
+ // fall-through
+ case HTTP_UNAUTHORIZED:
+ return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy);
+
+ case HTTP_TEMP_REDIRECT:
+ // "If the 307 status code is received in response to a request other than GET or HEAD,
+ // the user agent MUST NOT automatically redirect the request"
+ if (!userRequest.method().equals("GET") && !userRequest.method().equals("HEAD")) {
+ return null;
+ }
+ // fall-through
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_MOVED_TEMP:
+ case HTTP_SEE_OTHER:
+ // Does the client allow redirects?
+ if (!client.getFollowRedirects()) return null;
+
+ String location = userResponse.header("Location");
+ if (location == null) return null;
+ URL url = new URL(userRequest.url(), location);
+
+ // Don't follow redirects to unsupported protocols.
+ if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) return null;
+
+ // If configured, don't follow redirects between SSL and non-SSL.
+ boolean sameProtocol = url.getProtocol().equals(userRequest.url().getProtocol());
+ if (!sameProtocol && !client.getFollowSslRedirects()) return null;
+
+ // Redirects don't include a request body.
+ Request.Builder requestBuilder = userRequest.newBuilder();
+ if (HttpMethod.hasRequestBody(userRequest.method())) {
+ requestBuilder.method("GET", null);
+ requestBuilder.removeHeader("Transfer-Encoding");
+ requestBuilder.removeHeader("Content-Length");
+ requestBuilder.removeHeader("Content-Type");
+ }
+
+ // When redirecting across hosts, drop all authentication headers. This
+ // is potentially annoying to the application layer since they have no
+ // way to retain them.
+ if (!sameConnection(url)) {
+ requestBuilder.removeHeader("Authorization");
+ }
+
+ return requestBuilder.url(url).build();
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if an HTTP request for {@code followUp} can reuse the
+ * connection used by this engine.
+ */
+ public boolean sameConnection(URL followUp) {
+ URL url = userRequest.url();
+ return url.getHost().equals(followUp.getHost())
+ && Util.getEffectivePort(url) == Util.getEffectivePort(followUp)
+ && url.getProtocol().equals(followUp.getProtocol());
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpMethod.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpMethod.java
new file mode 100755
index 00000000..c3741495
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpMethod.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp.internal.http;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public final class HttpMethod {
+ public static final Set METHODS = new LinkedHashSet(Arrays.asList(
+ "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH"));
+
+ public static boolean invalidatesCache(String method) {
+ return method.equals("POST")
+ || method.equals("PATCH")
+ || method.equals("PUT")
+ || method.equals("DELETE");
+ }
+
+ public static boolean hasRequestBody(String method) {
+ return method.equals("POST")
+ || method.equals("PUT")
+ || method.equals("PATCH")
+ || method.equals("DELETE"); // Permitted as spec is ambiguous.
+ }
+
+ private HttpMethod() {
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpTransport.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpTransport.java
new file mode 100755
index 00000000..8584a145
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/HttpTransport.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+
+import java.io.IOException;
+
+public final class HttpTransport implements Transport {
+ private final HttpEngine httpEngine;
+ private final HttpConnection httpConnection;
+
+ public HttpTransport(HttpEngine httpEngine, HttpConnection httpConnection) {
+ this.httpEngine = httpEngine;
+ this.httpConnection = httpConnection;
+ }
+
+ @Override public Sink createRequestBody(Request request) throws IOException {
+ long contentLength = OkHeaders.contentLength(request);
+
+ if (httpEngine.bufferRequestBody) {
+ if (contentLength > Integer.MAX_VALUE) {
+ throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ + "setChunkedStreamingMode() for requests larger than 2 GiB.");
+ }
+
+ if (contentLength != -1) {
+ // Buffer a request body of a known length.
+ writeRequestHeaders(request);
+ return new RetryableSink((int) contentLength);
+ } else {
+ // Buffer a request body of an unknown length. Don't write request
+ // headers until the entire body is ready; otherwise we can't set the
+ // Content-Length header correctly.
+ return new RetryableSink();
+ }
+ }
+
+ if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
+ // Stream a request body of unknown length.
+ writeRequestHeaders(request);
+ return httpConnection.newChunkedSink();
+ }
+
+ if (contentLength != -1) {
+ // Stream a request body of a known length.
+ writeRequestHeaders(request);
+ return httpConnection.newFixedLengthSink(contentLength);
+ }
+
+ throw new IllegalStateException(
+ "Cannot stream a request body without chunked encoding or a known content length!");
+ }
+
+ @Override public void flushRequest() throws IOException {
+ httpConnection.flush();
+ }
+
+ @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+ httpConnection.writeRequestBody(requestBody);
+ }
+
+ /**
+ * Prepares the HTTP headers and sends them to the server.
+ *
+ * For streaming requests with a body, headers must be prepared
+ * before the output stream has been written to. Otherwise
+ * the body would need to be buffered!
+ *
+ *
For non-streaming requests with a body, headers must be prepared
+ * after the output stream has been written to and closed.
+ * This ensures that the {@code Content-Length} header field receives the
+ * proper value.
+ */
+ public void writeRequestHeaders(Request request) throws IOException {
+ httpEngine.writingRequestHeaders();
+ String requestLine = RequestLine.get(request,
+ httpEngine.getConnection().getRoute().getProxy().type(),
+ httpEngine.getConnection().getProtocol());
+ httpConnection.writeRequest(request.headers(), requestLine);
+ }
+
+ @Override public Response.Builder readResponseHeaders() throws IOException {
+ return httpConnection.readResponse();
+ }
+
+ @Override public void releaseConnectionOnIdle() throws IOException {
+ if (canReuseConnection()) {
+ httpConnection.poolOnIdle();
+ } else {
+ httpConnection.closeOnIdle();
+ }
+ }
+
+ @Override public boolean canReuseConnection() {
+ // If the request specified that the connection shouldn't be reused, don't reuse it.
+ if ("close".equalsIgnoreCase(httpEngine.getRequest().header("Connection"))) {
+ return false;
+ }
+
+ // If the response specified that the connection shouldn't be reused, don't reuse it.
+ if ("close".equalsIgnoreCase(httpEngine.getResponse().header("Connection"))) {
+ return false;
+ }
+
+ if (httpConnection.isClosed()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override public void emptyTransferStream() throws IOException {
+ httpConnection.emptyResponseBody();
+ }
+
+ @Override public Source getTransferStream(CacheRequest cacheRequest) throws IOException {
+ if (!httpEngine.hasResponseBody()) {
+ return httpConnection.newFixedLengthSource(cacheRequest, 0);
+ }
+
+ if ("chunked".equalsIgnoreCase(httpEngine.getResponse().header("Transfer-Encoding"))) {
+ return httpConnection.newChunkedSource(cacheRequest, httpEngine);
+ }
+
+ long contentLength = OkHeaders.contentLength(httpEngine.getResponse());
+ if (contentLength != -1) {
+ return httpConnection.newFixedLengthSource(cacheRequest, contentLength);
+ }
+
+ // Wrap the input stream from the connection (rather than just returning
+ // "socketIn" directly here), so that we can control its use after the
+ // reference escapes.
+ return httpConnection.newUnknownLengthSource(cacheRequest);
+ }
+
+ @Override public void disconnect(HttpEngine engine) throws IOException {
+ httpConnection.closeIfOwnedBy(engine);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/OkHeaders.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/OkHeaders.java
new file mode 100755
index 00000000..87315f46
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/OkHeaders.java
@@ -0,0 +1,266 @@
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Authenticator;
+import com.contentstack.okhttp.Challenge;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.internal.Platform;
+import com.contentstack.okhttp.internal.Util;
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+
+/** Headers and utilities for internal use by OkHttp. */
+public final class OkHeaders {
+ private static final Comparator FIELD_NAME_COMPARATOR = new Comparator() {
+ // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
+ @Override public int compare(String a, String b) {
+ if (a == b) {
+ return 0;
+ } else if (a == null) {
+ return -1;
+ } else if (b == null) {
+ return 1;
+ } else {
+ return String.CASE_INSENSITIVE_ORDER.compare(a, b);
+ }
+ }
+ };
+
+ static final String PREFIX = Platform.get().getPrefix();
+
+ /**
+ * Synthetic response header: the local time when the request was sent.
+ */
+ public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
+
+ /**
+ * Synthetic response header: the local time when the response was received.
+ */
+ public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
+
+ /**
+ * Synthetic response header: the selected
+ * {@link Protocol protocol} ("spdy/3.1", "http/1.1", etc).
+ */
+ public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol";
+
+ private OkHeaders() {
+ }
+
+ public static long contentLength(Request request) {
+ return contentLength(request.headers());
+ }
+
+ public static long contentLength(Response response) {
+ return contentLength(response.headers());
+ }
+
+ public static long contentLength(Headers headers) {
+ return stringToLong(headers.get("Content-Length"));
+ }
+
+ private static long stringToLong(String s) {
+ if (s == null) return -1;
+ try {
+ return Long.parseLong(s);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns an immutable map containing each field to its list of values.
+ *
+ * @param valueForNullKey the request line for requests, or the status line
+ * for responses. If non-null, this value is mapped to the null key.
+ */
+ public static Map> toMultimap(Headers headers, String valueForNullKey) {
+ Map> result = new TreeMap>(FIELD_NAME_COMPARATOR);
+ for (int i = 0; i < headers.size(); i++) {
+ String fieldName = headers.name(i);
+ String value = headers.value(i);
+
+ List allValues = new ArrayList();
+ List otherValues = result.get(fieldName);
+ if (otherValues != null) {
+ allValues.addAll(otherValues);
+ }
+ allValues.add(value);
+ result.put(fieldName, Collections.unmodifiableList(allValues));
+ }
+ if (valueForNullKey != null) {
+ result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ public static void addCookies(Request.Builder builder, Map> cookieHeaders) {
+ for (Map.Entry> entry : cookieHeaders.entrySet()) {
+ String key = entry.getKey();
+ if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
+ && !entry.getValue().isEmpty()) {
+ builder.addHeader(key, buildCookieHeader(entry.getValue()));
+ }
+ }
+ }
+
+ /**
+ * Send all cookies in one big header, as recommended by
+ * RFC 6265.
+ */
+ private static String buildCookieHeader(List 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();
+ }
+
+ /**
+ * Returns true if none of the Vary headers have changed between {@code
+ * cachedRequest} and {@code newRequest}.
+ */
+ public static boolean varyMatches(
+ Response cachedResponse, Headers cachedRequest, Request newRequest) {
+ for (String field : varyFields(cachedResponse)) {
+ if (!Util.equal(cachedRequest.values(field), newRequest.headers(field))) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if a Vary header contains an asterisk. Such responses cannot
+ * be cached.
+ */
+ public static boolean hasVaryAll(Response response) {
+ return varyFields(response).contains("*");
+ }
+
+ private static Set varyFields(Response response) {
+ Set result = Collections.emptySet();
+ Headers headers = response.headers();
+ for (int i = 0; i < headers.size(); i++) {
+ if (!"Vary".equalsIgnoreCase(headers.name(i))) continue;
+
+ String value = headers.value(i);
+ if (result.isEmpty()) {
+ result = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+ }
+ for (String varyField : value.split(",")) {
+ result.add(varyField.trim());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the subset of the headers in {@code response}'s request that
+ * impact the content of response's body.
+ */
+ public static Headers varyHeaders(Response response) {
+ Set varyFields = varyFields(response);
+ if (varyFields.isEmpty()) return new Headers.Builder().build();
+
+ // Use the request headers sent over the network, since that's what the
+ // response varies on. Otherwise OkHttp-supplied headers like
+ // "Accept-Encoding: gzip" may be lost.
+ Headers requestHeaders = response.networkResponse().request().headers();
+
+ Headers.Builder result = new Headers.Builder();
+ for (int i = 0; i < requestHeaders.size(); i++) {
+ String fieldName = requestHeaders.name(i);
+ if (varyFields.contains(fieldName)) {
+ result.add(fieldName, requestHeaders.value(i));
+ }
+ }
+ return result.build();
+ }
+
+ /**
+ * Returns true if {@code fieldName} is an end-to-end HTTP header, as
+ * defined by RFC 2616, 13.5.1.
+ */
+ static boolean isEndToEnd(String fieldName) {
+ return !"Connection".equalsIgnoreCase(fieldName)
+ && !"Keep-Alive".equalsIgnoreCase(fieldName)
+ && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
+ && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
+ && !"TE".equalsIgnoreCase(fieldName)
+ && !"Trailers".equalsIgnoreCase(fieldName)
+ && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
+ && !"Upgrade".equalsIgnoreCase(fieldName);
+ }
+
+ /**
+ * Parse RFC 2617 challenges. This API is only interested in the scheme
+ * name and realm.
+ */
+ public static List parseChallenges(Headers responseHeaders, String challengeHeader) {
+ // auth-scheme = token
+ // auth-param = token "=" ( token | quoted-string )
+ // challenge = auth-scheme 1*SP 1#auth-param
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ List result = new ArrayList();
+ for (int h = 0; h < responseHeaders.size(); h++) {
+ if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(h))) {
+ continue;
+ }
+ String value = responseHeaders.value(h);
+ int pos = 0;
+ while (pos < value.length()) {
+ int tokenStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, " ");
+
+ String scheme = value.substring(tokenStart, pos).trim();
+ pos = HeaderParser.skipWhitespace(value, pos);
+
+ // This currently only handles schemes with a 'realm' parameter;
+ // 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(true, pos, "realm=\"", 0, "realm=\"".length())) {
+ break; // Unexpected challenge parameter; give up!
+ }
+
+ pos += "realm=\"".length();
+ int realmStart = pos;
+ pos = HeaderParser.skipUntil(value, pos, "\"");
+ String realm = value.substring(realmStart, pos);
+ pos++; // Consume '"' close quote.
+ pos = HeaderParser.skipUntil(value, pos, ",");
+ pos++; // Consume ',' comma.
+ pos = HeaderParser.skipWhitespace(value, pos);
+ result.add(new Challenge(scheme, realm));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * React to a failed authorization response by looking up new credentials.
+ * Returns a request for a subsequent attempt, or null if no further attempts
+ * should be made.
+ */
+ public static Request processAuthHeader(Authenticator authenticator, Response response,
+ Proxy proxy) throws IOException {
+ return response.code() == HTTP_PROXY_AUTH
+ ? authenticator.authenticateProxy(proxy, response)
+ : authenticator.authenticate(proxy, response);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RequestLine.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RequestLine.java
new file mode 100755
index 00000000..360e8ef7
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RequestLine.java
@@ -0,0 +1,58 @@
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+
+public final class RequestLine {
+ private RequestLine() {
+ }
+
+ /**
+ * Returns the request status line, like "GET / HTTP/1.1". This is exposed
+ * to the application by {@link HttpURLConnection#getHeaderFields}, so it
+ * needs to be set even if the transport is SPDY.
+ */
+ static String get(Request request, Proxy.Type proxyType, Protocol protocol) {
+ StringBuilder result = new StringBuilder();
+ result.append(request.method());
+ result.append(' ');
+
+ if (includeAuthorityInRequestLine(request, proxyType)) {
+ result.append(request.url());
+ } else {
+ result.append(requestPath(request.url()));
+ }
+
+ result.append(' ');
+ result.append(version(protocol));
+ return result.toString();
+ }
+
+ /**
+ * Returns true if the request line should contain the full URL with host
+ * and port (like "GET http://android.com/foo HTTP/1.1") or only the path
+ * (like "GET /foo HTTP/1.1").
+ */
+ private static boolean includeAuthorityInRequestLine(Request request, Proxy.Type proxyType) {
+ return !request.isHttps() && proxyType == Proxy.Type.HTTP;
+ }
+
+ /**
+ * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never empty,
+ * even if the request URL is. Includes the query component if it exists.
+ */
+ public static String requestPath(URL url) {
+ String pathAndQuery = url.getFile();
+ if (pathAndQuery == null) return "/";
+ if (!pathAndQuery.startsWith("/")) return "/" + pathAndQuery;
+ return pathAndQuery;
+ }
+
+ public static String version(Protocol protocol) {
+ return protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1";
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RetryableSink.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RetryableSink.java
new file mode 100755
index 00000000..72448d14
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RetryableSink.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Timeout;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/**
+ * An HTTP request body that's completely buffered in memory. This allows
+ * the post body to be transparently re-sent if the HTTP request must be
+ * sent multiple times.
+ */
+public final class RetryableSink implements Sink {
+ private boolean closed;
+ private final int limit;
+ private final Buffer content = new Buffer();
+
+ public RetryableSink(int limit) {
+ this.limit = limit;
+ }
+
+ public RetryableSink() {
+ this(-1);
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+ closed = true;
+ if (content.size() < limit) {
+ throw new ProtocolException(
+ "content-length promised " + limit + " bytes, but received " + content.size());
+ }
+ }
+
+ @Override public void write(Buffer source, long byteCount) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ Util.checkOffsetAndCount(source.size(), 0, byteCount);
+ if (limit != -1 && content.size() > limit - byteCount) {
+ throw new ProtocolException("exceeded content-length limit of " + limit + " bytes");
+ }
+ content.write(source, byteCount);
+ }
+
+ @Override public void flush() throws IOException {
+ }
+
+ @Override public Timeout timeout() {
+ return Timeout.NONE;
+ }
+
+ public long contentLength() throws IOException {
+ return content.size();
+ }
+
+ public void writeToSocket(BufferedSink socketOut) throws IOException {
+ // Clone the content; otherwise we won't have data to retry.
+ socketOut.writeAll(content.clone());
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RouteSelector.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RouteSelector.java
new file mode 100755
index 00000000..9619b54b
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/RouteSelector.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Address;
+import com.contentstack.okhttp.Connection;
+import com.contentstack.okhttp.ConnectionPool;
+import com.contentstack.okhttp.HostResolver;
+import com.contentstack.okhttp.OkHttpClient;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Route;
+import com.contentstack.okhttp.internal.Internal;
+import com.contentstack.okhttp.internal.RouteDatabase;
+import com.contentstack.okhttp.internal.Util;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLProtocolException;
+import javax.net.ssl.SSLSocketFactory;
+
+import static com.contentstack.okhttp.internal.Util.getEffectivePort;
+
+/**
+ * Selects routes to connect to an origin server. Each connection requires a
+ * choice of proxy server, IP address, and TLS mode. Connections may also be
+ * recycled.
+ */
+public final class RouteSelector {
+ public static final String TLS_V1 = "TLSv1";
+ public static final String SSL_V3 = "SSLv3";
+
+ private final Address address;
+ private final URI uri;
+ private final HostResolver hostResolver;
+ private final OkHttpClient client;
+ private final ProxySelector proxySelector;
+ private final ConnectionPool pool;
+ private final RouteDatabase routeDatabase;
+ private final Request request;
+
+ /* The most recently attempted route. */
+ private Proxy lastProxy;
+ private InetSocketAddress lastInetSocketAddress;
+
+ /* State for negotiating the next proxy to use. */
+ private boolean hasNextProxy;
+ private Proxy userSpecifiedProxy;
+ private Iterator proxySelectorProxies;
+
+ /* State for negotiating the next InetSocketAddress to use. */
+ private InetAddress[] socketAddresses;
+ private int nextSocketAddressIndex;
+ private int socketPort;
+
+ /* TLS version to attempt with the connection. */
+ private String nextTlsVersion;
+
+ /* State for negotiating failed routes */
+ private final List postponedRoutes = new ArrayList();
+
+ private RouteSelector(Address address, URI uri, OkHttpClient client, Request request) {
+ this.address = address;
+ this.uri = uri;
+ this.client = client;
+ this.proxySelector = client.getProxySelector();
+ this.pool = client.getConnectionPool();
+ this.routeDatabase = Internal.instance.routeDatabase(client);
+ this.hostResolver = client.getHostResolver();
+ this.request = request;
+
+ resetNextProxy(uri, address.getProxy());
+ }
+
+ public static RouteSelector get(Request request, OkHttpClient client) throws IOException {
+ String uriHost = request.url().getHost();
+ if (uriHost == null || uriHost.length() == 0) {
+ throw new UnknownHostException(request.url().toString());
+ }
+
+ SSLSocketFactory sslSocketFactory = null;
+ HostnameVerifier hostnameVerifier = null;
+ if (request.isHttps()) {
+ sslSocketFactory = client.getSslSocketFactory();
+ hostnameVerifier = client.getHostnameVerifier();
+ }
+
+ Address address = new Address(uriHost, getEffectivePort(request.url()),
+ client.getSocketFactory(), sslSocketFactory, hostnameVerifier, client.getAuthenticator(),
+ client.getProxy(), client.getProtocols());
+
+ return new RouteSelector(address, request.uri(), client, request);
+ }
+
+ /**
+ * Returns true if there's another route to attempt. Every address has at
+ * least one route.
+ */
+ public boolean hasNext() {
+ return hasNextTlsVersion()
+ || hasNextInetSocketAddress()
+ || hasNextProxy()
+ || hasNextPostponed();
+ }
+
+ /** Selects a route to attempt and connects it if it isn't already. */
+ public Connection next(HttpEngine owner) throws IOException {
+ Connection connection = nextUnconnected();
+ Internal.instance.connectAndSetOwner(client, connection, owner, request);
+ return connection;
+ }
+
+ /**
+ * Returns the next connection to attempt.
+ *
+ * @throws NoSuchElementException if there are no more routes to attempt.
+ */
+ Connection nextUnconnected() throws IOException {
+ // Always prefer pooled connections over new connections.
+ for (Connection pooled; (pooled = pool.get(address)) != null; ) {
+ if (request.method().equals("GET") || Internal.instance.isReadable(pooled)) return pooled;
+ pooled.getSocket().close();
+ }
+
+ // Compute the next route to attempt.
+ if (!hasNextTlsVersion()) {
+ if (!hasNextInetSocketAddress()) {
+ if (!hasNextProxy()) {
+ if (!hasNextPostponed()) {
+ throw new NoSuchElementException();
+ }
+ return new Connection(pool, nextPostponed());
+ }
+ lastProxy = nextProxy();
+ resetNextInetSocketAddress(lastProxy);
+ }
+ lastInetSocketAddress = nextInetSocketAddress();
+ resetNextTlsVersion();
+ }
+
+ String tlsVersion = nextTlsVersion();
+ Route route = new Route(address, lastProxy, lastInetSocketAddress, tlsVersion);
+ if (routeDatabase.shouldPostpone(route)) {
+ postponedRoutes.add(route);
+ // We will only recurse in order to skip previously failed routes. They will be
+ // tried last.
+ return nextUnconnected();
+ }
+
+ return new Connection(pool, route);
+ }
+
+ /**
+ * Clients should invoke this method when they encounter a connectivity
+ * failure on a connection returned by this route selector.
+ */
+ public void connectFailed(Connection connection, IOException failure) {
+ // If this is a recycled connection, don't count its failure against the route.
+ if (Internal.instance.recycleCount(connection) > 0) return;
+
+ Route failedRoute = connection.getRoute();
+ if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) {
+ // Tell the proxy selector when we fail to connect on a fresh connection.
+ proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure);
+ }
+
+ routeDatabase.failed(failedRoute);
+
+ // If the previously returned route's problem was not related to TLS, and
+ // the next route only changes the TLS mode, we shouldn't even attempt it.
+ // This suppresses it in both this selector and also in the route database.
+ if (!(failure instanceof SSLHandshakeException) && !(failure instanceof SSLProtocolException)) {
+ while (hasNextTlsVersion()) {
+ Route toSuppress = new Route(address, lastProxy, lastInetSocketAddress, nextTlsVersion());
+ routeDatabase.failed(toSuppress);
+ }
+ }
+ }
+
+ /** Resets {@link #nextProxy} to the first option. */
+ private void resetNextProxy(URI uri, Proxy proxy) {
+ this.hasNextProxy = true; // This includes NO_PROXY!
+ if (proxy != null) {
+ this.userSpecifiedProxy = proxy;
+ } else {
+ List proxyList = proxySelector.select(uri);
+ if (proxyList != null) {
+ this.proxySelectorProxies = proxyList.iterator();
+ }
+ }
+ }
+
+ /** Returns true if there's another proxy to try. */
+ private boolean hasNextProxy() {
+ return hasNextProxy;
+ }
+
+ /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
+ private Proxy nextProxy() {
+ // If the user specifies a proxy, try that and only that.
+ if (userSpecifiedProxy != null) {
+ hasNextProxy = false;
+ return userSpecifiedProxy;
+ }
+
+ // Try each of the ProxySelector choices until one connection succeeds. If none succeed
+ // then we'll try a direct connection below.
+ if (proxySelectorProxies != null) {
+ while (proxySelectorProxies.hasNext()) {
+ Proxy candidate = proxySelectorProxies.next();
+ if (candidate.type() != Proxy.Type.DIRECT) {
+ return candidate;
+ }
+ }
+ }
+
+ // Finally try a direct connection.
+ hasNextProxy = false;
+ return Proxy.NO_PROXY;
+ }
+
+ /** Resets {@link #nextInetSocketAddress} to the first option. */
+ private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
+ socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
+
+ String socketHost;
+ if (proxy.type() == Proxy.Type.DIRECT) {
+ socketHost = uri.getHost();
+ socketPort = Util.getEffectivePort(uri);
+ } else {
+ SocketAddress proxyAddress = proxy.address();
+ if (!(proxyAddress instanceof InetSocketAddress)) {
+ throw new IllegalArgumentException(
+ "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
+ }
+ InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
+ socketHost = proxySocketAddress.getHostName();
+ socketPort = proxySocketAddress.getPort();
+ }
+
+ // Try each address for best behavior in mixed IPv4/IPv6 environments.
+ socketAddresses = hostResolver.getAllByName(socketHost);
+ nextSocketAddressIndex = 0;
+ }
+
+ /** Returns true if there's another socket address to try. */
+ private boolean hasNextInetSocketAddress() {
+ return socketAddresses != null;
+ }
+
+ /** Returns the next socket address to try. */
+ private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
+ InetSocketAddress result =
+ new InetSocketAddress(socketAddresses[nextSocketAddressIndex++], socketPort);
+ if (nextSocketAddressIndex == socketAddresses.length) {
+ socketAddresses = null; // So that hasNextInetSocketAddress() returns false.
+ nextSocketAddressIndex = 0;
+ }
+
+ return result;
+ }
+
+ /**
+ * Resets {@link #nextTlsVersion} to the first option. For routes that don't
+ * use SSL, this returns {@link #SSL_V3} so that there is no SSL fallback.
+ */
+ private void resetNextTlsVersion() {
+ nextTlsVersion = (address.getSslSocketFactory() != null) ? TLS_V1 : SSL_V3;
+ }
+
+ /** Returns true if there's another TLS version to try. */
+ private boolean hasNextTlsVersion() {
+ return nextTlsVersion != null;
+ }
+
+ /** Returns the next TLS mode to try. */
+ private String nextTlsVersion() {
+ if (nextTlsVersion == null) {
+ throw new IllegalStateException("No next TLS version");
+ } else if (nextTlsVersion.equals(TLS_V1)) {
+ nextTlsVersion = SSL_V3;
+ return TLS_V1;
+ } else if (nextTlsVersion.equals(SSL_V3)) {
+ nextTlsVersion = null; // So that hasNextTlsVersion() returns false.
+ return SSL_V3;
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ /** Returns true if there is another postponed route to try. */
+ private boolean hasNextPostponed() {
+ return !postponedRoutes.isEmpty();
+ }
+
+ /** Returns the next postponed route to try. */
+ private Route nextPostponed() {
+ return postponedRoutes.remove(0);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/SpdyTransport.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/SpdyTransport.java
new file mode 100755
index 00000000..c037b5a3
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/SpdyTransport.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.spdy.ErrorCode;
+import com.contentstack.okhttp.internal.spdy.Header;
+import com.contentstack.okhttp.internal.spdy.SpdyConnection;
+import com.contentstack.okhttp.internal.spdy.SpdyStream;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.ByteString;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+import com.contentstack.okio.Timeout;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public final class SpdyTransport implements Transport {
+ /** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */
+ private static final List SPDY_3_PROHIBITED_HEADERS = Util.immutableList(
+ ByteString.encodeUtf8("connection"),
+ ByteString.encodeUtf8("host"),
+ ByteString.encodeUtf8("keep-alive"),
+ ByteString.encodeUtf8("proxy-connection"),
+ ByteString.encodeUtf8("transfer-encoding"));
+
+ /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
+ private static final List HTTP_2_PROHIBITED_HEADERS = Util.immutableList(
+ ByteString.encodeUtf8("connection"),
+ ByteString.encodeUtf8("host"),
+ ByteString.encodeUtf8("keep-alive"),
+ ByteString.encodeUtf8("proxy-connection"),
+ ByteString.encodeUtf8("te"),
+ ByteString.encodeUtf8("transfer-encoding"),
+ ByteString.encodeUtf8("encoding"),
+ ByteString.encodeUtf8("upgrade"));
+
+ private final HttpEngine httpEngine;
+ private final SpdyConnection spdyConnection;
+ private SpdyStream stream;
+
+ public SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) {
+ this.httpEngine = httpEngine;
+ this.spdyConnection = spdyConnection;
+ }
+
+ @Override public Sink createRequestBody(Request request) throws IOException {
+ // If bufferRequestBody is set, we must buffer the whole request
+ writeRequestHeaders(request);
+ return stream.getSink();
+ }
+
+ @Override public void writeRequestHeaders(Request request) throws IOException {
+ if (stream != null) return;
+
+ httpEngine.writingRequestHeaders();
+ boolean hasRequestBody = httpEngine.hasRequestBody();
+ boolean hasResponseBody = true;
+ String version = RequestLine.version(httpEngine.getConnection().getProtocol());
+ stream = spdyConnection.newStream(
+ writeNameValueBlock(request, spdyConnection.getProtocol(), version), hasRequestBody,
+ hasResponseBody);
+ stream.readTimeout().timeout(httpEngine.client.getReadTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void flushRequest() throws IOException {
+ stream.getSink().close();
+ }
+
+ @Override public Response.Builder readResponseHeaders() throws IOException {
+ return readNameValueBlock(stream.getResponseHeaders(), spdyConnection.getProtocol());
+ }
+
+ /**
+ * Returns a list of alternating names and values containing a SPDY request.
+ * Names are all lowercase. No names are repeated. If any name has multiple
+ * values, they are concatenated using "\0" as a delimiter.
+ */
+ public static List writeNameValueBlock(Request request, Protocol protocol,
+ String version) {
+ Headers headers = request.headers();
+ List result = new ArrayList(headers.size() + 10);
+ result.add(new Header(Header.TARGET_METHOD, request.method()));
+ result.add(new Header(Header.TARGET_PATH, RequestLine.requestPath(request.url())));
+ String host = HttpEngine.hostHeader(request.url());
+ if (Protocol.SPDY_3 == protocol) {
+ result.add(new Header(Header.VERSION, version));
+ result.add(new Header(Header.TARGET_HOST, host));
+ } else if (Protocol.HTTP_2 == protocol) {
+ result.add(new Header(Header.TARGET_AUTHORITY, host)); // Optional in HTTP/2
+ } else {
+ throw new AssertionError();
+ }
+ result.add(new Header(Header.TARGET_SCHEME, request.url().getProtocol()));
+
+ Set names = new LinkedHashSet();
+ for (int i = 0; i < headers.size(); i++) {
+ // header names must be lowercase.
+ ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
+ String value = headers.value(i);
+
+ // Drop headers that are forbidden when layering HTTP over SPDY.
+ if (isProhibitedHeader(protocol, name)) continue;
+
+ // They shouldn't be set, but if they are, drop them. We've already written them!
+ if (name.equals(Header.TARGET_METHOD)
+ || name.equals(Header.TARGET_PATH)
+ || name.equals(Header.TARGET_SCHEME)
+ || name.equals(Header.TARGET_AUTHORITY)
+ || name.equals(Header.TARGET_HOST)
+ || name.equals(Header.VERSION)) {
+ continue;
+ }
+
+ // If we haven't seen this name before, add the pair to the end of the list...
+ if (names.add(name)) {
+ result.add(new Header(name, value));
+ continue;
+ }
+
+ // ...otherwise concatenate the existing values and this value.
+ for (int j = 0; j < result.size(); j++) {
+ if (result.get(j).name.equals(name)) {
+ String concatenated = joinOnNull(result.get(j).value.utf8(), value);
+ result.set(j, new Header(name, concatenated));
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String joinOnNull(String first, String second) {
+ return new StringBuilder(first).append('\0').append(second).toString();
+ }
+
+ /** Returns headers for a name value block containing a SPDY response. */
+ public static Response.Builder readNameValueBlock(List headerBlock,
+ Protocol protocol) throws IOException {
+ String status = null;
+ String version = "HTTP/1.1"; // :version present only in spdy/3.
+
+ Headers.Builder headersBuilder = new Headers.Builder();
+ headersBuilder.set(OkHeaders.SELECTED_PROTOCOL, protocol.toString());
+ for (int i = 0; i < headerBlock.size(); i++) {
+ ByteString name = headerBlock.get(i).name;
+ String values = headerBlock.get(i).value.utf8();
+ for (int start = 0; start < values.length(); ) {
+ int end = values.indexOf('\0', start);
+ if (end == -1) {
+ end = values.length();
+ }
+ String value = values.substring(start, end);
+ if (name.equals(Header.RESPONSE_STATUS)) {
+ status = value;
+ } else if (name.equals(Header.VERSION)) {
+ version = value;
+ } else if (!isProhibitedHeader(protocol, name)) { // Don't write forbidden headers!
+ headersBuilder.add(name.utf8(), value);
+ }
+ start = end + 1;
+ }
+ }
+ if (status == null) throw new ProtocolException("Expected ':status' header not present");
+ if (version == null) throw new ProtocolException("Expected ':version' header not present");
+
+ StatusLine statusLine = StatusLine.parse(version + " " + status);
+ return new Response.Builder()
+ .protocol(protocol)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(headersBuilder.build());
+ }
+
+ @Override public void emptyTransferStream() {
+ // Do nothing.
+ }
+
+ @Override public Source getTransferStream(CacheRequest cacheRequest) throws IOException {
+ return new SpdySource(stream, cacheRequest);
+ }
+
+ @Override public void releaseConnectionOnIdle() {
+ }
+
+ @Override public void disconnect(HttpEngine engine) throws IOException {
+ stream.close(ErrorCode.CANCEL);
+ }
+
+ @Override public boolean canReuseConnection() {
+ return true; // SpdyConnection.isClosed() ?
+ }
+
+ /** When true, this header should not be emitted or consumed. */
+ private static boolean isProhibitedHeader(Protocol protocol, ByteString name) {
+ if (protocol == Protocol.SPDY_3) {
+ return SPDY_3_PROHIBITED_HEADERS.contains(name);
+ } else if (protocol == Protocol.HTTP_2) {
+ return HTTP_2_PROHIBITED_HEADERS.contains(name);
+ } else {
+ throw new AssertionError(protocol);
+ }
+ }
+
+ /** An HTTP message body terminated by the end of the underlying stream. */
+ private static class SpdySource implements Source {
+ private final SpdyStream stream;
+ private final Source source;
+ private final CacheRequest cacheRequest;
+ private final Sink cacheBody;
+
+ private boolean inputExhausted;
+ private boolean closed;
+
+ SpdySource(SpdyStream stream, CacheRequest cacheRequest) throws IOException {
+ this.stream = stream;
+ this.source = stream.getSource();
+
+ // Some apps return a null body; for compatibility we treat that like a null cache request.
+ Sink cacheBody = cacheRequest != null ? cacheRequest.body() : null;
+ if (cacheBody == null) {
+ cacheRequest = null;
+ }
+
+ this.cacheBody = cacheBody;
+ this.cacheRequest = cacheRequest;
+ }
+
+ @Override public long read(Buffer buffer, long byteCount)
+ throws IOException {
+ if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+ if (closed) throw new IllegalStateException("closed");
+ if (inputExhausted) return -1;
+
+ long read = source.read(buffer, byteCount);
+ if (read == -1) {
+ inputExhausted = true;
+ if (cacheRequest != null) {
+ cacheBody.close();
+ }
+ return -1;
+ }
+
+ if (cacheBody != null) {
+ // Get buffer.copyTo(cacheBody, read);
+ cacheBody.write(buffer.clone(), read);
+ }
+
+ return read;
+ }
+
+ @Override public Timeout timeout() {
+ return source.timeout();
+ }
+
+ @Override public void close() throws IOException {
+ if (closed) return;
+
+ if (!inputExhausted && cacheBody != null) {
+ discardStream(); // Could make inputExhausted true!
+ }
+
+ closed = true;
+
+ if (!inputExhausted) {
+ stream.closeLater(ErrorCode.CANCEL);
+ if (cacheRequest != null) {
+ cacheRequest.abort();
+ }
+ }
+ }
+
+ private boolean discardStream() {
+ long oldTimeoutNanos = stream.readTimeout().timeoutNanos();
+ stream.readTimeout().timeout(DISCARD_STREAM_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ try {
+ Util.skipAll(this, DISCARD_STREAM_TIMEOUT_MILLIS);
+ return true;
+ } catch (IOException e) {
+ return false;
+ } finally {
+ stream.readTimeout().timeout(oldTimeoutNanos, TimeUnit.NANOSECONDS);
+ }
+ }
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/StatusLine.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/StatusLine.java
new file mode 100755
index 00000000..54a1515d
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/StatusLine.java
@@ -0,0 +1,90 @@
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Response;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/** An HTTP response status line like "HTTP/1.1 200 OK". */
+public final class StatusLine {
+ /** Numeric status code, 307: Temporary Redirect. */
+ public static final int HTTP_TEMP_REDIRECT = 307;
+ public static final int HTTP_CONTINUE = 100;
+
+ public final Protocol protocol;
+ public final int code;
+ public final String message;
+
+ public StatusLine(Protocol protocol, int code, String message) {
+ this.protocol = protocol;
+ this.code = code;
+ this.message = message;
+ }
+
+ public static StatusLine get(Response response) {
+ return new StatusLine(response.protocol(), response.code(), response.message());
+ }
+
+ public static StatusLine parse(String statusLine) throws IOException {
+ // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
+
+ // Parse protocol like "HTTP/1.1" followed by a space.
+ int codeStart;
+ Protocol protocol;
+ if (statusLine.startsWith("HTTP/1.")) {
+ if (statusLine.length() < 9 || statusLine.charAt(8) != ' ') {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ int httpMinorVersion = statusLine.charAt(7) - '0';
+ codeStart = 9;
+ if (httpMinorVersion == 0) {
+ protocol = Protocol.HTTP_1_0;
+ } else if (httpMinorVersion == 1) {
+ protocol = Protocol.HTTP_1_1;
+ } else {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ } else if (statusLine.startsWith("ICY ")) {
+ // Shoutcast uses ICY instead of "HTTP/1.0".
+ protocol = Protocol.HTTP_1_0;
+ codeStart = 4;
+ } else {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+
+ // Parse response code like "200". Always 3 digits.
+ if (statusLine.length() < codeStart + 3) {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ int code;
+ try {
+ code = Integer.parseInt(statusLine.substring(codeStart, codeStart + 3));
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+
+ // Parse an optional response message like "OK" or "Not Modified". If it
+ // exists, it is separated from the response code by a space.
+ String message = "";
+ if (statusLine.length() > codeStart + 3) {
+ if (statusLine.charAt(codeStart + 3) != ' ') {
+ throw new ProtocolException("Unexpected status line: " + statusLine);
+ }
+ message = statusLine.substring(codeStart + 4);
+ }
+
+ return new StatusLine(protocol, code, message);
+ }
+
+ @Override public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1");
+ result.append(' ').append(code);
+ if (message != null) {
+ result.append(' ').append(message);
+ }
+ return result.toString();
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/http/Transport.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/Transport.java
new file mode 100755
index 00000000..b8a9d6e1
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/http/Transport.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.http;
+
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okio.Sink;
+import com.contentstack.okio.Source;
+
+import java.io.IOException;
+
+public interface Transport {
+ /**
+ * The timeout to use while discarding a stream of input data. Since this is
+ * used for connection reuse, this timeout should be significantly less than
+ * the time it takes to establish a new connection.
+ */
+ int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
+
+ /**
+ * Returns an output stream where the request body can be written. The
+ * returned stream will of one of two publishType:
+ *
+ * - Direct. Bytes are written to the socket and
+ * forgotten. This is most efficient, particularly for large request
+ * bodies. The returned stream may be buffered; the caller must call
+ * {@link #flushRequest} before reading the response.
+ * - Buffered. Bytes are written to an in memory
+ * buffer, and must be explicitly flushed with a call to {@link
+ * #writeRequestBody}. This allows HTTP authorization (401, 407)
+ * responses to be retransmitted transparently.
+ *
+ */
+ // Don't bother retransmitting the request body? It's quite a corner
+ // case and there's uncertainty whether Firefox or Chrome do this
+ Sink createRequestBody(Request request) throws IOException;
+
+ /** This should update the HTTP engine's sentRequestMillis field. */
+ void writeRequestHeaders(Request request) throws IOException;
+
+ /**
+ * Sends the request body returned by {@link #createRequestBody} to the
+ * remote peer.
+ */
+ void writeRequestBody(RetryableSink requestBody) throws IOException;
+
+ /** Flush the request body to the underlying socket. */
+ void flushRequest() throws IOException;
+
+ /** Read response headers and update the cookie manager. */
+ Response.Builder readResponseHeaders() throws IOException;
+
+ /** Notify the transport that no response body will be read. */
+ void emptyTransferStream() throws IOException;
+
+ // Make this the content stream?
+ Source getTransferStream(CacheRequest cacheRequest) throws IOException;
+
+ /**
+ * Configures the response body to pool or close the socket connection when
+ * the response body is closed.
+ */
+ void releaseConnectionOnIdle() throws IOException;
+
+ void disconnect(HttpEngine engine) throws IOException;
+
+ /**
+ * Returns true if the socket connection held by this transport can be reused
+ * for a follow-up exchange.
+ */
+ boolean canReuseConnection();
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/CacheAdapter.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/CacheAdapter.java
new file mode 100755
index 00000000..2b2bb36c
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/CacheAdapter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp.internal.huc;
+
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.internal.InternalCache;
+import com.contentstack.okhttp.internal.http.CacheRequest;
+import com.contentstack.okhttp.internal.http.CacheStrategy;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Sink;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+/** Adapts {@link ResponseCache} to {@link InternalCache}. */
+public final class CacheAdapter implements InternalCache {
+ private final ResponseCache delegate;
+
+ public CacheAdapter(ResponseCache delegate) {
+ this.delegate = delegate;
+ }
+
+ public ResponseCache getDelegate() {
+ return delegate;
+ }
+
+ @Override public Response get(Request request) throws IOException {
+ CacheResponse javaResponse = getJavaCachedResponse(request);
+ if (javaResponse == null) {
+ return null;
+ }
+ return JavaApiConverter.createOkResponse(request, javaResponse);
+ }
+
+ @Override public CacheRequest put(Response response) throws IOException {
+ URI uri = response.request().uri();
+ HttpURLConnection connection = JavaApiConverter.createJavaUrlConnection(response);
+ final java.net.CacheRequest request = delegate.put(uri, connection);
+ if (request == null) {
+ return null;
+ }
+ return new CacheRequest() {
+ @Override public Sink body() throws IOException {
+ OutputStream body = request.getBody();
+ return body != null ? Okio.sink(body) : null;
+ }
+
+ @Override public void abort() {
+ request.abort();
+ }
+ };
+ }
+
+ @Override public void remove(Request request) throws IOException {
+ // This method is treated as optional and there is no obvious way of implementing it with
+ // ResponseCache. Removing items from the cache due to modifications made from this client is
+ // not essential given that modifications could be made from any other client. We have to assume
+ // that it's ok to keep using the cached data. Otherwise the server shouldn't declare it as
+ // cacheable or the client should be careful about caching it.
+ }
+
+ @Override public void update(Response cached, Response network) throws IOException {
+ // This method is treated as optional and there is no obvious way of implementing it with
+ // ResponseCache. Updating headers is useful if the server changes the metadata for a resource
+ // (e.g. max age) to extend or truncate the life of that resource in the cache. If the metadata
+ // is not updated the caching behavior may not be optimal, but will obey the metadata sent
+ // with the original cached response.
+ }
+
+ @Override public void trackConditionalCacheHit() {
+ // This method is optional.
+ }
+
+ @Override public void trackResponse(CacheStrategy cacheStrategy) {
+ // This method is optional.
+ }
+
+ /**
+ * Returns the {@link CacheResponse} from the delegate by converting the
+ * OkHttp {@link Request} into the arguments required by the {@link ResponseCache}.
+ */
+ private CacheResponse getJavaCachedResponse(Request request) throws IOException {
+ Map> headers = JavaApiConverter.extractJavaHeaders(request);
+ return delegate.get(request.uri(), request.method(), headers);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/DelegatingHttpsURLConnection.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/DelegatingHttpsURLConnection.java
new file mode 100755
index 00000000..b2fbd442
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/DelegatingHttpsURLConnection.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp.internal.huc;
+
+import com.contentstack.okhttp.Handshake;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Implement an HTTPS connection by delegating to an HTTP connection for
+ * everything but the HTTPS-specific stuff.
+ */
+abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
+ private final HttpURLConnection delegate;
+
+ public DelegatingHttpsURLConnection(HttpURLConnection delegate) {
+ super(delegate.getURL());
+ this.delegate = delegate;
+ }
+
+ protected abstract Handshake handshake();
+
+ @Override public abstract void setHostnameVerifier(HostnameVerifier hostnameVerifier);
+
+ @Override public abstract HostnameVerifier getHostnameVerifier();
+
+ @Override public abstract void setSSLSocketFactory(SSLSocketFactory sslSocketFactory);
+
+ @Override public abstract SSLSocketFactory getSSLSocketFactory();
+
+ @Override public String getCipherSuite() {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.cipherSuite() : null;
+ }
+
+ @Override public Certificate[] getLocalCertificates() {
+ Handshake handshake = handshake();
+ if (handshake == null) return null;
+ List result = handshake.localCertificates();
+ return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+ }
+
+ @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
+ Handshake handshake = handshake();
+ if (handshake == null) return null;
+ List result = handshake.peerCertificates();
+ return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+ }
+
+ @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.peerPrincipal() : null;
+ }
+
+ @Override public Principal getLocalPrincipal() {
+ Handshake handshake = handshake();
+ return handshake != null ? handshake.localPrincipal() : null;
+ }
+
+ @Override public void connect() throws IOException {
+ connected = true;
+ delegate.connect();
+ }
+
+ @Override public void disconnect() {
+ delegate.disconnect();
+ }
+
+ @Override public InputStream getErrorStream() {
+ return delegate.getErrorStream();
+ }
+
+ @Override public String getRequestMethod() {
+ return delegate.getRequestMethod();
+ }
+
+ @Override public int getResponseCode() throws IOException {
+ return delegate.getResponseCode();
+ }
+
+ @Override public String getResponseMessage() throws IOException {
+ return delegate.getResponseMessage();
+ }
+
+ @Override public void setRequestMethod(String method) throws ProtocolException {
+ delegate.setRequestMethod(method);
+ }
+
+ @Override public boolean usingProxy() {
+ return delegate.usingProxy();
+ }
+
+ @Override public boolean getInstanceFollowRedirects() {
+ return delegate.getInstanceFollowRedirects();
+ }
+
+ @Override public void setInstanceFollowRedirects(boolean followRedirects) {
+ delegate.setInstanceFollowRedirects(followRedirects);
+ }
+
+ @Override public boolean getAllowUserInteraction() {
+ return delegate.getAllowUserInteraction();
+ }
+
+ @Override public Object getContent() throws IOException {
+ return delegate.getContent();
+ }
+
+ @SuppressWarnings("unchecked") // Spec does not generify
+ @Override public Object getContent(Class[] types) throws IOException {
+ return delegate.getContent(types);
+ }
+
+ @Override public String getContentEncoding() {
+ return delegate.getContentEncoding();
+ }
+
+ @Override public int getContentLength() {
+ return delegate.getContentLength();
+ }
+
+ @Override public String getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override public long getDate() {
+ return delegate.getDate();
+ }
+
+ @Override public boolean getDefaultUseCaches() {
+ return delegate.getDefaultUseCaches();
+ }
+
+ @Override public boolean getDoInput() {
+ return delegate.getDoInput();
+ }
+
+ @Override public boolean getDoOutput() {
+ return delegate.getDoOutput();
+ }
+
+ @Override public long getExpiration() {
+ return delegate.getExpiration();
+ }
+
+ @Override public String getHeaderField(int pos) {
+ return delegate.getHeaderField(pos);
+ }
+
+ @Override public Map> getHeaderFields() {
+ return delegate.getHeaderFields();
+ }
+
+ @Override public Map> getRequestProperties() {
+ return delegate.getRequestProperties();
+ }
+
+ @Override public void addRequestProperty(String field, String newValue) {
+ delegate.addRequestProperty(field, newValue);
+ }
+
+ @Override public String getHeaderField(String key) {
+ return delegate.getHeaderField(key);
+ }
+
+ @Override public long getHeaderFieldDate(String field, long defaultValue) {
+ return delegate.getHeaderFieldDate(field, defaultValue);
+ }
+
+ @Override public int getHeaderFieldInt(String field, int defaultValue) {
+ return delegate.getHeaderFieldInt(field, defaultValue);
+ }
+
+ @Override public String getHeaderFieldKey(int position) {
+ return delegate.getHeaderFieldKey(position);
+ }
+
+ @Override public long getIfModifiedSince() {
+ return delegate.getIfModifiedSince();
+ }
+
+ @Override public InputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override public long getLastModified() {
+ return delegate.getLastModified();
+ }
+
+ @Override public OutputStream getOutputStream() throws IOException {
+ return delegate.getOutputStream();
+ }
+
+ @Override public Permission getPermission() throws IOException {
+ return delegate.getPermission();
+ }
+
+ @Override public String getRequestProperty(String field) {
+ return delegate.getRequestProperty(field);
+ }
+
+ @Override public URL getURL() {
+ return delegate.getURL();
+ }
+
+ @Override public boolean getUseCaches() {
+ return delegate.getUseCaches();
+ }
+
+ @Override public void setAllowUserInteraction(boolean newValue) {
+ delegate.setAllowUserInteraction(newValue);
+ }
+
+ @Override public void setDefaultUseCaches(boolean newValue) {
+ delegate.setDefaultUseCaches(newValue);
+ }
+
+ @Override public void setDoInput(boolean newValue) {
+ delegate.setDoInput(newValue);
+ }
+
+ @Override public void setDoOutput(boolean newValue) {
+ delegate.setDoOutput(newValue);
+ }
+
+ @Override public void setIfModifiedSince(long newValue) {
+ delegate.setIfModifiedSince(newValue);
+ }
+
+ @Override public void setRequestProperty(String field, String newValue) {
+ delegate.setRequestProperty(field, newValue);
+ }
+
+ @Override public void setUseCaches(boolean newValue) {
+ delegate.setUseCaches(newValue);
+ }
+
+ @Override public void setConnectTimeout(int timeoutMillis) {
+ delegate.setConnectTimeout(timeoutMillis);
+ }
+
+ @Override public int getConnectTimeout() {
+ return delegate.getConnectTimeout();
+ }
+
+ @Override public void setReadTimeout(int timeoutMillis) {
+ delegate.setReadTimeout(timeoutMillis);
+ }
+
+ @Override public int getReadTimeout() {
+ return delegate.getReadTimeout();
+ }
+
+ @Override public String toString() {
+ return delegate.toString();
+ }
+
+ @Override public void setFixedLengthStreamingMode(int contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+ }
+
+ @Override public void setChunkedStreamingMode(int chunkLength) {
+ delegate.setChunkedStreamingMode(chunkLength);
+ }
+
+ public long getContentLengthLong() {
+ return delegate.getContentLength();
+ }
+
+ public long getHeaderFieldLong(String field, long defaultValue) {
+ // Auto-generated method stub
+ return delegate.getHeaderFieldDate(field, defaultValue);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpURLConnectionImpl.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpURLConnectionImpl.java
new file mode 100755
index 00000000..85e7015e
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpURLConnectionImpl.java
@@ -0,0 +1,576 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.contentstack.okhttp.internal.huc;
+
+import com.contentstack.okhttp.Connection;
+import com.contentstack.okhttp.Handshake;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.OkHttpClient;
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.Route;
+import com.contentstack.okhttp.internal.Internal;
+import com.contentstack.okhttp.internal.Platform;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.HttpDate;
+import com.contentstack.okhttp.internal.http.HttpEngine;
+import com.contentstack.okhttp.internal.http.HttpMethod;
+import com.contentstack.okhttp.internal.http.OkHeaders;
+import com.contentstack.okhttp.internal.http.RetryableSink;
+import com.contentstack.okhttp.internal.http.StatusLine;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.Sink;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpRetryException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This implementation uses HttpEngine to send requests and receive responses.
+ * This class may use multiple HttpEngines to follow redirects, authentication
+ * retries, etc. to retrieve the final response body.
+ *
+ * What does 'connected' mean?
+ * This class inherits a {@code connected} field from the superclass. That field
+ * is not used to indicate not whether this URLConnection is
+ * currently connected. Instead, it indicates whether a connection has ever been
+ * attempted. Once a connection has been attempted, certain properties (request
+ * header fields, request method, etc.) are immutable.
+ */
+public class HttpURLConnectionImpl extends HttpURLConnection {
+
+ final OkHttpClient client;
+
+ private Headers.Builder requestHeaders = new Headers.Builder();
+
+ /** Like the superclass field of the same name, but a long and available on all platforms. */
+ private long fixedContentLength = -1;
+ private int redirectionCount;
+ protected IOException httpEngineFailure;
+ protected HttpEngine httpEngine;
+ /** Lazily created (with synthetic headers) on first call to getHeaders(). */
+ private Headers responseHeaders;
+
+ /**
+ * The most recently attempted route. This will be null if we haven't sent a
+ * request yet, or if the response comes from a cache.
+ */
+ private Route route;
+
+ /**
+ * The most recently received TLS handshake. This will be null if we haven't
+ * connected yet, or if the most recent connection was HTTP (and not HTTPS).
+ */
+ Handshake handshake;
+
+ public HttpURLConnectionImpl(URL url, OkHttpClient client) {
+ super(url);
+ this.client = client;
+ }
+
+ @Override public final void connect() throws IOException {
+ initHttpEngine();
+ boolean success;
+ do {
+ success = execute(false);
+ } while (!success);
+ }
+
+ @Override public final void disconnect() {
+ // Calling disconnect() before a connection exists should have no effect.
+ if (httpEngine == null) return;
+
+ httpEngine.disconnect();
+
+ // This doesn't close the stream because doing so would require all stream
+ // access to be synchronized. It's expected that the thread using the
+ // connection will close its streams directly. If it doesn't, the worst
+ // case is that the GzipSource's Inflater won't be released until it's
+ // finalized. (This logs a warning on Android.)
+ }
+
+ /**
+ * Returns an input stream from the server in the case of error such as the
+ * requested file (txt, htm, html) is not found on the remote server.
+ */
+ @Override public final InputStream getErrorStream() {
+ try {
+ HttpEngine response = getResponse();
+ if (response.hasResponseBody() && response.getResponse().code() >= HTTP_BAD_REQUEST) {
+ return response.getResponseBodyBytes();
+ }
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private Headers getHeaders() throws IOException {
+ if (responseHeaders == null) {
+ Response response = getResponse().getResponse();
+ Headers headers = response.headers();
+
+ responseHeaders = headers.newBuilder()
+ .add(Platform.get().getPrefix() + "-Response-Source", responseSourceHeader(response))
+ .build();
+ }
+ return responseHeaders;
+ }
+
+ private static String responseSourceHeader(Response response) {
+ if (response.networkResponse() == null) {
+ if (response.cacheResponse() == null) {
+ return "NONE";
+ }
+ return "CACHE " + response.code();
+ }
+ if (response.cacheResponse() == null) {
+ return "NETWORK " + response.code();
+ }
+ return "CONDITIONAL_CACHE " + response.networkResponse().code();
+ }
+
+ /**
+ * Returns the value of the field at {@code position}. Returns null if there
+ * are fewer than {@code position} headers.
+ */
+ @Override public final String getHeaderField(int position) {
+ try {
+ return getHeaders().value(position);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value of the field corresponding to the {@code fieldName}, or
+ * null if there is no such field. If the field has multiple values, the
+ * last value is returned.
+ */
+ @Override public final String getHeaderField(String fieldName) {
+ try {
+ return fieldName == null
+ ? StatusLine.get(getResponse().getResponse()).toString()
+ : getHeaders().get(fieldName);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Override public final String getHeaderFieldKey(int position) {
+ try {
+ return getHeaders().name(position);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Override public final Map> getHeaderFields() {
+ try {
+ return OkHeaders.toMultimap(getHeaders(),
+ StatusLine.get(getResponse().getResponse()).toString());
+ } catch (IOException e) {
+ return Collections.emptyMap();
+ }
+ }
+
+ @Override public final Map> getRequestProperties() {
+ if (connected) {
+ throw new IllegalStateException(
+ "Cannot access request header fields after connection is set");
+ }
+
+ return OkHeaders.toMultimap(requestHeaders.build(), null);
+ }
+
+ @Override public final InputStream getInputStream() throws IOException {
+ if (!doInput) {
+ throw new ProtocolException("This protocol does not support input");
+ }
+
+ HttpEngine response = getResponse();
+
+ // if the requested file does not exist, throw an exception formerly the
+ // Error page from the server was returned if the requested file was
+ // text/html this has changed to return FileNotFoundException for all
+ // file publishType
+ if (getResponseCode() >= HTTP_BAD_REQUEST) {
+ throw new FileNotFoundException(url.toString());
+ }
+
+ InputStream result = response.getResponseBodyBytes();
+ if (result == null) {
+ throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
+ }
+ return result;
+ }
+
+ @Override public final OutputStream getOutputStream() throws IOException {
+ connect();
+
+ BufferedSink sink = httpEngine.getBufferedRequestBody();
+ if (sink == null) {
+ throw new ProtocolException("method does not support a request body: " + method);
+ } else if (httpEngine.hasResponse()) {
+ throw new ProtocolException("cannot write request body after response has been read");
+ }
+
+ return sink.outputStream();
+ }
+
+ @Override public final Permission getPermission() throws IOException {
+ String hostName = getURL().getHost();
+ int hostPort = Util.getEffectivePort(getURL());
+ if (usingProxy()) {
+ InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
+ hostName = proxyAddress.getHostName();
+ hostPort = proxyAddress.getPort();
+ }
+ return new SocketPermission(hostName + ":" + hostPort, "connect, resolve");
+ }
+
+ @Override public final String getRequestProperty(String field) {
+ if (field == null) return null;
+ return requestHeaders.get(field);
+ }
+
+ @Override public void setConnectTimeout(int timeoutMillis) {
+ client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void setInstanceFollowRedirects(boolean followRedirects) {
+ client.setFollowRedirects(followRedirects);
+ }
+
+ @Override public int getConnectTimeout() {
+ return client.getConnectTimeout();
+ }
+
+ @Override public void setReadTimeout(int timeoutMillis) {
+ client.setReadTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override public int getReadTimeout() {
+ return client.getReadTimeout();
+ }
+
+ private void initHttpEngine() throws IOException {
+ if (httpEngineFailure != null) {
+ throw httpEngineFailure;
+ } else if (httpEngine != null) {
+ return;
+ }
+
+ connected = true;
+ try {
+ if (doOutput) {
+ if (method.equals("GET")) {
+ // they are requesting a stream to write to. This implies a POST method
+ method = "POST";
+ } else if (!HttpMethod.hasRequestBody(method)) {
+ // If the request method is neither POST nor PUT nor PATCH, then you're not writing
+ throw new ProtocolException(method + " does not support writing");
+ }
+ }
+ // If the user set content length to zero, we know there will not be a request body.
+ RetryableSink requestBody = doOutput && fixedContentLength == 0 ? Util.emptySink() : null;
+ httpEngine = newHttpEngine(method, null, requestBody, null);
+ } catch (IOException e) {
+ httpEngineFailure = e;
+ throw e;
+ }
+ }
+
+ private HttpEngine newHttpEngine(String method, Connection connection,
+ RetryableSink requestBody, Response priorResponse) {
+ Request.Builder builder = new Request.Builder()
+ .url(getURL())
+ .method(method, null /* No body; that's passed separately. */);
+ Headers headers = requestHeaders.build();
+ for (int i = 0; i < headers.size(); i++) {
+ builder.addHeader(headers.name(i), headers.value(i));
+ }
+
+ boolean bufferRequestBody = false;
+ if (HttpMethod.hasRequestBody(method)) {
+ // Specify how the request body is terminated.
+ if (fixedContentLength != -1) {
+ builder.header("Content-Length", Long.toString(fixedContentLength));
+ } else if (chunkLength > 0) {
+ builder.header("Transfer-Encoding", "chunked");
+ } else {
+ bufferRequestBody = true;
+ }
+
+ // Add a content type for the request body, if one isn't already present.
+ if (headers.get("Content-Type") == null) {
+ builder.header("Content-Type", "application/x-www-form-urlencoded");
+ }
+ }
+
+ if (headers.get("User-Agent") == null) {
+ builder.header("User-Agent", defaultUserAgent());
+ }
+
+ Request request = builder.build();
+
+ // If we're currently not using caches, make sure the engine's client doesn't have one.
+ OkHttpClient engineClient = client;
+ if (Internal.instance.internalCache(engineClient) != null && !getUseCaches()) {
+ engineClient = client.clone().setCache(null);
+ }
+
+ return new HttpEngine(engineClient, request, bufferRequestBody, connection, null, requestBody,
+ priorResponse);
+ }
+
+ private String defaultUserAgent() {
+ String agent = System.getProperty("http.agent");
+ return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+ }
+
+ /**
+ * Aggressively tries to get the final HTTP response, potentially making
+ * many HTTP requests in the process in order to cope with redirects and
+ * authentication.
+ */
+ private HttpEngine getResponse() throws IOException {
+ initHttpEngine();
+
+ if (httpEngine.hasResponse()) {
+ return httpEngine;
+ }
+
+ while (true) {
+ if (!execute(true)) {
+ continue;
+ }
+
+ Response response = httpEngine.getResponse();
+ Request followUp = httpEngine.followUpRequest();
+
+ if (followUp == null) {
+ httpEngine.releaseConnection();
+ return httpEngine;
+ }
+
+ if (response.isRedirect() && ++redirectionCount > HttpEngine.MAX_REDIRECTS) {
+ throw new ProtocolException("Too many redirects: " + redirectionCount);
+ }
+
+ // The first request was insufficient. Prepare for another...
+ url = followUp.url();
+ requestHeaders = followUp.headers().newBuilder();
+
+ // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM redirect
+ // should keep the same method, Chrome, Firefox and the RI all issue GETs
+ // when following any redirect.
+ Sink requestBody = httpEngine.getRequestBody();
+ if (!followUp.method().equals(method)) {
+ requestBody = null;
+ }
+
+ if (requestBody != null && !(requestBody instanceof RetryableSink)) {
+ throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
+ }
+
+ if (!httpEngine.sameConnection(followUp.url())) {
+ httpEngine.releaseConnection();
+ }
+
+ Connection connection = httpEngine.close();
+ httpEngine = newHttpEngine(followUp.method(), connection, (RetryableSink) requestBody,
+ response);
+ }
+ }
+
+ /**
+ * Sends a request and optionally reads a response. Returns true if the
+ * request was successfully executed, and false if the request can be
+ * retried. Throws an exception if the request failed permanently.
+ */
+ private boolean execute(boolean readResponse) throws IOException {
+ try {
+ httpEngine.sendRequest();
+ route = httpEngine.getRoute();
+ handshake = httpEngine.getConnection() != null
+ ? httpEngine.getConnection().getHandshake()
+ : null;
+ if (readResponse) {
+ httpEngine.readResponse();
+ }
+
+ return true;
+ } catch (IOException e) {
+ HttpEngine retryEngine = httpEngine.recover(e);
+ if (retryEngine != null) {
+ httpEngine = retryEngine;
+ return false;
+ }
+
+ // Give up; recovery is not possible.
+ httpEngineFailure = e;
+ throw e;
+ }
+ }
+
+ /**
+ * Returns true if either:
+ *
+ * - A specific proxy was explicitly configured for this connection.
+ *
- The response has already been retrieved, and a proxy was {@link
+ * java.net.ProxySelector selected} in order to get it.
+ *
+ *
+ * Warning: This method may return false before attempting
+ * to connect and true afterwards.
+ */
+ @Override public final boolean usingProxy() {
+ Proxy proxy = route != null
+ ? route.getProxy()
+ : client.getProxy();
+ return proxy != null && proxy.type() != Proxy.Type.DIRECT;
+ }
+
+ @Override public String getResponseMessage() throws IOException {
+ return getResponse().getResponse().message();
+ }
+
+ @Override public final int getResponseCode() throws IOException {
+ return getResponse().getResponse().code();
+ }
+
+ @Override public final void setRequestProperty(String field, String newValue) {
+ if (connected) {
+ throw new IllegalStateException("Cannot set request property after connection is made");
+ }
+ if (field == null) {
+ throw new NullPointerException("field == null");
+ }
+ if (newValue == null) {
+ // Silently ignore null header values for backwards compatibility with older
+ // android versions as well as with other URLConnection implementations.
+ //
+ // Some implementations send a malformed HTTP header when faced with
+ // such requests, we respect the spec and ignore the header.
+ Platform.get().logW("Ignoring header " + field + " because its value was null.");
+ return;
+ }
+
+ // Deprecate use of X-Android-Transports header?
+ if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+ setProtocols(newValue, false /* append */);
+ } else {
+ requestHeaders.set(field, newValue);
+ }
+ }
+
+ @Override public void setIfModifiedSince(long newValue) {
+ super.setIfModifiedSince(newValue);
+ if (ifModifiedSince != 0) {
+ requestHeaders.set("If-Modified-Since", HttpDate.format(new Date(ifModifiedSince)));
+ } else {
+ requestHeaders.removeAll("If-Modified-Since");
+ }
+ }
+
+ @Override public final void addRequestProperty(String field, String value) {
+ if (connected) {
+ throw new IllegalStateException("Cannot add request property after connection is made");
+ }
+ if (field == null) {
+ throw new NullPointerException("field == null");
+ }
+ if (value == null) {
+ // Silently ignore null header values for backwards compatibility with older
+ // android versions as well as with other URLConnection implementations.
+ //
+ // Some implementations send a malformed HTTP header when faced with
+ // such requests, we respect the spec and ignore the header.
+ Platform.get().logW("Ignoring header " + field + " because its value was null.");
+ return;
+ }
+
+ // Deprecate use of X-Android-Transports header?
+ if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+ setProtocols(value, true /* append */);
+ } else {
+ requestHeaders.add(field, value);
+ }
+ }
+
+ /*
+ * Splits and validates a comma-separated string of protocols.
+ * When append == false, we require that the transport list contains "http/1.1".
+ * Throws {@link IllegalStateException} when one of the protocols isn't
+ * defined in {@link Protocol OkHttp's protocol enumeration}.
+ */
+ private void setProtocols(String protocolsString, boolean append) {
+ List protocolsList = new ArrayList();
+ if (append) {
+ protocolsList.addAll(client.getProtocols());
+ }
+ for (String protocol : protocolsString.split(",", -1)) {
+ try {
+ protocolsList.add(Protocol.get(protocol));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ client.setProtocols(protocolsList);
+ }
+
+ @Override public void setRequestMethod(String method) throws ProtocolException {
+ if (!HttpMethod.METHODS.contains(method)) {
+ throw new ProtocolException(
+ "Expected one of " + HttpMethod.METHODS + " but was " + method);
+ }
+ this.method = method;
+ }
+
+ @Override public void setFixedLengthStreamingMode(int contentLength) {
+ //setFixedLengthStreamingMode(contentLength);
+ if (super.connected) throw new IllegalStateException("Already connected");
+ if (chunkLength > 0) throw new IllegalStateException("Already in chunked mode");
+ if (contentLength < 0) throw new IllegalArgumentException("contentLength < 0");
+ this.fixedContentLength = contentLength;
+ super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);
+ }
+
+ public long getHeaderFieldLong(String field, long defaultValue) {
+ // Auto-generated method stub
+ return getHeaderFieldDate(field, defaultValue);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpsURLConnectionImpl.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpsURLConnectionImpl.java
new file mode 100755
index 00000000..b6f9146e
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/HttpsURLConnectionImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.contentstack.okhttp.internal.huc;
+
+import com.contentstack.okhttp.Handshake;
+import com.contentstack.okhttp.OkHttpClient;
+
+import java.net.URL;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+public final class HttpsURLConnectionImpl extends DelegatingHttpsURLConnection {
+ private final HttpURLConnectionImpl delegate;
+
+ public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
+ this(new HttpURLConnectionImpl(url, client));
+ }
+
+ public HttpsURLConnectionImpl(HttpURLConnectionImpl delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ @Override protected Handshake handshake() {
+ if (delegate.httpEngine == null) {
+ throw new IllegalStateException("Connection has not yet been established");
+ }
+
+ // If there's a response, get the handshake from there so that caching
+ // works. Otherwise get the handshake from the connection because we might
+ // have not connected yet.
+ return delegate.httpEngine.hasResponse()
+ ? delegate.httpEngine.getResponse().handshake()
+ : delegate.handshake;
+ }
+
+ @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ delegate.client.setHostnameVerifier(hostnameVerifier);
+ }
+
+ @Override public HostnameVerifier getHostnameVerifier() {
+ return delegate.client.getHostnameVerifier();
+ }
+
+ @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+ delegate.client.setSslSocketFactory(sslSocketFactory);
+ }
+
+ @Override public SSLSocketFactory getSSLSocketFactory() {
+ return delegate.client.getSslSocketFactory();
+ }
+
+ @Override public long getContentLengthLong() {
+ return 0;
+ }
+
+ @Override
+ public void setFixedLengthStreamingMode(int contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+
+ }
+
+ @Override public long getHeaderFieldLong(String field, long defaultValue) {
+ return delegate.getHeaderFieldLong(field, defaultValue);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/JavaApiConverter.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/JavaApiConverter.java
new file mode 100755
index 00000000..bd0790d0
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/huc/JavaApiConverter.java
@@ -0,0 +1,688 @@
+/*
+ * Copyright (C) 2014 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.contentstack.okhttp.internal.huc;
+
+import com.contentstack.okhttp.Handshake;
+import com.contentstack.okhttp.Headers;
+import com.contentstack.okhttp.MediaType;
+import com.contentstack.okhttp.Request;
+import com.contentstack.okhttp.Response;
+import com.contentstack.okhttp.ResponseBody;
+import com.contentstack.okhttp.internal.Util;
+import com.contentstack.okhttp.internal.http.OkHeaders;
+import com.contentstack.okhttp.internal.http.StatusLine;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.Okio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Helper methods that convert between Java and OkHttp representations.
+ */
+public final class JavaApiConverter {
+
+ private JavaApiConverter() {
+ }
+
+ /**
+ * Creates an OkHttp {@link Response} using the supplied {@link URI} and {@link URLConnection}
+ * to supply the data. The URLConnection is assumed to already be connected.
+ */
+ public static Response createOkResponse(URI uri, URLConnection urlConnection) throws IOException {
+ HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
+
+ Response.Builder okResponseBuilder = new Response.Builder();
+
+ // Request: Create one from the URL connection.
+ // A connected HttpURLConnection does not permit access to request headers.
+ Map> requestHeaders = null;
+ Request okRequest = createOkRequest(uri, httpUrlConnection.getRequestMethod(), requestHeaders);
+ okResponseBuilder.request(okRequest);
+
+ // Status line
+ StatusLine statusLine = StatusLine.parse(extractStatusLine(httpUrlConnection));
+ okResponseBuilder.protocol(statusLine.protocol);
+ okResponseBuilder.code(statusLine.code);
+ okResponseBuilder.message(statusLine.message);
+
+ // Response headers
+ Headers okHeaders = extractOkResponseHeaders(httpUrlConnection);
+ okResponseBuilder.headers(okHeaders);
+
+ // Response body
+ ResponseBody okBody = createOkBody(okHeaders, urlConnection.getInputStream());
+ okResponseBuilder.body(okBody);
+
+ // Handle SSL handshake information as needed.
+ if (httpUrlConnection instanceof HttpsURLConnection) {
+ HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) httpUrlConnection;
+
+ Certificate[] peerCertificates;
+ try {
+ peerCertificates = httpsUrlConnection.getServerCertificates();
+ } catch (SSLPeerUnverifiedException e) {
+ peerCertificates = null;
+ }
+
+ Certificate[] localCertificates = httpsUrlConnection.getLocalCertificates();
+
+ Handshake handshake = Handshake.get(
+ httpsUrlConnection.getCipherSuite(), nullSafeImmutableList(peerCertificates),
+ nullSafeImmutableList(localCertificates));
+ okResponseBuilder.handshake(handshake);
+ }
+
+ return okResponseBuilder.build();
+ }
+
+ /**
+ * Creates an OkHttp {@link Response} using the supplied {@link Request} and {@link CacheResponse}
+ * to supply the data.
+ */
+ static Response createOkResponse(Request request, CacheResponse javaResponse)
+ throws IOException {
+ Response.Builder okResponseBuilder = new Response.Builder();
+
+ // Request: Use the one provided.
+ okResponseBuilder.request(request);
+
+ // Status line: Java has this as one of the headers.
+ StatusLine statusLine = StatusLine.parse(extractStatusLine(javaResponse));
+ okResponseBuilder.protocol(statusLine.protocol);
+ okResponseBuilder.code(statusLine.code);
+ okResponseBuilder.message(statusLine.message);
+
+ // Response headers
+ Headers okHeaders = extractOkHeaders(javaResponse);
+ okResponseBuilder.headers(okHeaders);
+
+ // Response body
+ ResponseBody okBody = createOkBody(okHeaders, javaResponse.getBody());
+ okResponseBuilder.body(okBody);
+
+ // Handle SSL handshake information as needed.
+ if (javaResponse instanceof SecureCacheResponse) {
+ SecureCacheResponse javaSecureCacheResponse = (SecureCacheResponse) javaResponse;
+
+ // Handshake doesn't support null lists.
+ List peerCertificates;
+ try {
+ peerCertificates = javaSecureCacheResponse.getServerCertificateChain();
+ } catch (SSLPeerUnverifiedException e) {
+ peerCertificates = Collections.emptyList();
+ }
+ List localCertificates = javaSecureCacheResponse.getLocalCertificateChain();
+ if (localCertificates == null) {
+ localCertificates = Collections.emptyList();
+ }
+ Handshake handshake = Handshake.get(
+ javaSecureCacheResponse.getCipherSuite(), peerCertificates, localCertificates);
+ okResponseBuilder.handshake(handshake);
+ }
+
+ return okResponseBuilder.build();
+ }
+
+ /**
+ * Creates an OkHttp {@link Request} from the supplied information.
+ *
+ * This method allows a {@code null} value for {@code requestHeaders} for situations
+ * where a connection is already connected and access to the headers has been lost.
+ * See {@link java.net.HttpURLConnection#getRequestProperties()} for details.
+ */
+ public static Request createOkRequest(
+ URI uri, String requestMethod, Map> requestHeaders) {
+
+ Request.Builder builder = new Request.Builder()
+ .url(uri.toString())
+ .method(requestMethod, null);
+
+ if (requestHeaders != null) {
+ Headers headers = extractOkHeaders(requestHeaders);
+ builder.headers(headers);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link java.net.CacheResponse} of the correct (sub)type using information
+ * gathered from the supplied {@link Response}.
+ */
+ public static CacheResponse createJavaCacheResponse(final Response response) {
+ final Headers headers = response.headers();
+ final ResponseBody body = response.body();
+ if (response.request().isHttps()) {
+ final Handshake handshake = response.handshake();
+ return new SecureCacheResponse() {
+ @Override
+ public String getCipherSuite() {
+ return handshake != null ? handshake.cipherSuite() : null;
+ }
+
+ @Override
+ public List getLocalCertificateChain() {
+ if (handshake == null) return null;
+ // Java requires null, not an empty list here.
+ List certificates = handshake.localCertificates();
+ return certificates.size() > 0 ? certificates : null;
+ }
+
+ @Override
+ public List getServerCertificateChain() throws SSLPeerUnverifiedException {
+ if (handshake == null) return null;
+ // Java requires null, not an empty list here.
+ List certificates = handshake.peerCertificates();
+ return certificates.size() > 0 ? certificates : null;
+ }
+
+ @Override
+ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ if (handshake == null) return null;
+ return handshake.peerPrincipal();
+ }
+
+ @Override
+ public Principal getLocalPrincipal() {
+ if (handshake == null) return null;
+ return handshake.localPrincipal();
+ }
+
+ @Override
+ public Map> getHeaders() throws IOException {
+ // Java requires that the entry with a null key be the status line.
+ return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ if (body == null) return null;
+ return body.byteStream();
+ }
+ };
+ } else {
+ return new CacheResponse() {
+ @Override
+ public Map> getHeaders() throws IOException {
+ // Java requires that the entry with a null key be the status line.
+ return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ if (body == null) return null;
+ return body.byteStream();
+ }
+ };
+ }
+ }
+
+ /**
+ * Creates an {@link java.net.HttpURLConnection} of the correct subclass from the supplied OkHttp
+ * {@link Response}.
+ */
+ static HttpURLConnection createJavaUrlConnection(Response okResponse) {
+ Request request = okResponse.request();
+ // Create an object of the correct class in case the ResponseCache uses instanceof.
+ if (request.isHttps()) {
+ return new CacheHttpsURLConnection(new CacheHttpURLConnection(okResponse));
+ } else {
+ return new CacheHttpURLConnection(okResponse);
+ }
+ }
+
+
+ static Map> extractJavaHeaders(Request request) {
+ return OkHeaders.toMultimap(request.headers(), null);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link java.net.CacheResponse}. Only real headers are
+ * extracted. See {@link #extractStatusLine(java.net.CacheResponse)}.
+ */
+ private static Headers extractOkHeaders(CacheResponse javaResponse) throws IOException {
+ Map> javaResponseHeaders = javaResponse.getHeaders();
+ return extractOkHeaders(javaResponseHeaders);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link java.net.HttpURLConnection}. Only real headers
+ * are extracted. See {@link #extractStatusLine(java.net.HttpURLConnection)}.
+ */
+ private static Headers extractOkResponseHeaders(HttpURLConnection httpUrlConnection) {
+ Map> javaResponseHeaders = httpUrlConnection.getHeaderFields();
+ return extractOkHeaders(javaResponseHeaders);
+ }
+
+ /**
+ * Extracts OkHttp headers from the supplied {@link Map}. Only real headers are
+ * extracted. Any entry (one with a {@code null} key) is discarded.
+ */
+ // @VisibleForTesting
+ static Headers extractOkHeaders(Map> javaHeaders) {
+ Headers.Builder okHeadersBuilder = new Headers.Builder();
+ for (Map.Entry> javaHeader : javaHeaders.entrySet()) {
+ String name = javaHeader.getKey();
+ if (name == null) {
+ // The Java API uses the null key to store the status line in responses.
+ // Earlier versions of OkHttp would use the null key to store the "request line" in
+ // requests. e.g. "GET / HTTP 1.1". Although this is no longer the case it must be
+ // explicitly ignored because Headers.Builder does not support null keys.
+ continue;
+ }
+ for (String value : javaHeader.getValue()) {
+ okHeadersBuilder.add(name, value);
+ }
+ }
+ return okHeadersBuilder.build();
+ }
+
+ /**
+ * Extracts the status line from the supplied Java API {@link java.net.HttpURLConnection}.
+ * As per the spec, the status line is held as the header with the null key. Returns {@code null}
+ * if there is no status line.
+ */
+ private static String extractStatusLine(HttpURLConnection httpUrlConnection) {
+ // Java specifies that this will be be response header with a null key.
+ return httpUrlConnection.getHeaderField(null);
+ }
+
+ /**
+ * Extracts the status line from the supplied Java API {@link java.net.CacheResponse}.
+ * As per the spec, the status line is held as the header with the null key. Returns {@code null}
+ * if there is no status line.
+ */
+ private static String extractStatusLine(CacheResponse javaResponse) throws IOException {
+ Map> javaResponseHeaders = javaResponse.getHeaders();
+ return extractStatusLine(javaResponseHeaders);
+ }
+
+ // VisibleForTesting
+ static String extractStatusLine(Map> javaResponseHeaders) {
+ List values = javaResponseHeaders.get(null);
+ if (values == null || values.size() == 0) {
+ return null;
+ }
+ return values.get(0);
+ }
+
+ /**
+ * Creates an OkHttp Response.Body containing the supplied information.
+ */
+ private static ResponseBody createOkBody(final Headers okHeaders, InputStream body) {
+ final BufferedSource source = Okio.buffer(Okio.source(body));
+ return new ResponseBody() {
+ @Override public MediaType contentType() {
+ String contentTypeHeader = okHeaders.get("Content-Type");
+ return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader);
+ }
+ @Override public long contentLength() {
+ return OkHeaders.contentLength(okHeaders);
+ }
+ @Override public BufferedSource source() {
+ return source;
+ }
+ };
+ }
+
+ /**
+ * An {@link java.net.HttpURLConnection} that represents an HTTP request at the point where
+ * the request has been made, and the response headers have been received, but the body content,
+ * if present, has not been read yet. This intended to provide enough information for
+ * {@link java.net.ResponseCache} subclasses and no more.
+ *
+ * Much of the method implementations are overrides to delegate to the OkHttp request and
+ * response, or to deny access to information as a real HttpURLConnection would after connection.
+ */
+ private static final class CacheHttpURLConnection extends HttpURLConnection {
+
+ private final Request request;
+ private final Response response;
+
+ public CacheHttpURLConnection(Response response) {
+ super(response.request().url());
+ this.request = response.request();
+ this.response = response;
+
+ // Configure URLConnection inherited fields.
+ this.connected = true;
+ this.doOutput = response.body() == null;
+
+ // Configure HttpUrlConnection inherited fields.
+ this.method = request.method();
+ }
+
+ // HTTP connection lifecycle methods
+
+ @Override
+ public void connect() throws IOException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void disconnect() {
+ throw throwRequestModificationException();
+ }
+
+ // HTTP Request methods
+
+ @Override
+ public void setRequestProperty(String key, String value) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void addRequestProperty(String key, String value) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public String getRequestProperty(String key) {
+ return request.header(key);
+ }
+
+ @Override
+ public Map> getRequestProperties() {
+ // This is to preserve RI and compatibility with OkHttp's HttpURLConnectionImpl. There seems
+ // no good reason why this should fail while getRequestProperty() is ok.
+ throw throwRequestHeaderAccessException();
+ }
+
+ @Override
+ public void setFixedLengthStreamingMode(int contentLength) {
+ throw throwRequestModificationException();
+ }
+
+
+
+ /*@Override
+ public void setFixedLengthStreamingMode(long contentLength) {
+ throw throwRequestModificationException();
+ }*/
+
+ @Override
+ public void setChunkedStreamingMode(int chunklen) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setInstanceFollowRedirects(boolean followRedirects) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getInstanceFollowRedirects() {
+ // Return the platform default.
+ return super.getInstanceFollowRedirects();
+ }
+
+ @Override
+ public void setRequestMethod(String method) throws ProtocolException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public String getRequestMethod() {
+ return request.method();
+ }
+
+ // HTTP Response methods
+
+ @Override
+ public String getHeaderFieldKey(int position) {
+ // Deal with index 0 meaning "status line"
+ if (position < 0) {
+ throw new IllegalArgumentException("Invalid header index: " + position);
+ }
+ if (position == 0) {
+ return null;
+ }
+ return response.headers().name(position - 1);
+ }
+
+ @Override
+ public String getHeaderField(int position) {
+ // Deal with index 0 meaning "status line"
+ if (position < 0) {
+ throw new IllegalArgumentException("Invalid header index: " + position);
+ }
+ if (position == 0) {
+ return StatusLine.get(response).toString();
+ }
+ return response.headers().value(position - 1);
+ }
+
+ @Override
+ public String getHeaderField(String fieldName) {
+ return fieldName == null
+ ? StatusLine.get(response).toString()
+ : response.headers().get(fieldName);
+ }
+
+ @Override
+ public Map> getHeaderFields() {
+ return OkHeaders.toMultimap(response.headers(), StatusLine.get(response).toString());
+ }
+
+ @Override
+ public int getResponseCode() throws IOException {
+ return response.code();
+ }
+
+ @Override
+ public String getResponseMessage() throws IOException {
+ return response.message();
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return null;
+ }
+
+ // HTTP miscellaneous methods
+
+ @Override
+ public boolean usingProxy() {
+ // It's safe to return false here, even if a proxy is in use. The problem is we don't
+ // necessarily know if we're going to use a proxy by the time we ask the cache for a response.
+ return false;
+ }
+
+ // URLConnection methods
+
+ @Override
+ public void setConnectTimeout(int timeout) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public int getConnectTimeout() {
+ // Impossible to say.
+ return 0;
+ }
+
+ @Override
+ public void setReadTimeout(int timeout) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public int getReadTimeout() {
+ // Impossible to say.
+ return 0;
+ }
+
+ @Override
+ public Object getContent() throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public Object getContent(Class[] classes) throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ throw throwResponseBodyAccessException();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public void setDoInput(boolean doInput) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getDoInput() {
+ return true;
+ }
+
+ @Override
+ public void setDoOutput(boolean doOutput) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getDoOutput() {
+ return request.body() != null;
+ }
+
+ @Override
+ public void setAllowUserInteraction(boolean allowUserInteraction) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getAllowUserInteraction() {
+ return false;
+ }
+
+ @Override
+ public void setUseCaches(boolean useCaches) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public boolean getUseCaches() {
+ return super.getUseCaches();
+ }
+
+ @Override
+ public void setIfModifiedSince(long ifModifiedSince) {
+ throw throwRequestModificationException();
+ }
+
+ @Override
+ public long getIfModifiedSince() {
+ return 0;
+ }
+
+ @Override
+ public boolean getDefaultUseCaches() {
+ return super.getDefaultUseCaches();
+ }
+
+ @Override
+ public void setDefaultUseCaches(boolean defaultUseCaches) {
+ super.setDefaultUseCaches(defaultUseCaches);
+ }
+ }
+
+ /** An HttpsURLConnection to offer to the cache. */
+ private static final class CacheHttpsURLConnection extends DelegatingHttpsURLConnection {
+ private final CacheHttpURLConnection delegate;
+
+ public CacheHttpsURLConnection(CacheHttpURLConnection delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ @Override protected Handshake handshake() {
+ return delegate.response.handshake();
+ }
+
+ @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ throw throwRequestModificationException();
+ }
+
+ @Override public HostnameVerifier getHostnameVerifier() {
+ throw throwRequestSslAccessException();
+ }
+
+ @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) {
+ throw throwRequestModificationException();
+ }
+
+ @Override public SSLSocketFactory getSSLSocketFactory() {
+ throw throwRequestSslAccessException();
+ }
+
+ @Override public long getContentLengthLong() {
+ return delegate.getContentLength();
+ }
+
+ @Override public void setFixedLengthStreamingMode(int contentLength) {
+ delegate.setFixedLengthStreamingMode(contentLength);
+ }
+
+ @Override public long getHeaderFieldLong(String field, long defaultValue) {
+ return delegate.getHeaderFieldInt(field, (int) defaultValue);
+ }
+ }
+
+ private static RuntimeException throwRequestModificationException() {
+ throw new UnsupportedOperationException("ResponseCache cannot modify the request.");
+ }
+
+ private static RuntimeException throwRequestHeaderAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access request headers");
+ }
+
+ private static RuntimeException throwRequestSslAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access SSL internals");
+ }
+
+ private static RuntimeException throwResponseBodyAccessException() {
+ throw new UnsupportedOperationException("ResponseCache cannot access the response body.");
+ }
+
+ private static List nullSafeImmutableList(T[] elements) {
+ return elements == null ? Collections.emptyList() : Util.immutableList(elements);
+ }
+
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/ErrorCode.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/ErrorCode.java
new file mode 100755
index 00000000..287c4585
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/ErrorCode.java
@@ -0,0 +1,89 @@
+/*
+ * 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.contentstack.okhttp.internal.spdy;
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-7
+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),
+
+ CONNECT_ERROR(10, -1, -1),
+
+ ENHANCE_YOUR_CALM(11, -1, -1),
+
+ INADEQUATE_SECURITY(12, -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/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameReader.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameReader.java
new file mode 100755
index 00000000..07acafc2
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameReader.java
@@ -0,0 +1,138 @@
+/*
+ * 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.contentstack.okhttp.internal.spdy;
+
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.ByteString;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+/** Reads transport frames for SPDY/3 or HTTP/2. */
+public interface FrameReader extends Closeable {
+ void readConnectionPreface() throws IOException;
+ boolean nextFrame(Handler handler) throws IOException;
+
+ public interface Handler {
+ void data(boolean inFinished, int streamId, BufferedSource source, 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 HEADERS and PUSH_PROMISE.
+ *
+ * @param outFinished true if the receiver should not send further frames.
+ * @param inFinished true if the sender will not send further frames.
+ * @param streamId the stream owning these headers.
+ * @param associatedStreamId the stream that triggered the sender to create
+ * this stream.
+ */
+ void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+ List headerBlock, HeadersMode headersMode);
+ void rstStream(int streamId, ErrorCode errorCode);
+ void settings(boolean clearPrevious, Settings settings);
+
+ /** HTTP/2 only. */
+ void ackSettings();
+
+ /**
+ * Read a connection-level ping from the peer. {@code ack} indicates this
+ * is a reply. Payload parameters are different between SPDY/3 and HTTP/2.
+ *
+ * In SPDY/3, only the first {@code payload1} parameter is set. If the
+ * reader is a client, it is an unsigned even number. Likewise, a server
+ * will receive an odd number.
+ *
+ * In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+ * set. The data is opaque binary, and there are no rules on the content.
+ */
+ void ping(boolean ack, int payload1, int payload2);
+
+ /**
+ * The peer tells us to stop creating streams. It is safe to replay
+ * streams with {@code ID > lastGoodStreamId} on a new connection. In-
+ * flight streams with {@code ID <= lastGoodStreamId} can only be replayed
+ * on a new connection if they are idempotent.
+ *
+ * @param lastGoodStreamId the last stream ID the peer processed before
+ * sending this message. If {@code lastGoodStreamId} is zero, the peer
+ * processed no frames.
+ * @param errorCode reason for closing the connection.
+ * @param debugData only valid for HTTP/2; opaque debug data to send.
+ */
+ void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
+
+ /**
+ * Notifies that an additional {@code windowSizeIncrement} bytes can be
+ * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+ */
+ void windowUpdate(int streamId, long windowSizeIncrement);
+
+ /**
+ * Called when reading a headers or priority frame. This may be used to
+ * change the stream's weight from the default (16) to a new value.
+ *
+ * @param streamId stream which has a priority change.
+ * @param streamDependency the stream ID this stream is dependent on.
+ * @param weight relative proportion of priority in [1..256].
+ * @param exclusive inserts this stream ID as the sole child of
+ * {@code streamDependency}.
+ */
+ void priority(int streamId, int streamDependency, int weight, boolean exclusive);
+
+ /**
+ * HTTP/2 only. Receive a push promise header block.
+ *
+ * A push promise contains all the headers that pertain to a server-initiated
+ * request, and a {@code promisedStreamId} to which response frames will be
+ * delivered. Push promise frames are sent as a part of the response to
+ * {@code streamId}.
+ *
+ * @param streamId client-initiated stream ID. Must be an odd number.
+ * @param promisedStreamId server-initiated stream ID. Must be an even
+ * number.
+ * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+ * {@code :authority}, and (@code :path}.
+ */
+ void pushPromise(int streamId, int promisedStreamId, List requestHeaders)
+ throws IOException;
+
+ /**
+ * HTTP/2 only. Expresses that resources for the connection or a client-
+ * initiated stream are available from a different network location or
+ * protocol configuration.
+ *
+ * See alt-svc
+ *
+ * @param streamId when a client-initiated stream ID (odd number), the
+ * origin of this alternate service is the origin of the stream. When
+ * zero, the origin is specified in the {@code origin} parameter.
+ * @param origin when present, the
+ * origin is typically
+ * represented as a combination of scheme, host and port. When empty,
+ * the origin is that of the {@code streamId}.
+ * @param protocol an ALPN protocol, such as {@code h2}.
+ * @param host an IP address or hostname.
+ * @param port the IP port associated with the service.
+ * @param maxAge time in seconds that this alternative is considered fresh.
+ */
+ void alternateService(int streamId, String origin, ByteString protocol, String host, int port,
+ long maxAge);
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameWriter.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameWriter.java
new file mode 100755
index 00000000..364858fc
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/FrameWriter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.contentstack.okhttp.internal.spdy;
+
+import com.contentstack.okio.Buffer;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+/** Writes transport frames for SPDY/3 or HTTP/2. */
+public interface FrameWriter extends Closeable {
+ /** HTTP/2 only. */
+ void connectionPreface() throws IOException;
+ void ackSettings() throws IOException;
+
+ /**
+ * HTTP/2 only. Send a push promise header block.
+ *
+ * A push promise contains all the headers that pertain to a server-initiated
+ * request, and a {@code promisedStreamId} to which response frames will be
+ * delivered. Push promise frames are sent as a part of the response to
+ * {@code streamId}. The {@code promisedStreamId} has a priority of one
+ * greater than {@code streamId}.
+ *
+ * @param streamId client-initiated stream ID. Must be an odd number.
+ * @param promisedStreamId server-initiated stream ID. Must be an even
+ * number.
+ * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+ * {@code :authority}, and (@code :path}.
+ */
+ void pushPromise(int streamId, int promisedStreamId, List requestHeaders)
+ throws IOException;
+
+ /** SPDY/3 only. */
+ void flush() throws IOException;
+ void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+ List headerBlock) throws IOException;
+ void synReply(boolean outFinished, int streamId, List headerBlock)
+ throws IOException;
+ void headers(int streamId, List headerBlock) throws IOException;
+ void rstStream(int streamId, ErrorCode errorCode) throws IOException;
+
+ /**
+ * {@code data.length} may be longer than the max length of the variant's data frame.
+ * Implementations must send multiple frames as necessary.
+ *
+ * @param source the buffer to draw bytes from. May be null if byteCount is 0.
+ */
+ void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException;
+
+ void data(boolean outFinished, int streamId, Buffer source) throws IOException;
+
+ /** Write okhttp's settings to the peer. */
+ void settings(Settings okHttpSettings) throws IOException;
+
+ /**
+ * Send a connection-level ping to the peer. {@code ack} indicates this is
+ * a reply. Payload parameters are different between SPDY/3 and HTTP/2.
+ *
+ * In SPDY/3, only the first {@code payload1} parameter is sent. If the
+ * sender is a client, it is an unsigned odd number. Likewise, a server
+ * will send an even number.
+ *
+ * In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+ * sent. The data is opaque binary, and there are no rules on the content.
+ */
+ void ping(boolean ack, int payload1, int payload2) throws IOException;
+
+ /**
+ * Tell the peer to stop creating streams and that we last processed
+ * {@code lastGoodStreamId}, or zero if no streams were processed.
+ *
+ * @param lastGoodStreamId the last stream ID processed, or zero if no
+ * streams were processed.
+ * @param errorCode reason for closing the connection.
+ * @param debugData only valid for HTTP/2; opaque debug data to send.
+ */
+ void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
+
+ /**
+ * Inform peer that an additional {@code windowSizeIncrement} bytes can be
+ * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+ */
+ void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Header.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Header.java
new file mode 100755
index 00000000..9ab8e8cb
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Header.java
@@ -0,0 +1,56 @@
+package com.contentstack.okhttp.internal.spdy;
+
+import com.contentstack.okio.ByteString;
+
+/** HTTP header: the name is an ASCII string, but the value can be UTF-8. */
+public final class Header {
+ // Special header names defined in the SPDY and HTTP/2 specs.
+ public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status");
+ public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
+ public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
+ public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
+ public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
+ public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
+ public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
+
+ /** Name in case-insensitive ASCII encoding. */
+ public final ByteString name;
+ /** Value in UTF-8 encoding. */
+ public final ByteString value;
+ final int hpackSize;
+
+
+ public Header(String name, String value) {
+ this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
+ }
+
+ public Header(ByteString name, String value) {
+ this(name, ByteString.encodeUtf8(value));
+ }
+
+ public Header(ByteString name, ByteString value) {
+ this.name = name;
+ this.value = value;
+ this.hpackSize = 32 + name.size() + value.size();
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof Header) {
+ Header that = (Header) other;
+ return this.name.equals(that.name)
+ && this.value.equals(that.value);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + name.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+
+ @Override public String toString() {
+ return String.format("%s: %s", name.utf8(), value.utf8());
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/HeadersMode.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/HeadersMode.java
new file mode 100755
index 00000000..61a7e337
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/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.contentstack.okhttp.internal.spdy;
+
+public 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/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/HpackDraft08.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/HpackDraft08.java
new file mode 100755
index 00000000..7e35f456
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/HpackDraft08.java
@@ -0,0 +1,490 @@
+/*
+ * 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.contentstack.okhttp.internal.spdy;
+
+import com.contentstack.okhttp.internal.BitArray;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.ByteString;
+import com.contentstack.okio.Okio;
+import com.contentstack.okio.Source;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Read and write HPACK v08.
+ *
+ * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+ *
+ * This implementation uses an array for the header table with a bitset for
+ * references. Dynamic entries are added to the array, starting in the last
+ * position moving forward. When the array fills, it is doubled.
+ */
+final class HpackDraft08 {
+ private static final int PREFIX_4_BITS = 0x0f;
+ private static final int PREFIX_6_BITS = 0x3f;
+ private static final int PREFIX_7_BITS = 0x7f;
+
+ private static final Header[] STATIC_HEADER_TABLE = new Header[] {
+ new Header(Header.TARGET_AUTHORITY, ""),
+ new Header(Header.TARGET_METHOD, "GET"),
+ new Header(Header.TARGET_METHOD, "POST"),
+ new Header(Header.TARGET_PATH, "/"),
+ new Header(Header.TARGET_PATH, "/index.html"),
+ new Header(Header.TARGET_SCHEME, "http"),
+ new Header(Header.TARGET_SCHEME, "https"),
+ new Header(Header.RESPONSE_STATUS, "200"),
+ new Header(Header.RESPONSE_STATUS, "204"),
+ new Header(Header.RESPONSE_STATUS, "206"),
+ new Header(Header.RESPONSE_STATUS, "304"),
+ new Header(Header.RESPONSE_STATUS, "400"),
+ new Header(Header.RESPONSE_STATUS, "404"),
+ new Header(Header.RESPONSE_STATUS, "500"),
+ new Header("accept-charset", ""),
+ new Header("accept-encoding", "gzip, deflate"),
+ new Header("accept-language", ""),
+ new Header("accept-ranges", ""),
+ new Header("accept", ""),
+ new Header("access-control-allow-origin", ""),
+ new Header("age", ""),
+ new Header("allow", ""),
+ new Header("authorization", ""),
+ new Header("cache-control", ""),
+ new Header("content-disposition", ""),
+ new Header("content-encoding", ""),
+ new Header("content-language", ""),
+ new Header("content-length", ""),
+ new Header("content-location", ""),
+ new Header("content-range", ""),
+ new Header("content-type", ""),
+ new Header("cookie", ""),
+ new Header("date", ""),
+ new Header("etag", ""),
+ new Header("expect", ""),
+ new Header("expires", ""),
+ new Header("from", ""),
+ new Header("host", ""),
+ new Header("if-match", ""),
+ new Header("if-modified-since", ""),
+ new Header("if-none-match", ""),
+ new Header("if-range", ""),
+ new Header("if-unmodified-since", ""),
+ new Header("last-modified", ""),
+ new Header("link", ""),
+ new Header("location", ""),
+ new Header("max-forwards", ""),
+ new Header("proxy-authenticate", ""),
+ new Header("proxy-authorization", ""),
+ new Header("range", ""),
+ new Header("referer", ""),
+ new Header("refresh", ""),
+ new Header("retry-after", ""),
+ new Header("server", ""),
+ new Header("set-cookie", ""),
+ new Header("strict-transport-security", ""),
+ new Header("transfer-encoding", ""),
+ new Header("user-agent", ""),
+ new Header("vary", ""),
+ new Header("via", ""),
+ new Header("www-authenticate", "")
+ };
+
+ private HpackDraft08() {
+ }
+
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08#section-3.2
+ static final class Reader {
+
+ private final List emittedHeaders = new ArrayList();
+ private final BufferedSource source;
+
+ private int maxHeaderTableByteCountSetting;
+ private int maxHeaderTableByteCount;
+ // Visible for testing.
+ Header[] headerTable = new Header[8];
+ // Array is populated back to front, so new entries always have lowest index.
+ int nextHeaderIndex = headerTable.length - 1;
+ int headerCount = 0;
+
+ /**
+ * Set bit positions indicate {@code headerTable[pos]} should be emitted.
+ */
+ // Using a BitArray as it has left-shift operator.
+ BitArray referencedHeaders = new BitArray.FixedCapacity();
+
+ /**
+ * Set bit positions indicate {@code headerTable[pos]} was already emitted.
+ */
+ BitArray emittedReferencedHeaders = new BitArray.FixedCapacity();
+ int headerTableByteCount = 0;
+
+ Reader(int maxHeaderTableByteCountSetting, Source source) {
+ this.maxHeaderTableByteCountSetting = maxHeaderTableByteCountSetting;
+ this.maxHeaderTableByteCount = maxHeaderTableByteCountSetting;
+ this.source = Okio.buffer(source);
+ }
+
+ int maxHeaderTableByteCount() {
+ return maxHeaderTableByteCount;
+ }
+
+ /**
+ * Called by the reader when the peer sent a new header table size setting.
+ * While this establishes the maximum header table size, the
+ * {@link #maxHeaderTableByteCount} set during processing may limit the
+ * table size to a smaller amount.
+ * Evicts entries or clears the table as needed.
+ */
+ void maxHeaderTableByteCountSetting(int newMaxHeaderTableByteCountSetting) {
+ this.maxHeaderTableByteCountSetting = newMaxHeaderTableByteCountSetting;
+ this.maxHeaderTableByteCount = maxHeaderTableByteCountSetting;
+ adjustHeaderTableByteCount();
+ }
+
+ private void adjustHeaderTableByteCount() {
+ if (maxHeaderTableByteCount < headerTableByteCount) {
+ if (maxHeaderTableByteCount == 0) {
+ clearHeaderTable();
+ } else {
+ evictToRecoverBytes(headerTableByteCount - maxHeaderTableByteCount);
+ }
+ }
+ }
+
+ private void clearHeaderTable() {
+ clearReferenceSet();
+ Arrays.fill(headerTable, null);
+ nextHeaderIndex = headerTable.length - 1;
+ headerCount = 0;
+ headerTableByteCount = 0;
+ }
+
+ /** Returns the count of entries evicted. */
+ private int evictToRecoverBytes(int bytesToRecover) {
+ int entriesToEvict = 0;
+ if (bytesToRecover > 0) {
+ // determine how many headers need to be evicted.
+ for (int j = headerTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
+ bytesToRecover -= headerTable[j].hpackSize;
+ headerTableByteCount -= headerTable[j].hpackSize;
+ headerCount--;
+ entriesToEvict++;
+ }
+ referencedHeaders.shiftLeft(entriesToEvict);
+ emittedReferencedHeaders.shiftLeft(entriesToEvict);
+ System.arraycopy(headerTable, nextHeaderIndex + 1, headerTable,
+ nextHeaderIndex + 1 + entriesToEvict, headerCount);
+ nextHeaderIndex += entriesToEvict;
+ }
+ return entriesToEvict;
+ }
+
+ /**
+ * Read {@code byteCount} bytes of headers from the source stream into the
+ * set of emitted headers. This implementation does not propagate the never
+ * indexed flag of a header.
+ */
+ void readHeaders() throws IOException {
+ while (!source.exhausted()) {
+ int b = source.readByte() & 0xff;
+ if (b == 0x80) { // 10000000
+ throw new IOException("index == 0");
+ } else if ((b & 0x80) == 0x80) { // 1NNNNNNN
+ int index = readInt(b, PREFIX_7_BITS);
+ readIndexedHeader(index - 1);
+ } else if (b == 0x40) { // 01000000
+ readLiteralHeaderWithIncrementalIndexingNewName();
+ } else if ((b & 0x40) == 0x40) { // 01NNNNNN
+ int index = readInt(b, PREFIX_6_BITS);
+ readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
+ } else if ((b & 0x20) == 0x20) { // 001NNNNN
+ if ((b & 0x10) == 0x10) { // 0011NNNN
+ if ((b & 0x0f) != 0) throw new IOException("Invalid header table state change " + b);
+ clearReferenceSet(); // 00110000
+ } else { // 0010NNNN
+ maxHeaderTableByteCount = readInt(b, PREFIX_4_BITS);
+ if (maxHeaderTableByteCount < 0
+ || maxHeaderTableByteCount > maxHeaderTableByteCountSetting) {
+ throw new IOException("Invalid header table byte count " + maxHeaderTableByteCount);
+ }
+ adjustHeaderTableByteCount();
+ }
+ } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.
+ readLiteralHeaderWithoutIndexingNewName();
+ } else { // 000?NNNN - Ignore never indexed bit.
+ int index = readInt(b, PREFIX_4_BITS);
+ readLiteralHeaderWithoutIndexingIndexedName(index - 1);
+ }
+ }
+ }
+
+ private void clearReferenceSet() {
+ referencedHeaders.clear();
+ emittedReferencedHeaders.clear();
+ }
+
+ void emitReferenceSet() {
+ for (int i = headerTable.length - 1; i != nextHeaderIndex; --i) {
+ if (referencedHeaders.get(i) && !emittedReferencedHeaders.get(i)) {
+ emittedHeaders.add(headerTable[i]);
+ }
+ }
+ }
+
+ /**
+ * Returns all headers emitted since they were last cleared, then clears the
+ * emitted headers.
+ */
+ List getAndReset() {
+ List result = new ArrayList(emittedHeaders);
+ emittedHeaders.clear();
+ emittedReferencedHeaders.clear();
+ return result;
+ }
+
+ private void readIndexedHeader(int index) throws IOException {
+ if (isStaticHeader(index)) {
+ index -= headerCount;
+ if (index > STATIC_HEADER_TABLE.length - 1) {
+ throw new IOException("Header index too large " + (index + 1));
+ }
+ Header staticEntry = STATIC_HEADER_TABLE[index];
+ if (maxHeaderTableByteCount == 0) {
+ emittedHeaders.add(staticEntry);
+ } else {
+ insertIntoHeaderTable(-1, staticEntry);
+ }
+ } else {
+ int headerTableIndex = headerTableIndex(index);
+ if (!referencedHeaders.get(headerTableIndex)) { // When re-referencing, emit immediately.
+ emittedHeaders.add(headerTable[headerTableIndex]);
+ emittedReferencedHeaders.set(headerTableIndex);
+ }
+ referencedHeaders.toggle(headerTableIndex);
+ }
+ }
+
+ // referencedHeaders is relative to nextHeaderIndex + 1.
+ private int headerTableIndex(int index) {
+ return nextHeaderIndex + 1 + index;
+ }
+
+ private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
+ ByteString name = getName(index);
+ ByteString value = readByteString();
+ emittedHeaders.add(new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
+ ByteString name = checkLowercase(readByteString());
+ ByteString value = readByteString();
+ emittedHeaders.add(new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
+ throws IOException {
+ ByteString name = getName(nameIndex);
+ ByteString value = readByteString();
+ insertIntoHeaderTable(-1, new Header(name, value));
+ }
+
+ private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
+ ByteString name = checkLowercase(readByteString());
+ ByteString value = readByteString();
+ insertIntoHeaderTable(-1, new Header(name, value));
+ }
+
+ private ByteString getName(int index) {
+ if (isStaticHeader(index)) {
+ return STATIC_HEADER_TABLE[index - headerCount].name;
+ } else {
+ return headerTable[headerTableIndex(index)].name;
+ }
+ }
+
+ private boolean isStaticHeader(int index) {
+ return index >= headerCount;
+ }
+
+ /** index == -1 when new. */
+ private void insertIntoHeaderTable(int index, Header entry) {
+ int delta = entry.hpackSize;
+ if (index != -1) { // Index -1 == new header.
+ delta -= headerTable[headerTableIndex(index)].hpackSize;
+ }
+
+ // if the new or replacement header is too big, drop all entries.
+ if (delta > maxHeaderTableByteCount) {
+ clearHeaderTable();
+ // emit the large header to the callback.
+ emittedHeaders.add(entry);
+ return;
+ }
+
+ // Evict headers to the required length.
+ int bytesToRecover = (headerTableByteCount + delta) - maxHeaderTableByteCount;
+ int entriesEvicted = evictToRecoverBytes(bytesToRecover);
+
+ if (index == -1) { // Adding a value to the header table.
+ if (headerCount + 1 > headerTable.length) { // Need to grow the header table.
+ Header[] doubled = new Header[headerTable.length * 2];
+ System.arraycopy(headerTable, 0, doubled, headerTable.length, headerTable.length);
+ if (doubled.length == 64) {
+ referencedHeaders = ((BitArray.FixedCapacity) referencedHeaders).toVariableCapacity();
+ emittedReferencedHeaders =
+ ((BitArray.FixedCapacity) emittedReferencedHeaders).toVariableCapacity();
+ }
+ referencedHeaders.shiftLeft(headerTable.length);
+ emittedReferencedHeaders.shiftLeft(headerTable.length);
+ nextHeaderIndex = headerTable.length - 1;
+ headerTable = doubled;
+ }
+ index = nextHeaderIndex--;
+ referencedHeaders.set(index);
+ headerTable[index] = entry;
+ headerCount++;
+ } else { // Replace value at same position.
+ index += headerTableIndex(index) + entriesEvicted;
+ referencedHeaders.set(index);
+ headerTable[index] = entry;
+ }
+ headerTableByteCount += delta;
+ }
+
+ private int readByte() throws IOException {
+ return source.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 potentially Huffman encoded byte string. */
+ ByteString readByteString() throws IOException {
+ int firstByte = readByte();
+ boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
+ int length = readInt(firstByte, PREFIX_7_BITS);
+
+ if (huffmanDecode) {
+ return ByteString.of(Huffman.get().decode(source.readByteArray(length)));
+ } else {
+ return source.readByteString(length);
+ }
+ }
+ }
+
+ private static final Map NAME_TO_FIRST_INDEX = nameToFirstIndex();
+
+ private static Map nameToFirstIndex() {
+ Map result = new LinkedHashMap(STATIC_HEADER_TABLE.length);
+ for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
+ if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
+ result.put(STATIC_HEADER_TABLE[i].name, i);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ static final class Writer {
+ private final Buffer out;
+
+ Writer(Buffer out) {
+ this.out = out;
+ }
+
+ /** This does not use "never indexed" semantics for sensitive headers. */
+ // https://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08#section-4.3.3
+ void writeHeaders(List headerBlock) throws IOException {
+ for (int i = 0, size = headerBlock.size(); i < size; i++) {
+ ByteString name = headerBlock.get(i).name.toAsciiLowercase();
+ Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
+ if (staticIndex != null) {
+ // Literal Header Field without Indexing - Indexed Name.
+ writeInt(staticIndex + 1, PREFIX_4_BITS, 0);
+ writeByteString(headerBlock.get(i).value);
+ } else {
+ out.writeByte(0x00); // Literal Header without Indexing - New Name.
+ writeByteString(name);
+ writeByteString(headerBlock.get(i).value);
+ }
+ }
+ }
+
+ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08#section-4.1.1
+ void writeInt(int value, int prefixMask, int bits) throws IOException {
+ // Write the raw value for a single byte value.
+ if (value < prefixMask) {
+ out.writeByte(bits | value);
+ return;
+ }
+
+ // Write the mask to start a multibyte value.
+ out.writeByte(bits | prefixMask);
+ value -= prefixMask;
+
+ // Write 7 bits at a time 'til we're done.
+ while (value >= 0x80) {
+ int b = value & 0x7f;
+ out.writeByte(b | 0x80);
+ value >>>= 7;
+ }
+ out.writeByte(value);
+ }
+
+ void writeByteString(ByteString data) throws IOException {
+ writeInt(data.size(), PREFIX_7_BITS, 0);
+ out.write(data);
+ }
+ }
+
+ /**
+ * An HTTP/2 response cannot contain uppercase header characters and must
+ * be treated as malformed.
+ */
+ private static ByteString checkLowercase(ByteString name) throws IOException {
+ for (int i = 0, length = name.size(); i < length; i++) {
+ byte c = name.getByte(i);
+ if (c >= 'A' && c <= 'Z') {
+ throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8());
+ }
+ }
+ return name;
+ }
+}
diff --git a/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Http20Draft13.java b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Http20Draft13.java
new file mode 100755
index 00000000..37cd4a1b
--- /dev/null
+++ b/contentstack/src/main/java/com/contentstack/okhttp/internal/spdy/Http20Draft13.java
@@ -0,0 +1,745 @@
+/*
+ * 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.contentstack.okhttp.internal.spdy;
+
+import com.contentstack.okhttp.Protocol;
+import com.contentstack.okio.Buffer;
+import com.contentstack.okio.BufferedSink;
+import com.contentstack.okio.BufferedSource;
+import com.contentstack.okio.ByteString;
+import com.contentstack.okio.Source;
+import com.contentstack.okio.Timeout;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static com.contentstack.okhttp.internal.spdy.Http20Draft13.FrameLogger.formatHeader;
+import static com.contentstack.okio.ByteString.EMPTY;
+import static java.lang.String.format;
+import static java.util.logging.Level.FINE;
+
+/**
+ * Read and write HTTP/2 v13 frames.
+ * http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
+ */
+public final class Http20Draft13 implements Variant {
+ private static final Logger logger = Logger.getLogger(Http20Draft13.class.getName());
+
+ @Override public Protocol getProtocol() {
+ return Protocol.HTTP_2;
+ }
+
+ private static final ByteString CONNECTION_PREFACE
+ = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
+
+ static final int MAX_FRAME_SIZE = 0x3fff; // 16383
+
+ static final byte TYPE_DATA = 0x0;
+ static final byte TYPE_HEADERS = 0x1;
+ static final byte TYPE_PRIORITY = 0x2;
+ static final byte TYPE_RST_STREAM = 0x3;
+ static final byte TYPE_SETTINGS = 0x4;
+ static final byte TYPE_PUSH_PROMISE = 0x5;
+ static final byte TYPE_PING = 0x6;
+ static final byte TYPE_GOAWAY = 0x7;
+ static final byte TYPE_WINDOW_UPDATE = 0x8;
+ static final byte TYPE_CONTINUATION = 0x9;
+
+ static final byte FLAG_NONE = 0x0;
+ static final byte FLAG_ACK = 0x1; // Used for settings and ping.
+ static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
+ static final byte FLAG_END_SEGMENT = 0x2;
+ static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
+ static final byte FLAG_END_PUSH_PROMISE = 0x4;
+ static final byte FLAG_PADDED = 0x8; // Used for headers and data.
+ static final byte FLAG_PRIORITY = 0x20; // Used for headers.
+ static final byte FLAG_COMPRESSED = 0x20; // Used for data.
+
+ /**
+ * Creates a frame reader with max header table size of 4096 and data frame
+ * compression disabled.
+ */
+ @Override public FrameReader newReader(BufferedSource source, boolean client) {
+ return new Reader(source, 4096, client);
+ }
+
+ @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
+ return new Writer(sink, client);
+ }
+
+ @Override public int maxFrameSize() {
+ return MAX_FRAME_SIZE;
+ }
+
+ static final class Reader implements FrameReader {
+ private final BufferedSource source;
+ private final ContinuationSource continuation;
+ private final boolean client;
+
+ // Visible for testing.
+ final HpackDraft08.Reader hpackReader;
+
+ Reader(BufferedSource source, int headerTableSize, boolean client) {
+ this.source = source;
+ this.client = client;
+ this.continuation = new ContinuationSource(this.source);
+ this.hpackReader = new HpackDraft08.Reader(headerTableSize, continuation);
+ }
+
+ @Override public void readConnectionPreface() throws IOException {
+ if (client) return; // Nothing to read; servers doesn't send a connection preface!
+ ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
+ if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
+ if (!CONNECTION_PREFACE.equals(connectionPreface)) {
+ throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
+ }
+ }
+
+ @Override public boolean nextFrame(Handler handler) throws IOException {
+ int w1;
+ int w2;
+ try {
+ w1 = source.readInt();
+ w2 = source.readInt();
+ } catch (IOException e) {
+ return false; // This might be a normal socket close.
+ }
+
+ // boolean r = (w1 & 0xc0000000) != 0; // Reserved: Ignore first 2 bits.
+ short length = (short) ((w1 & 0x3fff0000) >> 16); // 14-bit unsigned == MAX_FRAME_SIZE
+ byte type = (byte) ((w1 & 0xff00) >> 8);
+ byte flags = (byte) (w1 & 0xff);
+ // boolean r = (w2 & 0x80000000) != 0; // Reserved: Ignore first bit.
+ int streamId = (w2 & 0x7fffffff); // 31-bit opaque identifier.
+ if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+
+ switch (type) {
+ case TYPE_DATA:
+ readData(handler, length, flags, streamId);
+ break;
+
+ case TYPE_HEADERS:
+ readHeaders(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PRIORITY:
+ readPriority(handler, length, flags, streamId);
+ break;
+
+ case TYPE_RST_STREAM:
+ readRstStream(handler, length, flags, streamId);
+ break;
+
+ case TYPE_SETTINGS:
+ readSettings(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PUSH_PROMISE:
+ readPushPromise(handler, length, flags, streamId);
+ break;
+
+ case TYPE_PING:
+ readPing(handler, length, flags, streamId);
+ break;
+
+ case TYPE_GOAWAY:
+ readGoAway(handler, length, flags, streamId);
+ break;
+
+ case TYPE_WINDOW_UPDATE:
+ readWindowUpdate(handler, length, flags, streamId);
+ break;
+
+ default:
+ // Implementations MUST discard frames that have unknown or unsupported publishType.
+ source.skip(length);
+ }
+ return true;
+ }
+
+ private void readHeaders(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+ if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
+
+ boolean endStream = (flags & FLAG_END_STREAM) != 0;
+
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+
+ if ((flags & FLAG_PRIORITY) != 0) {
+ readPriority(handler, streamId);
+ length -= 5; // account for above read.
+ }
+
+ length = lengthWithoutPadding(length, flags, padding);
+
+ List headerBlock = readHeaderBlock(length, padding, flags, streamId);
+
+ handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
+ }
+
+ private List readHeaderBlock(short length, short padding, byte flags, int streamId)
+ throws IOException {
+ continuation.length = continuation.left = length;
+ continuation.padding = padding;
+ continuation.flags = flags;
+ continuation.streamId = streamId;
+
+ hpackReader.readHeaders();
+ hpackReader.emitReferenceSet();
+ // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3
+ return hpackReader.getAndReset();
+ }
+
+ private void readData(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+
+ boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+ boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
+ if (gzipped) {
+ throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
+ }
+
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+ length = lengthWithoutPadding(length, flags, padding);
+
+ handler.data(inFinished, streamId, source, length);
+ source.skip(padding);
+ }
+
+ private void readPriority(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+ if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
+ if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
+ readPriority(handler, streamId);
+ }
+
+ private void readPriority(Handler handler, int streamId) throws IOException {
+ int w1 = source.readInt();
+ boolean exclusive = (w1 & 0x80000000) != 0;
+ int streamDependency = (w1 & 0x7fffffff);
+ int weight = (source.readByte() & 0xff) + 1;
+ handler.priority(streamId, streamDependency, weight, exclusive);
+ }
+
+ private void readRstStream(Handler handler, short length, byte flags, 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 = source.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, short length, byte flags, int streamId)
+ throws IOException {
+ if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
+ if ((flags & FLAG_ACK) != 0) {
+ if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
+ handler.ackSettings();
+ return;
+ }
+
+ if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
+ Settings settings = new Settings();
+ for (int i = 0; i < length; i += 6) {
+ short id = source.readShort();
+ int value = source.readInt();
+
+ switch (id) {
+ case 1: // SETTINGS_HEADER_TABLE_SIZE
+ break;
+ case 2: // SETTINGS_ENABLE_PUSH
+ if (value != 0 && value != 1) {
+ throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
+ }
+ break;
+ case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
+ id = 4; // Renumbered in draft 10.
+ break;
+ case 4: // SETTINGS_INITIAL_WINDOW_SIZE
+ id = 7; // Renumbered in draft 10.
+ if (value < 0) {
+ throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
+ }
+ break;
+ case 5: // SETTINGS_COMPRESS_DATA
+ break;
+ default:
+ throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
+ }
+ settings.set(id, 0, value);
+ }
+ handler.settings(false, settings);
+ if (settings.getHeaderTableSize() >= 0) {
+ hpackReader.maxHeaderTableByteCountSetting(settings.getHeaderTableSize());
+ }
+ }
+
+ private void readPushPromise(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+ if (streamId == 0) {
+ throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
+ }
+ short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+ int promisedStreamId = source.readInt() & 0x7fffffff;
+ length -= 4; // account for above read.
+ length = lengthWithoutPadding(length, flags, padding);
+ List headerBlock = readHeaderBlock(length, padding, flags, streamId);
+ handler.pushPromise(streamId, promisedStreamId, headerBlock);
+ }
+
+ private void readPing(Handler handler, short length, byte flags, 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 = source.readInt();
+ int payload2 = source.readInt();
+ boolean ack = (flags & FLAG_ACK) != 0;
+ handler.ping(ack, payload1, payload2);
+ }
+
+ private void readGoAway(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+ if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
+ if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
+ int lastStreamId = source.readInt();
+ int errorCodeInt = source.readInt();
+ int opaqueDataLength = length - 8;
+ ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+ if (errorCode == null) {
+ throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+ }
+ ByteString debugData = EMPTY;
+ if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
+ debugData = source.readByteString(opaqueDataLength);
+ }
+ handler.goAway(lastStreamId, errorCode, debugData);
+ }
+
+ private void readWindowUpdate(Handler handler, short length, byte flags, int streamId)
+ throws IOException {
+ if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
+ long increment = (source.readInt() & 0x7fffffffL);
+ if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+ handler.windowUpdate(streamId, increment);
+ }
+
+ @Override public void close() throws IOException {
+ source.close();
+ }
+ }
+
+ static final class Writer implements FrameWriter {
+ private final BufferedSink sink;
+ private final boolean client;
+ private final Buffer hpackBuffer;
+ private final HpackDraft08.Writer hpackWriter;
+ private boolean closed;
+
+ Writer(BufferedSink sink, boolean client) {
+ this.sink = sink;
+ this.client = client;
+ this.hpackBuffer = new Buffer();
+ this.hpackWriter = new HpackDraft08.Writer(hpackBuffer);
+ }
+
+ @Override public synchronized void flush() throws IOException {
+ if (closed) throw new IOException("closed");
+ sink.flush();
+ }
+
+ @Override public synchronized void ackSettings() throws IOException {
+ if (closed) throw new IOException("closed");
+ int length = 0;
+ byte type = TYPE_SETTINGS;
+ byte flags = FLAG_ACK;
+ int streamId = 0;
+ frameHeader(streamId, length, type, flags);
+ sink.flush();
+ }
+
+ @Override public synchronized void connectionPreface() throws IOException {
+ if (closed) throw new IOException("closed");
+ if (!client) return; // Nothing to write; servers don't send connection headers!
+ if (logger.isLoggable(FINE)) {
+ logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
+ }
+ sink.write(CONNECTION_PREFACE.toByteArray());
+ sink.flush();
+ }
+
+ @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+ int streamId, int associatedStreamId, List headerBlock)
+ throws IOException {
+ if (inFinished) throw new UnsupportedOperationException();
+ if (closed) throw new IOException("closed");
+ headers(outFinished, streamId, headerBlock);
+ }
+
+ @Override public synchronized void synReply(boolean outFinished, int streamId,
+ List headerBlock) throws IOException {
+ if (closed) throw new IOException("closed");
+ headers(outFinished, streamId, headerBlock);
+ }
+
+ @Override public synchronized void headers(int streamId, List headerBlock)
+ throws IOException {
+ if (closed) throw new IOException("closed");
+ headers(false, streamId, headerBlock);
+ }
+
+ @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
+ List