• 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 <utility>
9 
10 #include "base/ranges/algorithm.h"
11 #include "base/strings/strcat.h"
12 #include "url/gurl.h"
13 
14 namespace net {
15 
16 CookieInclusionStatus::CookieInclusionStatus() = default;
17 
CookieInclusionStatus(ExclusionReason reason)18 CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason) {
19   exclusion_reasons_[reason] = true;
20 }
21 
CookieInclusionStatus(ExclusionReason reason,WarningReason warning)22 CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason,
23                                              WarningReason warning) {
24   exclusion_reasons_[reason] = true;
25   warning_reasons_[warning] = true;
26 }
27 
CookieInclusionStatus(WarningReason warning)28 CookieInclusionStatus::CookieInclusionStatus(WarningReason warning) {
29   warning_reasons_[warning] = true;
30 }
31 
32 CookieInclusionStatus::CookieInclusionStatus(
33     const CookieInclusionStatus& other) = default;
34 
35 CookieInclusionStatus& CookieInclusionStatus::operator=(
36     const CookieInclusionStatus& other) = default;
37 
operator ==(const CookieInclusionStatus & other) const38 bool CookieInclusionStatus::operator==(
39     const CookieInclusionStatus& other) const {
40   return exclusion_reasons_ == other.exclusion_reasons_ &&
41          warning_reasons_ == other.warning_reasons_;
42 }
43 
operator !=(const CookieInclusionStatus & other) const44 bool CookieInclusionStatus::operator!=(
45     const CookieInclusionStatus& other) const {
46   return !operator==(other);
47 }
48 
operator <(const CookieInclusionStatus & other) const49 bool CookieInclusionStatus::operator<(
50     const CookieInclusionStatus& other) const {
51   static_assert(NUM_EXCLUSION_REASONS <= sizeof(unsigned long) * CHAR_BIT,
52                 "use .ullong() instead");
53   static_assert(NUM_WARNING_REASONS <= sizeof(unsigned long) * CHAR_BIT,
54                 "use .ullong() instead");
55   return std::make_pair(exclusion_reasons_.to_ulong(),
56                         warning_reasons_.to_ulong()) <
57          std::make_pair(other.exclusion_reasons_.to_ulong(),
58                         other.warning_reasons_.to_ulong());
59 }
60 
IsInclude() const61 bool CookieInclusionStatus::IsInclude() const {
62   return exclusion_reasons_.none();
63 }
64 
HasExclusionReason(ExclusionReason reason) const65 bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const {
66   return exclusion_reasons_[reason];
67 }
68 
HasOnlyExclusionReason(ExclusionReason reason) const69 bool CookieInclusionStatus::HasOnlyExclusionReason(
70     ExclusionReason reason) const {
71   return exclusion_reasons_[reason] && exclusion_reasons_.count() == 1;
72 }
73 
AddExclusionReason(ExclusionReason reason)74 void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) {
75   exclusion_reasons_[reason] = true;
76   // If the cookie would be excluded for reasons other than the new SameSite
77   // rules, don't bother warning about it.
78   MaybeClearSameSiteWarning();
79   // If the cookie would be excluded for reasons unrelated to 3pcd, don't bother
80   // warning about 3pcd.
81   MaybeClearThirdPartyPhaseoutReason();
82 }
83 
RemoveExclusionReason(ExclusionReason reason)84 void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) {
85   exclusion_reasons_[reason] = false;
86 }
87 
RemoveExclusionReasons(const std::vector<ExclusionReason> & reasons)88 void CookieInclusionStatus::RemoveExclusionReasons(
89     const std::vector<ExclusionReason>& reasons) {
90   exclusion_reasons_ = ExclusionReasonsWithout(reasons);
91 }
92 
93 CookieInclusionStatus::ExclusionReasonBitset
ExclusionReasonsWithout(const std::vector<ExclusionReason> & reasons) const94 CookieInclusionStatus::ExclusionReasonsWithout(
95     const std::vector<ExclusionReason>& reasons) const {
96   CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_);
97   for (const ExclusionReason reason : reasons) {
98     result[reason] = false;
99   }
100   return result;
101 }
102 
MaybeClearSameSiteWarning()103 void CookieInclusionStatus::MaybeClearSameSiteWarning() {
104   if (ExclusionReasonsWithout({
105           EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
106           EXCLUDE_SAMESITE_NONE_INSECURE,
107       }) != 0u) {
108     RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
109     RemoveWarningReason(WARN_SAMESITE_NONE_INSECURE);
110     RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
111   }
112 
113   if (!ShouldRecordDowngradeMetrics()) {
114     RemoveWarningReason(WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
115     RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
116     RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
117     RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
118     RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
119 
120     RemoveWarningReason(WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
121   }
122 }
123 
MaybeClearThirdPartyPhaseoutReason()124 void CookieInclusionStatus::MaybeClearThirdPartyPhaseoutReason() {
125   if (!IsInclude()) {
126     RemoveWarningReason(WARN_THIRD_PARTY_PHASEOUT);
127   }
128   if (ExclusionReasonsWithout(
129           {EXCLUDE_THIRD_PARTY_PHASEOUT,
130            EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}) != 0u) {
131     // TODO(crbug.com/1516673): Once the bug is fixed, also remove
132     // EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET.
133     RemoveExclusionReason(EXCLUDE_THIRD_PARTY_PHASEOUT);
134   }
135 }
136 
ShouldRecordDowngradeMetrics() const137 bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
138   return ExclusionReasonsWithout({
139              EXCLUDE_SAMESITE_STRICT,
140              EXCLUDE_SAMESITE_LAX,
141              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
142          }) == 0u;
143 }
144 
ShouldWarn() const145 bool CookieInclusionStatus::ShouldWarn() const {
146   return warning_reasons_.any();
147 }
148 
HasWarningReason(WarningReason reason) const149 bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const {
150   return warning_reasons_[reason];
151 }
152 
HasSchemefulDowngradeWarning(CookieInclusionStatus::WarningReason * reason) const153 bool CookieInclusionStatus::HasSchemefulDowngradeWarning(
154     CookieInclusionStatus::WarningReason* reason) const {
155   if (!ShouldWarn())
156     return false;
157 
158   const CookieInclusionStatus::WarningReason kDowngradeWarnings[] = {
159       WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
160       WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
161       WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
162       WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
163       WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
164   };
165 
166   for (auto warning : kDowngradeWarnings) {
167     if (!HasWarningReason(warning))
168       continue;
169 
170     if (reason)
171       *reason = warning;
172 
173     return true;
174   }
175 
176   return false;
177 }
178 
AddWarningReason(WarningReason reason)179 void CookieInclusionStatus::AddWarningReason(WarningReason reason) {
180   warning_reasons_[reason] = true;
181 }
182 
RemoveWarningReason(WarningReason reason)183 void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) {
184   warning_reasons_[reason] = false;
185 }
186 
187 CookieInclusionStatus::ContextDowngradeMetricValues
GetBreakingDowngradeMetricsEnumValue(const GURL & url) const188 CookieInclusionStatus::GetBreakingDowngradeMetricsEnumValue(
189     const GURL& url) const {
190   bool url_is_secure = url.SchemeIsCryptographic();
191 
192   // Start the |reason| as something other than the downgrade warnings.
193   WarningReason reason = WarningReason::NUM_WARNING_REASONS;
194 
195   // Don't bother checking the return value because the default switch case
196   // will handle if no reason was found.
197   HasSchemefulDowngradeWarning(&reason);
198 
199   switch (reason) {
200     case WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE:
201       return url_is_secure
202                  ? ContextDowngradeMetricValues::kStrictLaxStrictSecure
203                  : ContextDowngradeMetricValues::kStrictLaxStrictInsecure;
204     case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE:
205       return url_is_secure
206                  ? ContextDowngradeMetricValues::kStrictCrossStrictSecure
207                  : ContextDowngradeMetricValues::kStrictCrossStrictInsecure;
208     case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE:
209       return url_is_secure
210                  ? ContextDowngradeMetricValues::kStrictCrossLaxSecure
211                  : ContextDowngradeMetricValues::kStrictCrossLaxInsecure;
212     case WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE:
213       return url_is_secure
214                  ? ContextDowngradeMetricValues::kLaxCrossStrictSecure
215                  : ContextDowngradeMetricValues::kLaxCrossStrictInsecure;
216     case WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE:
217       return url_is_secure ? ContextDowngradeMetricValues::kLaxCrossLaxSecure
218                            : ContextDowngradeMetricValues::kLaxCrossLaxInsecure;
219     default:
220       return url_is_secure ? ContextDowngradeMetricValues::kNoDowngradeSecure
221                            : ContextDowngradeMetricValues::kNoDowngradeInsecure;
222   }
223 }
224 
GetDebugString() const225 std::string CookieInclusionStatus::GetDebugString() const {
226   std::string out;
227 
228   if (IsInclude())
229     base::StrAppend(&out, {"INCLUDE, "});
230 
231   constexpr std::pair<ExclusionReason, const char*> exclusion_reasons[] = {
232       {EXCLUDE_UNKNOWN_ERROR, "EXCLUDE_UNKNOWN_ERROR"},
233       {EXCLUDE_HTTP_ONLY, "EXCLUDE_HTTP_ONLY"},
234       {EXCLUDE_SECURE_ONLY, "EXCLUDE_SECURE_ONLY"},
235       {EXCLUDE_DOMAIN_MISMATCH, "EXCLUDE_DOMAIN_MISMATCH"},
236       {EXCLUDE_NOT_ON_PATH, "EXCLUDE_NOT_ON_PATH"},
237       {EXCLUDE_SAMESITE_STRICT, "EXCLUDE_SAMESITE_STRICT"},
238       {EXCLUDE_SAMESITE_LAX, "EXCLUDE_SAMESITE_LAX"},
239       {EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
240        "EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX"},
241       {EXCLUDE_SAMESITE_NONE_INSECURE, "EXCLUDE_SAMESITE_NONE_INSECURE"},
242       {EXCLUDE_USER_PREFERENCES, "EXCLUDE_USER_PREFERENCES"},
243       {EXCLUDE_FAILURE_TO_STORE, "EXCLUDE_FAILURE_TO_STORE"},
244       {EXCLUDE_NONCOOKIEABLE_SCHEME, "EXCLUDE_NONCOOKIEABLE_SCHEME"},
245       {EXCLUDE_OVERWRITE_SECURE, "EXCLUDE_OVERWRITE_SECURE"},
246       {EXCLUDE_OVERWRITE_HTTP_ONLY, "EXCLUDE_OVERWRITE_HTTP_ONLY"},
247       {EXCLUDE_INVALID_DOMAIN, "EXCLUDE_INVALID_DOMAIN"},
248       {EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"},
249       {EXCLUDE_INVALID_PARTITIONED, "EXCLUDE_INVALID_PARTITIONED"},
250       {EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE,
251        "EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"},
252       {EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
253        "EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
254       {EXCLUDE_DOMAIN_NON_ASCII, "EXCLUDE_DOMAIN_NON_ASCII"},
255       {EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET,
256        "EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET"},
257       {EXCLUDE_PORT_MISMATCH, "EXCLUDE_PORT_MISMATCH"},
258       {EXCLUDE_SCHEME_MISMATCH, "EXCLUDE_SCHEME_MISMATCH"},
259       {EXCLUDE_SHADOWING_DOMAIN, "EXCLUDE_SHADOWING_DOMAIN"},
260       {EXCLUDE_DISALLOWED_CHARACTER, "EXCLUDE_DISALLOWED_CHARACTER"},
261       {EXCLUDE_THIRD_PARTY_PHASEOUT, "EXCLUDE_THIRD_PARTY_PHASEOUT"},
262       {EXCLUDE_NO_COOKIE_CONTENT, "EXCLUDE_NO_COOKIE_CONTENT"},
263   };
264   static_assert(
265       std::size(exclusion_reasons) == ExclusionReason::NUM_EXCLUSION_REASONS,
266       "Please ensure all ExclusionReason variants are enumerated in "
267       "GetDebugString");
268   static_assert(base::ranges::is_sorted(exclusion_reasons),
269                 "Please keep the ExclusionReason variants sorted in numerical "
270                 "order in GetDebugString");
271 
272   for (const auto& reason : exclusion_reasons) {
273     if (HasExclusionReason(reason.first))
274       base::StrAppend(&out, {reason.second, ", "});
275   }
276 
277   // Add warning
278   if (!ShouldWarn()) {
279     base::StrAppend(&out, {"DO_NOT_WARN"});
280     return out;
281   }
282 
283   constexpr std::pair<WarningReason, const char*> warning_reasons[] = {
284       {WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT,
285        "WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT"},
286       {WARN_SAMESITE_NONE_INSECURE, "WARN_SAMESITE_NONE_INSECURE"},
287       {WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE,
288        "WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE"},
289       {WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
290        "WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE"},
291       {WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
292        "WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE"},
293       {WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
294        "WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE"},
295       {WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
296        "WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE"},
297       {WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
298        "WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE"},
299       {WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC,
300        "WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC"},
301       {WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION,
302        "WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"},
303       {WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
304        "WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
305       {WARN_DOMAIN_NON_ASCII, "WARN_DOMAIN_NON_ASCII"},
306       {WARN_PORT_MISMATCH, "WARN_PORT_MISMATCH"},
307       {WARN_SCHEME_MISMATCH, "WARN_SCHEME_MISMATCH"},
308       {WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME,
309        "WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME"},
310       {WARN_SHADOWING_DOMAIN, "WARN_SHADOWING_DOMAIN"},
311       {WARN_THIRD_PARTY_PHASEOUT, "WARN_THIRD_PARTY_PHASEOUT"},
312   };
313   static_assert(
314       std::size(warning_reasons) == WarningReason::NUM_WARNING_REASONS,
315       "Please ensure all WarningReason variants are enumerated in "
316       "GetDebugString");
317   static_assert(base::ranges::is_sorted(warning_reasons),
318                 "Please keep the WarningReason variants sorted in numerical "
319                 "order in GetDebugString");
320 
321   for (const auto& reason : warning_reasons) {
322     if (HasWarningReason(reason.first))
323       base::StrAppend(&out, {reason.second, ", "});
324   }
325 
326   // Strip trailing comma and space.
327   out.erase(out.end() - 2, out.end());
328 
329   return out;
330 }
331 
HasExactlyExclusionReasonsForTesting(std::vector<CookieInclusionStatus::ExclusionReason> reasons) const332 bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting(
333     std::vector<CookieInclusionStatus::ExclusionReason> reasons) const {
334   CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons);
335   return expected.exclusion_reasons_ == exclusion_reasons_;
336 }
337 
HasExactlyWarningReasonsForTesting(std::vector<WarningReason> reasons) const338 bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting(
339     std::vector<WarningReason> reasons) const {
340   CookieInclusionStatus expected = MakeFromReasonsForTesting({}, reasons);
341   return expected.warning_reasons_ == warning_reasons_;
342 }
343 
344 // static
ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,uint32_t warning_reasons)345 bool CookieInclusionStatus::ValidateExclusionAndWarningFromWire(
346     uint32_t exclusion_reasons,
347     uint32_t warning_reasons) {
348   uint32_t exclusion_mask =
349       static_cast<uint32_t>(~0ul << ExclusionReason::NUM_EXCLUSION_REASONS);
350   uint32_t warning_mask =
351       static_cast<uint32_t>(~0ul << WarningReason::NUM_WARNING_REASONS);
352   return (exclusion_reasons & exclusion_mask) == 0 &&
353          (warning_reasons & warning_mask) == 0;
354 }
355 
MakeFromReasonsForTesting(std::vector<ExclusionReason> reasons,std::vector<WarningReason> warnings)356 CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting(
357     std::vector<ExclusionReason> reasons,
358     std::vector<WarningReason> warnings) {
359   CookieInclusionStatus status;
360   for (ExclusionReason reason : reasons) {
361     status.AddExclusionReason(reason);
362   }
363   for (WarningReason warning : warnings) {
364     status.AddWarningReason(warning);
365   }
366   return status;
367 }
368 
ExcludedByUserPreferences() const369 bool CookieInclusionStatus::ExcludedByUserPreferences() const {
370   if (HasOnlyExclusionReason(ExclusionReason::EXCLUDE_USER_PREFERENCES) ||
371       HasOnlyExclusionReason(ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT)) {
372     return true;
373   }
374   return exclusion_reasons_.count() == 2 &&
375          (exclusion_reasons_[ExclusionReason::EXCLUDE_USER_PREFERENCES] ||
376           exclusion_reasons_[ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT]) &&
377          exclusion_reasons_
378              [ExclusionReason::
379                   EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET];
380 }
381 
382 }  // namespace net
383