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