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