diff --git a/okhttp-android/build.gradle.kts b/okhttp-android/build.gradle.kts index 59a27124e4ff..99283055bd6f 100644 --- a/okhttp-android/build.gradle.kts +++ b/okhttp-android/build.gradle.kts @@ -43,6 +43,19 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } + + sourceSets { + getByName("main") { + assets { + srcDir("$buildDir/generated/sources/assets") + } + } + } +} + +val copyPublicSuffixDatabase = tasks.register("copyPublicSuffixDatabase") { + from(project(":okhttp").file("src/main/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz")) + into("$buildDir/generated/sources/assets/okhttp3/internal/publicsuffix") } dependencies { @@ -55,6 +68,7 @@ dependencies { debugImplementation(libs.findbugs.jsr305) compileOnly(libs.animalsniffer.annotations) compileOnly(libs.robolectric.android) + implementation("androidx.startup:startup-runtime:1.2.0") testImplementation(libs.junit) testImplementation(libs.junit.ktx) @@ -69,6 +83,8 @@ dependencies { androidTestImplementation(libs.assertk) androidTestImplementation(projects.mockwebserver3Junit4) androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1") + androidTestImplementation("androidx.test:core:1.6.1") } mavenPublishing { diff --git a/okhttp-android/src/androidTest/kotlin/okhttp3/android/PublicSuffixDatabaseTest.kt b/okhttp-android/src/androidTest/kotlin/okhttp3/android/PublicSuffixDatabaseTest.kt new file mode 100644 index 000000000000..c207abdc0733 --- /dev/null +++ b/okhttp-android/src/androidTest/kotlin/okhttp3/android/PublicSuffixDatabaseTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 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. + * + */ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +package okhttp3.android + +import assertk.assertThat +import assertk.assertions.isNotNull +import okhttp3.internal.platform.Platform +import okhttp3.internal.platform.android.AndroidContextPlatform +import okhttp3.internal.publicsuffix.PublicSuffixDatabase +import org.junit.Test + +/** + * Run with "./gradlew :android-test:connectedCheck -PandroidBuild=true" and make sure ANDROID_SDK_ROOT is set. + */ +class PublicSuffixDatabaseTest { + + @Test + fun testFromAsset() { + assertThat((Platform.get() as AndroidContextPlatform).context).isNotNull() + + val db = PublicSuffixDatabase.get() + db.getEffectiveTldPlusOne("www.smh.com.au") + } +} diff --git a/okhttp-android/src/main/AndroidManifest.xml b/okhttp-android/src/main/AndroidManifest.xml index 4fe7e5ca898e..8ba96a50874e 100644 --- a/okhttp-android/src/main/AndroidManifest.xml +++ b/okhttp-android/src/main/AndroidManifest.xml @@ -1,6 +1,18 @@ + xmlns:tools="http://schemas.android.com/tools" + package="okhttp.android"> + + + + + diff --git a/okhttp-android/src/main/kotlin/okhttp3/android/OkHttpStartupInitializer.kt b/okhttp-android/src/main/kotlin/okhttp3/android/OkHttpStartupInitializer.kt new file mode 100644 index 000000000000..6c5f6bfa9b7d --- /dev/null +++ b/okhttp-android/src/main/kotlin/okhttp3/android/OkHttpStartupInitializer.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 Block, 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. + */ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +package okhttp3.android + +import android.content.Context +import androidx.startup.Initializer +import okhttp3.internal.platform.AndroidPlatform +import okhttp3.internal.platform.Platform +import okhttp3.internal.platform.android.AndroidContextPlatform + +/** + * An [Initializer] that initializes the OkHttp [Platform] instance. + * + * This initializer sets the Android context for the platform if it's an [AndroidPlatform]. + * This allows OkHttp to access Android-specific features like the application's cache directory. + * + * This initializer has no dependencies. + */ +class OkHttpStartupInitializer : Initializer { + override fun create(context: Context): Platform { + val platform = Platform.get() + + if (platform is AndroidContextPlatform) { + platform.setAndroidContext(context) + } + + return platform + } + + override fun dependencies(): List>> { + return emptyList() + } +} diff --git a/okhttp-android/src/main/resources/META-INF/proguard/okhttp3.pro b/okhttp-android/src/main/resources/META-INF/proguard/okhttp3.pro new file mode 100644 index 000000000000..050195d175ed --- /dev/null +++ b/okhttp-android/src/main/resources/META-INF/proguard/okhttp3.pro @@ -0,0 +1,15 @@ +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keeppackagenames okhttp3.internal.publicsuffix.* +-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz + +# OkHttp platform used only on JVM and when Conscrypt and other security providers are available. +-dontwarn okhttp3.internal.platform.** +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** diff --git a/okhttp/build.gradle.kts b/okhttp/build.gradle.kts index 084db6e649d4..cba9998c6fda 100644 --- a/okhttp/build.gradle.kts +++ b/okhttp/build.gradle.kts @@ -22,11 +22,11 @@ val copyKotlinTemplates = tasks.register("copyKotlinTemplates") { into("$buildDir/generated/sources/kotlinTemplates") // Tag as an input to regenerate after an update - inputs.file("src/test/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz") + inputs.file("src/main/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz") filteringCharset = Charsets.UTF_8.toString() - val databaseGz = project.file("src/test/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz") + val databaseGz = project.file("src/main/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz") val listBytes = databaseGz.readBytes().toByteStringExpression() expand( diff --git a/okhttp/src/main/kotlin/okhttp3/internal/platform/Android10Platform.kt b/okhttp/src/main/kotlin/okhttp3/internal/platform/Android10Platform.kt index 02df7e3fdfde..aac706960313 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/platform/Android10Platform.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/platform/Android10Platform.kt @@ -26,6 +26,7 @@ import okhttp3.Protocol import okhttp3.internal.SuppressSignatureCheck import okhttp3.internal.platform.android.Android10SocketAdapter import okhttp3.internal.platform.android.AndroidCertificateChainCleaner +import okhttp3.internal.platform.android.AndroidContextPlatform import okhttp3.internal.platform.android.AndroidSocketAdapter import okhttp3.internal.platform.android.BouncyCastleSocketAdapter import okhttp3.internal.platform.android.ConscryptSocketAdapter @@ -34,7 +35,7 @@ import okhttp3.internal.tls.CertificateChainCleaner /** Android 10+ (API 29+). */ @SuppressSignatureCheck -class Android10Platform : Platform() { +class Android10Platform : AndroidContextPlatform() { private val socketAdapters = listOfNotNull( Android10SocketAdapter.buildIfSupported(), diff --git a/okhttp/src/main/kotlin/okhttp3/internal/platform/AndroidPlatform.kt b/okhttp/src/main/kotlin/okhttp3/internal/platform/AndroidPlatform.kt index 8a6f67a8670f..98c26ffba9be 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/platform/AndroidPlatform.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/platform/AndroidPlatform.kt @@ -15,6 +15,7 @@ */ package okhttp3.internal.platform +import android.content.Context import android.os.Build import android.security.NetworkSecurityPolicy import java.io.IOException @@ -30,18 +31,34 @@ import javax.net.ssl.X509TrustManager import okhttp3.Protocol import okhttp3.internal.SuppressSignatureCheck import okhttp3.internal.platform.android.AndroidCertificateChainCleaner +import okhttp3.internal.platform.android.AndroidContextPlatform import okhttp3.internal.platform.android.AndroidSocketAdapter import okhttp3.internal.platform.android.BouncyCastleSocketAdapter import okhttp3.internal.platform.android.ConscryptSocketAdapter import okhttp3.internal.platform.android.DeferredSocketAdapter import okhttp3.internal.platform.android.StandardAndroidSocketAdapter +import okhttp3.internal.publicsuffix.PublicSuffixList +import okhttp3.internal.publicsuffix.ResourcePublicSuffixList import okhttp3.internal.tls.BasicTrustRootIndex import okhttp3.internal.tls.CertificateChainCleaner import okhttp3.internal.tls.TrustRootIndex +import okio.source /** Android 5 to 9 (API 21 to 28). */ @SuppressSignatureCheck -class AndroidPlatform : Platform() { +class AndroidPlatform : AndroidContextPlatform() { + + override fun publicSuffixList(): PublicSuffixList { + val context = this.context + return if (context == null) { + super.publicSuffixList() + } else { + return ResourcePublicSuffixList { + context.assets.open("okhttp3/internal/publicsuffix/PublicSuffixDatabase").source() + } + } + } + private val socketAdapters = listOfNotNull( StandardAndroidSocketAdapter.buildIfSupported(), diff --git a/okhttp/src/main/kotlin/okhttp3/internal/platform/Platform.kt b/okhttp/src/main/kotlin/okhttp3/internal/platform/Platform.kt index 4483de380150..fb106fc0047a 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/platform/Platform.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/platform/Platform.kt @@ -36,6 +36,8 @@ import javax.net.ssl.X509TrustManager import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.internal.platform.android.AndroidLog +import okhttp3.internal.publicsuffix.EmbeddedPublicSuffixList +import okhttp3.internal.publicsuffix.PublicSuffixList import okhttp3.internal.readFieldOrNull import okhttp3.internal.tls.BasicCertificateChainCleaner import okhttp3.internal.tls.BasicTrustRootIndex @@ -72,9 +74,12 @@ import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement * Supported on Android 6.0+ via `NetworkSecurityPolicy`. */ open class Platform { + /** Prefix used on custom headers. */ fun getPrefix() = "OkHttp" + internal open fun publicSuffixList(): PublicSuffixList = EmbeddedPublicSuffixList + open fun newSSLContext(): SSLContext = SSLContext.getInstance("TLS") open fun platformTrustManager(): X509TrustManager { diff --git a/okhttp/src/main/kotlin/okhttp3/internal/platform/android/AndroidContextPlatform.kt b/okhttp/src/main/kotlin/okhttp3/internal/platform/android/AndroidContextPlatform.kt new file mode 100644 index 000000000000..9bf4bc26ae48 --- /dev/null +++ b/okhttp/src/main/kotlin/okhttp3/internal/platform/android/AndroidContextPlatform.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Block, 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.internal.platform.android + +import android.content.Context +import okhttp3.internal.platform.Platform + +/** + * Platform interface for accessing the Android Context. + * + * This interface provides a way to access the Android Context from Platform implementations. + */ +abstract class AndroidContextPlatform: Platform() { + var context: Context? = null + + fun setAndroidContext(context: Context) { + this.context = context + } + +} diff --git a/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt index 88094ad1c600..5225fbcf10ba 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt @@ -17,6 +17,7 @@ package okhttp3.internal.publicsuffix import java.net.IDN import okhttp3.internal.and +import okhttp3.internal.platform.Platform import okio.ByteString import okio.ByteString.Companion.encodeUtf8 @@ -153,7 +154,7 @@ class PublicSuffixDatabase internal constructor( private const val EXCEPTION_MARKER = '!' - private val instance = PublicSuffixDatabase(EmbeddedPublicSuffixList) + private val instance = PublicSuffixDatabase(Platform.get().publicSuffixList()) fun get(): PublicSuffixDatabase { return instance diff --git a/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixList.kt b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixList.kt index 3fdfaa659edc..816117c0a85e 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixList.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixList.kt @@ -20,7 +20,7 @@ import okio.ByteString /** * Basic I/O for the PublicSuffixDatabase.gz. */ -internal interface PublicSuffixList { +interface PublicSuffixList { fun ensureLoaded() val bytes: ByteString diff --git a/okhttp/src/test/java/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt similarity index 86% rename from okhttp/src/test/java/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt rename to okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt index 3efacb9e66d1..bd72a6f9c75e 100644 --- a/okhttp/src/test/java/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/ResourcePublicSuffixList.kt @@ -5,16 +5,17 @@ import java.io.InterruptedIOException import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicBoolean import okhttp3.internal.platform.Platform +import okhttp3.internal.platform.android.AndroidContextPlatform import okio.ByteString import okio.FileSystem import okio.GzipSource import okio.Path import okio.Path.Companion.toPath +import okio.Source import okio.buffer -internal class ResourcePublicSuffixList( - val path: Path = PUBLIC_SUFFIX_RESOURCE, - val fileSystem: FileSystem = FileSystem.RESOURCES, +class ResourcePublicSuffixList( + val sourceProvider: () -> Source, ) : PublicSuffixList { /** True after we've attempted to read the list for the first time. */ private val listRead = AtomicBoolean(false) @@ -29,13 +30,24 @@ internal class ResourcePublicSuffixList( override lateinit var bytes: ByteString override lateinit var exceptionBytes: ByteString + constructor( + path: Path = PUBLIC_SUFFIX_RESOURCE, + fileSystem: FileSystem = FileSystem.RESOURCES, + ) : this({ + val platform = Platform.get() + check(!Platform.isAndroid || (platform as? AndroidContextPlatform)?.context == null) { + "PublicSuffixDatabase.gz loaded from resources on Android" + } + GzipSource(fileSystem.source(path)) + }) + @Throws(IOException::class) private fun readTheList() { var publicSuffixListBytes: ByteString? var publicSuffixExceptionListBytes: ByteString? try { - GzipSource(fileSystem.source(path)).buffer().use { bufferedSource -> + sourceProvider().buffer().use { bufferedSource -> val totalBytes = bufferedSource.readInt() publicSuffixListBytes = bufferedSource.readByteString(totalBytes.toLong()) diff --git a/okhttp/src/test/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz b/okhttp/src/main/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz similarity index 100% rename from okhttp/src/test/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz rename to okhttp/src/main/resources/okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz