blob: 4f91b599bc59a3f7df1815f28255bdc15357f890 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/net/cookies/system_cookie_util.h"
#import <Foundation/Foundation.h>
#include "base/strings/sys_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "net/cookies/cookie_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace net {
namespace {
const char kCookieDomain[] = "domain";
const char kCookieName[] = "name";
const char kCookiePath[] = "path/";
const char kCookieValue[] = "value";
const char kCookieValueInvalidUtf8[] = "\x81r\xe4\xbd\xa0\xe5\xa5\xbd";
const char kGetCookiesResultHistogram[] =
"IOS.Cookies.GetCookiesForURLCallResult";
void CheckSystemCookie(const base::Time& expires, bool secure, bool httponly) {
net::CookieSameSite same_site = net::CookieSameSite::NO_RESTRICTION;
if (@available(iOS 13, *)) {
// SamesitePolicy property of NSHTTPCookieStore is available on iOS 13+.
same_site = net::CookieSameSite::LAX_MODE;
}
// Generate a canonical cookie.
std::unique_ptr<net::CanonicalCookie> canonical_cookie =
net::CanonicalCookie::CreateUnsafeCookieForTesting(
kCookieName, kCookieValue, kCookieDomain, kCookiePath,
base::Time(), // creation
expires,
base::Time(), // last_access
secure, httponly, same_site, net::COOKIE_PRIORITY_DEFAULT,
false /* same_party */);
// Convert it to system cookie.
NSHTTPCookie* system_cookie =
SystemCookieFromCanonicalCookie(*canonical_cookie);
// Check the attributes.
EXPECT_TRUE(system_cookie);
EXPECT_NSEQ(base::SysUTF8ToNSString(kCookieName), [system_cookie name]);
EXPECT_NSEQ(base::SysUTF8ToNSString(kCookieValue), [system_cookie value]);
EXPECT_NSEQ(base::SysUTF8ToNSString(kCookieDomain), [system_cookie domain]);
EXPECT_NSEQ(base::SysUTF8ToNSString(kCookiePath), [system_cookie path]);
EXPECT_EQ(secure, [system_cookie isSecure]);
EXPECT_EQ(httponly, [system_cookie isHTTPOnly]);
EXPECT_EQ(expires.is_null(), [system_cookie isSessionOnly]);
if (@available(iOS 13, *)) {
EXPECT_NSEQ(NSHTTPCookieSameSiteLax, [system_cookie sameSitePolicy]);
}
// Allow 1 second difference as iOS rounds expiry time to the nearest second.
base::Time system_cookie_expire_date = base::Time::FromDoubleT(
[[system_cookie expiresDate] timeIntervalSince1970]);
EXPECT_LE(expires - base::TimeDelta::FromSeconds(1),
system_cookie_expire_date);
EXPECT_GE(expires + base::TimeDelta::FromSeconds(1),
system_cookie_expire_date);
}
void VerifyGetCookiesResultHistogram(
const base::HistogramTester& histogram_tester,
GetCookiesForURLCallResult expected_value) {
histogram_tester.ExpectBucketCount(
kGetCookiesResultHistogram,
static_cast<base::HistogramBase::Sample>(expected_value), 1);
}
} // namespace
using CookieUtil = PlatformTest;
TEST_F(CookieUtil, CanonicalCookieFromSystemCookie) {
base::Time creation_time = base::Time::Now();
base::Time expire_date = creation_time + base::TimeDelta::FromHours(2);
NSDate* system_expire_date =
[NSDate dateWithTimeIntervalSince1970:expire_date.ToDoubleT()];
NSMutableDictionary* properties =
[NSMutableDictionary dictionaryWithDictionary:@{
NSHTTPCookieDomain : @"foo",
NSHTTPCookieName : @"a",
NSHTTPCookiePath : @"/",
NSHTTPCookieValue : @"b",
NSHTTPCookieExpires : system_expire_date,
@"HttpOnly" : @YES,
}];
if (@available(iOS 13, *)) {
// sameSitePolicy is only available on iOS 13+.
properties[NSHTTPCookieSameSitePolicy] = NSHTTPCookieSameSiteStrict;
}
NSHTTPCookie* system_cookie =
[[NSHTTPCookie alloc] initWithProperties:properties];
ASSERT_TRUE(system_cookie);
std::unique_ptr<net::CanonicalCookie> chrome_cookie =
CanonicalCookieFromSystemCookie(system_cookie, creation_time);
EXPECT_EQ("a", chrome_cookie->Name());
EXPECT_EQ("b", chrome_cookie->Value());
EXPECT_EQ("foo", chrome_cookie->Domain());
EXPECT_EQ("/", chrome_cookie->Path());
EXPECT_EQ(creation_time, chrome_cookie->CreationDate());
EXPECT_TRUE(chrome_cookie->LastAccessDate().is_null());
EXPECT_TRUE(chrome_cookie->IsPersistent());
// Allow 1 second difference as iOS rounds expiry time to the nearest second.
EXPECT_LE(expire_date - base::TimeDelta::FromSeconds(1),
chrome_cookie->ExpiryDate());
EXPECT_GE(expire_date + base::TimeDelta::FromSeconds(1),
chrome_cookie->ExpiryDate());
EXPECT_FALSE(chrome_cookie->IsSecure());
EXPECT_TRUE(chrome_cookie->IsHttpOnly());
EXPECT_EQ(net::COOKIE_PRIORITY_DEFAULT, chrome_cookie->Priority());
if (@available(iOS 13, *)) {
EXPECT_EQ(net::CookieSameSite::STRICT_MODE, chrome_cookie->SameSite());
}
// Test session and secure cookie.
system_cookie = [[NSHTTPCookie alloc] initWithProperties:@{
NSHTTPCookieDomain : @"foo",
NSHTTPCookieName : @"a",
NSHTTPCookiePath : @"/",
NSHTTPCookieValue : @"b",
NSHTTPCookieSecure : @"Y",
}];
ASSERT_TRUE(system_cookie);
chrome_cookie = CanonicalCookieFromSystemCookie(system_cookie, creation_time);
EXPECT_FALSE(chrome_cookie->IsPersistent());
EXPECT_TRUE(chrome_cookie->IsSecure());
// Test a non-Canonical cookie does not cause a crash.
system_cookie = [[NSHTTPCookie alloc] initWithProperties:@{
NSHTTPCookieDomain : @"foo",
// Malformed name will make the resulting cookie non-canonical.
NSHTTPCookieName : @"A=",
NSHTTPCookiePath : @"/",
NSHTTPCookieValue : @"b",
}];
EXPECT_FALSE(CanonicalCookieFromSystemCookie(system_cookie, creation_time));
}
// Tests that histogram is reported correctly based on the input.
TEST_F(CookieUtil, ReportGetCookiesForURLResult) {
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(kGetCookiesResultHistogram, 0);
ReportGetCookiesForURLResult(SystemCookieStoreType::kNSHTTPSystemCookieStore,
/*has_cookies=*/true);
VerifyGetCookiesResultHistogram(
histogram_tester,
GetCookiesForURLCallResult::kCookiesFoundOnNSHTTPSystemCookieStore);
histogram_tester.ExpectTotalCount(kGetCookiesResultHistogram, 1);
ReportGetCookiesForURLResult(SystemCookieStoreType::kNSHTTPSystemCookieStore,
/*has_cookies=*/false);
VerifyGetCookiesResultHistogram(
histogram_tester,
GetCookiesForURLCallResult::kNoCookiesOnNSHTTPSystemCookieStore);
histogram_tester.ExpectTotalCount(kGetCookiesResultHistogram, 2);
ReportGetCookiesForURLResult(SystemCookieStoreType::kCookieMonster,
/*has_cookies=*/false);
VerifyGetCookiesResultHistogram(
histogram_tester, GetCookiesForURLCallResult::kNoCookiesOnCookieMonster);
histogram_tester.ExpectTotalCount(kGetCookiesResultHistogram, 3);
ReportGetCookiesForURLResult(SystemCookieStoreType::kWKHTTPSystemCookieStore,
/*has_cookies=*/true);
VerifyGetCookiesResultHistogram(
histogram_tester,
GetCookiesForURLCallResult::kCookiesFoundOnWKHTTPSystemCookieStore);
histogram_tester.ExpectTotalCount(kGetCookiesResultHistogram, 4);
}
TEST_F(CookieUtil, SystemCookieFromCanonicalCookie) {
base::Time expire_date = base::Time::Now() + base::TimeDelta::FromHours(2);
// Test various combinations of session, secure and httponly attributes.
CheckSystemCookie(expire_date, false, false);
CheckSystemCookie(base::Time(), true, false);
CheckSystemCookie(expire_date, false, true);
CheckSystemCookie(base::Time(), true, true);
}
TEST_F(CookieUtil, SystemCookieFromBadCanonicalCookie) {
// Generate a bad canonical cookie (value is invalid utf8).
std::unique_ptr<net::CanonicalCookie> bad_canonical_cookie =
net::CanonicalCookie::CreateUnsafeCookieForTesting(
kCookieName, kCookieValueInvalidUtf8, kCookieDomain, kCookiePath,
base::Time(), // creation
base::Time(), // expires
base::Time(), // last_access
false, // secure
false, // httponly
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
false /* same_party */);
// Convert it to system cookie.
NSHTTPCookie* system_cookie =
SystemCookieFromCanonicalCookie(*bad_canonical_cookie);
EXPECT_TRUE(system_cookie == nil);
}
TEST_F(CookieUtil, SystemCookiesFromCanonicalCookieList) {
base::Time expire_date = base::Time::Now() + base::TimeDelta::FromHours(2);
net::CookieList cookie_list = {
*net::CanonicalCookie::CreateUnsafeCookieForTesting(
"name1", "value1", "domain1", "path1/",
base::Time(), // creation
expire_date,
base::Time(), // last_access
false, // secure
false, // httponly
net::CookieSameSite::UNSPECIFIED, net::COOKIE_PRIORITY_DEFAULT,
false /* same_party */),
*net::CanonicalCookie::CreateUnsafeCookieForTesting(
"name2", "value2", "domain2", "path2/",
base::Time(), // creation
expire_date,
base::Time(), // last_access
false, // secure
false, // httponly
net::CookieSameSite::UNSPECIFIED, net::COOKIE_PRIORITY_DEFAULT,
false /* same_party */),
};
NSArray<NSHTTPCookie*>* system_cookies =
SystemCookiesFromCanonicalCookieList(cookie_list);
ASSERT_EQ(2UL, system_cookies.count);
EXPECT_NSEQ(@"name1", system_cookies[0].name);
EXPECT_NSEQ(@"value1", system_cookies[0].value);
EXPECT_NSEQ(@"domain1", system_cookies[0].domain);
EXPECT_NSEQ(@"path1/", system_cookies[0].path);
EXPECT_NSEQ(@"name2", system_cookies[1].name);
EXPECT_NSEQ(@"value2", system_cookies[1].value);
EXPECT_NSEQ(@"domain2", system_cookies[1].domain);
EXPECT_NSEQ(@"path2/", system_cookies[1].path);
}
} // namespace net