From 0e94b255b89853efca2c6313ab4b89be4a6d3c77 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 10 Feb 2024 12:52:34 +0000 Subject: [PATCH 1/6] Avoid expensive init on main --- .../src/main/kotlin/okhttp3/OkHttpClient.kt | 63 +++++++++++-------- .../okhttp3/OkHttpClientConstructionTest.kt | 55 ++++++++++++++++ 2 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt diff --git a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt index f9057a63dec9..0156ec05735f 100644 --- a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt @@ -205,14 +205,15 @@ open class OkHttpClient internal constructor( @get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactory - private val sslSocketFactoryOrNull: SSLSocketFactory? + private val sslInitializedFields: Lazy? @get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactory - get() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client") + get() = sslInitializedFields?.value?.sslSocketFactory ?: throw IllegalStateException("CLEARTEXT-only client") @get:JvmName("x509TrustManager") val x509TrustManager: X509TrustManager? + get() = sslInitializedFields?.value?.x509TrustManager @get:JvmName("connectionSpecs") val connectionSpecs: List = @@ -226,9 +227,11 @@ open class OkHttpClient internal constructor( @get:JvmName("certificatePinner") val certificatePinner: CertificatePinner + get() = sslInitializedFields?.value?.certificatePinner ?: CertificatePinner.DEFAULT @get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner? + get() = sslInitializedFields?.value?.certificateChainCleaner /** * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but @@ -269,29 +272,42 @@ open class OkHttpClient internal constructor( init { if (connectionSpecs.none { it.isTls }) { - this.sslSocketFactoryOrNull = null - this.certificateChainCleaner = null - this.x509TrustManager = null - this.certificatePinner = CertificatePinner.DEFAULT + this.sslInitializedFields = null } else if (builder.sslSocketFactoryOrNull != null) { - this.sslSocketFactoryOrNull = builder.sslSocketFactoryOrNull - this.certificateChainCleaner = builder.certificateChainCleaner!! - this.x509TrustManager = builder.x509TrustManagerOrNull!! - this.certificatePinner = - builder.certificatePinner - .withCertificateChainCleaner(certificateChainCleaner!!) + this.sslInitializedFields = + lazyOf( + SSLInitializedFields( + builder.x509TrustManagerOrNull!!, + builder.sslSocketFactoryOrNull!!, + builder.certificateChainCleaner!!, + builder.certificatePinner.withCertificateChainCleaner(builder.certificateChainCleaner!!), + ), + ) } else { - this.x509TrustManager = Platform.get().platformTrustManager() - this.sslSocketFactoryOrNull = Platform.get().newSslSocketFactory(x509TrustManager!!) - this.certificateChainCleaner = CertificateChainCleaner.get(x509TrustManager!!) - this.certificatePinner = - builder.certificatePinner - .withCertificateChainCleaner(certificateChainCleaner!!) + this.sslInitializedFields = + lazy { + val platform = Platform.get() + val trustManager = platform.platformTrustManager() + val certificateChainCleaner = CertificateChainCleaner.get(trustManager) + SSLInitializedFields( + trustManager, + platform.newSslSocketFactory(trustManager), + certificateChainCleaner, + builder.certificatePinner.withCertificateChainCleaner(certificateChainCleaner), + ) + } } verifyClientState() } + private data class SSLInitializedFields( + val x509TrustManager: X509TrustManager, + val sslSocketFactory: SSLSocketFactory, + val certificateChainCleaner: CertificateChainCleaner, + val certificatePinner: CertificatePinner, + ) + private fun verifyClientState() { check(null !in (interceptors as List)) { "Null interceptor: $interceptors" @@ -301,14 +317,9 @@ open class OkHttpClient internal constructor( } if (connectionSpecs.none { it.isTls }) { - check(sslSocketFactoryOrNull == null) - check(certificateChainCleaner == null) - check(x509TrustManager == null) - check(certificatePinner == CertificatePinner.DEFAULT) + check(sslInitializedFields == null) { "ssl initialized for plaintext client" } } else { - checkNotNull(sslSocketFactoryOrNull) { "sslSocketFactory == null" } - checkNotNull(certificateChainCleaner) { "certificateChainCleaner == null" } - checkNotNull(x509TrustManager) { "x509TrustManager == null" } + checkNotNull(sslInitializedFields) { "ssl not initialized for client" } } } @@ -597,7 +608,7 @@ open class OkHttpClient internal constructor( this.proxySelector = okHttpClient.proxySelector this.proxyAuthenticator = okHttpClient.proxyAuthenticator this.socketFactory = okHttpClient.socketFactory - this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull + this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactory this.x509TrustManagerOrNull = okHttpClient.x509TrustManager this.connectionSpecs = okHttpClient.connectionSpecs this.protocols = okHttpClient.protocols diff --git a/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt new file mode 100644 index 000000000000..1a702911858b --- /dev/null +++ b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt @@ -0,0 +1,55 @@ +/* + * 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 okhttp3 + +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.internal.platform.Platform +import okhttp3.testing.PlatformRule +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class OkHttpClientConstructionTest { + @RegisterExtension + var platform = PlatformRule() + + @Test fun constructionDoesntTriggerPlatformOrSSL() { + Platform.resetForTests(platform = ExplosivePlatform()) + + val client = OkHttpClient() + + assertNotNull(client.toString()) + + client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) + } + + class ExplosivePlatform : Platform() { + override fun newSSLContext(): SSLContext { + TODO("Avoid call") + } + + override fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory { + TODO("Avoid call") + } + + override fun platformTrustManager(): X509TrustManager { + TODO("Avoid call") + } + } +} From 2ab1256032196d9ed44318f808e4e4248c07d47c Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 10 Feb 2024 13:53:24 +0000 Subject: [PATCH 2/6] Fixes --- .../src/main/kotlin/okhttp3/OkHttpClient.kt | 76 +++++++++++-------- .../okhttp3/OkHttpClientConstructionTest.kt | 34 +++++++-- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt index 0156ec05735f..81300d1cabf4 100644 --- a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt @@ -225,9 +225,14 @@ open class OkHttpClient internal constructor( @get:JvmName("hostnameVerifier") val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier + private lateinit var _certificatePinner: CertificatePinner + @get:JvmName("certificatePinner") - val certificatePinner: CertificatePinner - get() = sslInitializedFields?.value?.certificatePinner ?: CertificatePinner.DEFAULT + val certificatePinner: CertificatePinner by lazy { + certificateChainCleaner?.let { + _certificatePinner.withCertificateChainCleaner(it) + } ?: _certificatePinner + } @get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner? @@ -273,16 +278,10 @@ open class OkHttpClient internal constructor( init { if (connectionSpecs.none { it.isTls }) { this.sslInitializedFields = null - } else if (builder.sslSocketFactoryOrNull != null) { - this.sslInitializedFields = - lazyOf( - SSLInitializedFields( - builder.x509TrustManagerOrNull!!, - builder.sslSocketFactoryOrNull!!, - builder.certificateChainCleaner!!, - builder.certificatePinner.withCertificateChainCleaner(builder.certificateChainCleaner!!), - ), - ) + this._certificatePinner = CertificatePinner.DEFAULT + } else if (builder.sslInitializedFields != null) { + this.sslInitializedFields = builder.sslInitializedFields + this._certificatePinner = builder.certificatePinner } else { this.sslInitializedFields = lazy { @@ -293,19 +292,18 @@ open class OkHttpClient internal constructor( trustManager, platform.newSslSocketFactory(trustManager), certificateChainCleaner, - builder.certificatePinner.withCertificateChainCleaner(certificateChainCleaner), ) } + this._certificatePinner = builder.certificatePinner } verifyClientState() } - private data class SSLInitializedFields( + internal data class SSLInitializedFields( val x509TrustManager: X509TrustManager, val sslSocketFactory: SSLSocketFactory, val certificateChainCleaner: CertificateChainCleaner, - val certificatePinner: CertificatePinner, ) private fun verifyClientState() { @@ -318,6 +316,7 @@ open class OkHttpClient internal constructor( if (connectionSpecs.none { it.isTls }) { check(sslInitializedFields == null) { "ssl initialized for plaintext client" } + check(certificatePinner == CertificatePinner.DEFAULT) } else { checkNotNull(sslInitializedFields) { "ssl not initialized for client" } } @@ -574,13 +573,11 @@ open class OkHttpClient internal constructor( internal var proxySelector: ProxySelector? = null internal var proxyAuthenticator: Authenticator = Authenticator.NONE internal var socketFactory: SocketFactory = SocketFactory.getDefault() - internal var sslSocketFactoryOrNull: SSLSocketFactory? = null - internal var x509TrustManagerOrNull: X509TrustManager? = null + internal var sslInitializedFields: Lazy? = null internal var connectionSpecs: List = DEFAULT_CONNECTION_SPECS internal var protocols: List = DEFAULT_PROTOCOLS internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT - internal var certificateChainCleaner: CertificateChainCleaner? = null internal var callTimeout = 0 internal var connectTimeout = 10_000 internal var readTimeout = 10_000 @@ -608,13 +605,11 @@ open class OkHttpClient internal constructor( this.proxySelector = okHttpClient.proxySelector this.proxyAuthenticator = okHttpClient.proxyAuthenticator this.socketFactory = okHttpClient.socketFactory - this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactory - this.x509TrustManagerOrNull = okHttpClient.x509TrustManager + this.sslInitializedFields = okHttpClient.sslInitializedFields this.connectionSpecs = okHttpClient.connectionSpecs this.protocols = okHttpClient.protocols this.hostnameVerifier = okHttpClient.hostnameVerifier - this.certificatePinner = okHttpClient.certificatePinner - this.certificateChainCleaner = okHttpClient.certificateChainCleaner + this.certificatePinner = okHttpClient._certificatePinner this.callTimeout = okHttpClient.callTimeoutMillis this.connectTimeout = okHttpClient.connectTimeoutMillis this.readTimeout = okHttpClient.readTimeoutMillis @@ -877,18 +872,25 @@ open class OkHttpClient internal constructor( ) fun sslSocketFactory(sslSocketFactory: SSLSocketFactory) = apply { - if (sslSocketFactory != this.sslSocketFactoryOrNull) { + if (sslSocketFactory != sslInitializedFields?.value?.sslSocketFactory) { this.routeDatabase = null } - this.sslSocketFactoryOrNull = sslSocketFactory - this.x509TrustManagerOrNull = - Platform.get().trustManager(sslSocketFactory) ?: throw IllegalStateException( - "Unable to extract the trust manager on ${Platform.get()}, " + + val platform = Platform.get() + val trustManager = + platform.trustManager(sslSocketFactory) ?: throw IllegalStateException( + "Unable to extract the trust manager on $platform, " + "sslSocketFactory is ${sslSocketFactory.javaClass}", ) - this.certificateChainCleaner = - Platform.get().buildCertificateChainCleaner(x509TrustManagerOrNull!!) + // Expensive copy assuming SSL already initialized + sslInitializedFields = + lazyOf( + SSLInitializedFields( + trustManager, + sslSocketFactory = sslSocketFactory, + certificateChainCleaner = platform.buildCertificateChainCleaner(trustManager), + ), + ) } /** @@ -940,13 +942,21 @@ open class OkHttpClient internal constructor( sslSocketFactory: SSLSocketFactory, trustManager: X509TrustManager, ) = apply { - if (sslSocketFactory != this.sslSocketFactoryOrNull || trustManager != this.x509TrustManagerOrNull) { + val existingSsl = sslInitializedFields?.value + + if (sslSocketFactory != existingSsl?.sslSocketFactory || trustManager != existingSsl?.x509TrustManager) { this.routeDatabase = null } - this.sslSocketFactoryOrNull = sslSocketFactory - this.certificateChainCleaner = CertificateChainCleaner.get(trustManager) - this.x509TrustManagerOrNull = trustManager + // Expensive copy assuming SSL already initialized + sslInitializedFields = + lazyOf( + SSLInitializedFields( + trustManager, + sslSocketFactory = sslSocketFactory, + certificateChainCleaner = CertificateChainCleaner.get(trustManager), + ), + ) } fun connectionSpecs(connectionSpecs: List) = diff --git a/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt index 1a702911858b..0f5b862d1354 100644 --- a/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt +++ b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt @@ -15,6 +15,7 @@ */ package okhttp3 +import java.security.NoSuchAlgorithmException import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -23,6 +24,7 @@ import okhttp3.internal.platform.Platform import okhttp3.testing.PlatformRule import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.RegisterExtension class OkHttpClientConstructionTest { @@ -30,7 +32,7 @@ class OkHttpClientConstructionTest { var platform = PlatformRule() @Test fun constructionDoesntTriggerPlatformOrSSL() { - Platform.resetForTests(platform = ExplosivePlatform()) + Platform.resetForTests(platform = ExplosivePlatform { TODO("Avoid call") }) val client = OkHttpClient() @@ -39,17 +41,39 @@ class OkHttpClientConstructionTest { client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) } - class ExplosivePlatform : Platform() { + @Test fun cloneDoesntTriggerPlatformOrSSL() { + Platform.resetForTests(platform = ExplosivePlatform { TODO("Avoid call") }) + + val client = OkHttpClient() + + val client2 = client.newBuilder().build() + assertNotNull(client2.toString()) + } + + @Test fun triggersOnExecute() { + Platform.resetForTests(platform = ExplosivePlatform { throw NoSuchAlgorithmException() }) + + val client = OkHttpClient() + + val call = client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) + + val exception = + assertThrows { + call.execute() + } + } + + class ExplosivePlatform(private val explode: () -> Nothing) : Platform() { override fun newSSLContext(): SSLContext { - TODO("Avoid call") + explode() } override fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory { - TODO("Avoid call") + explode() } override fun platformTrustManager(): X509TrustManager { - TODO("Avoid call") + explode() } } } From dfa7c345694322a0ef057722690122dee3a8a714 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 10 Feb 2024 14:06:19 +0000 Subject: [PATCH 3/6] Fixes --- okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt | 6 ++++-- okhttp/src/test/java/okhttp3/OkHttpClientTest.kt | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt index 81300d1cabf4..95d084664e3b 100644 --- a/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/main/kotlin/okhttp3/OkHttpClient.kt @@ -1052,11 +1052,13 @@ open class OkHttpClient internal constructor( */ fun certificatePinner(certificatePinner: CertificatePinner) = apply { - if (certificatePinner != this.certificatePinner) { + val cleanCertificatePinner = CertificatePinner(certificatePinner.pins) + + if (cleanCertificatePinner != this.certificatePinner) { this.routeDatabase = null } - this.certificatePinner = certificatePinner + this.certificatePinner = cleanCertificatePinner } /** diff --git a/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt b/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt index 8bc455b09692..8c90db88d168 100644 --- a/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt +++ b/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt @@ -362,6 +362,15 @@ class OkHttpClientTest { .routeDatabase, ) + // identical CertificatePinner + assertSame( + client.routeDatabase, + client.newBuilder() + .certificatePinner(CertificatePinner.Builder().build()) + .build() + .routeDatabase, + ) + // logically different scope of client for route db assertNotSame( client.routeDatabase, @@ -422,7 +431,9 @@ class OkHttpClientTest { assertNotSame( client.routeDatabase, client.newBuilder() - .certificatePinner(CertificatePinner.Builder().build()) + .certificatePinner(CertificatePinner.Builder() + .add("san.com", "sha1/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + .build()) .build() .routeDatabase, ) From 4c2c12c12733d44575833b133f950d806de039da Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 10 Feb 2024 14:11:01 +0000 Subject: [PATCH 4/6] Fixes --- .../src/test/java/okhttp3/OkHttpClientConstructionTest.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt index 0f5b862d1354..cf73e13935d8 100644 --- a/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt +++ b/okhttp/src/test/java/okhttp3/OkHttpClientConstructionTest.kt @@ -57,10 +57,9 @@ class OkHttpClientConstructionTest { val call = client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) - val exception = - assertThrows { - call.execute() - } + assertThrows { + call.execute() + } } class ExplosivePlatform(private val explode: () -> Nothing) : Platform() { From 769811c04d0565d41f561f2dcf6b6b2486f5ff92 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 10 Feb 2024 14:31:06 +0000 Subject: [PATCH 5/6] Fixes --- okhttp/src/test/java/okhttp3/OkHttpClientTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt b/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt index 8c90db88d168..b6a443e57d4a 100644 --- a/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt +++ b/okhttp/src/test/java/okhttp3/OkHttpClientTest.kt @@ -431,9 +431,11 @@ class OkHttpClientTest { assertNotSame( client.routeDatabase, client.newBuilder() - .certificatePinner(CertificatePinner.Builder() - .add("san.com", "sha1/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") - .build()) + .certificatePinner( + CertificatePinner.Builder() + .add("san.com", "sha1/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + .build(), + ) .build() .routeDatabase, ) From 38e8c4b018881650bb7a145a43295b32cb35b41f Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 4 Jan 2025 15:59:20 +0000 Subject: [PATCH 6/6] Fix merge --- .../okhttp3/OkHttpClientConstructionTest.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 okhttp/src/jvmTest/kotlin/okhttp3/OkHttpClientConstructionTest.kt diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/OkHttpClientConstructionTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/OkHttpClientConstructionTest.kt new file mode 100644 index 000000000000..cf73e13935d8 --- /dev/null +++ b/okhttp/src/jvmTest/kotlin/okhttp3/OkHttpClientConstructionTest.kt @@ -0,0 +1,78 @@ +/* + * 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 okhttp3 + +import java.security.NoSuchAlgorithmException +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.internal.platform.Platform +import okhttp3.testing.PlatformRule +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.RegisterExtension + +class OkHttpClientConstructionTest { + @RegisterExtension + var platform = PlatformRule() + + @Test fun constructionDoesntTriggerPlatformOrSSL() { + Platform.resetForTests(platform = ExplosivePlatform { TODO("Avoid call") }) + + val client = OkHttpClient() + + assertNotNull(client.toString()) + + client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) + } + + @Test fun cloneDoesntTriggerPlatformOrSSL() { + Platform.resetForTests(platform = ExplosivePlatform { TODO("Avoid call") }) + + val client = OkHttpClient() + + val client2 = client.newBuilder().build() + assertNotNull(client2.toString()) + } + + @Test fun triggersOnExecute() { + Platform.resetForTests(platform = ExplosivePlatform { throw NoSuchAlgorithmException() }) + + val client = OkHttpClient() + + val call = client.newCall(Request("https://example.org/robots.txt".toHttpUrl())) + + assertThrows { + call.execute() + } + } + + class ExplosivePlatform(private val explode: () -> Nothing) : Platform() { + override fun newSSLContext(): SSLContext { + explode() + } + + override fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory { + explode() + } + + override fun platformTrustManager(): X509TrustManager { + explode() + } + } +}