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_access_result.h"
18 #include "net/cookies/cookie_constants.h"
19 #include "net/cookies/cookie_inclusion_status.h"
20 #include "net/cookies/cookie_options.h"
21 #include "url/third_party/mozilla/url_parse.h"
22 
23 class GURL;
24 
25 namespace net {
26 
27 class ParsedCookie;
28 class CanonicalCookie;
29 
30 struct CookieWithAccessResult;
31 struct CookieAndLineWithAccessResult;
32 
33 using CookieList = std::vector<CanonicalCookie>;
34 using CookieAndLineAccessResultList =
35     std::vector<CookieAndLineWithAccessResult>;
36 using CookieAccessResultList = std::vector<CookieWithAccessResult>;
37 
38 class NET_EXPORT CanonicalCookie {
39  public:
40   using UniqueCookieKey = std::tuple<std::string, std::string, std::string>;
41 
42   CanonicalCookie();
43   CanonicalCookie(const CanonicalCookie& other);
44 
45   // This constructor does not validate or canonicalize their inputs;
46   // the resulting CanonicalCookies should not be relied on to be canonical
47   // unless the caller has done appropriate validation and canonicalization
48   // themselves.
49   // NOTE: Prefer using CreateSanitizedCookie() over directly using this
50   // constructor.
51   CanonicalCookie(const std::string& name,
52                   const std::string& value,
53                   const std::string& domain,
54                   const std::string& path,
55                   const base::Time& creation,
56                   const base::Time& expiration,
57                   const base::Time& last_access,
58                   bool secure,
59                   bool httponly,
60                   CookieSameSite same_site,
61                   CookiePriority priority,
62                   bool same_party,
63                   CookieSourceScheme scheme_secure = CookieSourceScheme::kUnset,
64                   int source_port = url::PORT_UNSPECIFIED);
65 
66   ~CanonicalCookie();
67 
68   // Supports the default copy constructor.
69 
70   // Creates a new |CanonicalCookie| from the |cookie_line| and the
71   // |creation_time|.  Canonicalizes inputs.  May return nullptr if
72   // an attribute value is invalid.  |url| must be valid.  |creation_time| may
73   // not be null. Sets optional |status| to the relevant CookieInclusionStatus
74   // if provided.  |server_time| indicates what the server sending us the Cookie
75   // thought the current time was when the cookie was produced.  This is used to
76   // adjust for clock skew between server and host.
77   //
78   // SameSite and HttpOnly related parameters are not checked here,
79   // so creation of CanonicalCookies with e.g. SameSite=Strict from a cross-site
80   // context is allowed. Create() also does not check whether |url| has a secure
81   // scheme if attempting to create a Secure cookie. The Secure, SameSite, and
82   // HttpOnly related parameters should be checked when setting the cookie in
83   // the CookieStore.
84   //
85   // If a cookie is returned, |cookie->IsCanonical()| will be true.
86   static std::unique_ptr<CanonicalCookie> Create(
87       const GURL& url,
88       const std::string& cookie_line,
89       const base::Time& creation_time,
90       base::Optional<base::Time> server_time,
91       CookieInclusionStatus* status = nullptr);
92 
93   // Create a canonical cookie based on sanitizing the passed inputs in the
94   // context of the passed URL.  Returns a null unique pointer if the inputs
95   // cannot be sanitized.  If a cookie is created, |cookie->IsCanonical()|
96   // will be true.
97   static std::unique_ptr<CanonicalCookie> CreateSanitizedCookie(
98       const GURL& url,
99       const std::string& name,
100       const std::string& value,
101       const std::string& domain,
102       const std::string& path,
103       base::Time creation_time,
104       base::Time expiration_time,
105       base::Time last_access_time,
106       bool secure,
107       bool http_only,
108       CookieSameSite same_site,
109       CookiePriority priority,
110       bool same_party);
111 
112   // FromStorage is a factory method which is meant for creating a new
113   // CanonicalCookie using properties of a previously existing cookie
114   // that was already ingested into the cookie store.
115   // This should NOT be used to create a new CanonicalCookie that was not
116   // already in the store.
117   // Returns nullptr if the resulting cookie is not canonical,
118   // i.e. cc->IsCanonical() returns false.
119   static std::unique_ptr<CanonicalCookie> FromStorage(
120       const std::string& name,
121       const std::string& value,
122       const std::string& domain,
123       const std::string& path,
124       const base::Time& creation,
125       const base::Time& expiration,
126       const base::Time& last_access,
127       bool secure,
128       bool httponly,
129       CookieSameSite same_site,
130       CookiePriority priority,
131       bool same_party,
132       CookieSourceScheme source_scheme,
133       int source_port);
134 
Name()135   const std::string& Name() const { return name_; }
Value()136   const std::string& Value() const { return value_; }
137   // We represent the cookie's host-only-flag as the absence of a leading dot in
138   // Domain(). See IsDomainCookie() and IsHostCookie() below.
139   // If you want the "cookie's domain" as described in RFC 6265bis, use
140   // DomainWithoutDot().
Domain()141   const std::string& Domain() const { return domain_; }
Path()142   const std::string& Path() const { return path_; }
CreationDate()143   const base::Time& CreationDate() const { return creation_date_; }
LastAccessDate()144   const base::Time& LastAccessDate() const { return last_access_date_; }
IsPersistent()145   bool IsPersistent() const { return !expiry_date_.is_null(); }
ExpiryDate()146   const base::Time& ExpiryDate() const { return expiry_date_; }
IsSecure()147   bool IsSecure() const { return secure_; }
IsHttpOnly()148   bool IsHttpOnly() const { return httponly_; }
SameSite()149   CookieSameSite SameSite() const { return same_site_; }
Priority()150   CookiePriority Priority() const { return priority_; }
IsSameParty()151   bool IsSameParty() const { return same_party_; }
152   // Returns an enum indicating the source scheme that set this cookie. This is
153   // not part of the cookie spec but is being used to collect metrics for a
154   // potential change to the cookie spec.
SourceScheme()155   CookieSourceScheme SourceScheme() const { return source_scheme_; }
156   // Returns the port of the origin that originally set this cookie (the
157   // source port). This is not part of the cookie spec but is being used to
158   // collect metrics for a potential change to the cookie spec.
SourcePort()159   int SourcePort() const { return source_port_; }
IsDomainCookie()160   bool IsDomainCookie() const {
161     return !domain_.empty() && domain_[0] == '.'; }
IsHostCookie()162   bool IsHostCookie() const { return !IsDomainCookie(); }
163 
164   // Returns the cookie's domain, with the leading dot removed, if present.
165   // This corresponds to the "cookie's domain" as described in RFC 6265bis.
166   std::string DomainWithoutDot() const;
167 
IsExpired(const base::Time & current)168   bool IsExpired(const base::Time& current) const {
169     return !expiry_date_.is_null() && current >= expiry_date_;
170   }
171 
172   // Are the cookies considered equivalent in the eyes of RFC 2965.
173   // The RFC says that name must match (case-sensitive), domain must
174   // match (case insensitive), and path must match (case sensitive).
175   // For the case insensitive domain compare, we rely on the domain
176   // having been canonicalized (in
177   // GetCookieDomainWithString->CanonicalizeHost).
IsEquivalent(const CanonicalCookie & ecc)178   bool IsEquivalent(const CanonicalCookie& ecc) const {
179     // It seems like it would make sense to take secure, httponly, and samesite
180     // into account, but the RFC doesn't specify this.
181     // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForKey().
182     return (name_ == ecc.Name() && domain_ == ecc.Domain()
183             && path_ == ecc.Path());
184   }
185 
186   // Returns a key such that two cookies with the same UniqueKey() are
187   // guaranteed to be equivalent in the sense of IsEquivalent().
UniqueKey()188   UniqueCookieKey UniqueKey() const {
189     return std::make_tuple(name_, domain_, path_);
190   }
191 
192   // Checks a looser set of equivalency rules than 'IsEquivalent()' in order
193   // to support the stricter 'Secure' behaviors specified in Step 12 of
194   // https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05#section-5.4
195   // which originated from the proposal in
196   // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone#section-3
197   //
198   // Returns 'true' if this cookie's name matches |secure_cookie|, and this
199   // cookie is a domain-match for |secure_cookie| (or vice versa), and
200   // |secure_cookie|'s path is "on" this cookie's path (as per 'IsOnPath()').
201   //
202   // Note that while the domain-match cuts both ways (e.g. 'example.com'
203   // matches 'www.example.com' in either direction), the path-match is
204   // unidirectional (e.g. '/login/en' matches '/login' and '/', but
205   // '/login' and '/' do not match '/login/en').
206   //
207   // Conceptually:
208   // If new_cookie.IsEquivalentForSecureCookieMatching(secure_cookie) is true,
209   // this means that new_cookie would "shadow" secure_cookie: they would would
210   // be indistinguishable when serialized into a Cookie header. This is
211   // important because, if an attacker is attempting to set new_cookie, it
212   // should not be allowed to mislead the server into using new_cookie's value
213   // instead of secure_cookie's.
214   //
215   // The reason for the asymmetric path comparison ("cookie1=bad; path=/a/b"
216   // from an insecure source is not allowed if "cookie1=good; secure; path=/a"
217   // exists, but "cookie2=bad; path=/a" from an insecure source is allowed if
218   // "cookie2=good; secure; path=/a/b" exists) is because cookies in the Cookie
219   // header are serialized with longer path first. (See CookieSorter in
220   // cookie_monster.cc.) That is, they would be serialized as "Cookie:
221   // cookie1=bad; cookie1=good" in one case, and "Cookie: cookie2=good;
222   // cookie2=bad" in the other case. The first scenario is not allowed because
223   // the attacker injects the bad value, whereas the second scenario is ok
224   // because the good value is still listed first.
225   bool IsEquivalentForSecureCookieMatching(
226       const CanonicalCookie& secure_cookie) const;
227 
SetSourceScheme(CookieSourceScheme source_scheme)228   void SetSourceScheme(CookieSourceScheme source_scheme) {
229     source_scheme_ = source_scheme;
230   }
231 
232   // Set the source port value. Performs a range check and sets the port to
233   // url::PORT_INVALID if value isn't in [0,65535] or url::PORT_UNSPECIFIED.
234   void SetSourcePort(int port);
235 
SetLastAccessDate(const base::Time & date)236   void SetLastAccessDate(const base::Time& date) {
237     last_access_date_ = date;
238   }
SetCreationDate(const base::Time & date)239   void SetCreationDate(const base::Time& date) { creation_date_ = date; }
240 
241   // Returns true if the given |url_path| path-matches this cookie's cookie-path
242   // as described in section 5.1.4 in RFC 6265. This returns true if |path_| and
243   // |url_path| are identical, or if |url_path| is a subdirectory of |path_|.
244   bool IsOnPath(const std::string& url_path) const;
245 
246   // This returns true if this cookie's |domain_| indicates that it can be
247   // accessed by |host|.
248   //
249   // In the case where |domain_| has no leading dot, this is a host cookie and
250   // will only domain match if |host| is identical to |domain_|.
251   //
252   // In the case where |domain_| has a leading dot, this is a domain cookie. It
253   // will match |host| if |domain_| is a suffix of |host|, or if |domain_| is
254   // exactly equal to |host| plus a leading dot.
255   //
256   // Note that this isn't quite the same as the "domain-match" algorithm in RFC
257   // 6265bis, since our implementation uses the presence of a leading dot in the
258   // |domain_| string in place of the spec's host-only-flag. That is, if
259   // |domain_| has no leading dot, then we only consider it matching if |host|
260   // is identical (which reflects the intended behavior when the cookie has a
261   // host-only-flag), whereas the RFC also treats them as domain-matching if
262   // |domain_| is a subdomain of |host|.
263   bool IsDomainMatch(const std::string& host) const;
264 
265   // Returns if the cookie should be included (and if not, why) for the given
266   // request |url| using the CookieInclusionStatus enum. HTTP only cookies can
267   // be filter by using appropriate cookie |options|. PLEASE NOTE that this
268   // method does not check whether a cookie is expired or not!
269   CookieAccessResult IncludeForRequestURL(
270       const GURL& url,
271       const CookieOptions& options,
272       CookieAccessSemantics access_semantics =
273           CookieAccessSemantics::UNKNOWN) const;
274 
275   // Returns if the cookie with given attributes can be set in context described
276   // by |options|, and if no, describes why.
277   // WARNING: this does not cover checking whether secure cookies are set in
278   // a secure schema, since whether the schema is secure isn't part of
279   // |options|.
280   CookieAccessResult IsSetPermittedInContext(
281       const CookieOptions& options,
282       CookieAccessSemantics access_semantics =
283           CookieAccessSemantics::UNKNOWN) const;
284 
285   // Overload that updates an existing |status| rather than returning a new one.
286   void IsSetPermittedInContext(const CookieOptions& options,
287                                CookieAccessSemantics access_semantics,
288                                CookieAccessResult* access_result) const;
289 
290   std::string DebugString() const;
291 
292   static std::string CanonPathWithString(const GURL& url,
293                                          const std::string& path_string);
294 
295   // Returns a "null" time if expiration was unspecified or invalid.
296   static base::Time CanonExpiration(const ParsedCookie& pc,
297                                     const base::Time& current,
298                                     const base::Time& server_time);
299 
300   // Cookie ordering methods.
301 
302   // Returns true if the cookie is less than |other|, considering only name,
303   // domain and path. In particular, two equivalent cookies (see IsEquivalent())
304   // are identical for PartialCompare().
305   bool PartialCompare(const CanonicalCookie& other) const;
306 
307   // Return whether this object is a valid CanonicalCookie().  Invalid
308   // cookies may be constructed by the detailed constructor.
309   // A cookie is considered canonical if-and-only-if:
310   // * It can be created by CanonicalCookie::Create, or
311   // * It is identical to a cookie created by CanonicalCookie::Create except
312   //   that the creation time is null, or
313   // * It can be derived from a cookie created by CanonicalCookie::Create by
314   //   entry into and retrieval from a cookie store (specifically, this means
315   //   by the setting of an creation time in place of a null creation time, and
316   //   the setting of a last access time).
317   // An additional requirement on a CanonicalCookie is that if the last
318   // access time is non-null, the creation time must also be non-null and
319   // greater than the last access time.
320   bool IsCanonical() const;
321 
322   // Returns whether the effective SameSite mode is SameSite=None (i.e. no
323   // SameSite restrictions).
324   bool IsEffectivelySameSiteNone(CookieAccessSemantics access_semantics =
325                                      CookieAccessSemantics::UNKNOWN) const;
326 
327   CookieEffectiveSameSite GetEffectiveSameSiteForTesting(
328       CookieAccessSemantics access_semantics =
329           CookieAccessSemantics::UNKNOWN) const;
330 
331   // Returns the cookie line (e.g. "cookie1=value1; cookie2=value2") represented
332   // by |cookies|. The string is built in the same order as the given list.
333   static std::string BuildCookieLine(const CookieList& cookies);
334 
335   // Same as above but takes a CookieAccessResultList
336   // (ignores the access result).
337   static std::string BuildCookieLine(const CookieAccessResultList& cookies);
338 
339  private:
340   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms);
341 
342   // The special cookie prefixes as defined in
343   // https://tools.ietf.org/html/draft-west-cookie-prefixes
344   //
345   // This enum is being histogrammed; do not reorder or remove values.
346   enum CookiePrefix {
347     COOKIE_PREFIX_NONE = 0,
348     COOKIE_PREFIX_SECURE,
349     COOKIE_PREFIX_HOST,
350     COOKIE_PREFIX_LAST
351   };
352 
353   // Returns the CookiePrefix (or COOKIE_PREFIX_NONE if none) that
354   // applies to the given cookie |name|.
355   static CookiePrefix GetCookiePrefix(const std::string& name);
356   // Records histograms to measure how often cookie prefixes appear in
357   // the wild and how often they would be blocked.
358   static void RecordCookiePrefixMetrics(CookiePrefix prefix,
359                                         bool is_cookie_valid);
360   // Returns true if a prefixed cookie does not violate any of the rules
361   // for that cookie.
362   static bool IsCookiePrefixValid(CookiePrefix prefix,
363                                   const GURL& url,
364                                   const ParsedCookie& parsed_cookie);
365   static bool IsCookiePrefixValid(CookiePrefix prefix,
366                                   const GURL& url,
367                                   bool secure,
368                                   const std::string& domain,
369                                   const std::string& path);
370 
371   // Returns the effective SameSite mode to apply to this cookie. Depends on the
372   // value of the given SameSite attribute and whether the
373   // SameSiteByDefaultCookies feature is enabled, as well as the access
374   // semantics of the cookie.
375   // Note: If you are converting to a different representation of a cookie, you
376   // probably want to use SameSite() instead of this method. Otherwise, if you
377   // are considering using this method, consider whether you should use
378   // IncludeForRequestURL() or IsSetPermittedInContext() instead of doing the
379   // SameSite computation yourself.
380   CookieEffectiveSameSite GetEffectiveSameSite(
381       CookieAccessSemantics access_semantics) const;
382 
383   // Returns whether the cookie was created at most |age_threshold| ago.
384   bool IsRecentlyCreated(base::TimeDelta age_threshold) const;
385 
386   // Returns true iff the cookie does not violate any rules associated with
387   // creating a cookie with the SameParty attribute. In particular, if a cookie
388   // has SameParty, then it must be Secure and must not be SameSite=Strict.
389   static bool IsCookieSamePartyValid(const ParsedCookie& parsed_cookie);
390   static bool IsCookieSamePartyValid(bool is_same_party,
391                                      bool is_secure,
392                                      CookieSameSite same_site);
393 
394   // Keep defaults here in sync with
395   // services/network/public/interfaces/cookie_manager.mojom.
396   std::string name_;
397   std::string value_;
398   std::string domain_;
399   std::string path_;
400   base::Time creation_date_;
401   base::Time expiry_date_;
402   base::Time last_access_date_;
403   bool secure_{false};
404   bool httponly_{false};
405   CookieSameSite same_site_{CookieSameSite::NO_RESTRICTION};
406   CookiePriority priority_{COOKIE_PRIORITY_MEDIUM};
407   bool same_party_{false};
408   CookieSourceScheme source_scheme_{CookieSourceScheme::kUnset};
409   // This can be [0,65535], PORT_UNSPECIFIED, or PORT_INVALID.
410   // PORT_UNSPECIFIED is used for cookies which already existed in the cookie
411   // store prior to this change and therefore their port is unknown.
412   // PORT_INVALID is an error for when an out of range port is provided.
413   int source_port_{url::PORT_UNSPECIFIED};
414 };
415 
416 // Used to pass excluded cookie information when it's possible that the
417 // canonical cookie object may not be available.
418 struct NET_EXPORT CookieAndLineWithAccessResult {
419   CookieAndLineWithAccessResult();
420   CookieAndLineWithAccessResult(base::Optional<CanonicalCookie> cookie,
421                                 std::string cookie_string,
422                                 CookieAccessResult access_result);
423   CookieAndLineWithAccessResult(
424       const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
425 
426   CookieAndLineWithAccessResult& operator=(
427       const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
428 
429   CookieAndLineWithAccessResult(
430       CookieAndLineWithAccessResult&& cookie_and_line_with_access_result);
431 
432   ~CookieAndLineWithAccessResult();
433 
434   base::Optional<CanonicalCookie> cookie;
435   std::string cookie_string;
436   CookieAccessResult access_result;
437 };
438 
439 struct CookieWithAccessResult {
440   CanonicalCookie cookie;
441   CookieAccessResult access_result;
442 };
443 
444 }  // namespace net
445 
446 #endif  // NET_COOKIES_CANONICAL_COOKIE_H_
447