CookieInclusionStatus: Support warnings and multiple exclusion reasons

Previously CookieInclusionStatus was just an enum with one value for
inclusion and a number of other values for exclusion due to different
reasons.

This changes CookieInclusionStatus to hold multiple exclusion reasons,
with inclusion represented as a lack of any exclusion reasons. This is
implemented as a bit vector with each exclusion reason represented as
a single bit.

This also adds warnings to CookieInclusionStatus, which indicate when
warnings should be emitted about a cookie (regardless of inclusion or
exclusion). This removes the need for the previous hack which indicated
warnings by making a duplicate of the cookie with a different
CookieInclusionStatus.

Bug: 993843
Change-Id: I8f4f76ea5c7225cd01342786b20f9e0b05f582c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1756157
Commit-Queue: Lily Chen <[email protected]>
Reviewed-by: Sylvain Defresne <[email protected]>
Reviewed-by: Victor Costan <[email protected]>
Reviewed-by: Boris Sazonov <[email protected]>
Reviewed-by: Toni Baržić <[email protected]>
Reviewed-by: Christian Dullweber <[email protected]>
Reviewed-by: Kyle Horimoto <[email protected]>
Reviewed-by: Wez <[email protected]>
Reviewed-by: Maks Orlovich <[email protected]>
Reviewed-by: Karan Bhatia <[email protected]>
Reviewed-by: Balazs Engedy <[email protected]>
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: John Abd-El-Malek <[email protected]>
Reviewed-by: Andrey Kosyakov <[email protected]>
Cr-Commit-Position: refs/heads/master@{#691902}
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 3f81a43..da86e5f 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -416,9 +416,9 @@
   ProcessStrictTransportSecurityHeader();
   ProcessExpectCTHeader();
 
-  // Clear |cs_status_list_| after any processing in case
+  // Clear |set_cookie_status_list_| after any processing in case
   // SaveCookiesAndNotifyHeadersComplete is called again.
-  request_->set_maybe_stored_cookies(std::move(cs_status_list_));
+  request_->set_maybe_stored_cookies(std::move(set_cookie_status_list_));
 
   // The HTTP transaction may be restarted several times for the purposes
   // of sending authorization information. Each time it restarts, we get
@@ -642,20 +642,20 @@
     const CookieStatusList& cookies_with_status_list,
     const CookieStatusList& excluded_list) {
   DCHECK(request_->maybe_sent_cookies().empty());
-  CookieStatusList maybe_sent_cookies = excluded_list;
 
-  // TODO(crbug.com/993843): Pass in meaningful statuses, then actually use the
-  // statuses passed in.
+  // TODO(chlily): This is just for passing to CanGetCookies(), however the
+  // CookieList parameter of CanGetCookies(), which eventually gets passed to
+  // the NetworkDelegate, never actually gets used anywhere except in tests. The
+  // parameter should be removed.
   CookieList cookie_list =
       net::cookie_util::StripStatuses(cookies_with_status_list);
 
-  net::CanonicalCookie::CookieInclusionStatus status_for_cookie_list =
-      CanonicalCookie::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES;
-  if (!cookie_list.empty() && CanGetCookies(cookie_list)) {
-    status_for_cookie_list = CanonicalCookie::CookieInclusionStatus::INCLUDE;
+  bool can_get_cookies = CanGetCookies(cookie_list);
+  if (!cookies_with_status_list.empty() && can_get_cookies) {
     LogCookieUMA(cookie_list, *request_, request_info_);
 
-    std::string cookie_line = CanonicalCookie::BuildCookieLine(cookie_list);
+    std::string cookie_line =
+        CanonicalCookie::BuildCookieLine(cookies_with_status_list);
     UMA_HISTOGRAM_COUNTS_10000("Cookie.HeaderLength", cookie_line.length());
     request_info_.extra_headers.SetHeader(HttpRequestHeaders::kCookie,
                                           cookie_line);
@@ -664,30 +664,28 @@
     request_info_.privacy_mode = PRIVACY_MODE_DISABLED;
   }
 
-  // Report status for things in |cookie_list| after the delegate got a chance
-  // to block them.
-  for (const auto& cookie : cookie_list)
-    maybe_sent_cookies.push_back({cookie, status_for_cookie_list});
-
-  // Copy any cookies that would not be sent under SameSiteByDefaultCookies
-  // and/or CookiesWithoutSameSiteMustBeSecure with an informative status into
-  // the |maybe_sent_cookies| list so that we can display appropriate console
-  // warning messages about them. I.e. they are still included in the Cookie
-  // header, but they are *also* copied into |maybe_sent_cookies| with the
-  // CookieInclusionStatus that *would* apply. This special-casing will go away
-  // once SameSiteByDefaultCookies and CookiesWithoutSameSiteMustBeSecure are on
-  // by default, as the affected cookies will just be excluded in the first
-  // place.
-  for (const CanonicalCookie& cookie : cookie_list) {
-    CanonicalCookie::CookieInclusionStatus
-        include_but_maybe_would_exclude_status =
-            cookie_util::CookieWouldBeExcludedDueToSameSite(cookie, options);
-    if (include_but_maybe_would_exclude_status !=
-        CanonicalCookie::CookieInclusionStatus::INCLUDE) {
-      maybe_sent_cookies.push_back(
-          {cookie, include_but_maybe_would_exclude_status});
+  // Report status for things in |excluded_list| and |cookies_with_status_list|
+  // after the delegate got a chance to block them.
+  CookieStatusList maybe_sent_cookies = excluded_list;
+  // CanGetCookies only looks at the fields of the URLRequest, not the cookies
+  // it is passed, so if CanGetCookies(cookie_list) is false, then
+  // CanGetCookies(excluded_list) would also be false, so tag also the
+  // excluded cookies as having been blocked by user preferences.
+  if (!can_get_cookies) {
+    for (CookieStatusList::iterator it = maybe_sent_cookies.begin();
+         it != maybe_sent_cookies.end(); ++it) {
+      it->status.AddExclusionReason(
+          CanonicalCookie::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
     }
   }
+  for (const auto& cookie_with_status : cookies_with_status_list) {
+    CanonicalCookie::CookieInclusionStatus status = cookie_with_status.status;
+    if (!can_get_cookies) {
+      status.AddExclusionReason(
+          CanonicalCookie::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
+    }
+    maybe_sent_cookies.push_back({cookie_with_status.cookie, status});
+  }
 
   request_->set_maybe_sent_cookies(std::move(maybe_sent_cookies));
 
@@ -695,7 +693,7 @@
 }
 
 void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) {
-  DCHECK(cs_status_list_.empty());
+  DCHECK(set_cookie_status_list_.empty());
   DCHECK_EQ(0, num_cookie_lines_left_);
 
   // End of the call started in OnStartCompleted.
@@ -753,29 +751,26 @@
         request_->url(), cookie_string, base::Time::Now(), server_time,
         &returned_status);
 
-    if (returned_status != CanonicalCookie::CookieInclusionStatus::INCLUDE) {
-      OnSetCookieResult(options, base::nullopt, std::move(cookie_string),
+    base::Optional<CanonicalCookie> cookie_to_return = base::nullopt;
+    if (returned_status.IsInclude()) {
+      DCHECK(cookie);
+      // Make a copy of the cookie if we successfully made one.
+      cookie_to_return = *cookie;
+    }
+    if (cookie && !CanSetCookie(*cookie, &options)) {
+      returned_status.AddExclusionReason(
+          CanonicalCookie::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
+    }
+    if (!returned_status.IsInclude()) {
+      OnSetCookieResult(options, cookie_to_return, std::move(cookie_string),
                         returned_status);
       continue;
     }
-    DCHECK(cookie);
-
-    if (!CanSetCookie(*cookie, &options)) {
-      OnSetCookieResult(
-          options, base::make_optional<CanonicalCookie>(*cookie),
-          std::move(cookie_string),
-          CanonicalCookie::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES);
-      continue;
-    }
-
-    // Make a copy for the make_optional.
-    CanonicalCookie cookie_copy = *cookie;
 
     request_->context()->cookie_store()->SetCanonicalCookieAsync(
         std::move(cookie), request_->url().scheme(), options,
         base::BindOnce(&URLRequestHttpJob::OnSetCookieResult,
-                       weak_factory_.GetWeakPtr(), options,
-                       base::make_optional<CanonicalCookie>(cookie_copy),
+                       weak_factory_.GetWeakPtr(), options, cookie_to_return,
                        cookie_string));
   }
   // Removing the 1 that |num_cookie_lines_left| started with, signifing that
@@ -791,34 +786,14 @@
     base::Optional<CanonicalCookie> cookie,
     std::string cookie_string,
     CanonicalCookie::CookieInclusionStatus status) {
-  cs_status_list_.emplace_back(std::move(cookie), std::move(cookie_string),
-                               status);
-
-  if (status == CanonicalCookie::CookieInclusionStatus::INCLUDE) {
-    DCHECK(cookie.has_value());
-    // Copy any cookies that would not be sent under SameSiteByDefaultCookies
-    // and/or CookiesWithoutSameSiteMustBeSecure into |cs_status_list_| with a
-    // descriptive status so that we can display appropriate console warning
-    // messages about them. I.e. they are still set, but they are *also* copied
-    // into |cs_status_list_| with the CookieInclusionStatus that *would* apply.
-    // This special-casing will go away once SameSiteByDefaultCookies and
-    // CookiesWithoutSameSiteMustBeSecure are on by default, as the affected
-    // cookies will just be excluded in the first place.
-    CanonicalCookie::CookieInclusionStatus
-        include_but_maybe_would_exclude_status =
-            cookie_util::CookieWouldBeExcludedDueToSameSite(cookie.value(),
-                                                            options);
-    if (include_but_maybe_would_exclude_status !=
-        CanonicalCookie::CookieInclusionStatus::INCLUDE) {
-      cs_status_list_.emplace_back(std::move(cookie), std::move(cookie_string),
-                                   include_but_maybe_would_exclude_status);
-    }
-  }
+  set_cookie_status_list_.emplace_back(std::move(cookie),
+                                       std::move(cookie_string), status);
 
   num_cookie_lines_left_--;
 
-  // If all the cookie lines have been handled, |cs_status_list_| now reflects
-  // the result of all Set-Cookie lines, and the request can be continued.
+  // If all the cookie lines have been handled, |set_cookie_status_list_| now
+  // reflects the result of all Set-Cookie lines, and the request can be
+  // continued.
   if (num_cookie_lines_left_ == 0)
     NotifyHeadersComplete();
 }