• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/cookies/cookie_inclusion_status.h"
6 
7 #include <initializer_list>
8 #include <string_view>
9 #include <tuple>
10 #include <utility>
11 
12 #include "base/notreached.h"
13 #include "base/ranges/algorithm.h"
14 #include "base/strings/strcat.h"
15 #include "url/gurl.h"
16 
17 namespace net {
18 
19 CookieInclusionStatus::CookieInclusionStatus() = default;
20 
21 CookieInclusionStatus::CookieInclusionStatus(
22     const CookieInclusionStatus& other) = default;
23 
24 CookieInclusionStatus& CookieInclusionStatus::operator=(
25     const CookieInclusionStatus& other) = default;
26 
27 bool CookieInclusionStatus::operator==(
28     const CookieInclusionStatus& other) const = default;
29 
30 bool CookieInclusionStatus::operator!=(
31     const CookieInclusionStatus& other) const = default;
32 
IsInclude() const33 bool CookieInclusionStatus::IsInclude() const {
34   return exclusion_reasons_.none();
35 }
36 
HasExclusionReason(ExclusionReason reason) const37 bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const {
38   return exclusion_reasons_[reason];
39 }
40 
HasOnlyExclusionReason(ExclusionReason reason) const41 bool CookieInclusionStatus::HasOnlyExclusionReason(
42     ExclusionReason reason) const {
43   return exclusion_reasons_[reason] && exclusion_reasons_.count() == 1;
44 }
45 
AddExclusionReason(ExclusionReason reason)46 void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) {
47   exclusion_reasons_[reason] = true;
48   // If the cookie would be excluded for reasons other than the new SameSite
49   // rules, don't bother warning about it.
50   MaybeClearSameSiteWarning();
51   // If the cookie would be excluded for reasons unrelated to 3pcd, don't bother
52   // warning about 3pcd.
53   MaybeClearThirdPartyPhaseoutReason();
54   // If the cookie would have been excluded, clear the exemption reason.
55   exemption_reason_ = ExemptionReason::kNone;
56 }
57 
RemoveExclusionReason(ExclusionReason reason)58 void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) {
59   exclusion_reasons_[reason] = false;
60 }
61 
RemoveExclusionReasons(const std::vector<ExclusionReason> & reasons)62 void CookieInclusionStatus::RemoveExclusionReasons(
63     const std::vector<ExclusionReason>& reasons) {
64   exclusion_reasons_ = ExclusionReasonsWithout(reasons);
65 }
66 
MaybeSetExemptionReason(ExemptionReason reason)67 void CookieInclusionStatus::MaybeSetExemptionReason(ExemptionReason reason) {
68   if (IsInclude() && exemption_reason_ == ExemptionReason::kNone) {
69     exemption_reason_ = reason;
70   }
71 }
72 
73 CookieInclusionStatus::ExclusionReasonBitset
ExclusionReasonsWithout(const std::vector<ExclusionReason> & reasons) const74 CookieInclusionStatus::ExclusionReasonsWithout(
75     const std::vector<ExclusionReason>& reasons) const {
76   CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_);
77   for (const ExclusionReason reason : reasons) {
78     result[reason] = false;
79   }
80   return result;
81 }
82 
MaybeClearSameSiteWarning()83 void CookieInclusionStatus::MaybeClearSameSiteWarning() {
84   if (ExclusionReasonsWithout({
85           EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
86           EXCLUDE_SAMESITE_NONE_INSECURE,
87       }) != 0u) {
88     RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
89     RemoveWarningReason(WARN_SAMESITE_NONE_INSECURE);
90     RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
91   }
92 
93   if (!ShouldRecordDowngradeMetrics()) {
94     RemoveWarningReason(WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
95     RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
96     RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
97     RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
98     RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
99 
100     RemoveWarningReason(WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
101   }
102 }
103 
MaybeClearThirdPartyPhaseoutReason()104 void CookieInclusionStatus::MaybeClearThirdPartyPhaseoutReason() {
105   if (!IsInclude()) {
106     RemoveWarningReason(WARN_THIRD_PARTY_PHASEOUT);
107   }
108   if (ExclusionReasonsWithout(
109           {EXCLUDE_THIRD_PARTY_PHASEOUT,
110            EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}) != 0u) {
111     RemoveExclusionReason(EXCLUDE_THIRD_PARTY_PHASEOUT);
112     RemoveExclusionReason(EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
113   }
114 }
115 
ShouldRecordDowngradeMetrics() const116 bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
117   return ExclusionReasonsWithout({
118              EXCLUDE_SAMESITE_STRICT,
119              EXCLUDE_SAMESITE_LAX,
120              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
121          }) == 0u;
122 }
123 
ShouldWarn() const124 bool CookieInclusionStatus::ShouldWarn() const {
125   return warning_reasons_.any();
126 }
127 
HasWarningReason(WarningReason reason) const128 bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const {
129   return warning_reasons_[reason];
130 }
131 
HasSchemefulDowngradeWarning(CookieInclusionStatus::WarningReason * reason) const132 bool CookieInclusionStatus::HasSchemefulDowngradeWarning(
133     CookieInclusionStatus::WarningReason* reason) const {
134   if (!ShouldWarn())
135     return false;
136 
137   const CookieInclusionStatus::WarningReason kDowngradeWarnings[] = {
138       WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
139       WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
140       WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
141       WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
142       WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
143   };
144 
145   for (auto warning : kDowngradeWarnings) {
146     if (!HasWarningReason(warning))
147       continue;
148 
149     if (reason)
150       *reason = warning;
151 
152     return true;
153   }
154 
155   return false;
156 }
157 
AddWarningReason(WarningReason reason)158 void CookieInclusionStatus::AddWarningReason(WarningReason reason) {
159   warning_reasons_[reason] = true;
160 }
161 
RemoveWarningReason(WarningReason reason)162 void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) {
163   warning_reasons_[reason] = false;
164 }
165 
166 CookieInclusionStatus::ContextDowngradeMetricValues
GetBreakingDowngradeMetricsEnumValue(const GURL & url) const167 CookieInclusionStatus::GetBreakingDowngradeMetricsEnumValue(
168     const GURL& url) const {
169   bool url_is_secure = url.SchemeIsCryptographic();
170 
171   // Start the |reason| as something other than the downgrade warnings.
172   WarningReason reason = WarningReason::NUM_WARNING_REASONS;
173 
174   // Don't bother checking the return value because the default switch case
175   // will handle if no reason was found.
176   HasSchemefulDowngradeWarning(&reason);
177 
178   switch (reason) {
179     case WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE:
180       return url_is_secure
181                  ? ContextDowngradeMetricValues::kStrictLaxStrictSecure
182                  : ContextDowngradeMetricValues::kStrictLaxStrictInsecure;
183     case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE:
184       return url_is_secure
185                  ? ContextDowngradeMetricValues::kStrictCrossStrictSecure
186                  : ContextDowngradeMetricValues::kStrictCrossStrictInsecure;
187     case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE:
188       return url_is_secure
189                  ? ContextDowngradeMetricValues::kStrictCrossLaxSecure
190                  : ContextDowngradeMetricValues::kStrictCrossLaxInsecure;
191     case WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE:
192       return url_is_secure
193                  ? ContextDowngradeMetricValues::kLaxCrossStrictSecure
194                  : ContextDowngradeMetricValues::kLaxCrossStrictInsecure;
195     case WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE:
196       return url_is_secure ? ContextDowngradeMetricValues::kLaxCrossLaxSecure
197                            : ContextDowngradeMetricValues::kLaxCrossLaxInsecure;
198     default:
199       return url_is_secure ? ContextDowngradeMetricValues::kNoDowngradeSecure
200                            : ContextDowngradeMetricValues::kNoDowngradeInsecure;
201   }
202 }
203 
GetDebugString() const204 std::string CookieInclusionStatus::GetDebugString() const {
205   std::string out;
206 
207   if (IsInclude())
208     base::StrAppend(&out, {"INCLUDE, "});
209 
210   constexpr std::pair<ExclusionReason, const char*> exclusion_reasons[] = {
211       {EXCLUDE_UNKNOWN_ERROR, "EXCLUDE_UNKNOWN_ERROR"},
212       {EXCLUDE_HTTP_ONLY, "EXCLUDE_HTTP_ONLY"},
213       {EXCLUDE_SECURE_ONLY, "EXCLUDE_SECURE_ONLY"},
214       {EXCLUDE_DOMAIN_MISMATCH, "EXCLUDE_DOMAIN_MISMATCH"},
215       {EXCLUDE_NOT_ON_PATH, "EXCLUDE_NOT_ON_PATH"},
216       {EXCLUDE_SAMESITE_STRICT, "EXCLUDE_SAMESITE_STRICT"},
217       {EXCLUDE_SAMESITE_LAX, "EXCLUDE_SAMESITE_LAX"},
218       {EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
219        "EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX"},
220       {EXCLUDE_SAMESITE_NONE_INSECURE, "EXCLUDE_SAMESITE_NONE_INSECURE"},
221       {EXCLUDE_USER_PREFERENCES, "EXCLUDE_USER_PREFERENCES"},
222       {EXCLUDE_FAILURE_TO_STORE, "EXCLUDE_FAILURE_TO_STORE"},
223       {EXCLUDE_NONCOOKIEABLE_SCHEME, "EXCLUDE_NONCOOKIEABLE_SCHEME"},
224       {EXCLUDE_OVERWRITE_SECURE, "EXCLUDE_OVERWRITE_SECURE"},
225       {EXCLUDE_OVERWRITE_HTTP_ONLY, "EXCLUDE_OVERWRITE_HTTP_ONLY"},
226       {EXCLUDE_INVALID_DOMAIN, "EXCLUDE_INVALID_DOMAIN"},
227       {EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"},
228       {EXCLUDE_INVALID_PARTITIONED, "EXCLUDE_INVALID_PARTITIONED"},
229       {EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE,
230        "EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"},
231       {EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
232        "EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
233       {EXCLUDE_DOMAIN_NON_ASCII, "EXCLUDE_DOMAIN_NON_ASCII"},
234       {EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET,
235        "EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET"},
236       {EXCLUDE_PORT_MISMATCH, "EXCLUDE_PORT_MISMATCH"},
237       {EXCLUDE_SCHEME_MISMATCH, "EXCLUDE_SCHEME_MISMATCH"},
238       {EXCLUDE_SHADOWING_DOMAIN, "EXCLUDE_SHADOWING_DOMAIN"},
239       {EXCLUDE_DISALLOWED_CHARACTER, "EXCLUDE_DISALLOWED_CHARACTER"},
240       {EXCLUDE_THIRD_PARTY_PHASEOUT, "EXCLUDE_THIRD_PARTY_PHASEOUT"},
241       {EXCLUDE_NO_COOKIE_CONTENT, "EXCLUDE_NO_COOKIE_CONTENT"},
242   };
243   static_assert(
244       std::size(exclusion_reasons) == ExclusionReason::NUM_EXCLUSION_REASONS,
245       "Please ensure all ExclusionReason variants are enumerated in "
246       "GetDebugString");
247   static_assert(base::ranges::is_sorted(exclusion_reasons),
248                 "Please keep the ExclusionReason variants sorted in numerical "
249                 "order in GetDebugString");
250 
251   for (const auto& reason : exclusion_reasons) {
252     if (HasExclusionReason(reason.first))
253       base::StrAppend(&out, {reason.second, ", "});
254   }
255 
256   // Add warning
257   if (!ShouldWarn()) {
258     base::StrAppend(&out, {"DO_NOT_WARN, "});
259   }
260 
261   constexpr std::pair<WarningReason, const char*> warning_reasons[] = {
262       {WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT,
263        "WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT"},
264       {WARN_SAMESITE_NONE_INSECURE, "WARN_SAMESITE_NONE_INSECURE"},
265       {WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE,
266        "WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE"},
267       {WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
268        "WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE"},
269       {WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
270        "WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE"},
271       {WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
272        "WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE"},
273       {WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
274        "WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE"},
275       {WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
276        "WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE"},
277       {WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC,
278        "WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC"},
279       {WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION,
280        "WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"},
281       {WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
282        "WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
283       {WARN_DOMAIN_NON_ASCII, "WARN_DOMAIN_NON_ASCII"},
284       {WARN_PORT_MISMATCH, "WARN_PORT_MISMATCH"},
285       {WARN_SCHEME_MISMATCH, "WARN_SCHEME_MISMATCH"},
286       {WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME,
287        "WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME"},
288       {WARN_SHADOWING_DOMAIN, "WARN_SHADOWING_DOMAIN"},
289       {WARN_THIRD_PARTY_PHASEOUT, "WARN_THIRD_PARTY_PHASEOUT"},
290   };
291   static_assert(
292       std::size(warning_reasons) == WarningReason::NUM_WARNING_REASONS,
293       "Please ensure all WarningReason variants are enumerated in "
294       "GetDebugString");
295   static_assert(base::ranges::is_sorted(warning_reasons),
296                 "Please keep the WarningReason variants sorted in numerical "
297                 "order in GetDebugString");
298 
299   for (const auto& reason : warning_reasons) {
300     if (HasWarningReason(reason.first))
301       base::StrAppend(&out, {reason.second, ", "});
302   }
303 
304   // Add exemption reason
305   if (exemption_reason() == CookieInclusionStatus::ExemptionReason::kNone) {
306     base::StrAppend(&out, {"NO_EXEMPTION"});
307     return out;
308   }
309 
310   std::string_view reason;
311   switch (exemption_reason()) {
312     case ExemptionReason::kUserSetting:
313       reason = "ExemptionUserSetting";
314       break;
315     case ExemptionReason::k3PCDMetadata:
316       reason = "Exemption3PCDMetadata";
317       break;
318     case ExemptionReason::k3PCDDeprecationTrial:
319       reason = "Exemption3PCDDeprecationTrial";
320       break;
321     case ExemptionReason::kTopLevel3PCDDeprecationTrial:
322       reason = "ExemptionTopLevel3PCDDeprecationTrial";
323       break;
324     case ExemptionReason::k3PCDHeuristics:
325       reason = "Exemption3PCDHeuristics";
326       break;
327     case ExemptionReason::kEnterprisePolicy:
328       reason = "ExemptionEnterprisePolicy";
329       break;
330     case ExemptionReason::kStorageAccess:
331       reason = "ExemptionStorageAccess";
332       break;
333     case ExemptionReason::kTopLevelStorageAccess:
334       reason = "ExemptionTopLevelStorageAccess";
335       break;
336     case ExemptionReason::kScheme:
337       reason = "ExemptionScheme";
338       break;
339     case ExemptionReason::kNone:
340       NOTREACHED();
341   };
342   base::StrAppend(&out, {reason});
343 
344   return out;
345 }
346 
HasExactlyExclusionReasonsForTesting(const std::vector<CookieInclusionStatus::ExclusionReason> & reasons) const347 bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting(
348     const std::vector<CookieInclusionStatus::ExclusionReason>& reasons) const {
349   CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons);
350   return expected.exclusion_reasons_ == exclusion_reasons_;
351 }
352 
HasExactlyWarningReasonsForTesting(const std::vector<WarningReason> & reasons) const353 bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting(
354     const std::vector<WarningReason>& reasons) const {
355   CookieInclusionStatus expected = MakeFromReasonsForTesting({}, reasons);
356   return expected.warning_reasons_ == warning_reasons_;
357 }
358 
359 // static
ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,uint32_t warning_reasons)360 bool CookieInclusionStatus::ValidateExclusionAndWarningFromWire(
361     uint32_t exclusion_reasons,
362     uint32_t warning_reasons) {
363   uint32_t exclusion_mask =
364       static_cast<uint32_t>(~0ul << ExclusionReason::NUM_EXCLUSION_REASONS);
365   uint32_t warning_mask =
366       static_cast<uint32_t>(~0ul << WarningReason::NUM_WARNING_REASONS);
367   return (exclusion_reasons & exclusion_mask) == 0 &&
368          (warning_reasons & warning_mask) == 0;
369 }
370 
MakeFromReasonsForTesting(const std::vector<ExclusionReason> & exclusions,const std::vector<WarningReason> & warnings,ExemptionReason exemption)371 CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting(
372     const std::vector<ExclusionReason>& exclusions,
373     const std::vector<WarningReason>& warnings,
374     ExemptionReason exemption) {
375   CookieInclusionStatus status;
376   for (ExclusionReason reason : exclusions) {
377     status.AddExclusionReason(reason);
378   }
379   for (WarningReason warning : warnings) {
380     status.AddWarningReason(warning);
381   }
382   status.MaybeSetExemptionReason(exemption);
383 
384   for (auto reason : exclusions) {
385     CHECK(status.HasExclusionReason(reason))
386         << "Exemption " << reason << " could not be applied";
387   }
388   CHECK_EQ(status.exclusion_reasons_.count(), exclusions.size());
389   for (auto reason : warnings) {
390     CHECK(status.HasWarningReason(reason))
391         << "Warning " << reason << " could not be applied";
392   }
393   CHECK_EQ(status.warning_reasons_.count(), warnings.size());
394   CHECK_EQ(status.exemption_reason(), exemption)
395       << "Exemption " << static_cast<int>(exemption) << " could not be applied";
396 
397   return status;
398 }
399 
ExcludedByUserPreferencesOrTPCD() const400 bool CookieInclusionStatus::ExcludedByUserPreferencesOrTPCD() const {
401   if (HasOnlyExclusionReason(ExclusionReason::EXCLUDE_USER_PREFERENCES) ||
402       HasOnlyExclusionReason(ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT)) {
403     return true;
404   }
405   return exclusion_reasons_.count() == 2 &&
406          exclusion_reasons_[ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT] &&
407          exclusion_reasons_
408              [ExclusionReason::
409                   EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET];
410 }
411 
412 }  // namespace net
413