1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef NET_COOKIES_CANONICAL_COOKIE_H_
6 #define NET_COOKIES_CANONICAL_COOKIE_H_
7 
8 #include <memory>
9 #include <string>
10 #include <tuple>
11 #include <vector>
12 
13 #include "base/gtest_prod_util.h"
14 #include "base/optional.h"
15 #include "base/time/time.h"
16 #include "net/base/net_export.h"
17 #include "net/cookies/cookie_constants.h"
18 #include "net/cookies/cookie_options.h"
19 
20 class GURL;
21 
22 namespace net {
23 
24 class ParsedCookie;
25 class CanonicalCookie;
26 
27 struct CookieWithStatus;
28 struct CookieAndLineWithStatus;
29 
30 using CookieList = std::vector<CanonicalCookie>;
31 using CookieStatusList = std::vector<CookieWithStatus>;
32 using CookieAndLineStatusList = std::vector<CookieAndLineWithStatus>;
33 
34 class NET_EXPORT CanonicalCookie {
35  public:
36   class CookieInclusionStatus;
37   using UniqueCookieKey = std::tuple<std::string, std::string, std::string>;
38 
39   CanonicalCookie();
40   CanonicalCookie(const CanonicalCookie& other);
41 
42   // This constructor does not validate or canonicalize their inputs;
43   // the resulting CanonicalCookies should not be relied on to be canonical
44   // unless the caller has done appropriate validation and canonicalization
45   // themselves.
46   // NOTE: Prefer using CreateSanitizedCookie() over directly using this
47   // constructor.
48   CanonicalCookie(
49       const std::string& name,
50       const std::string& value,
51       const std::string& domain,
52       const std::string& path,
53       const base::Time& creation,
54       const base::Time& expiration,
55       const base::Time& last_access,
56       bool secure,
57       bool httponly,
58       CookieSameSite same_site,
59       CookiePriority priority,
60       CookieSourceScheme scheme_secure = CookieSourceScheme::kUnset);
61 
62   ~CanonicalCookie();
63 
64   // Supports the default copy constructor.
65 
66   // Creates a new |CanonicalCookie| from the |cookie_line| and the
67   // |creation_time|.  Canonicalizes inputs.  May return nullptr if
68   // an attribute value is invalid.  |url| must be valid.  |creation_time| may
69   // not be null. Sets optional |status| to the relevant CookieInclusionStatus
70   // if provided.  |server_time| indicates what the server sending us the Cookie
71   // thought the current time was when the cookie was produced.  This is used to
72   // adjust for clock skew between server and host.
73   //
74   // SameSite and HttpOnly related parameters are not checked here,
75   // so creation of CanonicalCookies with e.g. SameSite=Strict from a cross-site
76   // context is allowed. Create() also does not check whether |url| has a secure
77   // scheme if attempting to create a Secure cookie. The Secure, SameSite, and
78   // HttpOnly related parameters should be checked when setting the cookie in
79   // the CookieStore.
80   //
81   // If a cookie is returned, |cookie->IsCanonical()| will be true.
82   static std::unique_ptr<CanonicalCookie> Create(
83       const GURL& url,
84       const std::string& cookie_line,
85       const base::Time& creation_time,
86       base::Optional<base::Time> server_time,
87       CookieInclusionStatus* status = nullptr);
88 
89   // Create a canonical cookie based on sanitizing the passed inputs in the
90   // context of the passed URL.  Returns a null unique pointer if the inputs
91   // cannot be sanitized.  If a cookie is created, |cookie->IsCanonical()|
92   // will be true.
93   static std::unique_ptr<CanonicalCookie> CreateSanitizedCookie(
94       const GURL& url,
95       const std::string& name,
96       const std::string& value,
97       const std::string& domain,
98       const std::string& path,
99       base::Time creation_time,
100       base::Time expiration_time,
101       base::Time last_access_time,
102       bool secure,
103       bool http_only,
104       CookieSameSite same_site,
105       CookiePriority priority);
106 
Name()107   const std::string& Name() const { return name_; }
Value()108   const std::string& Value() const { return value_; }
Domain()109   const std::string& Domain() const { return domain_; }
Path()110   const std::string& Path() const { return path_; }
CreationDate()111   const base::Time& CreationDate() const { return creation_date_; }
LastAccessDate()112   const base::Time& LastAccessDate() const { return last_access_date_; }
IsPersistent()113   bool IsPersistent() const { return !expiry_date_.is_null(); }
ExpiryDate()114   const base::Time& ExpiryDate() const { return expiry_date_; }
IsSecure()115   bool IsSecure() const { return secure_; }
IsHttpOnly()116   bool IsHttpOnly() const { return httponly_; }
SameSite()117   CookieSameSite SameSite() const { return same_site_; }
Priority()118   CookiePriority Priority() const { return priority_; }
119   // Returns an enum indicating the source scheme that set this cookie. This is
120   // not part of the cookie spec but is being used to collect metrics for a
121   // potential change to the cookie spec.
SourceScheme()122   CookieSourceScheme SourceScheme() const { return source_scheme_; }
IsDomainCookie()123   bool IsDomainCookie() const {
124     return !domain_.empty() && domain_[0] == '.'; }
IsHostCookie()125   bool IsHostCookie() const { return !IsDomainCookie(); }
126 
IsExpired(const base::Time & current)127   bool IsExpired(const base::Time& current) const {
128     return !expiry_date_.is_null() && current >= expiry_date_;
129   }
130 
131   // Are the cookies considered equivalent in the eyes of RFC 2965.
132   // The RFC says that name must match (case-sensitive), domain must
133   // match (case insensitive), and path must match (case sensitive).
134   // For the case insensitive domain compare, we rely on the domain
135   // having been canonicalized (in
136   // GetCookieDomainWithString->CanonicalizeHost).
IsEquivalent(const CanonicalCookie & ecc)137   bool IsEquivalent(const CanonicalCookie& ecc) const {
138     // It seems like it would make sense to take secure, httponly, and samesite
139     // into account, but the RFC doesn't specify this.
140     // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost().
141     return (name_ == ecc.Name() && domain_ == ecc.Domain()
142             && path_ == ecc.Path());
143   }
144 
145   // Returns a key such that two cookies with the same UniqueKey() are
146   // guaranteed to be equivalent in the sense of IsEquivalent().
UniqueKey()147   UniqueCookieKey UniqueKey() const {
148     return std::make_tuple(name_, domain_, path_);
149   }
150 
151   // Checks a looser set of equivalency rules than 'IsEquivalent()' in order
152   // to support the stricter 'Secure' behaviors specified in
153   // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone#section-3
154   //
155   // Returns 'true' if this cookie's name matches |ecc|, and this cookie is
156   // a domain-match for |ecc| (or vice versa), and |ecc|'s path is "on" this
157   // cookie's path (as per 'IsOnPath()').
158   //
159   // Note that while the domain-match cuts both ways (e.g. 'example.com'
160   // matches 'www.example.com' in either direction), the path-match is
161   // unidirectional (e.g. '/login/en' matches '/login' and '/', but
162   // '/login' and '/' do not match '/login/en').
163   bool IsEquivalentForSecureCookieMatching(const CanonicalCookie& ecc) const;
164 
SetSourceScheme(CookieSourceScheme source_scheme)165   void SetSourceScheme(CookieSourceScheme source_scheme) {
166     source_scheme_ = source_scheme;
167   }
SetLastAccessDate(const base::Time & date)168   void SetLastAccessDate(const base::Time& date) {
169     last_access_date_ = date;
170   }
SetCreationDate(const base::Time & date)171   void SetCreationDate(const base::Time& date) { creation_date_ = date; }
172 
173   // Returns true if the given |url_path| path-matches the cookie-path as
174   // described in section 5.1.4 in RFC 6265.
175   bool IsOnPath(const std::string& url_path) const;
176 
177   // Returns true if the cookie domain matches the given |host| as described in
178   // section 5.1.3 of RFC 6265.
179   bool IsDomainMatch(const std::string& host) const;
180 
181   // Returns if the cookie should be included (and if not, why) for the given
182   // request |url| using the CookieInclusionStatus enum. HTTP only cookies can
183   // be filter by using appropriate cookie |options|. PLEASE NOTE that this
184   // method does not check whether a cookie is expired or not!
185   CookieInclusionStatus IncludeForRequestURL(
186       const GURL& url,
187       const CookieOptions& options,
188       CookieAccessSemantics access_semantics =
189           CookieAccessSemantics::UNKNOWN) const;
190 
191   // Returns if the cookie with given attributes can be set in context described
192   // by |options|, and if no, describes why.
193   // WARNING: this does not cover checking whether secure cookies are set in
194   // a secure schema, since whether the schema is secure isn't part of
195   // |options|.
196   CookieInclusionStatus IsSetPermittedInContext(
197       const CookieOptions& options,
198       CookieAccessSemantics access_semantics =
199           CookieAccessSemantics::UNKNOWN) const;
200 
201   // Overload that updates an existing |status| rather than returning a new one.
202   void IsSetPermittedInContext(const CookieOptions& options,
203                                CookieAccessSemantics access_semantics,
204                                CookieInclusionStatus* status) const;
205 
206   std::string DebugString() const;
207 
208   static std::string CanonPathWithString(const GURL& url,
209                                          const std::string& path_string);
210 
211   // Returns a "null" time if expiration was unspecified or invalid.
212   static base::Time CanonExpiration(const ParsedCookie& pc,
213                                     const base::Time& current,
214                                     const base::Time& server_time);
215 
216   // Cookie ordering methods.
217 
218   // Returns true if the cookie is less than |other|, considering only name,
219   // domain and path. In particular, two equivalent cookies (see IsEquivalent())
220   // are identical for PartialCompare().
221   bool PartialCompare(const CanonicalCookie& other) const;
222 
223   // Return whether this object is a valid CanonicalCookie().  Invalid
224   // cookies may be constructed by the detailed constructor.
225   // A cookie is considered canonical if-and-only-if:
226   // * It can be created by CanonicalCookie::Create, or
227   // * It is identical to a cookie created by CanonicalCookie::Create except
228   //   that the creation time is null, or
229   // * It can be derived from a cookie created by CanonicalCookie::Create by
230   //   entry into and retrieval from a cookie store (specifically, this means
231   //   by the setting of an creation time in place of a null creation time, and
232   //   the setting of a last access time).
233   // An additional requirement on a CanonicalCookie is that if the last
234   // access time is non-null, the creation time must also be non-null and
235   // greater than the last access time.
236   bool IsCanonical() const;
237 
238   // Returns whether the effective SameSite mode is SameSite=None (i.e. no
239   // SameSite restrictions).
240   bool IsEffectivelySameSiteNone(CookieAccessSemantics access_semantics =
241                                      CookieAccessSemantics::UNKNOWN) const;
242 
243   CookieEffectiveSameSite GetEffectiveSameSiteForTesting(
244       CookieAccessSemantics access_semantics =
245           CookieAccessSemantics::UNKNOWN) const;
246 
247   // Returns the cookie line (e.g. "cookie1=value1; cookie2=value2") represented
248   // by |cookies|. The string is built in the same order as the given list.
249   static std::string BuildCookieLine(const CookieList& cookies);
250 
251   // Same as above but takes a CookieStatusList (ignores the statuses).
252   static std::string BuildCookieLine(const CookieStatusList& cookies);
253 
254  private:
255   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms);
256 
257   // The special cookie prefixes as defined in
258   // https://tools.ietf.org/html/draft-west-cookie-prefixes
259   //
260   // This enum is being histogrammed; do not reorder or remove values.
261   enum CookiePrefix {
262     COOKIE_PREFIX_NONE = 0,
263     COOKIE_PREFIX_SECURE,
264     COOKIE_PREFIX_HOST,
265     COOKIE_PREFIX_LAST
266   };
267 
268   // Applies the appropriate warning for the given cross-scheme
269   // SameSiteCookieContext.
270   void AddSameSiteCrossSchemeWarning(
271       CookieInclusionStatus* status,
272       const CookieOptions::SameSiteCookieContext context) const;
273 
274   // Returns the CookiePrefix (or COOKIE_PREFIX_NONE if none) that
275   // applies to the given cookie |name|.
276   static CookiePrefix GetCookiePrefix(const std::string& name);
277   // Records histograms to measure how often cookie prefixes appear in
278   // the wild and how often they would be blocked.
279   static void RecordCookiePrefixMetrics(CookiePrefix prefix,
280                                         bool is_cookie_valid);
281   // Returns true if a prefixed cookie does not violate any of the rules
282   // for that cookie.
283   static bool IsCookiePrefixValid(CookiePrefix prefix,
284                                   const GURL& url,
285                                   const ParsedCookie& parsed_cookie);
286   static bool IsCookiePrefixValid(CookiePrefix prefix,
287                                   const GURL& url,
288                                   bool secure,
289                                   const std::string& domain,
290                                   const std::string& path);
291 
292   // Returns the effective SameSite mode to apply to this cookie. Depends on the
293   // value of the given SameSite attribute and whether the
294   // SameSiteByDefaultCookies feature is enabled, as well as the access
295   // semantics of the cookie.
296   // Note: If you are converting to a different representation of a cookie, you
297   // probably want to use SameSite() instead of this method. Otherwise, if you
298   // are considering using this method, consider whether you should use
299   // IncludeForRequestURL() or IsSetPermittedInContext() instead of doing the
300   // SameSite computation yourself.
301   CookieEffectiveSameSite GetEffectiveSameSite(
302       CookieAccessSemantics access_semantics) const;
303 
304   // Returns whether the cookie was created at most |age_threshold| ago.
305   bool IsRecentlyCreated(base::TimeDelta age_threshold) const;
306 
307   // Returns the cookie's domain, with the leading dot removed, if present.
308   std::string DomainWithoutDot() const;
309 
310   std::string name_;
311   std::string value_;
312   std::string domain_;
313   std::string path_;
314   base::Time creation_date_;
315   base::Time expiry_date_;
316   base::Time last_access_date_;
317   bool secure_;
318   bool httponly_;
319   CookieSameSite same_site_;
320   CookiePriority priority_;
321   CookieSourceScheme source_scheme_;
322 };
323 
324 // This class represents if a cookie was included or excluded in a cookie get or
325 // set operation, and if excluded why. It holds a vector of reasons for
326 // exclusion, where cookie inclusion is represented by the absence of any
327 // exclusion reasons. Also marks whether a cookie should be warned about, e.g.
328 // for deprecation or intervention reasons.
329 // TODO(chlily): Rename/move this to just net::CookieInclusionStatus.
330 class NET_EXPORT CanonicalCookie::CookieInclusionStatus {
331  public:
332   // Types of reasons why a cookie might be excluded.
333   // If adding a ExclusionReason, please also update the GetDebugString()
334   // method.
335   enum ExclusionReason {
336     EXCLUDE_UNKNOWN_ERROR = 0,
337 
338     EXCLUDE_HTTP_ONLY = 1,
339     EXCLUDE_SECURE_ONLY = 2,
340     EXCLUDE_DOMAIN_MISMATCH = 3,
341     EXCLUDE_NOT_ON_PATH = 4,
342     EXCLUDE_SAMESITE_STRICT = 5,
343     EXCLUDE_SAMESITE_LAX = 6,
344 
345     // The following two are used for the SameSiteByDefaultCookies experiment,
346     // where if the SameSite attribute is not specified, it will be treated as
347     // SameSite=Lax by default.
348     EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX = 7,
349     // This is used if SameSite=None is specified, but the cookie is not
350     // Secure.
351     EXCLUDE_SAMESITE_NONE_INSECURE = 8,
352     EXCLUDE_USER_PREFERENCES = 9,
353 
354     // Statuses specific to setting cookies
355     EXCLUDE_FAILURE_TO_STORE = 10,
356     EXCLUDE_NONCOOKIEABLE_SCHEME = 11,
357     EXCLUDE_OVERWRITE_SECURE = 12,
358     EXCLUDE_OVERWRITE_HTTP_ONLY = 13,
359     EXCLUDE_INVALID_DOMAIN = 14,
360     EXCLUDE_INVALID_PREFIX = 15,
361 
362     // This should be kept last.
363     NUM_EXCLUSION_REASONS
364   };
365 
366   // Reason to warn about a cookie. If you add one, please update
367   // GetDebugString().
368   enum WarningReason {
369     // Of the following 3 SameSite warnings, there will be, at most, a single
370     // active one.
371 
372     // Warn if a cookie with unspecified SameSite attribute is used in a
373     // cross-site context.
374     WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT = 0,
375     // Warn if a cookie with SameSite=None is not Secure.
376     WARN_SAMESITE_NONE_INSECURE = 1,
377     // Warn if a cookie with unspecified SameSite attribute is defaulted into
378     // Lax and is sent on a request with unsafe method, only because it is new
379     // enough to activate the Lax-allow-unsafe intervention.
380     WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE = 2,
381 
382     // The following warnings indicate that a SameSite cookie is being sent/set
383     // across schemes and with what same-site context.
384     // See CookieOptions::SameSiteCookieContext.
385     WARN_SAMESITE_LAX_METHOD_UNSAFE_CROSS_SCHEME_SECURE_URL = 3,
386     WARN_SAMESITE_LAX_CROSS_SCHEME_SECURE_URL = 4,
387     WARN_SAMESITE_STRICT_CROSS_SCHEME_SECURE_URL = 5,
388     WARN_SAMESITE_LAX_METHOD_UNSAFE_CROSS_SCHEME_INSECURE_URL = 6,
389     WARN_SAMESITE_LAX_CROSS_SCHEME_INSECURE_URL = 7,
390     WARN_SAMESITE_STRICT_CROSS_SCHEME_INSECURE_URL = 8,
391 
392     // This should be kept last.
393     NUM_WARNING_REASONS
394   };
395 
396   // Makes a status that says include and should not warn.
397   CookieInclusionStatus();
398 
399   // Make a status that contains the given exclusion reason.
400   explicit CookieInclusionStatus(ExclusionReason reason);
401   // Makes a status that contains the given exclusion reason and warning.
402   CookieInclusionStatus(ExclusionReason reason, WarningReason warning);
403 
404   bool operator==(const CookieInclusionStatus& other) const;
405   bool operator!=(const CookieInclusionStatus& other) const;
406 
407   // Whether the status is to include the cookie, and has no other reasons for
408   // exclusion.
409   bool IsInclude() const;
410 
411   // Whether the given reason for exclusion is present.
412   bool HasExclusionReason(ExclusionReason status_type) const;
413 
414   // Add an exclusion reason.
415   void AddExclusionReason(ExclusionReason status_type);
416 
417   // Remove an exclusion reason.
418   void RemoveExclusionReason(ExclusionReason reason);
419 
420   // If the cookie would have been excluded for reasons other than
421   // SAMESITE_UNSPECIFIED_TREATED_AS_LAX or SAMESITE_NONE_INSECURE, don't bother
422   // warning about it (clear the warning).
423   void MaybeClearSameSiteWarning();
424 
425   // Whether the cookie should be warned about.
426   bool ShouldWarn() const;
427 
428   // Whether the given reason for warning is present.
429   bool HasWarningReason(WarningReason reason) const;
430 
431   // Whether a cross-scheme warning is present.
432   // If the function returns true and |reason| is valid then |reason| will
433   // contain which warning was found.
434   bool HasCrossSchemeWarning(
435       CookieInclusionStatus::WarningReason* reason = nullptr) const;
436 
437   // Add an warning reason.
438   void AddWarningReason(WarningReason reason);
439 
440   // Remove an warning reason.
441   void RemoveWarningReason(WarningReason reason);
442 
443   // Used for serialization/deserialization.
exclusion_reasons()444   uint32_t exclusion_reasons() const { return exclusion_reasons_; }
set_exclusion_reasons(uint32_t exclusion_reasons)445   void set_exclusion_reasons(uint32_t exclusion_reasons) {
446     exclusion_reasons_ = exclusion_reasons;
447   }
448 
warning_reasons()449   uint32_t warning_reasons() const { return warning_reasons_; }
set_warning_reasons(uint32_t warning_reasons)450   void set_warning_reasons(uint32_t warning_reasons) {
451     warning_reasons_ = warning_reasons;
452   }
453 
454   // Get exclusion reason(s) and warning in string format.
455   std::string GetDebugString() const;
456 
457   // Checks that the underlying bit vector representation doesn't contain any
458   // extraneous bits that are not mapped to any enum values. Does not check
459   // for reasons which semantically cannot coexist.
460   bool IsValid() const;
461 
462   // Checks whether the exclusion reasons are exactly the set of exclusion
463   // reasons in the vector. (Ignores warnings.)
464   bool HasExactlyExclusionReasonsForTesting(
465       std::vector<ExclusionReason> reasons) const;
466 
467   // Checks whether the warning reasons are exactly the set of warning
468   // reasons in the vector. (Ignores exclusions.)
469   bool HasExactlyWarningReasonsForTesting(
470       std::vector<WarningReason> reasons) const;
471 
472   // Makes a status that contains the given exclusion reasons and warning.
473   static CookieInclusionStatus MakeFromReasonsForTesting(
474       std::vector<ExclusionReason> reasons,
475       std::vector<WarningReason> warnings = std::vector<WarningReason>());
476 
477  private:
478   // A bit vector of the applicable exclusion reasons.
479   uint32_t exclusion_reasons_ = 0u;
480 
481   // A bit vector of the applicable warning reasons.
482   uint32_t warning_reasons_ = 0u;
483 };
484 
485 NET_EXPORT inline std::ostream& operator<<(
486     std::ostream& os,
487     const CanonicalCookie::CookieInclusionStatus status) {
488   return os << status.GetDebugString();
489 }
490 
491 // These enable us to pass along a list of excluded cookie with the reason they
492 // were excluded
493 struct CookieWithStatus {
494   CanonicalCookie cookie;
495   CanonicalCookie::CookieInclusionStatus status;
496 };
497 
498 // Used to pass excluded cookie information when it's possible that the
499 // canonical cookie object may not be available.
500 struct NET_EXPORT CookieAndLineWithStatus {
501   CookieAndLineWithStatus();
502   CookieAndLineWithStatus(base::Optional<CanonicalCookie> cookie,
503                           std::string cookie_string,
504                           CanonicalCookie::CookieInclusionStatus status);
505   CookieAndLineWithStatus(
506       const CookieAndLineWithStatus& cookie_and_line_with_status);
507 
508   CookieAndLineWithStatus& operator=(
509       const CookieAndLineWithStatus& cookie_and_line_with_status);
510 
511   CookieAndLineWithStatus(
512       CookieAndLineWithStatus&& cookie_and_line_with_status);
513 
514   ~CookieAndLineWithStatus();
515 
516   base::Optional<CanonicalCookie> cookie;
517   std::string cookie_string;
518   CanonicalCookie::CookieInclusionStatus status;
519 };
520 
521 }  // namespace net
522 
523 #endif  // NET_COOKIES_CANONICAL_COOKIE_H_
524