1 // Copyright 2012 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 // Brought to you by number 42.
6
7 #ifndef NET_COOKIES_COOKIE_OPTIONS_H_
8 #define NET_COOKIES_COOKIE_OPTIONS_H_
9
10 #include <ostream>
11 #include <string>
12
13 #include "base/check_op.h"
14 #include "net/base/net_export.h"
15
16 namespace net {
17
18 class NET_EXPORT CookieOptions {
19 public:
20
21 // Relation between the cookie and the navigational environment.
22 class NET_EXPORT SameSiteCookieContext {
23 public:
24 // CROSS_SITE to SAME_SITE_STRICT are ordered from least to most trusted
25 // environment. Don't renumber, used in histograms.
26 enum class ContextType {
27 CROSS_SITE = 0,
28 // Same rules as lax but the http method is unsafe.
29 SAME_SITE_LAX_METHOD_UNSAFE = 1,
30 SAME_SITE_LAX = 2,
31 SAME_SITE_STRICT = 3,
32
33 // Keep last, used for histograms.
34 COUNT
35 };
36
37 // Holds metadata about the factors that went into deciding the ContextType.
38 //
39 // These values may be used for recording histograms or
40 // CookieInclusionStatus warnings, but SHOULD NOT be relied
41 // upon for cookie inclusion decisions. Use only the ContextTypes for that.
42 //
43 // When adding a field, also update CompleteEquivalenceForTesting.
44 struct NET_EXPORT ContextMetadata {
45 // Possible "downgrades" for the SameSite context type, e.g. from a more
46 // trusted context to a less trusted context, as a result of some behavior
47 // change affecting the same-site calculation.
48 enum class ContextDowngradeType {
49 // Context not downgraded.
50 kNoDowngrade,
51 // Context was originally strictly same-site, was downgraded to laxly
52 // same-site.
53 kStrictToLax,
54 // Context was originally strictly same-site, was downgraded to
55 // cross-site.
56 kStrictToCross,
57 // Context was originally laxly same-site, was downgraded to cross-site.
58 kLaxToCross,
59 };
60
61 // These values are persisted to logs. Entries should not be renumbered
62 // and numeric values should never be reused.
63 // This enum is to help collect metrics for https://crbug.com/1221316.
64 // Specifically it's to help indicate how many cookies are accessed with a
65 // given redirect type in order to provide a denominator for the
66 // Cookie.CrossSiteRedirectDowngradeChangesInclusion2.* metrics.
67 // Note: for this enum the notation A->B->C means that a navigation from
68 // A to B was redirected to C. I.e.: A is the initator of the navigation
69 // and C is the final url.
70 enum class ContextRedirectTypeBug1221316 {
71 kUnset =
72 0, // Indicates this value was unused and shouldn't be read. E.x.:
73 // A javascript access means this value is meaningless.
74 kNoRedirect = 1, // There weren't any redirects. E.x.: A->B, A->A
75 kCrossSiteRedirect =
76 2, // There was a redirect but it didn't start and
77 // end at the same site or the redirect was to a different site
78 // than the site-for-cookies. E.x.: A->B->C or B->B->B when the
79 // site-for-cookies is A.
80 kPartialSameSiteRedirect =
81 3, // There was a redirect and the start and
82 // end are the same site. E.x.: A->B->A. Only this one could
83 // potentially set cross_site_redirect_downgrade.
84 kAllSameSiteRedirect = 4, // There was a redirect and all urls are the
85 // same site. E.x.:, A->A->A
86 kMaxValue = kAllSameSiteRedirect
87 };
88
89 // These values are persisted to logs. Entries should not be renumbered
90 // and numeric values should never be reused.
91 enum class HttpMethod {
92 // kUnset indicates this enum wasn't applicable in the context.
93 kUnset = -1,
94 // kUnknown indicates we were unable to convert the method string to
95 // this enum.
96 kUnknown = 0,
97 kGet = 1,
98 kHead = 2,
99 kPost = 3,
100 KPut = 4,
101 kDelete = 5,
102 kConnect = 6,
103 kOptions = 7,
104 kTrace = 8,
105 kPatch = 9,
106 kMaxValue = kPatch
107 };
108
109 // Records the type of any context downgrade due to a cross-site redirect,
110 // i.e. whether the spec change in
111 // https://github.com/httpwg/http-extensions/pull/1348 changed the result
112 // of the context calculation. Note that a lax-to-cross downgrade can only
113 // happen for response cookies, because a laxly same-site context only
114 // happens for a top-level cross-site request, which cannot be downgraded
115 // due to a cross-site redirect to a non-top-level cross-site request.
116 // This only records whether the context was downgraded, not whether the
117 // cookie's inclusion result was changed.
118 ContextDowngradeType cross_site_redirect_downgrade =
119 ContextDowngradeType::kNoDowngrade;
120
121 ContextRedirectTypeBug1221316 redirect_type_bug_1221316 =
122 ContextRedirectTypeBug1221316::kUnset;
123
124 // Records the HTTP method of requests that result in a cross-site
125 // redirect downgrade. May be kUnset if there wasn't a downgrade or if the
126 // cookie access wasn't due to a request.
127 //
128 // Note that this field is always set when there was a context
129 // downgrade but the associated histrogram is only recorded when that
130 // context downgrade results in a change in inclusion status.
131 HttpMethod http_method_bug_1221316 = HttpMethod::kUnset;
132 };
133
134 // The following three constructors apply default values for the metadata
135 // members.
SameSiteCookieContext()136 SameSiteCookieContext()
137 : SameSiteCookieContext(ContextType::CROSS_SITE,
138 ContextType::CROSS_SITE) {}
139
SameSiteCookieContext(ContextType same_site_context)140 explicit SameSiteCookieContext(ContextType same_site_context)
141 : SameSiteCookieContext(same_site_context, same_site_context) {}
142
SameSiteCookieContext(ContextType same_site_context,ContextType schemeful_same_site_context)143 SameSiteCookieContext(ContextType same_site_context,
144 ContextType schemeful_same_site_context)
145 : SameSiteCookieContext(same_site_context,
146 schemeful_same_site_context,
147 ContextMetadata(),
148 ContextMetadata()) {}
149
150 // Schemeful and schemeless context types are consistency-checked against
151 // each other, but the metadata is stored as-is (i.e. the values in
152 // `metadata` and `schemeful_metadata` may be logically inconsistent), as
153 // the metadata is not relied upon for correctness.
SameSiteCookieContext(ContextType same_site_context,ContextType schemeful_same_site_context,ContextMetadata metadata,ContextMetadata schemeful_metadata)154 SameSiteCookieContext(ContextType same_site_context,
155 ContextType schemeful_same_site_context,
156 ContextMetadata metadata,
157 ContextMetadata schemeful_metadata)
158 : context_(same_site_context),
159 schemeful_context_(schemeful_same_site_context),
160 metadata_(metadata),
161 schemeful_metadata_(schemeful_metadata) {
162 DCHECK_LE(schemeful_context_, context_);
163 }
164
165 // Convenience method which returns a SameSiteCookieContext with the most
166 // inclusive contexts. This allows access to all SameSite cookies.
167 static SameSiteCookieContext MakeInclusive();
168
169 // Convenience method which returns a SameSiteCookieContext with the most
170 // inclusive contexts for set. This allows setting all SameSite cookies.
171 static SameSiteCookieContext MakeInclusiveForSet();
172
173 // Returns the context for determining SameSite cookie inclusion.
174 ContextType GetContextForCookieInclusion() const;
175
176 // Returns the metadata describing how this context was calculated, under
177 // the currently applicable schemeful/schemeless mode.
178 // TODO(chlily): Should take the CookieAccessSemantics as well, to
179 // accurately account for the context actually used for a given cookie.
180 const ContextMetadata& GetMetadataForCurrentSchemefulMode() const;
181
182 // If you're just trying to determine if a cookie is accessible you likely
183 // want to use GetContextForCookieInclusion() which will return the correct
184 // context regardless the status of same-site features.
context()185 ContextType context() const { return context_; }
schemeful_context()186 ContextType schemeful_context() const { return schemeful_context_; }
187
188 // You probably want to use GetMetadataForCurrentSchemefulMode() instead of
189 // these getters, since that takes into account the applicable schemeful
190 // mode.
metadata()191 const ContextMetadata& metadata() const { return metadata_; }
metadata()192 ContextMetadata& metadata() { return metadata_; }
schemeful_metadata()193 const ContextMetadata& schemeful_metadata() const {
194 return schemeful_metadata_;
195 }
schemeful_metadata()196 ContextMetadata& schemeful_metadata() { return schemeful_metadata_; }
197
198 // Sets context types. Does not check for consistency between context and
199 // schemeful context. Does not touch the metadata.
200 void SetContextTypesForTesting(ContextType context_type,
201 ContextType schemeful_context_type);
202
203 // Returns whether the context types and all fields of the metadata structs
204 // are the same.
205 bool CompleteEquivalenceForTesting(
206 const SameSiteCookieContext& other) const;
207
208 // Equality operators disregard any metadata! (Only the context types are
209 // compared, not how they were computed.)
210 NET_EXPORT friend bool operator==(
211 const CookieOptions::SameSiteCookieContext& lhs,
212 const CookieOptions::SameSiteCookieContext& rhs);
213 NET_EXPORT friend bool operator!=(
214 const CookieOptions::SameSiteCookieContext& lhs,
215 const CookieOptions::SameSiteCookieContext& rhs);
216
217 private:
218 ContextType context_;
219 ContextType schemeful_context_;
220
221 ContextMetadata metadata_;
222 ContextMetadata schemeful_metadata_;
223 };
224
225 // Creates a CookieOptions object which:
226 //
227 // * Excludes HttpOnly cookies
228 // * Excludes SameSite cookies
229 // * Updates last-accessed time.
230 // * Does not report excluded cookies in APIs that can do so.
231 //
232 // These settings can be altered by calling:
233 //
234 // * |set_{include,exclude}_httponly()|
235 // * |set_same_site_cookie_context()|
236 // * |set_do_not_update_access_time()|
237 CookieOptions();
238 CookieOptions(const CookieOptions& other);
239 CookieOptions(CookieOptions&& other);
240 ~CookieOptions();
241
242 CookieOptions& operator=(const CookieOptions&);
243 CookieOptions& operator=(CookieOptions&&);
244
set_exclude_httponly()245 void set_exclude_httponly() { exclude_httponly_ = true; }
set_include_httponly()246 void set_include_httponly() { exclude_httponly_ = false; }
exclude_httponly()247 bool exclude_httponly() const { return exclude_httponly_; }
248
249 // How trusted is the current browser environment when it comes to accessing
250 // SameSite cookies. Default is not trusted, e.g. CROSS_SITE.
set_same_site_cookie_context(const SameSiteCookieContext & context)251 void set_same_site_cookie_context(const SameSiteCookieContext& context) {
252 same_site_cookie_context_ = context;
253 }
254
same_site_cookie_context()255 const SameSiteCookieContext& same_site_cookie_context() const {
256 return same_site_cookie_context_;
257 }
258
set_update_access_time()259 void set_update_access_time() { update_access_time_ = true; }
set_do_not_update_access_time()260 void set_do_not_update_access_time() { update_access_time_ = false; }
update_access_time()261 bool update_access_time() const { return update_access_time_; }
262
set_return_excluded_cookies()263 void set_return_excluded_cookies() { return_excluded_cookies_ = true; }
unset_return_excluded_cookies()264 void unset_return_excluded_cookies() { return_excluded_cookies_ = false; }
return_excluded_cookies()265 bool return_excluded_cookies() const { return return_excluded_cookies_; }
266
267 // Convenience method for where you need a CookieOptions that will
268 // work for getting/setting all types of cookies, including HttpOnly and
269 // SameSite cookies. Also specifies not to update the access time, because
270 // usually this is done to get all the cookies to check that they are correct,
271 // including the creation time. This basically makes a CookieOptions that is
272 // the opposite of the default CookieOptions.
273 static CookieOptions MakeAllInclusive();
274
275 private:
276 // Keep default values in sync with
277 // content/public/common/cookie_manager.mojom.
278 bool exclude_httponly_ = true;
279 SameSiteCookieContext same_site_cookie_context_;
280 bool update_access_time_ = true;
281 bool return_excluded_cookies_ = false;
282 };
283
284 NET_EXPORT bool operator==(
285 const CookieOptions::SameSiteCookieContext::ContextMetadata& lhs,
286 const CookieOptions::SameSiteCookieContext::ContextMetadata& rhs);
287 NET_EXPORT bool operator!=(
288 const CookieOptions::SameSiteCookieContext::ContextMetadata& lhs,
289 const CookieOptions::SameSiteCookieContext::ContextMetadata& rhs);
290
291 // Allows gtest to print more helpful error messages instead of printing hex.
292 // (No need to null-check `os` because we can assume gtest will properly pass a
293 // non-null pointer, and it is dereferenced immediately anyway.)
PrintTo(CookieOptions::SameSiteCookieContext::ContextType ct,std::ostream * os)294 inline void PrintTo(CookieOptions::SameSiteCookieContext::ContextType ct,
295 std::ostream* os) {
296 *os << static_cast<int>(ct);
297 }
298
PrintTo(const CookieOptions::SameSiteCookieContext::ContextMetadata & m,std::ostream * os)299 inline void PrintTo(
300 const CookieOptions::SameSiteCookieContext::ContextMetadata& m,
301 std::ostream* os) {
302 *os << "{";
303 *os << " cross_site_redirect_downgrade: "
304 << static_cast<int>(m.cross_site_redirect_downgrade);
305 *os << ", redirect_type_bug_1221316: "
306 << static_cast<int>(m.redirect_type_bug_1221316);
307 *os << ", http_method_bug_1221316: "
308 << static_cast<int>(m.http_method_bug_1221316);
309 *os << " }";
310 }
311
PrintTo(const CookieOptions::SameSiteCookieContext & sscc,std::ostream * os)312 inline void PrintTo(const CookieOptions::SameSiteCookieContext& sscc,
313 std::ostream* os) {
314 *os << "{ context: ";
315 PrintTo(sscc.context(), os);
316 *os << ", schemeful_context: ";
317 PrintTo(sscc.schemeful_context(), os);
318 *os << ", metadata: ";
319 PrintTo(sscc.metadata(), os);
320 *os << ", schemeful_metadata: ";
321 PrintTo(sscc.schemeful_metadata(), os);
322 *os << " }";
323 }
324
325 } // namespace net
326
327 #endif // NET_COOKIES_COOKIE_OPTIONS_H_
328