diff options
author | Neil Fuller <nfuller@google.com> | 2015-02-11 09:33:10 +0000 |
---|---|---|
committer | Neil Fuller <nfuller@google.com> | 2015-02-11 10:15:24 +0000 |
commit | 3be78b8b0ca13d9e05e2327acb8d8654f719a3f6 (patch) | |
tree | 68d9e2f18e43ccc4e75bf9fca8cdbd4db44640a4 | |
parent | 576c6599deeae3ca87e545c67ce765d9c4062b14 (diff) | |
download | okhttp-3be78b8b0ca13d9e05e2327acb8d8654f719a3f6.tar.gz |
A rollup of recent upstream commits for OkHttp
squareup/okhttp commits from:
0a197466608681593cc9be9487965a0b1d5c244c
to:
b609edd07864d7191dcda8ba1f6c833c9fe170ad
squareup/okio commits from:
654ddf5e8f6311fda77e429c22d5e0e15f713b8d
to
82358df7f09e18aa42348836c614212085bbf045
Changes that might affect Android:
1) Cache control request headers: If-None-Match
or If-Modified-Since sent, never both.
2) Make okhttp behave more like a private, not a
shared cache.
3) SSLPeerUnverifiedException now thrown on
hostname verification errors, not IOException.
Change-Id: I3a2e8ae9bebfec84eaf8eb2aaa70085fa40fadd5
17 files changed, 300 insertions, 56 deletions
diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java index a63bbd4..39fbf6f 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java @@ -116,7 +116,7 @@ public final class MockWebServer { private Dispatcher dispatcher = new QueueDispatcher(); private int port = -1; - private InetAddress inetAddress; + private InetSocketAddress inetSocketAddress; private boolean protocolNegotiationEnabled = true; private List<Protocol> protocols = Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1); @@ -132,15 +132,18 @@ public final class MockWebServer { } public String getHostName() { - if (inetAddress == null) throw new IllegalStateException("Call start() before getHostName()"); - return inetAddress.getHostName(); + if (inetSocketAddress == null) { + throw new IllegalStateException("Call start() before getHostName()"); + } + return inetSocketAddress.getHostName(); } public Proxy toProxyAddress() { - if (inetAddress == null) { + if (inetSocketAddress == null) { throw new IllegalStateException("Call start() before toProxyAddress()"); } - return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(inetAddress, getPort())); + InetSocketAddress address = new InetSocketAddress(inetSocketAddress.getAddress(), getPort()); + return new Proxy(Proxy.Type.HTTP, address); } /** @@ -276,22 +279,45 @@ public final class MockWebServer { } /** - * Starts the server. + * Starts the server on the loopback interface for the given port. * * @param port the port to listen to, or 0 for any available port. Automated * tests should always use port 0 to avoid flakiness when a specific port * is unavailable. */ public void start(int port) throws IOException { + start(InetAddress.getByName("localhost"), port); + } + + /** + * Starts the server on the given address and port. + * + * @param inetAddress the address to create the server socket on + * + * @param port the port to listen to, or 0 for any available port. Automated + * tests should always use port 0 to avoid flakiness when a specific port + * is unavailable. + */ + public void start(InetAddress inetAddress, int port) throws IOException { + start(new InetSocketAddress(inetAddress, port)); + } + + /** + * Starts the server and binds to the given socket address. + * + * @param inetSocketAddress the socket address to bind the server on + */ + private void start(InetSocketAddress inetSocketAddress) throws IOException { if (executor != null) throw new IllegalStateException("start() already called"); executor = Executors.newCachedThreadPool(Util.threadFactory("MockWebServer", false)); - inetAddress = InetAddress.getByName("localhost"); + this.inetSocketAddress = inetSocketAddress; serverSocket = serverSocketFactory.createServerSocket(); - serverSocket.setReuseAddress(port != 0); // Reuse the port if the port number was specified. - serverSocket.bind(new InetSocketAddress(inetAddress, port), 50); + // Reuse if the user specified a port + serverSocket.setReuseAddress(inetSocketAddress.getPort() != 0); + serverSocket.bind(inetSocketAddress, 50); - this.port = serverSocket.getLocalPort(); - executor.execute(new NamedRunnable("MockWebServer %s", this.port) { + port = serverSocket.getLocalPort(); + executor.execute(new NamedRunnable("MockWebServer %s", port) { @Override protected void execute() { try { logger.info(MockWebServer.this + " starting to accept connections"); diff --git a/okhttp-android-support/src/test/java/com/squareup/okhttp/internal/huc/ResponseCacheTest.java b/okhttp-android-support/src/test/java/com/squareup/okhttp/internal/huc/ResponseCacheTest.java index 5fdb2fc..3c91fb5 100644 --- a/okhttp-android-support/src/test/java/com/squareup/okhttp/internal/huc/ResponseCacheTest.java +++ b/okhttp-android-support/src/test/java/com/squareup/okhttp/internal/huc/ResponseCacheTest.java @@ -77,7 +77,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -628,7 +627,7 @@ public final class ResponseCacheTest { .addHeader("Last-Modified: " + lastModifiedDate) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); assertEquals("v1", conditionalRequest.getHeader("If-None-Match")); - assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since")); + assertNull(conditionalRequest.getHeader("If-Modified-Since")); } @Test public void etagAndExpirationDateInTheFuture() throws Exception { diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheControlTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheControlTest.java index e08adf3..5d13767 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheControlTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheControlTest.java @@ -33,6 +33,7 @@ public final class CacheControlTest { assertFalse(cacheControl.noStore()); assertEquals(-1, cacheControl.maxAgeSeconds()); assertEquals(-1, cacheControl.sMaxAgeSeconds()); + assertFalse(cacheControl.isPrivate()); assertFalse(cacheControl.isPublic()); assertFalse(cacheControl.mustRevalidate()); assertEquals(-1, cacheControl.maxStaleSeconds()); @@ -62,6 +63,7 @@ public final class CacheControlTest { // These members are accessible to response headers only. assertEquals(-1, cacheControl.sMaxAgeSeconds()); + assertFalse(cacheControl.isPrivate()); assertFalse(cacheControl.isPublic()); assertFalse(cacheControl.mustRevalidate()); } @@ -83,7 +85,7 @@ public final class CacheControlTest { } @Test public void parse() throws Exception { - String header = "no-cache, no-store, max-age=1, s-maxage=2, public, must-revalidate, " + String header = "no-cache, no-store, max-age=1, s-maxage=2, private, public, must-revalidate, " + "max-stale=3, min-fresh=4, only-if-cached, no-transform"; CacheControl cacheControl = CacheControl.parse(new Headers.Builder() .set("Cache-Control", header) @@ -92,6 +94,7 @@ public final class CacheControlTest { assertTrue(cacheControl.noStore()); assertEquals(1, cacheControl.maxAgeSeconds()); assertEquals(2, cacheControl.sMaxAgeSeconds()); + assertTrue(cacheControl.isPrivate()); assertTrue(cacheControl.isPublic()); assertTrue(cacheControl.mustRevalidate()); assertEquals(3, cacheControl.maxStaleSeconds()); @@ -101,6 +104,26 @@ public final class CacheControlTest { assertEquals(header, cacheControl.toString()); } + @Test public void parseIgnoreCacheControlExtensions() throws Exception { + // Example from http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.6 + String header = "private, community=\"UCI\""; + CacheControl cacheControl = CacheControl.parse(new Headers.Builder() + .set("Cache-Control", header) + .build()); + assertFalse(cacheControl.noCache()); + assertFalse(cacheControl.noStore()); + assertEquals(-1, cacheControl.maxAgeSeconds()); + assertEquals(-1, cacheControl.sMaxAgeSeconds()); + assertTrue(cacheControl.isPrivate()); + assertFalse(cacheControl.isPublic()); + assertFalse(cacheControl.mustRevalidate()); + assertEquals(-1, cacheControl.maxStaleSeconds()); + assertEquals(-1, cacheControl.minFreshSeconds()); + assertFalse(cacheControl.onlyIfCached()); + assertFalse(cacheControl.noTransform()); + assertEquals(header, cacheControl.toString()); + } + @Test public void parseCacheControlAndPragmaAreCombined() { Headers headers = Headers.of("Cache-Control", "max-age=12", "Pragma", "must-revalidate", "Pragma", "public"); diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java index d422143..af0f506 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/CacheTest.java @@ -23,20 +23,6 @@ import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.RecordedRequest; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.GzipSink; -import okio.Okio; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import java.io.File; import java.io.IOException; import java.net.CookieHandler; @@ -59,6 +45,19 @@ import java.util.NoSuchElementException; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import okio.Buffer; +import okio.BufferedSink; +import okio.BufferedSource; +import okio.GzipSink; +import okio.Okio; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END; import static org.junit.Assert.assertEquals; @@ -819,6 +818,7 @@ public final class CacheTest { assertEquals("v1", conditionalRequest.getHeader("If-None-Match")); } + /** If both If-Modified-Since and If-None-Match conditions apply, send only If-None-Match. */ @Test public void etagAndExpirationDateInThePast() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() @@ -826,7 +826,7 @@ public final class CacheTest { .addHeader("Last-Modified: " + lastModifiedDate) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); assertEquals("v1", conditionalRequest.getHeader("If-None-Match")); - assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since")); + assertNull(conditionalRequest.getHeader("If-Modified-Since")); } @Test public void etagAndExpirationDateInTheFuture() throws Exception { diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionSpecTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionSpecTest.java index df1e58f..2267c2a 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionSpecTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionSpecTest.java @@ -166,6 +166,16 @@ public final class ConnectionSpecTest { assertEquals(expectedCipherSet, expectedCipherSet); } + @Test + public void tls_stringCiphersAndVersions() throws Exception { + // Supporting arbitrary input strings allows users to enable suites and versions that are not + // yet known to the library, but are supported by the platform. + ConnectionSpec tlsSpec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites("MAGIC-CIPHER") + .tlsVersions("TLS9k") + .build(); + } + private static Set<String> createSet(String... values) { return new LinkedHashSet<String>(Arrays.asList(values)); } diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/InterceptorTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/InterceptorTest.java index 8d16b07..d186f4a 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/InterceptorTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/InterceptorTest.java @@ -371,6 +371,65 @@ public final class InterceptorTest { assertEquals(response.body().string(), "b"); } + /** Make sure interceptors can interact with the OkHttp client. */ + @Test public void interceptorMakesAnUnrelatedRequest() throws Exception { + server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor. + server.enqueue(new MockResponse().setBody("b")); // Fetched directly. + + client.interceptors().add(new Interceptor() { + @Override public Response intercept(Chain chain) throws IOException { + if (chain.request().url().getPath().equals("/b")) { + Request requestA = new Request.Builder() + .url(server.getUrl("/a")) + .build(); + Response responseA = client.newCall(requestA).execute(); + assertEquals("a", responseA.body().string()); + } + + return chain.proceed(chain.request()); + } + }); + + Request requestB = new Request.Builder() + .url(server.getUrl("/b")) + .build(); + Response responseB = client.newCall(requestB).execute(); + assertEquals("b", responseB.body().string()); + } + + /** Make sure interceptors can interact with the OkHttp client asynchronously. */ + @Test public void interceptorMakesAnUnrelatedAsyncRequest() throws Exception { + server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor. + server.enqueue(new MockResponse().setBody("b")); // Fetched directly. + + client.interceptors().add(new Interceptor() { + @Override public Response intercept(Chain chain) throws IOException { + if (chain.request().url().getPath().equals("/b")) { + Request requestA = new Request.Builder() + .url(server.getUrl("/a")) + .build(); + + try { + RecordingCallback callbackA = new RecordingCallback(); + client.newCall(requestA).enqueue(callbackA); + callbackA.await(requestA.url()).assertBody("a"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + return chain.proceed(chain.request()); + } + }); + + Request requestB = new Request.Builder() + .url(server.getUrl("/b")) + .build(); + RecordingCallback callbackB = new RecordingCallback(); + client.newCall(requestB).enqueue(callbackB); + callbackB.await(requestB.url()).assertBody("b"); + } + private RequestBody uppercase(final RequestBody original) { return new RequestBody() { @Override public MediaType contentType() { diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/OkHttpClientTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/OkHttpClientTest.java index 0bb8d1a..1fdaf1b 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/OkHttpClientTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/OkHttpClientTest.java @@ -32,6 +32,7 @@ import java.net.URLConnection; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.net.SocketFactory; import org.junit.After; import org.junit.Test; @@ -56,6 +57,34 @@ public final class OkHttpClientTest { Authenticator.setDefault(DEFAULT_AUTHENTICATOR); } + @Test public void timeoutValidRange() { + OkHttpClient client = new OkHttpClient(); + try { + client.setConnectTimeout(1, TimeUnit.NANOSECONDS); + } catch (IllegalArgumentException ignored) { + } + try { + client.setWriteTimeout(1, TimeUnit.NANOSECONDS); + } catch (IllegalArgumentException ignored) { + } + try { + client.setReadTimeout(1, TimeUnit.NANOSECONDS); + } catch (IllegalArgumentException ignored) { + } + try { + client.setConnectTimeout(365, TimeUnit.DAYS); + } catch (IllegalArgumentException ignored) { + } + try { + client.setWriteTimeout(365, TimeUnit.DAYS); + } catch (IllegalArgumentException ignored) { + } + try { + client.setReadTimeout(365, TimeUnit.DAYS); + } catch (IllegalArgumentException ignored) { + } + } + /** Confirm that {@code copyWithDefaults} gets expected constant values. */ @Test public void copyWithDefaultsWhenDefaultIsAConstant() throws Exception { OkHttpClient client = new OkHttpClient().copyWithDefaults(); diff --git a/okhttp-urlconnection/src/test/java/com/squareup/okhttp/UrlConnectionCacheTest.java b/okhttp-urlconnection/src/test/java/com/squareup/okhttp/UrlConnectionCacheTest.java index c46fd07..db0ed8f 100644 --- a/okhttp-urlconnection/src/test/java/com/squareup/okhttp/UrlConnectionCacheTest.java +++ b/okhttp-urlconnection/src/test/java/com/squareup/okhttp/UrlConnectionCacheTest.java @@ -717,7 +717,7 @@ public final class UrlConnectionCacheTest { .addHeader("Last-Modified: " + lastModifiedDate) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); assertEquals("v1", conditionalRequest.getHeader("If-None-Match")); - assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since")); + assertNull(conditionalRequest.getHeader("If-Modified-Since")); } @Test public void etagAndExpirationDateInTheFuture() throws Exception { diff --git a/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java b/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java index f7d9f30..2ee8982 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java +++ b/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java @@ -34,6 +34,7 @@ public final class CacheControl { private final boolean noStore; private final int maxAgeSeconds; private final int sMaxAgeSeconds; + private final boolean isPrivate; private final boolean isPublic; private final boolean mustRevalidate; private final int maxStaleSeconds; @@ -44,12 +45,13 @@ public final class CacheControl { String headerValue; // Lazily computed, if absent. private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds, - boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds, - boolean onlyIfCached, boolean noTransform, String headerValue) { + boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, + int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) { this.noCache = noCache; this.noStore = noStore; this.maxAgeSeconds = maxAgeSeconds; this.sMaxAgeSeconds = sMaxAgeSeconds; + this.isPrivate = isPrivate; this.isPublic = isPublic; this.mustRevalidate = mustRevalidate; this.maxStaleSeconds = maxStaleSeconds; @@ -64,6 +66,7 @@ public final class CacheControl { this.noStore = builder.noStore; this.maxAgeSeconds = builder.maxAgeSeconds; this.sMaxAgeSeconds = -1; + this.isPrivate = false; this.isPublic = false; this.mustRevalidate = false; this.maxStaleSeconds = builder.maxStaleSeconds; @@ -106,6 +109,10 @@ public final class CacheControl { return sMaxAgeSeconds; } + public boolean isPrivate() { + return isPrivate; + } + public boolean isPublic() { return isPublic; } @@ -146,6 +153,7 @@ public final class CacheControl { boolean noStore = false; int maxAgeSeconds = -1; int sMaxAgeSeconds = -1; + boolean isPrivate = false; boolean isPublic = false; boolean mustRevalidate = false; int maxStaleSeconds = -1; @@ -212,6 +220,8 @@ public final class CacheControl { maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); } else if ("s-maxage".equalsIgnoreCase(directive)) { sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); + } else if ("private".equalsIgnoreCase(directive)) { + isPrivate = true; } else if ("public".equalsIgnoreCase(directive)) { isPublic = true; } else if ("must-revalidate".equalsIgnoreCase(directive)) { @@ -231,7 +241,7 @@ public final class CacheControl { if (!canUseHeaderValue) { headerValue = null; } - return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPublic, + return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic, mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue); } @@ -246,6 +256,7 @@ public final class CacheControl { if (noStore) result.append("no-store, "); if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", "); if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", "); + if (isPrivate) result.append("private, "); if (isPublic) result.append("public, "); if (mustRevalidate) result.append("must-revalidate, "); if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", "); diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index 8d8586e..7dddc3a 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -32,6 +32,7 @@ import java.net.Socket; import java.net.URL; import java.security.cert.X509Certificate; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import okio.Source; @@ -254,7 +255,7 @@ public final class Connection { // Verify that the socket's certificates are acceptable for the target host. if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) { X509Certificate cert = (X509Certificate) sslSocket.getSession().getPeerCertificates()[0]; - throw new IOException("Hostname " + route.address.uriHost + " not verified:" + throw new SSLPeerUnverifiedException("Hostname " + route.address.uriHost + " not verified:" + "\n certificate: " + CertificatePinner.pin(cert) + "\n DN: " + cert.getSubjectDN().getName() + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); diff --git a/okhttp/src/main/java/com/squareup/okhttp/ConnectionSpec.java b/okhttp/src/main/java/com/squareup/okhttp/ConnectionSpec.java index e905052..254bcb1 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/ConnectionSpec.java +++ b/okhttp/src/main/java/com/squareup/okhttp/ConnectionSpec.java @@ -176,6 +176,7 @@ public final class ConnectionSpec { @Override public boolean equals(Object other) { if (!(other instanceof ConnectionSpec)) return false; + if (other == this) return true; ConnectionSpec that = (ConnectionSpec) other; if (this.tls != that.tls) return false; @@ -237,12 +238,20 @@ public final class ConnectionSpec { for (int i = 0; i < cipherSuites.length; i++) { strings[i] = cipherSuites[i].javaName; } - - return cipherSuites(strings); + this.cipherSuites = strings; + return this; } - Builder cipherSuites(String[] cipherSuites) { - this.cipherSuites = cipherSuites; // No defensive copy. + public Builder cipherSuites(String... cipherSuites) { + if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections"); + + if (cipherSuites == null) { + this.cipherSuites = null; + } else { + // This makes a defensive copy! + this.cipherSuites = cipherSuites.clone(); + } + return this; } @@ -254,12 +263,20 @@ public final class ConnectionSpec { for (int i = 0; i < tlsVersions.length; i++) { strings[i] = tlsVersions[i].javaName; } - - return tlsVersions(strings); + this.tlsVersions = strings; + return this; } - Builder tlsVersions(String... tlsVersions) { - this.tlsVersions = tlsVersions; // No defensive copy. + public Builder tlsVersions(String... tlsVersions) { + if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections"); + + if (tlsVersions == null) { + this.tlsVersions = null; + } else { + // This makes a defensive copy! + this.tlsVersions = tlsVersions.clone(); + } + return this; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java index 95eb7b0..a696c0c 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java @@ -22,7 +22,7 @@ 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.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -59,7 +59,7 @@ public final class Dispatcher { public synchronized ExecutorService getExecutorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); + new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index 56b55f9..a8b60db 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -203,7 +203,8 @@ public class OkHttpClient implements Cloneable { } /** - * Sets the default connect timeout for new connections. A value of 0 means no timeout. + * Sets the default connect timeout for new connections. A value of 0 means no timeout, otherwise + * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds. * * @see URLConnection#setConnectTimeout(int) */ @@ -212,6 +213,7 @@ public class OkHttpClient implements Cloneable { if (unit == null) throw new IllegalArgumentException("unit == null"); long millis = unit.toMillis(timeout); if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large."); + if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); connectTimeout = (int) millis; } @@ -221,7 +223,8 @@ public class OkHttpClient implements Cloneable { } /** - * Sets the default read timeout for new connections. A value of 0 means no timeout. + * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise + * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds. * * @see URLConnection#setReadTimeout(int) */ @@ -230,6 +233,7 @@ public class OkHttpClient implements Cloneable { if (unit == null) throw new IllegalArgumentException("unit == null"); long millis = unit.toMillis(timeout); if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large."); + if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); readTimeout = (int) millis; } @@ -239,13 +243,15 @@ public class OkHttpClient implements Cloneable { } /** - * Sets the default write timeout for new connections. A value of 0 means no timeout. + * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise + * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds. */ 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."); + if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); writeTimeout = (int) millis; } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/CacheStrategy.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/CacheStrategy.java index 7af04aa..3f07edd 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/CacheStrategy.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/CacheStrategy.java @@ -67,10 +67,12 @@ public final class CacheStrategy { case HTTP_MOVED_TEMP: case HTTP_TEMP_REDIRECT: // These codes can only be cached with the right response headers. + // http://tools.ietf.org/html/rfc7234#section-3 + // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage. if (response.header("Expires") != null || response.cacheControl().maxAgeSeconds() != -1 - || response.cacheControl().sMaxAgeSeconds() != -1 - || response.cacheControl().isPublic()) { + || response.cacheControl().isPublic() + || response.cacheControl().isPrivate()) { break; } // Fall-through. @@ -223,16 +225,14 @@ public final class CacheStrategy { Request.Builder conditionalRequestBuilder = request.newBuilder(); - if (lastModified != null) { + if (etag != null) { + conditionalRequestBuilder.header("If-None-Match", etag); + } else 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) diff --git a/okio/okio/src/main/java/okio/ByteString.java b/okio/okio/src/main/java/okio/ByteString.java index a42fbe6..d8335d1 100644 --- a/okio/okio/src/main/java/okio/ByteString.java +++ b/okio/okio/src/main/java/okio/ByteString.java @@ -211,6 +211,37 @@ public final class ByteString implements Serializable { return this; } + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. + */ + public ByteString substring(int beginIndex) { + return substring(beginIndex, data.length); + } + + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if + * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. + */ + public ByteString substring(int beginIndex, int endIndex) { + if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); + if (endIndex > data.length) { + throw new IllegalArgumentException("endIndex > length(" + data.length + ")"); + } + + int subLen = endIndex - beginIndex; + if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex"); + + if ((beginIndex == 0) && (endIndex == data.length)) { + return this; + } + + byte[] copy = new byte[subLen]; + System.arraycopy(data, beginIndex, copy, 0, subLen); + return new ByteString(copy); + } + /** Returns the byte at {@code pos}. */ public byte getByte(int pos) { return data[pos]; diff --git a/okio/okio/src/main/java/okio/Sink.java b/okio/okio/src/main/java/okio/Sink.java index 38c46de..370e83b 100644 --- a/okio/okio/src/main/java/okio/Sink.java +++ b/okio/okio/src/main/java/okio/Sink.java @@ -16,6 +16,7 @@ package okio; import java.io.Closeable; +import java.io.Flushable; import java.io.IOException; /** @@ -47,12 +48,12 @@ import java.io.IOException; * Use {@link Okio#sink} to adapt an {@code OutputStream} to a sink. Use {@link * BufferedSink#outputStream} to adapt a sink to an {@code OutputStream}. */ -public interface Sink extends Closeable { +public interface Sink extends Closeable, Flushable { /** Removes {@code byteCount} bytes from {@code source} and appends them to this. */ void write(Buffer source, long byteCount) throws IOException; /** Pushes all buffered bytes to their final destination. */ - void flush() throws IOException; + @Override void flush() throws IOException; /** Returns the timeout for this sink. */ Timeout timeout(); diff --git a/okio/okio/src/test/java/okio/ByteStringTest.java b/okio/okio/src/test/java/okio/ByteStringTest.java index 528ed05..ec98f7d 100644 --- a/okio/okio/src/test/java/okio/ByteStringTest.java +++ b/okio/okio/src/test/java/okio/ByteStringTest.java @@ -117,6 +117,37 @@ public class ByteStringTest { @Test public void toAsciiStartsUppercaseEndsLowercase() throws Exception { assertEquals(ByteString.encodeUtf8("ABCD"), ByteString.encodeUtf8("ABcd").toAsciiUppercase()); } + + @Test public void substring() throws Exception { + ByteString byteString = ByteString.encodeUtf8("Hello, World!"); + + assertEquals(byteString.substring(0), byteString); + assertEquals(byteString.substring(0, 5), ByteString.encodeUtf8("Hello")); + assertEquals(byteString.substring(7), ByteString.encodeUtf8("World!")); + assertEquals(byteString.substring(6, 6), ByteString.encodeUtf8("")); + } + + @Test public void substringWithInvalidBounds() throws Exception { + ByteString byteString = ByteString.encodeUtf8("Hello, World!"); + + try { + byteString.substring(-1); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + byteString.substring(0, 14); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + byteString.substring(8, 7); + fail(); + } catch (IllegalArgumentException expected) { + } + } @Test public void write() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); |