diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index 5700f275c045..2224d627cc36 100644 --- a/okhttp/api/android/okhttp.api +++ b/okhttp/api/android/okhttp.api @@ -402,7 +402,7 @@ public final class okhttp3/Cookie { public final fun -deprecated_persistent ()Z public final fun -deprecated_secure ()Z public final fun -deprecated_value ()Ljava/lang/String; - public synthetic fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;ZZZZLjava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;ZZZZLjava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun domain ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public final fun expiresAt ()J @@ -414,6 +414,7 @@ public final class okhttp3/Cookie { public final fun newBuilder ()Lokhttp3/Cookie$Builder; public static final fun parse (Lokhttp3/HttpUrl;Ljava/lang/String;)Lokhttp3/Cookie; public static final fun parseAll (Lokhttp3/HttpUrl;Lokhttp3/Headers;)Ljava/util/List; + public final fun partitioned ()Z public final fun path ()Ljava/lang/String; public final fun persistent ()Z public final fun sameSite ()Ljava/lang/String; @@ -430,6 +431,7 @@ public final class okhttp3/Cookie$Builder { public final fun hostOnlyDomain (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun httpOnly ()Lokhttp3/Cookie$Builder; public final fun name (Ljava/lang/String;)Lokhttp3/Cookie$Builder; + public final fun partitioned ()Lokhttp3/Cookie$Builder; public final fun path (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun sameSite (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun secure ()Lokhttp3/Cookie$Builder; diff --git a/okhttp/api/jvm/okhttp.api b/okhttp/api/jvm/okhttp.api index d065655fb030..33bb3db64ad1 100644 --- a/okhttp/api/jvm/okhttp.api +++ b/okhttp/api/jvm/okhttp.api @@ -402,7 +402,7 @@ public final class okhttp3/Cookie { public final fun -deprecated_persistent ()Z public final fun -deprecated_secure ()Z public final fun -deprecated_value ()Ljava/lang/String; - public synthetic fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;ZZZZLjava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;ZZZZLjava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun domain ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public final fun expiresAt ()J @@ -414,6 +414,7 @@ public final class okhttp3/Cookie { public final fun newBuilder ()Lokhttp3/Cookie$Builder; public static final fun parse (Lokhttp3/HttpUrl;Ljava/lang/String;)Lokhttp3/Cookie; public static final fun parseAll (Lokhttp3/HttpUrl;Lokhttp3/Headers;)Ljava/util/List; + public final fun partitioned ()Z public final fun path ()Ljava/lang/String; public final fun persistent ()Z public final fun sameSite ()Ljava/lang/String; @@ -430,6 +431,7 @@ public final class okhttp3/Cookie$Builder { public final fun hostOnlyDomain (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun httpOnly ()Lokhttp3/Cookie$Builder; public final fun name (Ljava/lang/String;)Lokhttp3/Cookie$Builder; + public final fun partitioned ()Lokhttp3/Cookie$Builder; public final fun path (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun sameSite (Ljava/lang/String;)Lokhttp3/Cookie$Builder; public final fun secure ()Lokhttp3/Cookie$Builder; diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Cookie.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Cookie.kt index 0b36b3f908c0..9a03ab1bb0a9 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Cookie.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Cookie.kt @@ -119,6 +119,12 @@ class Cookie private constructor( */ @get:JvmName("sameSite") val sameSite: String?, + /** + * Returns true if this cookie has the `Partitioned` attribute set. In web browsers this restricts + * the cookie to a separate storage jar per top-level site (CHIPS). Browsers require [secure] to + * also be true before honoring this attribute. + */ + @get:JvmName("partitioned") val partitioned: Boolean, ) { /** * Returns true if this cookie should be included on a request to [url]. In addition to this @@ -149,7 +155,8 @@ class Cookie private constructor( other.httpOnly == httpOnly && other.persistent == persistent && other.hostOnly == hostOnly && - other.sameSite == sameSite + other.sameSite == sameSite && + other.partitioned == partitioned @IgnoreJRERequirement // As of AGP 3.4.1, D8 desugars API 24 hashCode methods. override fun hashCode(): Int { @@ -164,6 +171,7 @@ class Cookie private constructor( result = 31 * result + persistent.hashCode() result = 31 * result + hostOnly.hashCode() result = 31 * result + sameSite.hashCode() + result = 31 * result + partitioned.hashCode() return result } @@ -282,6 +290,10 @@ class Cookie private constructor( append("; samesite=").append(sameSite) } + if (partitioned) { + append("; partitioned") + } + return toString() } } @@ -303,6 +315,7 @@ class Cookie private constructor( private var persistent = false private var hostOnly = false private var sameSite: String? = null + private var partitioned = false internal constructor(cookie: Cookie) : this() { this.name = cookie.name @@ -315,6 +328,7 @@ class Cookie private constructor( this.persistent = cookie.persistent this.hostOnly = cookie.hostOnly this.sameSite = cookie.sameSite + this.partitioned = cookie.partitioned } fun name(name: String) = @@ -383,6 +397,11 @@ class Cookie private constructor( this.sameSite = sameSite } + fun partitioned() = + apply { + this.partitioned = true + } + fun build(): Cookie = Cookie( name ?: throw NullPointerException("builder.name == null"), @@ -395,6 +414,7 @@ class Cookie private constructor( persistent, hostOnly, sameSite, + partitioned, ) } @@ -472,6 +492,7 @@ class Cookie private constructor( var hostOnly = true var persistent = false var sameSite: String? = null + var partitioned = false var pos = cookiePairEnd + 1 val limit = setCookie.length @@ -530,6 +551,10 @@ class Cookie private constructor( attributeName.equals("samesite", ignoreCase = true) -> { sameSite = attributeValue } + + attributeName.equals("partitioned", ignoreCase = true) -> { + partitioned = true + } } pos = attributePairEnd + 1 @@ -586,6 +611,7 @@ class Cookie private constructor( persistent, hostOnly, sameSite, + partitioned, ) } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CookieTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CookieTest.kt index 7f00f30e4c75..478fb84e1b7f 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CookieTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CookieTest.kt @@ -362,6 +362,18 @@ class CookieTest { assertThat(parse(url, "a=b; Secure")!!.secure).isTrue() } + @Test fun partitioned() { + assertThat(parse(url, "a=b")!!.partitioned).isFalse() + assertThat(parse(url, "a=b; Partitioned")!!.partitioned).isTrue() + } + + @Test fun securePartitioned() { + val cookie = parse(url, "a=b; Secure; SameSite=None; Partitioned") + assertThat(cookie!!.secure).isTrue() + assertThat(cookie.partitioned).isTrue() + assertThat(cookie.toString()).isEqualTo("a=b; path=/; secure; samesite=None; partitioned") + } + @Test fun maxAgeTakesPrecedenceOverExpires() { // Max-Age = 1, Expires = 2. In either order. assertThat(parseCookie(0L, url, "a=b; Max-Age=1; Expires=Thu, 01 Jan 1970 00:00:02 GMT")!!.expiresAt) @@ -434,6 +446,7 @@ class CookieTest { assertThat(cookie.persistent).isFalse() assertThat(cookie.hostOnly).isFalse() assertThat(cookie.sameSite).isNull() + assertThat(cookie.partitioned).isFalse() } @Test fun newBuilder() { @@ -565,6 +578,18 @@ class CookieTest { assertThat(cookie.httpOnly).isTrue() } + @Test fun builderPartitioned() { + val cookie = + Cookie + .Builder() + .name("a") + .value("b") + .hostOnlyDomain("example.com") + .partitioned() + .build() + assertThat(cookie.partitioned).isTrue() + } + @Test fun builderIpv6() { val cookie = Cookie @@ -658,6 +683,8 @@ class CookieTest { "a=b; Path=/c; Domain=example.com; Secure; HttpOnly; SameSite=Lax", "a=b; Path=/c; Domain=example.com; Max-Age=5; HttpOnly; SameSite=Lax", "a=b; Path=/c; Domain=example.com; Max-Age=5; Secure; ; SameSite=Lax", + "a=b; Path=/c; Domain=example.com; Max-Age=5; Secure; HttpOnly; Partitioned", + "a=b; Path=/c; Domain=example.com; Max-Age=5; Secure; HttpOnly; SameSite=None; Partitioned", ) for (stringA in cookieStrings) { val cookieA = parseCookie(0, url, stringA!!) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index ed0ea0a3bcac..818827c6ac36 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -343,6 +343,7 @@ class KotlinSourceModernTest { val path: String = cookie.path val httpOnly: Boolean = cookie.httpOnly val secure: Boolean = cookie.secure + val partitioned: Boolean = cookie.partitioned val matches: Boolean = cookie.matches("".toHttpUrl()) val parsedCookie: Cookie? = Cookie.parse("".toHttpUrl(), "") val cookies: List = Cookie.parseAll("".toHttpUrl(), headersOf()) @@ -360,6 +361,7 @@ class KotlinSourceModernTest { builder = builder.secure() builder = builder.httpOnly() builder = builder.sameSite("None") + builder = builder.partitioned() val cookie: Cookie = builder.build() }