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/strings/strcat.h"
11 #include "url/gurl.h"
12
13 namespace net {
14
15 CookieInclusionStatus::CookieInclusionStatus() = default;
16
CookieInclusionStatus(ExclusionReason reason)17 CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason) {
18 exclusion_reasons_[reason] = true;
19 }
20
CookieInclusionStatus(ExclusionReason reason,WarningReason warning)21 CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason,
22 WarningReason warning) {
23 exclusion_reasons_[reason] = true;
24 warning_reasons_[warning] = true;
25 }
26
CookieInclusionStatus(WarningReason warning)27 CookieInclusionStatus::CookieInclusionStatus(WarningReason warning) {
28 warning_reasons_[warning] = true;
29 }
30
31 CookieInclusionStatus::CookieInclusionStatus(
32 const CookieInclusionStatus& other) = default;
33
34 CookieInclusionStatus& CookieInclusionStatus::operator=(
35 const CookieInclusionStatus& other) = default;
36
operator ==(const CookieInclusionStatus & other) const37 bool CookieInclusionStatus::operator==(
38 const CookieInclusionStatus& other) const {
39 return exclusion_reasons_ == other.exclusion_reasons_ &&
40 warning_reasons_ == other.warning_reasons_;
41 }
42
operator !=(const CookieInclusionStatus & other) const43 bool CookieInclusionStatus::operator!=(
44 const CookieInclusionStatus& other) const {
45 return !operator==(other);
46 }
47
IsInclude() const48 bool CookieInclusionStatus::IsInclude() const {
49 return exclusion_reasons_.none();
50 }
51
HasExclusionReason(ExclusionReason reason) const52 bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const {
53 return exclusion_reasons_[reason];
54 }
55
HasOnlyExclusionReason(ExclusionReason reason) const56 bool CookieInclusionStatus::HasOnlyExclusionReason(
57 ExclusionReason reason) const {
58 return exclusion_reasons_[reason] && exclusion_reasons_.count() == 1;
59 }
60
AddExclusionReason(ExclusionReason reason)61 void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) {
62 exclusion_reasons_[reason] = true;
63 // If the cookie would be excluded for reasons other than the new SameSite
64 // rules, don't bother warning about it.
65 MaybeClearSameSiteWarning();
66 }
67
RemoveExclusionReason(ExclusionReason reason)68 void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) {
69 exclusion_reasons_[reason] = false;
70 }
71
RemoveExclusionReasons(const std::vector<ExclusionReason> & reasons)72 void CookieInclusionStatus::RemoveExclusionReasons(
73 const std::vector<ExclusionReason>& reasons) {
74 exclusion_reasons_ = ExclusionReasonsWithout(reasons);
75 }
76
77 CookieInclusionStatus::ExclusionReasonBitset
ExclusionReasonsWithout(const std::vector<ExclusionReason> & reasons) const78 CookieInclusionStatus::ExclusionReasonsWithout(
79 const std::vector<ExclusionReason>& reasons) const {
80 CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_);
81 for (const ExclusionReason reason : reasons) {
82 result[reason] = false;
83 }
84 return result;
85 }
86
MaybeClearSameSiteWarning()87 void CookieInclusionStatus::MaybeClearSameSiteWarning() {
88 if (ExclusionReasonsWithout({
89 EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
90 EXCLUDE_SAMESITE_NONE_INSECURE,
91 }) != 0u) {
92 RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
93 RemoveWarningReason(WARN_SAMESITE_NONE_INSECURE);
94 RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
95 }
96
97 if (!ShouldRecordDowngradeMetrics()) {
98 RemoveWarningReason(WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
99 RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
100 RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
101 RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
102 RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
103
104 RemoveWarningReason(WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
105 }
106 }
107
ShouldRecordDowngradeMetrics() const108 bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
109 return ExclusionReasonsWithout({
110 EXCLUDE_SAMESITE_STRICT,
111 EXCLUDE_SAMESITE_LAX,
112 EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
113 }) == 0u;
114 }
115
ShouldWarn() const116 bool CookieInclusionStatus::ShouldWarn() const {
117 return warning_reasons_.any();
118 }
119
HasWarningReason(WarningReason reason) const120 bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const {
121 return warning_reasons_[reason];
122 }
123
HasSchemefulDowngradeWarning(CookieInclusionStatus::WarningReason * reason) const124 bool CookieInclusionStatus::HasSchemefulDowngradeWarning(
125 CookieInclusionStatus::WarningReason* reason) const {
126 if (!ShouldWarn())
127 return false;
128
129 const CookieInclusionStatus::WarningReason kDowngradeWarnings[] = {
130 WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
131 WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
132 WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
133 WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
134 WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
135 };
136
137 for (auto warning : kDowngradeWarnings) {
138 if (!HasWarningReason(warning))
139 continue;
140
141 if (reason)
142 *reason = warning;
143
144 return true;
145 }
146
147 return false;
148 }
149
AddWarningReason(WarningReason reason)150 void CookieInclusionStatus::AddWarningReason(WarningReason reason) {
151 warning_reasons_[reason] = true;
152 }
153
RemoveWarningReason(WarningReason reason)154 void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) {
155 warning_reasons_[reason] = false;
156 }
157
158 CookieInclusionStatus::ContextDowngradeMetricValues
GetBreakingDowngradeMetricsEnumValue(const GURL & url) const159 CookieInclusionStatus::GetBreakingDowngradeMetricsEnumValue(
160 const GURL& url) const {
161 bool url_is_secure = url.SchemeIsCryptographic();
162
163 // Start the |reason| as something other than the downgrade warnings.
164 WarningReason reason = WarningReason::NUM_WARNING_REASONS;
165
166 // Don't bother checking the return value because the default switch case
167 // will handle if no reason was found.
168 HasSchemefulDowngradeWarning(&reason);
169
170 switch (reason) {
171 case WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE:
172 return url_is_secure
173 ? ContextDowngradeMetricValues::kStrictLaxStrictSecure
174 : ContextDowngradeMetricValues::kStrictLaxStrictInsecure;
175 case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE:
176 return url_is_secure
177 ? ContextDowngradeMetricValues::kStrictCrossStrictSecure
178 : ContextDowngradeMetricValues::kStrictCrossStrictInsecure;
179 case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE:
180 return url_is_secure
181 ? ContextDowngradeMetricValues::kStrictCrossLaxSecure
182 : ContextDowngradeMetricValues::kStrictCrossLaxInsecure;
183 case WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE:
184 return url_is_secure
185 ? ContextDowngradeMetricValues::kLaxCrossStrictSecure
186 : ContextDowngradeMetricValues::kLaxCrossStrictInsecure;
187 case WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE:
188 return url_is_secure ? ContextDowngradeMetricValues::kLaxCrossLaxSecure
189 : ContextDowngradeMetricValues::kLaxCrossLaxInsecure;
190 default:
191 return url_is_secure ? ContextDowngradeMetricValues::kNoDowngradeSecure
192 : ContextDowngradeMetricValues::kNoDowngradeInsecure;
193 }
194 }
195
GetDebugString() const196 std::string CookieInclusionStatus::GetDebugString() const {
197 std::string out;
198
199 if (IsInclude())
200 base::StrAppend(&out, {"INCLUDE, "});
201 for (const auto& reason :
202 std::initializer_list<std::pair<ExclusionReason, std::string>>{
203 {EXCLUDE_UNKNOWN_ERROR, "EXCLUDE_UNKNOWN_ERROR"},
204 {EXCLUDE_HTTP_ONLY, "EXCLUDE_HTTP_ONLY"},
205 {EXCLUDE_SECURE_ONLY, "EXCLUDE_SECURE_ONLY"},
206 {EXCLUDE_DOMAIN_MISMATCH, "EXCLUDE_DOMAIN_MISMATCH"},
207 {EXCLUDE_NOT_ON_PATH, "EXCLUDE_NOT_ON_PATH"},
208 {EXCLUDE_SAMESITE_STRICT, "EXCLUDE_SAMESITE_STRICT"},
209 {EXCLUDE_SAMESITE_LAX, "EXCLUDE_SAMESITE_LAX"},
210 {EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
211 "EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX"},
212 {EXCLUDE_SAMESITE_NONE_INSECURE, "EXCLUDE_SAMESITE_NONE_INSECURE"},
213 {EXCLUDE_USER_PREFERENCES, "EXCLUDE_USER_PREFERENCES"},
214 {EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT,
215 "EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT"},
216 {EXCLUDE_FAILURE_TO_STORE, "EXCLUDE_FAILURE_TO_STORE"},
217 {EXCLUDE_NONCOOKIEABLE_SCHEME, "EXCLUDE_NONCOOKIEABLE_SCHEME"},
218 {EXCLUDE_OVERWRITE_SECURE, "EXCLUDE_OVERWRITE_SECURE"},
219 {EXCLUDE_OVERWRITE_HTTP_ONLY, "EXCLUDE_OVERWRITE_HTTP_ONLY"},
220 {EXCLUDE_INVALID_DOMAIN, "EXCLUDE_INVALID_DOMAIN"},
221 {EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"},
222 {EXCLUDE_INVALID_SAMEPARTY, "EXCLUDE_INVALID_SAMEPARTY"},
223 {EXCLUDE_INVALID_PARTITIONED, "EXCLUDE_INVALID_PARTITIONED"},
224 {EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE,
225 "EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"},
226 {EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
227 "EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
228 {EXCLUDE_DOMAIN_NON_ASCII, "EXCLUDE_DOMAIN_NON_ASCII"},
229 }) {
230 if (HasExclusionReason(reason.first))
231 base::StrAppend(&out, {reason.second, ", "});
232 }
233
234 // Add warning
235 if (!ShouldWarn()) {
236 base::StrAppend(&out, {"DO_NOT_WARN"});
237 return out;
238 }
239
240 for (const auto& reason :
241 std::initializer_list<std::pair<WarningReason, std::string>>{
242 {WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT,
243 "WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT"},
244 {WARN_SAMESITE_NONE_INSECURE, "WARN_SAMESITE_NONE_INSECURE"},
245 {WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE,
246 "WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE"},
247 {WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
248 "WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE"},
249 {WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
250 "WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE"},
251 {WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
252 "WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE"},
253 {WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
254 "WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE"},
255 {WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
256 "WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE"},
257 {WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC,
258 "WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC"},
259 {WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE,
260 "WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE"},
261 {WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE,
262 "WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE"},
263 {WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION,
264 "WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"},
265 {WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
266 "WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
267 {WARN_DOMAIN_NON_ASCII, "WARN_DOMAIN_NON_ASCII"},
268 }) {
269 if (HasWarningReason(reason.first))
270 base::StrAppend(&out, {reason.second, ", "});
271 }
272
273 // Strip trailing comma and space.
274 out.erase(out.end() - 2, out.end());
275
276 return out;
277 }
278
HasExactlyExclusionReasonsForTesting(std::vector<CookieInclusionStatus::ExclusionReason> reasons) const279 bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting(
280 std::vector<CookieInclusionStatus::ExclusionReason> reasons) const {
281 CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons);
282 return expected.exclusion_reasons_ == exclusion_reasons_;
283 }
284
HasExactlyWarningReasonsForTesting(std::vector<WarningReason> reasons) const285 bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting(
286 std::vector<WarningReason> reasons) const {
287 CookieInclusionStatus expected = MakeFromReasonsForTesting({}, reasons);
288 return expected.warning_reasons_ == warning_reasons_;
289 }
290
291 // static
ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,uint32_t warning_reasons)292 bool CookieInclusionStatus::ValidateExclusionAndWarningFromWire(
293 uint32_t exclusion_reasons,
294 uint32_t warning_reasons) {
295 uint32_t exclusion_mask =
296 static_cast<uint32_t>(~0ul << ExclusionReason::NUM_EXCLUSION_REASONS);
297 uint32_t warning_mask =
298 static_cast<uint32_t>(~0ul << WarningReason::NUM_WARNING_REASONS);
299 return (exclusion_reasons & exclusion_mask) == 0 &&
300 (warning_reasons & warning_mask) == 0;
301 }
302
MakeFromReasonsForTesting(std::vector<ExclusionReason> reasons,std::vector<WarningReason> warnings)303 CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting(
304 std::vector<ExclusionReason> reasons,
305 std::vector<WarningReason> warnings) {
306 CookieInclusionStatus status;
307 for (ExclusionReason reason : reasons) {
308 status.AddExclusionReason(reason);
309 }
310 for (WarningReason warning : warnings) {
311 status.AddWarningReason(warning);
312 }
313 return status;
314 }
315
ExcludedByUserPreferences() const316 bool CookieInclusionStatus::ExcludedByUserPreferences() const {
317 if (HasOnlyExclusionReason(ExclusionReason::EXCLUDE_USER_PREFERENCES))
318 return true;
319 return exclusion_reasons_.count() == 2 &&
320 exclusion_reasons_[ExclusionReason::EXCLUDE_USER_PREFERENCES] &&
321 exclusion_reasons_
322 [ExclusionReason::
323 EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET];
324 }
325
326 } // namespace net
327