diff --git a/native-image-tests/build.gradle.kts b/native-image-tests/build.gradle.kts index ba843c763fc3..03657004d3b6 100644 --- a/native-image-tests/build.gradle.kts +++ b/native-image-tests/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { implementation(libs.assertj.core) implementation(projects.mockwebserver3) implementation(projects.mockwebserver) - implementation(projects.okhttpUrlconnection) + implementation(projects.okhttpJavaNetCookiejar) implementation(projects.mockwebserver3Junit4) implementation(projects.mockwebserver3Junit5) implementation(libs.aqute.resolve) diff --git a/okhttp-java-net-cookiejar/README.md b/okhttp-java-net-cookiejar/README.md new file mode 100644 index 000000000000..d41d166fce09 --- /dev/null +++ b/okhttp-java-net-cookiejar/README.md @@ -0,0 +1,11 @@ +OkHttp java.net.CookieHandler +============================= + +This module integrates OkHttp with `CookieHandler` from `java.net`. +This used to be part of `okhttp-urlconnection` + +### Download + +```kotlin +testImplementation("com.squareup.okhttp3:okhttp-java-net-cookiehandler:4.11.0") +``` diff --git a/okhttp-java-net-cookiejar/api/okhttp-java-net-cookiejar.api b/okhttp-java-net-cookiejar/api/okhttp-java-net-cookiejar.api new file mode 100644 index 000000000000..1f2c11d2e000 --- /dev/null +++ b/okhttp-java-net-cookiejar/api/okhttp-java-net-cookiejar.api @@ -0,0 +1,6 @@ +public final class okhttp3/java/net/cookiejar/JavaNetCookieJar : okhttp3/CookieJar { + public fun (Ljava/net/CookieHandler;)V + public fun loadForRequest (Lokhttp3/HttpUrl;)Ljava/util/List; + public fun saveFromResponse (Lokhttp3/HttpUrl;Ljava/util/List;)V +} + diff --git a/okhttp-java-net-cookiejar/build.gradle.kts b/okhttp-java-net-cookiejar/build.gradle.kts new file mode 100644 index 000000000000..5d74471a86f3 --- /dev/null +++ b/okhttp-java-net-cookiejar/build.gradle.kts @@ -0,0 +1,25 @@ +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm + +plugins { + kotlin("jvm") + id("org.jetbrains.dokka") + id("com.vanniktech.maven.publish.base") + id("binary-compatibility-validator") +} + +project.applyOsgi( + "Export-Package: okhttp3.java.net.cookiejar", + "Automatic-Module-Name: okhttp3.java.net.cookiejar", + "Bundle-SymbolicName: com.squareup.okhttp3.java.net.cookiejar" +) + +dependencies { + api(projects.okhttp) + compileOnly(libs.findbugs.jsr305) + compileOnly(libs.animalsniffer.annotations) +} + +mavenPublishing { + configure(KotlinJvm(javadocJar = JavadocJar.Empty())) +} diff --git a/okhttp-java-net-cookiejar/src/main/kotlin/okhttp3/java/net/cookiejar/JavaNetCookieJar.kt b/okhttp-java-net-cookiejar/src/main/kotlin/okhttp3/java/net/cookiejar/JavaNetCookieJar.kt new file mode 100644 index 000000000000..2e2ecb80b044 --- /dev/null +++ b/okhttp-java-net-cookiejar/src/main/kotlin/okhttp3/java/net/cookiejar/JavaNetCookieJar.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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.java.net.cookiejar + + +import java.io.IOException +import java.net.CookieHandler +import java.net.HttpCookie +import java.util.Collections +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.internal.cookieToString +import okhttp3.internal.delimiterOffset +import okhttp3.internal.platform.Platform +import okhttp3.internal.platform.Platform.Companion.WARN +import okhttp3.internal.trimSubstring + +/** A cookie jar that delegates to a [java.net.CookieHandler]. */ +class JavaNetCookieJar(private val cookieHandler: CookieHandler) : CookieJar { + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val cookieStrings = mutableListOf() + for (cookie in cookies) { + cookieStrings.add(cookieToString(cookie, true)) + } + val multimap = mapOf("Set-Cookie" to cookieStrings) + try { + cookieHandler.put(url.toUri(), multimap) + } catch (e: IOException) { + Platform.get().log("Saving cookies failed for " + url.resolve("/...")!!, WARN, e) + } + } + + override fun loadForRequest(url: HttpUrl): List { + val cookieHeaders = try { + // The RI passes all headers. We don't have 'em, so we don't pass 'em! + cookieHandler.get(url.toUri(), emptyMap>()) + } catch (e: IOException) { + Platform.get().log("Loading cookies failed for " + url.resolve("/...")!!, WARN, e) + return emptyList() + } + + var cookies: MutableList? = null + for ((key, value) in cookieHeaders) { + if (("Cookie".equals(key, ignoreCase = true) || "Cookie2".equals(key, ignoreCase = true)) && + value.isNotEmpty()) { + for (header in value) { + if (cookies == null) cookies = mutableListOf() + cookies.addAll(decodeHeaderAsJavaNetCookies(url, header)) + } + } + } + + return if (cookies != null) { + Collections.unmodifiableList(cookies) + } else { + emptyList() + } + } + + /** + * Convert a request header to OkHttp's cookies via [HttpCookie]. That extra step handles + * multiple cookies in a single request header, which [Cookie.parse] doesn't support. + */ + private fun decodeHeaderAsJavaNetCookies(url: HttpUrl, header: String): List { + val result = mutableListOf() + var pos = 0 + val limit = header.length + var pairEnd: Int + while (pos < limit) { + pairEnd = header.delimiterOffset(";,", pos, limit) + val equalsSign = header.delimiterOffset('=', pos, pairEnd) + val name = header.trimSubstring(pos, equalsSign) + if (name.startsWith("$")) { + pos = pairEnd + 1 + continue + } + + // We have either name=value or just a name. + var value = if (equalsSign < pairEnd) { + header.trimSubstring(equalsSign + 1, pairEnd) + } else { + "" + } + + // If the value is "quoted", drop the quotes. + if (value.startsWith("\"") && value.endsWith("\"") && value.length >= 2) { + value = value.substring(1, value.length - 1) + } + + result.add(Cookie.Builder() + .name(name) + .value(value) + .domain(url.host) + .build()) + pos = pairEnd + 1 + } + return result + } +} diff --git a/okhttp-urlconnection/README.md b/okhttp-urlconnection/README.md index 20d572e28969..59237d6dad6f 100644 --- a/okhttp-urlconnection/README.md +++ b/okhttp-urlconnection/README.md @@ -3,6 +3,8 @@ OkHttp URLConnection This module integrates OkHttp with `Authenticator` and `CookieHandler` from `java.net`. +This module is obsolete; prefer `okhttp-java-net-cookiejar`. + ### Download ```kotlin diff --git a/okhttp-urlconnection/build.gradle.kts b/okhttp-urlconnection/build.gradle.kts index e7702908ca73..587be51048f5 100644 --- a/okhttp-urlconnection/build.gradle.kts +++ b/okhttp-urlconnection/build.gradle.kts @@ -17,14 +17,9 @@ project.applyOsgi( dependencies { api(projects.okhttp) + api(projects.okhttpJavaNetCookiejar) compileOnly(libs.findbugs.jsr305) compileOnly(libs.animalsniffer.annotations) - - testImplementation(projects.okhttpTestingSupport) - testImplementation(projects.okhttpTls) - testImplementation(projects.mockwebserver) - testImplementation(libs.junit) - testImplementation(libs.assertj.core) } mavenPublishing { diff --git a/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetAuthenticator.kt b/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetAuthenticator.kt index f7453dfb798d..c6157b75a144 100644 --- a/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetAuthenticator.kt +++ b/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetAuthenticator.kt @@ -16,14 +16,21 @@ package okhttp3 import java.io.IOException -import java.net.Authenticator import okhttp3.Authenticator.Companion.JAVA_NET_AUTHENTICATOR /** - * Adapts [Authenticator] to [okhttp3.Authenticator]. Configure OkHttp to use [Authenticator] with - * [okhttp3.OkHttpClient.Builder.authenticator] or [okhttp3.OkHttpClient.Builder.proxyAuthenticator]. + * Do not use this. + * + * Instead, configure your OkHttpClient.Builder to use `Authenticator.JAVA_NET_AUTHENTICATOR`: + * + * ``` + * val okHttpClient = OkHttpClient.Builder() + * .authenticator(okhttp3.Authenticator.Companion.JAVA_NET_AUTHENTICATOR) + * .build() + * ``` */ -class JavaNetAuthenticator : okhttp3.Authenticator { +@Deprecated(message = "Use okhttp3.Authenticator.Companion.JAVA_NET_AUTHENTICATOR instead") +class JavaNetAuthenticator : Authenticator { @Throws(IOException::class) override fun authenticate(route: Route?, response: Response): Request? = JAVA_NET_AUTHENTICATOR.authenticate(route, response) diff --git a/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetCookieJar.kt b/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetCookieJar.kt index eb68d0da78e8..49f17a5037fd 100644 --- a/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetCookieJar.kt +++ b/okhttp-urlconnection/src/main/kotlin/okhttp3/JavaNetCookieJar.kt @@ -14,98 +14,25 @@ * limitations under the License. */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + package okhttp3 -import java.io.IOException import java.net.CookieHandler -import java.net.HttpCookie -import java.util.Collections -import okhttp3.internal.cookieToString -import okhttp3.internal.delimiterOffset -import okhttp3.internal.platform.Platform -import okhttp3.internal.platform.Platform.Companion.WARN -import okhttp3.internal.trimSubstring - -/** A cookie jar that delegates to a [java.net.CookieHandler]. */ -class JavaNetCookieJar(private val cookieHandler: CookieHandler) : CookieJar { - - override fun saveFromResponse(url: HttpUrl, cookies: List) { - val cookieStrings = mutableListOf() - for (cookie in cookies) { - cookieStrings.add(cookieToString(cookie, true)) - } - val multimap = mapOf("Set-Cookie" to cookieStrings) - try { - cookieHandler.put(url.toUri(), multimap) - } catch (e: IOException) { - Platform.get().log("Saving cookies failed for " + url.resolve("/...")!!, WARN, e) - } - } - - override fun loadForRequest(url: HttpUrl): List { - val cookieHeaders = try { - // The RI passes all headers. We don't have 'em, so we don't pass 'em! - cookieHandler.get(url.toUri(), emptyMap>()) - } catch (e: IOException) { - Platform.get().log("Loading cookies failed for " + url.resolve("/...")!!, WARN, e) - return emptyList() - } - - var cookies: MutableList? = null - for ((key, value) in cookieHeaders) { - if (("Cookie".equals(key, ignoreCase = true) || "Cookie2".equals(key, ignoreCase = true)) && - value.isNotEmpty()) { - for (header in value) { - if (cookies == null) cookies = mutableListOf() - cookies.addAll(decodeHeaderAsJavaNetCookies(url, header)) - } - } - } - - return if (cookies != null) { - Collections.unmodifiableList(cookies) - } else { - emptyList() - } - } - /** - * Convert a request header to OkHttp's cookies via [HttpCookie]. That extra step handles - * multiple cookies in a single request header, which [Cookie.parse] doesn't support. - */ - private fun decodeHeaderAsJavaNetCookies(url: HttpUrl, header: String): List { - val result = mutableListOf() - var pos = 0 - val limit = header.length - var pairEnd: Int - while (pos < limit) { - pairEnd = header.delimiterOffset(";,", pos, limit) - val equalsSign = header.delimiterOffset('=', pos, pairEnd) - val name = header.trimSubstring(pos, equalsSign) - if (name.startsWith("$")) { - pos = pairEnd + 1 - continue - } - - // We have either name=value or just a name. - var value = if (equalsSign < pairEnd) { - header.trimSubstring(equalsSign + 1, pairEnd) - } else { - "" - } - - // If the value is "quoted", drop the quotes. - if (value.startsWith("\"") && value.endsWith("\"") && value.length >= 2) { - value = value.substring(1, value.length - 1) - } - - result.add(Cookie.Builder() - .name(name) - .value(value) - .domain(url.host) - .build()) - pos = pairEnd + 1 - } - return result - } +/** + * A cookie jar that delegates to a [java.net.CookieHandler]. + * + * This implementation delegates everything to [okhttp3.java.net.cookiejar.JavaNetCookieJar], which + * conforms to the package-naming limitations of JPMS. + * + * Callers should prefer to use [okhttp3.java.net.cookiejar.JavaNetCookieJar] directly. + */ +class JavaNetCookieJar private constructor( + delegate: okhttp3.java.net.cookiejar.JavaNetCookieJar, +) : CookieJar by delegate { + constructor(cookieHandler: CookieHandler) : this( + okhttp3.java.net.cookiejar.JavaNetCookieJar( + cookieHandler + ) + ) } diff --git a/okhttp/build.gradle.kts b/okhttp/build.gradle.kts index cd1ca09e6680..87e09f9d295a 100644 --- a/okhttp/build.gradle.kts +++ b/okhttp/build.gradle.kts @@ -109,6 +109,7 @@ kotlin { getByName("jvmTest") { dependencies { dependsOn(commonTest) + implementation(projects.okhttpJavaNetCookiejar) implementation(projects.okhttpTls) implementation(projects.okhttpUrlconnection) implementation(projects.mockwebserver3) diff --git a/okhttp/src/jvmTest/java/okhttp3/CacheCorruptionTest.kt b/okhttp/src/jvmTest/java/okhttp3/CacheCorruptionTest.kt index 802512e25b68..0c9f2b42570f 100644 --- a/okhttp/src/jvmTest/java/okhttp3/CacheCorruptionTest.kt +++ b/okhttp/src/jvmTest/java/okhttp3/CacheCorruptionTest.kt @@ -29,6 +29,7 @@ import mockwebserver3.MockResponse import mockwebserver3.MockWebServer import okhttp3.Headers.Companion.headersOf import okhttp3.internal.buildCache +import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.okio.LoggingFilesystem import okhttp3.testing.PlatformRule import okio.Path.Companion.toPath diff --git a/okhttp/src/jvmTest/java/okhttp3/CacheTest.java b/okhttp/src/jvmTest/java/okhttp3/CacheTest.java index 9821426ef3d5..2dceb96cf480 100644 --- a/okhttp/src/jvmTest/java/okhttp3/CacheTest.java +++ b/okhttp/src/jvmTest/java/okhttp3/CacheTest.java @@ -40,6 +40,7 @@ import mockwebserver3.junit5.internal.MockWebServerInstance; import okhttp3.internal.Internal; import okhttp3.internal.platform.Platform; +import okhttp3.java.net.cookiejar.JavaNetCookieJar; import okhttp3.testing.PlatformRule; import okhttp3.tls.HandshakeCertificates; import okio.Buffer; diff --git a/okhttp/src/jvmTest/java/okhttp3/CallTest.kt b/okhttp/src/jvmTest/java/okhttp3/CallTest.kt index d81a19d128e2..c517f1751bec 100644 --- a/okhttp/src/jvmTest/java/okhttp3/CallTest.kt +++ b/okhttp/src/jvmTest/java/okhttp3/CallTest.kt @@ -78,6 +78,7 @@ import okhttp3.internal.http.HTTP_EARLY_HINTS import okhttp3.internal.http.HTTP_PROCESSING import okhttp3.internal.http.RecordingProxySelector import okhttp3.internal.userAgent +import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.okio.LoggingFilesystem import okhttp3.testing.Flaky import okhttp3.testing.PlatformRule diff --git a/okhttp/src/jvmTest/java/okhttp3/CookiesTest.java b/okhttp/src/jvmTest/java/okhttp3/CookiesTest.java index 46c6157a1cc8..5a3b621d55ef 100644 --- a/okhttp/src/jvmTest/java/okhttp3/CookiesTest.java +++ b/okhttp/src/jvmTest/java/okhttp3/CookiesTest.java @@ -30,6 +30,7 @@ import mockwebserver3.MockResponse; import mockwebserver3.MockWebServer; import mockwebserver3.RecordedRequest; +import okhttp3.java.net.cookiejar.JavaNetCookieJar; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; diff --git a/okhttp/src/jvmTest/java/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/java/okhttp3/KotlinSourceModernTest.kt index 0822eee7a12b..0e93e2ef2d9e 100644 --- a/okhttp/src/jvmTest/java/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/java/okhttp3/KotlinSourceModernTest.kt @@ -57,9 +57,11 @@ import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.ResponseBody.Companion.asResponseBody import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.internal.authenticator.JavaNetAuthenticator import okhttp3.internal.http2.Settings import okhttp3.internal.proxy.NullProxySelector import okhttp3.internal.tls.OkHostnameVerifier +import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.LoggingEventListener import okhttp3.mockwebserver.MockResponse diff --git a/okhttp/src/jvmTest/java/okhttp3/URLConnectionTest.kt b/okhttp/src/jvmTest/java/okhttp3/URLConnectionTest.kt index 56f16b277328..c62d6b9817f2 100644 --- a/okhttp/src/jvmTest/java/okhttp3/URLConnectionTest.kt +++ b/okhttp/src/jvmTest/java/okhttp3/URLConnectionTest.kt @@ -69,10 +69,12 @@ import okhttp3.TestUtil.assertSuppressed import okhttp3.internal.RecordingAuthenticator import okhttp3.internal.RecordingOkAuthenticator import okhttp3.internal.addHeaderLenient +import okhttp3.internal.authenticator.JavaNetAuthenticator import okhttp3.internal.http.HTTP_PERM_REDIRECT import okhttp3.internal.http.HTTP_TEMP_REDIRECT import okhttp3.internal.platform.Platform.Companion.get import okhttp3.internal.userAgent +import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.testing.Flaky import okhttp3.testing.PlatformRule import okio.Buffer diff --git a/settings.gradle.kts b/settings.gradle.kts index e2fef7b82692..d8ee7affa618 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,15 +25,15 @@ include(":okcurl") include(":okhttp") include(":okhttp-bom") include(":okhttp-brotli") +include(":okhttp-coroutines") include(":okhttp-dnsoverhttps") include(":okhttp-hpacktests") include(":okhttp-idna-mapping-table") +include(":okhttp-java-net-cookiejar") include(":okhttp-logging-interceptor") -project(":okhttp-logging-interceptor").name = "logging-interceptor" include(":okhttp-sse") include(":okhttp-testing-support") include(":okhttp-tls") -include(":okhttp-coroutines") include(":okhttp-urlconnection") include(":samples:compare") include(":samples:crawler") @@ -44,6 +44,8 @@ include(":samples:static-server") include(":samples:tlssurvey") include(":samples:unixdomainsockets") +project(":okhttp-logging-interceptor").name = "logging-interceptor" + if (!isKnownBrokenIntelliJ()) { include(":okhttp-android") include(":android-test")