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