Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ Releases

Our [change log][changelog] has release history.

The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/okhttp/4.4.0/jar).
The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/okhttp/4.4.1/jar).

```kotlin
implementation("com.squareup.okhttp3:okhttp:4.4.0")
implementation("com.squareup.okhttp3:okhttp:4.4.1")
```

Snapshot builds are [available][snap]. [R8 and ProGuard][r8_proguard] rules are available.
Expand All @@ -113,10 +113,10 @@ MockWebServer

OkHttp includes a library for testing HTTP, HTTPS, and HTTP/2 clients.

The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/mockwebserver/4.4.0/jar).
The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/mockwebserver/4.4.1/jar).

```kotlin
testImplementation("com.squareup.okhttp3:mockwebserver:4.4.0")
testImplementation("com.squareup.okhttp3:mockwebserver:4.4.1")
```

License
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ ext.publishedArtifactId = { project ->
allprojects {
group = 'com.squareup.okhttp3'
project.ext.artifactId = rootProject.ext.publishedArtifactId(project)
version = '4.4.0'
version = '4.4.2-SNAPSHOT'

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion mockwebserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ server.setDispatcher(dispatcher);
### Download

```kotlin
testImplementation("com.squareup.okhttp3:mockwebserver:4.4.0")
testImplementation("com.squareup.okhttp3:mockwebserver:4.4.1")
```

### License
Expand Down
2 changes: 1 addition & 1 deletion okhttp-brotli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ OkHttpClient client = new OkHttpClient.Builder()
```

```kotlin
implementation("com.squareup.okhttp3:okhttp-brotli:4.4.0")
implementation("com.squareup.okhttp3:okhttp-brotli:4.4.1")
```

[1]: https://github.com/google/brotli
2 changes: 1 addition & 1 deletion okhttp-dnsoverhttps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ API is not considered stable and may change at any time.
### Download

```kotlin
testImplementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.4.0")
testImplementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.4.1")
```
2 changes: 1 addition & 1 deletion okhttp-logging-interceptor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Download
--------

```kotlin
implementation("com.squareup.okhttp3:logging-interceptor:4.4.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.4.1")
```


Expand Down
2 changes: 1 addition & 1 deletion okhttp-sse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ API is not considered stable and may change at any time.
### Download

```kotlin
testImplementation("com.squareup.okhttp3:okhttp-sse:4.4.0")
testImplementation("com.squareup.okhttp3:okhttp-sse:4.4.1")
```
2 changes: 1 addition & 1 deletion okhttp-tls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ Download
--------

```kotlin
implementation("com.squareup.okhttp3:okhttp-tls:4.4.0")
implementation("com.squareup.okhttp3:okhttp-tls:4.4.1")
```

[held_certificate]: http://square.github.io/okhttp/4.x/okhttp-tls/okhttp3.tls/-held-certificate/
Expand Down
2 changes: 1 addition & 1 deletion okhttp-urlconnection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ This module integrates OkHttp with `Authenticator` and `CookieHandler` from `jav
### Download

```kotlin
testImplementation("com.squareup.okhttp3:okhttp-urlconnection:4.4.0")
testImplementation("com.squareup.okhttp3:okhttp-urlconnection:4.4.1")
```
15 changes: 8 additions & 7 deletions okhttp/src/main/java/okhttp3/Handshake.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ class Handshake internal constructor(
peerCertificatesFn: () -> List<Certificate>
) {
/** Returns a possibly-empty list of certificates that identify the remote peer. */
@get:JvmName("peerCertificates") val peerCertificates: List<Certificate> by lazy(
peerCertificatesFn)
@get:JvmName("peerCertificates") val peerCertificates: List<Certificate> by lazy {
try {
peerCertificatesFn()
} catch (spue: SSLPeerUnverifiedException) {
listOf<Certificate>()
}
}

@JvmName("-deprecated_tlsVersion")
@Deprecated(
Expand Down Expand Up @@ -121,11 +126,7 @@ class Handshake internal constructor(
}

override fun toString(): String {
val peerCertificatesString = try {
peerCertificates.map { it.name }.toString()
} catch (_: SSLPeerUnverifiedException) {
"Failed: SSLPeerUnverifiedException"
}
val peerCertificatesString = peerCertificates.map { it.name }.toString()
return "Handshake{" +
"tlsVersion=$tlsVersion " +
"cipherSuite=$cipherSuite " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import okio.buffer
class Exchange(
internal val call: RealCall,
internal val eventListener: EventListener,
private val finder: ExchangeFinder,
internal val finder: ExchangeFinder,
private val codec: ExchangeCodec
) {
/** Returns true if the request body need not complete before the response body starts. */
Expand Down
18 changes: 15 additions & 3 deletions okhttp/src/main/java/okhttp3/internal/connection/ExchangeFinder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.io.IOException
import java.net.Socket
import okhttp3.Address
import okhttp3.EventListener
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Route
import okhttp3.internal.assertThreadDoesntHoldLock
Expand Down Expand Up @@ -142,9 +143,10 @@ class ExchangeFinder(
synchronized(connectionPool) {
if (call.isCanceled()) throw IOException("Canceled")

releasedConnection = call.connection
toClose = if (call.connection != null &&
(call.connection!!.noNewExchanges || !call.connection!!.supportsUrl(address.url))) {
val callConnection = call.connection // changes within this overall method
releasedConnection = callConnection
toClose = if (callConnection != null && (callConnection.noNewExchanges ||
!sameHostAndPort(callConnection.route().address.url))) {
call.releaseConnectionNoEvents()
} else {
null
Expand Down Expand Up @@ -332,4 +334,14 @@ class ExchangeFinder(
connection.routeFailureCount == 0 &&
connection.route().address.url.canReuseConnectionFor(address.url)
}

/**
* Returns true if the host and port are unchanged from when this was created. This is used to
* detect if followups need to do a full connection-finding process including DNS resolution, and
* certificate pin checks.
*/
fun sameHostAndPort(url: HttpUrl): Boolean {
val routeUrl = address.url
return url.port == routeUrl.port && url.host == routeUrl.host
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -576,9 +576,14 @@ class RealConnection(
}

// We have a host mismatch. But if the certificate matches, we're still good.
return !noCoalescedConnections &&
handshake != null &&
OkHostnameVerifier.verify(url.host, handshake!!.peerCertificates[0] as X509Certificate)
return !noCoalescedConnections && handshake != null && certificateSupportHost(url, handshake!!)
}

private fun certificateSupportHost(url: HttpUrl, handshake: Handshake): Boolean {
val peerCertificates = handshake.peerCertificates

return peerCertificates.isNotEmpty() && OkHostnameVerifier.verify(url.host,
peerCertificates[0] as X509Certificate)
}

@Throws(SocketException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class RealInterceptorChain(
calls++

if (exchange != null) {
check(exchange.connection.supportsUrl(request.url)) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
Expand Down
102 changes: 100 additions & 2 deletions okhttp/src/test/java/okhttp3/ConnectionCoalescingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.X509TrustManager;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.testing.PlatformRule;
Expand Down Expand Up @@ -267,9 +270,25 @@ public final class ConnectionCoalescingTest {
}
}

@Test public void skipsOnRedirectWhenDnsDontMatch() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(301)
.addHeader("Location", url.newBuilder().host("differentdns.com").build()));
server.enqueue(new MockResponse()
.setBody("unexpected call"));

try {
Response response = execute(url);
response.close();
fail("expected a failed attempt to connect");
} catch (IOException expected) {
}
}

/** Not in the certificate SAN. */
@Test public void skipsWhenNotSubjectAltName() throws Exception {
server.enqueue(new MockResponse());
server.enqueue(new MockResponse());

assert200Http2Response(execute(url), server.getHostName());

Expand All @@ -278,7 +297,21 @@ public final class ConnectionCoalescingTest {
try {
execute(nonsanUrl);
fail("expected a failed attempt to connect");
} catch (IOException expected) {
} catch (SSLPeerUnverifiedException expected) {
}
}

@Test public void skipsOnRedirectWhenNotSubjectAltName() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(301)
.addHeader("Location", url.newBuilder().host("nonsan.com").build()));
server.enqueue(new MockResponse());

try {
Response response = execute(url);
response.close();
fail("expected a failed attempt to connect");
} catch (SSLPeerUnverifiedException expected) {
}
}

Expand Down Expand Up @@ -321,6 +354,24 @@ public final class ConnectionCoalescingTest {
}
}

@Test public void skipsOnRedirectWhenCertificatePinningFails() throws Exception {
CertificatePinner pinner = new CertificatePinner.Builder()
.add("san.com", "sha1/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.build();
client = client.newBuilder().certificatePinner(pinner).build();

server.enqueue(new MockResponse()
.setResponseCode(301)
.addHeader("Location", url.newBuilder().host("san.com").build()));
server.enqueue(new MockResponse());

try {
execute(url);
fail("expected a failed attempt to connect");
} catch (SSLPeerUnverifiedException expected) {
}
}

/**
* Skips coalescing when hostname verifier is overridden since the intention of the hostname
* verification is a black box.
Expand All @@ -341,6 +392,22 @@ public final class ConnectionCoalescingTest {
assertThat(client.connectionPool().connectionCount()).isEqualTo(2);
}

@Test public void skipsOnRedirectWhenHostnameVerifierUsed() throws Exception {
HostnameVerifier verifier = (name, session) -> true;
client = client.newBuilder().hostnameVerifier(verifier).build();

server.enqueue(new MockResponse()
.setResponseCode(301)
.addHeader("Location", url.newBuilder().host("san.com").build()));
server.enqueue(new MockResponse());

assert200Http2Response(execute(url), "san.com");

assertThat(client.connectionPool().connectionCount()).isEqualTo(2);
assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(0); // Fresh connection.
assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(0); // Fresh connection.
}

/**
* Check we would use an existing connection to a later DNS result instead of connecting to the
* first DNS result for the first time.
Expand Down Expand Up @@ -374,7 +441,6 @@ public final class ConnectionCoalescingTest {

/** Check that wildcard SANs are supported. */
@Test public void commonThenWildcard() throws Exception {

server.enqueue(new MockResponse());
server.enqueue(new MockResponse());

Expand Down Expand Up @@ -432,6 +498,38 @@ public final class ConnectionCoalescingTest {
assertThat(client.connectionPool().connectionCount()).isEqualTo(2);
}

/**
* Won't coalesce if we can't clean certs e.g. a dev setup.
*/
@Test public void redirectWithDevSetup() throws Exception {
X509TrustManager TRUST_MANAGER = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}

@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};

client = client.newBuilder().sslSocketFactory(client.sslSocketFactory(), TRUST_MANAGER).build();

server.enqueue(new MockResponse());
server.enqueue(new MockResponse());

assert200Http2Response(execute(url), server.getHostName());

HttpUrl sanUrl = url.newBuilder().host("san.com").build();
assert200Http2Response(execute(sanUrl), "san.com");

assertThat(client.connectionPool().connectionCount()).isEqualTo(2);
}

private Response execute(HttpUrl url) throws IOException {
return client.newCall(new Request.Builder().url(url).build()).execute();
}
Expand Down