xref: /aosp_15_r20/external/cronet/net/cookies/canonical_cookie.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 #ifndef NET_COOKIES_CANONICAL_COOKIE_H_
6 #define NET_COOKIES_CANONICAL_COOKIE_H_
7 
8 #include <memory>
9 #include <optional>
10 #include <string>
11 #include <string_view>
12 #include <vector>
13 
14 #include "base/feature_list.h"
15 #include "base/gtest_prod_util.h"
16 #include "base/time/time.h"
17 #include "base/types/pass_key.h"
18 #include "net/base/features.h"
19 #include "net/base/net_export.h"
20 #include "net/cookies/cookie_access_params.h"
21 #include "net/cookies/cookie_access_result.h"
22 #include "net/cookies/cookie_base.h"
23 #include "net/cookies/cookie_constants.h"
24 #include "net/cookies/cookie_inclusion_status.h"
25 #include "net/cookies/cookie_options.h"
26 #include "net/cookies/cookie_partition_key.h"
27 #include "url/third_party/mozilla/url_parse.h"
28 
29 class GURL;
30 
31 namespace net {
32 
33 class ParsedCookie;
34 class CanonicalCookie;
35 
36 struct CookieWithAccessResult;
37 struct CookieAndLineWithAccessResult;
38 
39 using CookieList = std::vector<CanonicalCookie>;
40 using CookieAndLineAccessResultList =
41     std::vector<CookieAndLineWithAccessResult>;
42 using CookieAccessResultList = std::vector<CookieWithAccessResult>;
43 
44 // Represents a real/concrete cookie, which may be sent on requests or set by a
45 // response if the request context and attributes allow it.
46 class NET_EXPORT CanonicalCookie : public CookieBase {
47  public:
48   CanonicalCookie();
49   CanonicalCookie(const CanonicalCookie& other);
50   CanonicalCookie(CanonicalCookie&& other);
51   CanonicalCookie& operator=(const CanonicalCookie& other);
52   CanonicalCookie& operator=(CanonicalCookie&& other);
53   ~CanonicalCookie() override;
54 
55   // This constructor does not validate or canonicalize their inputs;
56   // the resulting CanonicalCookies should not be relied on to be canonical
57   // unless the caller has done appropriate validation and canonicalization
58   // themselves.
59   //
60   // NOTE: Prefer using Create, CreateSanitizedCookie, or FromStorage (depending
61   // on the use case) over directly using this constructor.
62   //
63   // NOTE: Do not add any defaults to this constructor, we want every caller to
64   // understand and choose their inputs.
65   CanonicalCookie(base::PassKey<CanonicalCookie>,
66                   std::string name,
67                   std::string value,
68                   std::string domain,
69                   std::string path,
70                   base::Time creation,
71                   base::Time expiration,
72                   base::Time last_access,
73                   base::Time last_update,
74                   bool secure,
75                   bool httponly,
76                   CookieSameSite same_site,
77                   CookiePriority priority,
78                   std::optional<CookiePartitionKey> partition_key,
79                   CookieSourceScheme scheme_secure,
80                   int source_port,
81                   CookieSourceType source_type);
82 
83   // Creates a new `CanonicalCookie` from the `cookie_line` and the
84   // `creation_time`.  Canonicalizes inputs.  May return nullptr if
85   // an attribute value is invalid.  `url` must be valid.  `creation_time` may
86   // not be null. Sets optional `status` to the relevant CookieInclusionStatus
87   // if provided.  `server_time` indicates what the server sending us the Cookie
88   // thought the current time was when the cookie was produced.  This is used to
89   // adjust for clock skew between server and host.
90   //
91   // SameSite and HttpOnly related parameters are not checked here,
92   // so creation of CanonicalCookies with e.g. SameSite=Strict from a cross-site
93   // context is allowed. Create() also does not check whether `url` has a secure
94   // scheme if attempting to create a Secure cookie. The Secure, SameSite, and
95   // HttpOnly related parameters should be checked when setting the cookie in
96   // the CookieStore.
97   //
98   // The partition_key argument only needs to be present if the cookie line
99   // contains the Partitioned attribute. If the cookie line will never contain
100   // that attribute, you should use std::nullopt to indicate you intend to
101   // always create an unpartitioned cookie. If partition_key has a value but the
102   // cookie line does not contain the Partitioned attribute, the resulting
103   // cookie will be unpartitioned. If the partition_key is null, then the cookie
104   // will be unpartitioned even when the cookie line has the Partitioned
105   // attribute.
106   //
107   // The `block_truncated` argument indicates whether the '\0', '\n', and '\r'
108   // characters should cause the cookie to fail to be created if present
109   // (instead of truncating `cookie_line` at the first occurrence).
110   //
111   // If a cookie is returned, `cookie->IsCanonical()` will be true.
112   //
113   // NOTE: Do not add any defaults to this constructor, we want every caller to
114   // understand and choose their inputs.
115   static std::unique_ptr<CanonicalCookie> Create(
116       const GURL& url,
117       const std::string& cookie_line,
118       const base::Time& creation_time,
119       std::optional<base::Time> server_time,
120       std::optional<CookiePartitionKey> cookie_partition_key,
121       bool block_truncated,
122       CookieSourceType source_type,
123       CookieInclusionStatus* status);
124 
125   // Create a canonical cookie based on sanitizing the passed inputs in the
126   // context of the passed URL.  Returns a null unique pointer if the inputs
127   // cannot be sanitized.  If `status` is provided it will have any relevant
128   // CookieInclusionStatus rejection reasons set. If a cookie is created,
129   // `cookie->IsCanonical()` will be true.
130   //
131   // NOTE: Do not add any defaults to this constructor, we want every caller to
132   // understand and choose their inputs.
133   static std::unique_ptr<CanonicalCookie> CreateSanitizedCookie(
134       const GURL& url,
135       const std::string& name,
136       const std::string& value,
137       const std::string& domain,
138       const std::string& path,
139       base::Time creation_time,
140       base::Time expiration_time,
141       base::Time last_access_time,
142       bool secure,
143       bool http_only,
144       CookieSameSite same_site,
145       CookiePriority priority,
146       std::optional<CookiePartitionKey> partition_key,
147       CookieInclusionStatus* status);
148 
149   // FromStorage is a factory method which is meant for creating a new
150   // CanonicalCookie using properties of a previously existing cookie
151   // that was already ingested into the cookie store.
152   // This should NOT be used to create a new CanonicalCookie that was not
153   // already in the store.
154   // Returns nullptr if the resulting cookie is not canonical,
155   // i.e. cc->IsCanonical() returns false.
156   //
157   // NOTE: Do not add any defaults to this constructor, we want every caller to
158   // understand and choose their inputs.
159   static std::unique_ptr<CanonicalCookie> FromStorage(
160       std::string name,
161       std::string value,
162       std::string domain,
163       std::string path,
164       base::Time creation,
165       base::Time expiration,
166       base::Time last_access,
167       base::Time last_update,
168       bool secure,
169       bool httponly,
170       CookieSameSite same_site,
171       CookiePriority priority,
172       std::optional<CookiePartitionKey> partition_key,
173       CookieSourceScheme source_scheme,
174       int source_port,
175       CookieSourceType source_type);
176 
177   // Create a CanonicalCookie that is not guaranteed to actually be Canonical
178   // for tests. Use this only if you want to bypass parameter validation to
179   // create a cookie that otherwise shouldn't be possible to store.
180   static std::unique_ptr<CanonicalCookie> CreateUnsafeCookieForTesting(
181       const std::string& name,
182       const std::string& value,
183       const std::string& domain,
184       const std::string& path,
185       const base::Time& creation,
186       const base::Time& expiration,
187       const base::Time& last_access,
188       const base::Time& last_update,
189       bool secure,
190       bool httponly,
191       CookieSameSite same_site,
192       CookiePriority priority,
193       std::optional<CookiePartitionKey> partition_key = std::nullopt,
194       CookieSourceScheme scheme_secure = CookieSourceScheme::kUnset,
195       int source_port = url::PORT_UNSPECIFIED,
196       CookieSourceType source_type = CookieSourceType::kUnknown);
197 
198   // Like Create but with some more friendly defaults for use in tests.
199   static std::unique_ptr<CanonicalCookie> CreateForTesting(
200       const GURL& url,
201       const std::string& cookie_line,
202       const base::Time& creation_time,
203       std::optional<base::Time> server_time = std::nullopt,
204       std::optional<CookiePartitionKey> cookie_partition_key = std::nullopt,
205       bool block_truncated = true,
206       CookieSourceType source_type = CookieSourceType::kUnknown,
207       CookieInclusionStatus* status = nullptr);
208 
209   bool operator<(const CanonicalCookie& other) const {
210     // Use the cookie properties that uniquely identify a cookie to determine
211     // ordering.
212     return UniqueKey() < other.UniqueKey();
213   }
214 
215   bool operator==(const CanonicalCookie& other) const {
216     return IsEquivalent(other);
217   }
218 
219   // See CookieBase for other accessors.
Value()220   const std::string& Value() const { return value_; }
ExpiryDate()221   const base::Time& ExpiryDate() const { return expiry_date_; }
LastAccessDate()222   const base::Time& LastAccessDate() const { return last_access_date_; }
LastUpdateDate()223   const base::Time& LastUpdateDate() const { return last_update_date_; }
IsPersistent()224   bool IsPersistent() const { return !expiry_date_.is_null(); }
Priority()225   CookiePriority Priority() const { return priority_; }
SourceType()226   CookieSourceType SourceType() const { return source_type_; }
227 
IsExpired(const base::Time & current)228   bool IsExpired(const base::Time& current) const {
229     return !expiry_date_.is_null() && current >= expiry_date_;
230   }
231 
232   // Are the cookies considered equivalent in the eyes of RFC 2965.
233   // The RFC says that name must match (case-sensitive), domain must
234   // match (case insensitive), and path must match (case sensitive).
235   // For the case insensitive domain compare, we rely on the domain
236   // having been canonicalized (in
237   // GetCookieDomainWithString->CanonicalizeHost).
238   // If partitioned cookies are enabled, then we check the cookies have the same
239   // partition key in addition to the checks in RFC 2965.
240   //
241   // To support origin-bound cookies the check will also include the source
242   // scheme and/or port depending on the state of the associated feature.
243   // Additionally, domain cookies get a slightly different check which does not
244   // include the source port.
IsEquivalent(const CanonicalCookie & ecc)245   bool IsEquivalent(const CanonicalCookie& ecc) const {
246     // It seems like it would make sense to take secure, httponly, and samesite
247     // into account, but the RFC doesn't specify this.
248     // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForKey().
249 
250     // A host cookie will never match a domain cookie or vice-versa, this is
251     // because the "host-only-flag" is encoded within the `domain` field of the
252     // respective keys. So we don't need to explicitly check if ecc is also host
253     // or domain.
254     if (IsHostCookie()) {
255       return UniqueKey() == ecc.UniqueKey();
256     }
257     // Is domain cookie
258     return UniqueDomainKey() == ecc.UniqueDomainKey();
259   }
260 
261   // Checks a looser set of equivalency rules than 'IsEquivalent()' in order
262   // to support the stricter 'Secure' behaviors specified in Step 12 of
263   // https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05#section-5.4
264   // which originated from the proposal in
265   // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone#section-3
266   //
267   // Returns 'true' if this cookie's name matches |secure_cookie|, and this
268   // cookie is a domain-match for |secure_cookie| (or vice versa), and
269   // |secure_cookie|'s path is "on" this cookie's path (as per 'IsOnPath()').
270   // If partitioned cookies are enabled, it also checks that the cookie has
271   // the same partition key as |secure_cookie|.
272   //
273   // Note that while the domain-match cuts both ways (e.g. 'example.com'
274   // matches 'www.example.com' in either direction), the path-match is
275   // unidirectional (e.g. '/login/en' matches '/login' and '/', but
276   // '/login' and '/' do not match '/login/en').
277   //
278   // Conceptually:
279   // If new_cookie.IsEquivalentForSecureCookieMatching(secure_cookie) is true,
280   // this means that new_cookie would "shadow" secure_cookie: they would would
281   // be indistinguishable when serialized into a Cookie header. This is
282   // important because, if an attacker is attempting to set new_cookie, it
283   // should not be allowed to mislead the server into using new_cookie's value
284   // instead of secure_cookie's.
285   //
286   // The reason for the asymmetric path comparison ("cookie1=bad; path=/a/b"
287   // from an insecure source is not allowed if "cookie1=good; secure; path=/a"
288   // exists, but "cookie2=bad; path=/a" from an insecure source is allowed if
289   // "cookie2=good; secure; path=/a/b" exists) is because cookies in the Cookie
290   // header are serialized with longer path first. (See CookieSorter in
291   // cookie_monster.cc.) That is, they would be serialized as "Cookie:
292   // cookie1=bad; cookie1=good" in one case, and "Cookie: cookie2=good;
293   // cookie2=bad" in the other case. The first scenario is not allowed because
294   // the attacker injects the bad value, whereas the second scenario is ok
295   // because the good value is still listed first.
296   bool IsEquivalentForSecureCookieMatching(
297       const CanonicalCookie& secure_cookie) const;
298 
299   // Returns true if the |other| cookie's data members (instance variables)
300   // match, for comparing cookies in collections.
301   bool HasEquivalentDataMembers(const CanonicalCookie& other) const;
302 
303   // Similar to operator<, but considers all data members.
304   bool DataMembersPrecede(const CanonicalCookie& other) const;
305 
SetLastAccessDate(const base::Time & date)306   void SetLastAccessDate(const base::Time& date) {
307     last_access_date_ = date;
308   }
309 
310   std::string DebugString() const;
311 
312   // Returns the canonical path based on the specified url and path attribute
313   // value. Note that this method does not enforce character set or size
314   // checks on `path_string`.
315   static std::string CanonPathWithString(const GURL& url,
316                                          const std::string& path_string);
317 
318   // Returns a "null" time if expiration was unspecified or invalid.
319   static base::Time ParseExpiration(const ParsedCookie& pc,
320                                     const base::Time& current,
321                                     const base::Time& server_time);
322 
323   // Per rfc6265bis the maximum expiry date is no further than 400 days in the
324   // future.
325   static base::Time ValidateAndAdjustExpiryDate(const base::Time& expiry_date,
326                                                 const base::Time& creation_date,
327                                                 net::CookieSourceScheme scheme);
328 
329   // Cookie ordering methods.
330 
331   // Returns true if the cookie is less than |other|, considering only name,
332   // domain and path. In particular, two equivalent cookies (see IsEquivalent())
333   // are identical for PartialCompare().
334   bool PartialCompare(const CanonicalCookie& other) const;
335 
336   // Return whether this object is a valid CanonicalCookie().  Invalid
337   // cookies may be constructed by the detailed constructor.
338   // A cookie is considered canonical if-and-only-if:
339   // * It can be created by CanonicalCookie::Create, or
340   // * It is identical to a cookie created by CanonicalCookie::Create except
341   //   that the creation time is null, or
342   // * It can be derived from a cookie created by CanonicalCookie::Create by
343   //   entry into and retrieval from a cookie store (specifically, this means
344   //   by the setting of an creation time in place of a null creation time, and
345   //   the setting of a last access time).
346   // An additional requirement on a CanonicalCookie is that if the last
347   // access time is non-null, the creation time must also be non-null and
348   // greater than the last access time.
349   bool IsCanonical() const;
350 
351   // Return whether this object is a valid CanonicalCookie() when retrieving the
352   // cookie from the persistent store. Cookie that exist in the persistent store
353   // may have been created before more recent changes to the definition of
354   // "canonical". To ease the transition to the new definitions, and to prevent
355   // users from having their cookies deleted, this function supports the older
356   // definition of canonical. This function is intended to be temporary because
357   // as the number of older cookies (which are non-compliant with the newer
358   // definition of canonical) decay toward zero it can eventually be replaced
359   // by `IsCanonical()` to enforce the newer definition of canonical.
360   //
361   // A cookie is considered canonical by this function if-and-only-if:
362   // * It is considered canonical by IsCanonical()
363   // * TODO(crbug.com/1244172): Add exceptions once IsCanonical() starts
364   // enforcing them.
365   bool IsCanonicalForFromStorage() const;
366 
367   // Returns whether the effective SameSite mode is SameSite=None (i.e. no
368   // SameSite restrictions).
369   bool IsEffectivelySameSiteNone(CookieAccessSemantics access_semantics =
370                                      CookieAccessSemantics::UNKNOWN) const;
371 
372   CookieEffectiveSameSite GetEffectiveSameSiteForTesting(
373       CookieAccessSemantics access_semantics =
374           CookieAccessSemantics::UNKNOWN) const;
375 
376   // Returns the cookie line (e.g. "cookie1=value1; cookie2=value2") represented
377   // by |cookies|. The string is built in the same order as the given list.
378   static std::string BuildCookieLine(const CookieList& cookies);
379 
380   // Same as above but takes a CookieAccessResultList
381   // (ignores the access result).
382   static std::string BuildCookieLine(const CookieAccessResultList& cookies);
383 
384   // Takes a single CanonicalCookie and returns a cookie line containing the
385   // attributes of |cookie| formatted like a http set cookie header.
386   // (e.g. "cookie1=value1; domain=abc.com; path=/; secure").
387   static std::string BuildCookieAttributesLine(const CanonicalCookie& cookie);
388 
389  private:
390   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest,
391                            TestGetAndAdjustPortForTrustworthyUrls);
392   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms);
393   FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestHasHiddenPrefixName);
394 
395   // The special cookie prefixes as defined in
396   // https://tools.ietf.org/html/draft-west-cookie-prefixes
397   //
398   // This enum is being histogrammed; do not reorder or remove values.
399   enum CookiePrefix {
400     COOKIE_PREFIX_NONE = 0,
401     COOKIE_PREFIX_SECURE,
402     COOKIE_PREFIX_HOST,
403     COOKIE_PREFIX_LAST
404   };
405 
406   // Returns the CookiePrefix (or COOKIE_PREFIX_NONE if none) that
407   // applies to the given cookie |name|.
GetCookiePrefix(const std::string & name)408   static CookiePrefix GetCookiePrefix(const std::string& name) {
409     return GetCookiePrefix(name,
410                            base::FeatureList::IsEnabled(
411                                net::features::kCaseInsensitiveCookiePrefix));
412   }
413 
414   // Returns the CookiePrefix (or COOKIE_PREFIX_NONE if none) that
415   // applies to the given cookie |name|. If `check_insensitively` is true then
416   // the string comparison will be performed case insensitively.
417   static CookiePrefix GetCookiePrefix(const std::string& name,
418                                       bool check_insensitively);
419   // Records histograms to measure how often cookie prefixes appear in
420   // the wild and how often they would be blocked.
421   static void RecordCookiePrefixMetrics(CookiePrefix prefix_case_sensitive,
422                                         CookiePrefix prefix_case_insensitive,
423                                         bool is_insensitive_prefix_valid);
424   // Returns true if a prefixed cookie does not violate any of the rules
425   // for that cookie.
426   static bool IsCookiePrefixValid(CookiePrefix prefix,
427                                   const GURL& url,
428                                   const ParsedCookie& parsed_cookie);
429   static bool IsCookiePrefixValid(CookiePrefix prefix,
430                                   const GURL& url,
431                                   bool secure,
432                                   const std::string& domain,
433                                   const std::string& path);
434 
435   // Returns the appropriate port value for the given `source_url` depending on
436   // if the url is considered trustworthy or not.
437   //
438   // This function normally returns source_url.EffectiveIntPort(), but it can
439   // return a different port value if:
440   // * `source_url`'s scheme isn't cryptographically secure
441   // * `url_is_trustworthy` is true
442   // * `source_url`'s port is the default port for the scheme i.e.: 80
443   // If all these conditions are true then the returned value will be 443 to
444   // indicate that we're treating `source_url` as if it was secure.
445   static int GetAndAdjustPortForTrustworthyUrls(const GURL& source_url,
446                                                 bool url_is_trustworthy);
447 
448   // Checks for values that could be misinterpreted as a cookie name prefix.
449   static bool HasHiddenPrefixName(const std::string_view cookie_value);
450 
451   // Returns true iff the cookie is a partitioned cookie with a nonce or that
452   // does not violate the semantics of the Partitioned attribute:
453   // - Must have the Secure attribute OR the cookie partition contains a nonce.
454   static bool IsCookiePartitionedValid(const GURL& url,
455                                        const ParsedCookie& parsed_cookie,
456                                        bool partition_has_nonce);
457   static bool IsCookiePartitionedValid(const GURL& url,
458                                        bool secure,
459                                        bool is_partitioned,
460                                        bool partition_has_nonce);
461 
462   // CookieBase:
463   void PostIncludeForRequestURL(
464       const CookieAccessResult& access_result,
465       const CookieOptions& options_used,
466       CookieOptions::SameSiteCookieContext::ContextType
467           cookie_inclusion_context_used) const override;
468   void PostIsSetPermittedInContext(
469       const CookieAccessResult& access_result,
470       const CookieOptions& options_used) const override;
471 
472   // Keep defaults here in sync with
473   // services/network/public/interfaces/cookie_manager.mojom.
474   // These are the fields specific to CanonicalCookie. See CookieBase for other
475   // data fields.
476   std::string value_;
477   base::Time expiry_date_;
478   base::Time last_access_date_;
479   base::Time last_update_date_;
480   CookiePriority priority_{COOKIE_PRIORITY_MEDIUM};
481   CookieSourceType source_type_{CookieSourceType::kUnknown};
482 };
483 
484 // Used to pass excluded cookie information when it's possible that the
485 // canonical cookie object may not be available.
486 struct NET_EXPORT CookieAndLineWithAccessResult {
487   CookieAndLineWithAccessResult();
488   CookieAndLineWithAccessResult(std::optional<CanonicalCookie> cookie,
489                                 std::string cookie_string,
490                                 CookieAccessResult access_result);
491   CookieAndLineWithAccessResult(
492       const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
493 
494   CookieAndLineWithAccessResult& operator=(
495       const CookieAndLineWithAccessResult& cookie_and_line_with_access_result);
496 
497   CookieAndLineWithAccessResult(
498       CookieAndLineWithAccessResult&& cookie_and_line_with_access_result);
499 
500   ~CookieAndLineWithAccessResult();
501 
502   std::optional<CanonicalCookie> cookie;
503   std::string cookie_string;
504   CookieAccessResult access_result;
505 };
506 
507 struct CookieWithAccessResult {
508   CanonicalCookie cookie;
509   CookieAccessResult access_result;
510 };
511 
512 // Provided to allow gtest to create more helpful error messages, instead of
513 // printing hex.
PrintTo(const CanonicalCookie & cc,std::ostream * os)514 inline void PrintTo(const CanonicalCookie& cc, std::ostream* os) {
515   *os << "{ name=" << cc.Name() << ", value=" << cc.Value() << " }";
516 }
PrintTo(const CookieWithAccessResult & cwar,std::ostream * os)517 inline void PrintTo(const CookieWithAccessResult& cwar, std::ostream* os) {
518   *os << "{ ";
519   PrintTo(cwar.cookie, os);
520   *os << ", ";
521   PrintTo(cwar.access_result, os);
522   *os << " }";
523 }
PrintTo(const CookieAndLineWithAccessResult & calwar,std::ostream * os)524 inline void PrintTo(const CookieAndLineWithAccessResult& calwar,
525                     std::ostream* os) {
526   *os << "{ ";
527   if (calwar.cookie) {
528     PrintTo(*calwar.cookie, os);
529   } else {
530     *os << "nullopt";
531   }
532   *os << ", " << calwar.cookie_string << ", ";
533   PrintTo(calwar.access_result, os);
534   *os << " }";
535 }
536 
537 }  // namespace net
538 
539 #endif  // NET_COOKIES_CANONICAL_COOKIE_H_
540