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