diff --git a/ios/net/cookies/system_cookie_util.mm b/ios/net/cookies/system_cookie_util.mm index a38d3238deca96..0739470d3f7cdd 100644 --- a/ios/net/cookies/system_cookie_util.mm +++ b/ios/net/cookies/system_cookie_util.mm @@ -33,6 +33,11 @@ // Undocumented property of NSHTTPCookie. NSString* const kNSHTTPCookieHttpOnly = @"HttpOnly"; +// Possible value for SameSite policy. WebKit doesn't use the value "none" or +// any other value, it uses the empty value to represent none, and any value +// that is not "strict" or "lax" will be considered as none. +NSString* const kNSHTTPCookieSameSiteNone = @"none"; + // Key in NSUserDefaults telling wether a low cookie count must be reported as // an error. NSString* const kCheckCookieLossKey = @"CookieUtilIOSCheckCookieLoss"; @@ -70,16 +75,30 @@ void ReportUMACookieLoss(CookieLossType loss, CookieEvent event) { net::CanonicalCookie CanonicalCookieFromSystemCookie( NSHTTPCookie* cookie, const base::Time& ceation_time) { + net::CookieSameSite same_site = net::CookieSameSite::NO_RESTRICTION; + if (@available(iOS 13, *)) { + same_site = net::CookieSameSite::UNSPECIFIED; + if ([cookie.sameSitePolicy isEqual:NSHTTPCookieSameSiteLax]) + same_site = net::CookieSameSite::LAX_MODE; + + if ([cookie.sameSitePolicy isEqual:NSHTTPCookieSameSiteStrict]) + same_site = net::CookieSameSite::STRICT_MODE; + + if ([[cookie.sameSitePolicy lowercaseString] + isEqual:kNSHTTPCookieSameSiteNone]) + same_site = net::CookieSameSite::NO_RESTRICTION; + } + return net::CanonicalCookie( base::SysNSStringToUTF8([cookie name]), base::SysNSStringToUTF8([cookie value]), base::SysNSStringToUTF8([cookie domain]), base::SysNSStringToUTF8([cookie path]), ceation_time, base::Time::FromDoubleT([[cookie expiresDate] timeIntervalSince1970]), - base::Time(), [cookie isSecure], [cookie isHTTPOnly], - // TODO(mkwst): When iOS begins to support 'SameSite' and 'Priority' - // attributes, pass them through here. - net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT); + base::Time(), [cookie isSecure], [cookie isHTTPOnly], same_site, + // When iOS begins to support 'Priority' attribute, pass it + // through here. + net::COOKIE_PRIORITY_DEFAULT); } void ReportGetCookiesForURLResult(SystemCookieStoreType store_type, @@ -140,6 +159,28 @@ void ReportGetCookiesForURLCall(SystemCookieStoreType store_type) { [NSDate dateWithTimeIntervalSince1970:cookie.ExpiryDate().ToDoubleT()]; [properties setObject:expiry forKey:NSHTTPCookieExpires]; } + + if (@available(iOS 13, *)) { + // In iOS 13 sameSite property in NSHTTPCookie is used to specify the + // samesite policy. + NSString* same_site = @""; + switch (cookie.SameSite()) { + case net::CookieSameSite::LAX_MODE: + same_site = NSHTTPCookieSameSiteLax; + break; + case net::CookieSameSite::STRICT_MODE: + same_site = NSHTTPCookieSameSiteStrict; + break; + case net::CookieSameSite::NO_RESTRICTION: + same_site = kNSHTTPCookieSameSiteNone; + break; + case net::CookieSameSite::UNSPECIFIED: + // All other values of same site policy will be treated as no value . + break; + } + properties[NSHTTPCookieSameSitePolicy] = same_site; + } + if (cookie.IsSecure()) [properties setObject:@"Y" forKey:NSHTTPCookieSecure]; if (cookie.IsHttpOnly()) diff --git a/ios/net/cookies/system_cookie_util_unittest.mm b/ios/net/cookies/system_cookie_util_unittest.mm index fa53a0244cba1a..84d8387b8937f7 100644 --- a/ios/net/cookies/system_cookie_util_unittest.mm +++ b/ios/net/cookies/system_cookie_util_unittest.mm @@ -32,14 +32,18 @@ "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. net::CanonicalCookie canonical_cookie( kCookieName, kCookieValue, kCookieDomain, kCookiePath, base::Time(), // creation expires, base::Time(), // last_access - secure, httponly, net::CookieSameSite::NO_RESTRICTION, - net::COOKIE_PRIORITY_DEFAULT); + secure, httponly, same_site, net::COOKIE_PRIORITY_DEFAULT); // Convert it to system cookie. NSHTTPCookie* system_cookie = SystemCookieFromCanonicalCookie(canonical_cookie); @@ -53,6 +57,10 @@ void CheckSystemCookie(const base::Time& expires, bool secure, bool httponly) { 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]); @@ -79,14 +87,23 @@ void VerifyGetCookiesResultHistogram( base::Time expire_date = creation_time + base::TimeDelta::FromHours(2); NSDate* system_expire_date = [NSDate dateWithTimeIntervalSince1970:expire_date.ToDoubleT()]; - NSHTTPCookie* system_cookie = [[NSHTTPCookie alloc] initWithProperties:@{ - NSHTTPCookieDomain : @"foo", - NSHTTPCookieName : @"a", - NSHTTPCookiePath : @"/", - NSHTTPCookieValue : @"b", - NSHTTPCookieExpires : system_expire_date, - @"HttpOnly" : @YES, - }]; + 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); net::CanonicalCookie chrome_cookie = CanonicalCookieFromSystemCookie(system_cookie, creation_time); @@ -105,6 +122,9 @@ void VerifyGetCookiesResultHistogram( 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:@{ diff --git a/ios/web/download/download_session_cookie_storage.mm b/ios/web/download/download_session_cookie_storage.mm index 9138beac1dd4b7..a9296f924dfb8d 100644 --- a/ios/web/download/download_session_cookie_storage.mm +++ b/ios/web/download/download_session_cookie_storage.mm @@ -39,12 +39,23 @@ - (void)deleteCookie:(NSHTTPCookie*)cookie { - (nullable NSArray*)cookiesForURL:(NSURL*)URL { NSMutableArray* result = [NSMutableArray array]; GURL gURL = net::GURLWithNSURL(URL); - net::CookieOptions options; - options.set_include_httponly(); + // TODO(crbug.com/1018272): Compute the cookie access semantic, and update + // |options| with it. + net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); + net::CookieAccessSemantics cookieAccessSemantics = + net::CookieAccessSemantics::LEGACY; + if (@available(iOS 13, *)) { + // Using |UNKNOWN| semantics to allow the experiment to switch between non + // legacy (where cookies that don't have a specific same-site access policy + // and not secure will not be included), and legacy mode. + cookieAccessSemantics = net::CookieAccessSemantics::UNKNOWN; + } for (NSHTTPCookie* cookie in self.cookies) { net::CanonicalCookie canonical_cookie = net::CanonicalCookieFromSystemCookie(cookie, base::Time()); - if (canonical_cookie.IncludeForRequestURL(gURL, options).IsInclude()) + if (canonical_cookie + .IncludeForRequestURL(gURL, options, cookieAccessSemantics) + .IsInclude()) [result addObject:cookie]; } return [result copy]; diff --git a/ios/web/net/cookies/wk_http_system_cookie_store.mm b/ios/web/net/cookies/wk_http_system_cookie_store.mm index f5c0c12bc87866..ac89fb4a36ac64 100644 --- a/ios/web/net/cookies/wk_http_system_cookie_store.mm +++ b/ios/web/net/cookies/wk_http_system_cookie_store.mm @@ -43,9 +43,20 @@ bool ShouldIncludeForRequestUrl(NSHTTPCookie* cookie, const GURL& url) { // to support cookieOptions this function can be modified to support that. net::CanonicalCookie canonical_cookie = net::CanonicalCookieFromSystemCookie(cookie, base::Time()); - net::CookieOptions options; - options.set_include_httponly(); - return canonical_cookie.IncludeForRequestURL(url, options).IsInclude(); + // Cookies handled by this method are app specific cookies, so it's safe to + // use strict same site context. + net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); + net::CookieAccessSemantics cookie_access_semantics = + net::CookieAccessSemantics::LEGACY; + if (@available(iOS 13, *)) { + // Using |UNKNOWN| semantics to allow the experiment to switch between non + // legacy (where cookies that don't have a specific same-site access policy + // and not secure will not be included), and legacy mode. + cookie_access_semantics = net::CookieAccessSemantics::UNKNOWN; + } + return canonical_cookie + .IncludeForRequestURL(url, options, cookie_access_semantics) + .IsInclude(); } } // namespace