Add methods to CanonicalCookie to serialize/deserialize partition keys
to/from strings. The serialization scheme for a cookie partition key (absl::optional<net::SchemefulSite) is as follows: - If the key is absl::nullopt, the output is net::kEmptyPartitionKey - If the key contains a file origin, the output is SchemefulSite::SerializeFileSiteWithHost(). - Otherwise the output is SchemefulSite::Serialize(). The serialization method returns false if the partition key is invalid or the SchemefulSite's origin is opaque. If this method returns false the cookie should not be saved to disk. The deserialization scheme works as follows: - If the input is net::kEmptyPartitionKey, the output is absl::nullopt - Otherwise return SchemefulSite::Deserialize(input) The deserialization method returns false if the serialized key is invalid. If this method returns false the cookie should not be loaded from disk into a CanonicalCookie and should be deleted. This CL also adds the method SerializeFile[Origin|Site]WithHost to url::Origin and net::SchemefulSite respectively. Unlike Serialize, this method should only be called on file origins and preserves the origins' host. Bug: 1225444 Change-Id: Ic113b693dfc450857a43cbf2ca645a5adf6b016e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3032294 Reviewed-by: Matt Menke <mmenke@chromium.org> Reviewed-by: Mike West <mkwst@chromium.org> Reviewed-by: Lily Chen <chlily@chromium.org> Reviewed-by: Charlie Harrison <csharrison@chromium.org> Commit-Queue: Dylan Cutler <dylancutler@google.com> Cr-Commit-Position: refs/heads/master@{#905372}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
d4848f10ea
commit
b63c414661
chrome/browser/android/cookies
net
@ -41,6 +41,9 @@ void OnCookiesFetchFinished(const net::CookieList& cookies) {
|
||||
|
||||
int index = 0;
|
||||
for (auto i = cookies.cbegin(); i != cookies.cend(); ++i) {
|
||||
std::string pk = net::kEmptyCookiePartitionKey;
|
||||
if (!i->SerializePartitionKey(pk))
|
||||
continue;
|
||||
ScopedJavaLocalRef<jobject> java_cookie = Java_CookiesFetcher_createCookie(
|
||||
env, base::android::ConvertUTF8ToJavaString(env, i->Name()),
|
||||
base::android::ConvertUTF8ToJavaString(env, i->Value()),
|
||||
@ -51,10 +54,7 @@ void OnCookiesFetchFinished(const net::CookieList& cookies) {
|
||||
i->LastAccessDate().ToDeltaSinceWindowsEpoch().InMicroseconds(),
|
||||
i->IsSecure(), i->IsHttpOnly(), static_cast<int>(i->SameSite()),
|
||||
i->Priority(), i->IsSameParty(),
|
||||
// TODO(crbug.com/1225444) Use serialized partition key instead of
|
||||
// constant.
|
||||
base::android::ConvertUTF8ToJavaString(env,
|
||||
net::kEmptyCookiePartitionKey),
|
||||
base::android::ConvertUTF8ToJavaString(env, pk),
|
||||
static_cast<int>(i->SourceScheme()), i->SourcePort());
|
||||
env->SetObjectArrayElement(joa.obj(), index++, java_cookie.obj());
|
||||
}
|
||||
@ -104,6 +104,12 @@ static void JNI_CookiesFetcher_RestoreCookies(
|
||||
std::string domain_str(base::android::ConvertJavaStringToUTF8(env, domain));
|
||||
std::string path_str(base::android::ConvertJavaStringToUTF8(env, path));
|
||||
|
||||
absl::optional<net::SchemefulSite> pk;
|
||||
if (!net::CanonicalCookie::DeserializePartitionKey(
|
||||
base::android::ConvertJavaStringToUTF8(env, partition_key), pk)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<net::CanonicalCookie> cookie =
|
||||
net::CanonicalCookie::FromStorage(
|
||||
base::android::ConvertJavaStringToUTF8(env, name),
|
||||
@ -116,10 +122,8 @@ static void JNI_CookiesFetcher_RestoreCookies(
|
||||
base::Time::FromDeltaSinceWindowsEpoch(
|
||||
base::TimeDelta::FromMicroseconds(last_access)),
|
||||
secure, httponly, static_cast<net::CookieSameSite>(same_site),
|
||||
static_cast<net::CookiePriority>(priority), same_party,
|
||||
// TODO(crbug.com/1225444) Deserialize partition key argument.
|
||||
absl::nullopt, static_cast<net::CookieSourceScheme>(source_scheme),
|
||||
source_port);
|
||||
static_cast<net::CookiePriority>(priority), same_party, pk,
|
||||
static_cast<net::CookieSourceScheme>(source_scheme), source_port);
|
||||
if (!cookie)
|
||||
return;
|
||||
|
||||
|
@ -137,6 +137,11 @@ std::string SchemefulSite::Serialize() const {
|
||||
return site_as_origin_.Serialize();
|
||||
}
|
||||
|
||||
std::string SchemefulSite::SerializeFileSiteWithHost() const {
|
||||
DCHECK_EQ(url::kFileScheme, site_as_origin_.scheme());
|
||||
return site_as_origin_.GetTupleOrPrecursorTupleIfOpaque().Serialize();
|
||||
}
|
||||
|
||||
std::string SchemefulSite::GetDebugString() const {
|
||||
return site_as_origin_.GetDebugString();
|
||||
}
|
||||
|
@ -95,6 +95,12 @@ class NET_EXPORT SchemefulSite {
|
||||
// with their associated nonce is necessary, see `SerializeWithNonce()`.
|
||||
std::string Serialize() const;
|
||||
|
||||
// Serializes `site_as_origin_` in cases when it has a 'file' scheme but
|
||||
// we want to preserve the Origin's host.
|
||||
// This was added to serialize cookie partition keys, which may contain
|
||||
// file origins with a host.
|
||||
std::string SerializeFileSiteWithHost() const;
|
||||
|
||||
std::string GetDebugString() const;
|
||||
|
||||
// Gets the underlying site as a GURL. If the internal Origin is opaque,
|
||||
|
@ -234,6 +234,39 @@ TEST(SchemefulSiteTest, SerializationConsistent) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SchemefulSiteTest, SerializationFileSiteWithHost) {
|
||||
const struct {
|
||||
SchemefulSite site;
|
||||
std::string expected;
|
||||
} kTestCases[] = {
|
||||
{SchemefulSite(GURL("file:///etc/passwd")), "file://"},
|
||||
{SchemefulSite(GURL("file://example.com/etc/passwd")),
|
||||
"file://example.com"},
|
||||
{SchemefulSite(GURL("file://example.com")), "file://example.com"},
|
||||
};
|
||||
|
||||
for (const auto& test_case : kTestCases) {
|
||||
SCOPED_TRACE(test_case.site.GetDebugString());
|
||||
std::string serialized_site = test_case.site.SerializeFileSiteWithHost();
|
||||
EXPECT_EQ(test_case.expected, serialized_site);
|
||||
absl::optional<SchemefulSite> deserialized_site =
|
||||
SchemefulSite::Deserialize(serialized_site);
|
||||
EXPECT_TRUE(deserialized_site);
|
||||
EXPECT_EQ(test_case.site, deserialized_site);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SchemefulSiteTest, FileURLWithHostEquality) {
|
||||
// Two file URLs with different hosts should result in unequal SchemefulSites.
|
||||
SchemefulSite site1(GURL("file://foo/some/path.txt"));
|
||||
SchemefulSite site2(GURL("file://bar/some/path.txt"));
|
||||
EXPECT_NE(site1, site2);
|
||||
|
||||
// Two file URLs with the same host should result in equal SchemefulSites.
|
||||
SchemefulSite site3(GURL("file://foo/another/path.pdf"));
|
||||
EXPECT_EQ(site1, site3);
|
||||
}
|
||||
|
||||
TEST(SchemefulSiteTest, OpaqueSerialization) {
|
||||
// List of origins which should all share a schemeful site.
|
||||
SchemefulSite kTestSites[] = {
|
||||
|
@ -751,6 +751,37 @@ void CanonicalCookie::SetSourcePort(int port) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CanonicalCookie::SerializePartitionKey(std::string& out) const {
|
||||
if (!partition_key_) {
|
||||
out = kEmptyCookiePartitionKey;
|
||||
return true;
|
||||
}
|
||||
if (partition_key_->GetURL().SchemeIsFile()) {
|
||||
out = partition_key_->SerializeFileSiteWithHost();
|
||||
return true;
|
||||
}
|
||||
if (partition_key_->opaque())
|
||||
return false;
|
||||
out = partition_key_->Serialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool CanonicalCookie::DeserializePartitionKey(
|
||||
const std::string& in,
|
||||
absl::optional<SchemefulSite>& out) {
|
||||
if (in == kEmptyCookiePartitionKey) {
|
||||
out = absl::nullopt;
|
||||
return true;
|
||||
}
|
||||
auto schemeful_site = SchemefulSite::Deserialize(in);
|
||||
// SchemefulSite is opaque if the input is invalid.
|
||||
if (schemeful_site.opaque())
|
||||
return false;
|
||||
out = absl::make_optional(schemeful_site);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanonicalCookie::IsEquivalentForSecureCookieMatching(
|
||||
const CanonicalCookie& secure_cookie) const {
|
||||
// Names must be the same
|
||||
|
@ -175,8 +175,23 @@ class NET_EXPORT CanonicalCookie {
|
||||
return partition_key_;
|
||||
}
|
||||
|
||||
// TODO(crbug.com/1225444) Methods to serialize and deserialize partition
|
||||
// keys.
|
||||
// Methods for serializing and deserializing a partition key to/from a string.
|
||||
// This will be used for Android, storing persistent partitioned cookies, and
|
||||
// loading partitioned cookies into Java code.
|
||||
//
|
||||
// This function returns if the partition key is not opaque. We do not want
|
||||
// to serialize cookies with opaque origins in their partition key to disk,
|
||||
// because if the browser session ends we will not be able to attach the
|
||||
// saved cookie to any future requests. This is because opaque origins' nonces
|
||||
// are only stored in volatile memory.
|
||||
bool SerializePartitionKey(std::string& out) const WARN_UNUSED_RESULT;
|
||||
// Deserializes the result of the method above.
|
||||
// If the result is abls::nullopt, the resulting cookie is not partitioned.
|
||||
//
|
||||
// Returns if the resulting partition key is valid.
|
||||
static bool DeserializePartitionKey(const std::string& in,
|
||||
absl::optional<SchemefulSite>& out)
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
// Returns an enum indicating the scheme of the origin that
|
||||
// set this cookie. This is not part of the cookie spec but is being used to
|
||||
|
@ -553,6 +553,79 @@ TEST(CanonicalCookieTest, EmptyExpiry) {
|
||||
EXPECT_EQ(base::Time(), cookie->ExpiryDate());
|
||||
}
|
||||
|
||||
TEST(CanonicalCookieTest, PartitionKeySerialization) {
|
||||
// SerializePartitionKey: no parititon key
|
||||
std::unique_ptr<CanonicalCookie> cookie =
|
||||
CanonicalCookie::CreateUnsafeCookieForTesting(
|
||||
"__Host-A", "B", "www.example.com", "/", base::Time::Now(),
|
||||
base::Time::Now(), base::Time::Now(), /* secure */ true,
|
||||
/* httponly */ false, CookieSameSite::UNSPECIFIED,
|
||||
CookiePriority::COOKIE_PRIORITY_DEFAULT, /* sameparty */ false,
|
||||
absl::nullopt);
|
||||
std::string got;
|
||||
EXPECT_TRUE(cookie->SerializePartitionKey(got));
|
||||
EXPECT_EQ(net::kEmptyCookiePartitionKey, got);
|
||||
|
||||
// SerializePartitionKey: partition key present
|
||||
cookie = CanonicalCookie::CreateUnsafeCookieForTesting(
|
||||
"__Host-A", "B", "www.example.com", "/", base::Time::Now(),
|
||||
base::Time::Now(), base::Time::Now(), /* secure */ true,
|
||||
/* httponly */ false, CookieSameSite::UNSPECIFIED,
|
||||
CookiePriority::COOKIE_PRIORITY_DEFAULT, /* sameparty */ false,
|
||||
absl::make_optional(SchemefulSite(GURL("https://toplevelsite.com"))));
|
||||
EXPECT_TRUE(cookie->SerializePartitionKey(got));
|
||||
EXPECT_EQ("https://toplevelsite.com", got);
|
||||
|
||||
// SerializePartitionKey: local file URLs
|
||||
cookie = CanonicalCookie::CreateUnsafeCookieForTesting(
|
||||
"__Host-A", "B", "www.example.com", "/", base::Time::Now(),
|
||||
base::Time::Now(), base::Time::Now(), /* secure */ true,
|
||||
/* httponly */ false, CookieSameSite::UNSPECIFIED,
|
||||
CookiePriority::COOKIE_PRIORITY_DEFAULT, /* sameparty */ false,
|
||||
absl::make_optional(SchemefulSite(GURL("file:///path1/to/file.txt"))));
|
||||
EXPECT_TRUE(cookie->SerializePartitionKey(got));
|
||||
EXPECT_EQ("file://", got);
|
||||
|
||||
// SerializePartitionKey: file URLs with hostnames.
|
||||
cookie = CanonicalCookie::CreateUnsafeCookieForTesting(
|
||||
"__Host-A", "B", "www.example.com", "/", base::Time::Now(),
|
||||
base::Time::Now(), base::Time::Now(), /* secure */ true,
|
||||
/* httponly */ false, CookieSameSite::UNSPECIFIED,
|
||||
CookiePriority::COOKIE_PRIORITY_DEFAULT, /* sameparty */ false,
|
||||
absl::make_optional(
|
||||
SchemefulSite(GURL("file://toplevelsite1.com/path/to/file.txt"))));
|
||||
EXPECT_TRUE(cookie->SerializePartitionKey(got));
|
||||
EXPECT_EQ("file://toplevelsite1.com", got);
|
||||
|
||||
// SerializeParititonKey: opaque partition key
|
||||
auto opaque = SchemefulSite(url::Origin());
|
||||
cookie = CanonicalCookie::CreateUnsafeCookieForTesting(
|
||||
"__Host-A", "B", "www.example.com", "/", base::Time::Now(),
|
||||
base::Time::Now(), base::Time::Now(), /* secure */ true,
|
||||
/* httponly */ false, CookieSameSite::UNSPECIFIED,
|
||||
CookiePriority::COOKIE_PRIORITY_DEFAULT,
|
||||
/* sameparty */ false, absl::make_optional(opaque));
|
||||
EXPECT_FALSE(cookie->SerializePartitionKey(got));
|
||||
|
||||
// DeserializePartitionKey: empty partition key
|
||||
absl::optional<SchemefulSite> partition_key;
|
||||
EXPECT_TRUE(CanonicalCookie::DeserializePartitionKey(kEmptyCookiePartitionKey,
|
||||
partition_key));
|
||||
EXPECT_FALSE(partition_key);
|
||||
|
||||
// DeserializePartitionKey: example site
|
||||
SchemefulSite site = SchemefulSite(GURL("https://toplevelsite.com"));
|
||||
EXPECT_TRUE(CanonicalCookie::DeserializePartitionKey(site.Serialize(),
|
||||
partition_key));
|
||||
EXPECT_TRUE(partition_key);
|
||||
EXPECT_FALSE(partition_key.value().opaque());
|
||||
EXPECT_EQ(site, partition_key.value());
|
||||
|
||||
// DeserializePartitionKey: invalid partition key
|
||||
EXPECT_FALSE(CanonicalCookie::DeserializePartitionKey("abc123foobar!!",
|
||||
partition_key));
|
||||
}
|
||||
|
||||
TEST(CanonicalCookieTest, IsEquivalent) {
|
||||
GURL url("https://www.example.com/");
|
||||
std::string cookie_name = "A";
|
||||
|
Reference in New Issue
Block a user