diff options
author | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-18 13:13:34 +0000 |
---|---|---|
committer | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-18 13:13:34 +0000 |
commit | 5b9bc3505d5cd23898d0bdb350f33956070cc3a6 (patch) | |
tree | 2e28070459f0324ce2dd9288e803f8aa095781fd /net/cookies | |
parent | 64c186bdf8949a84c1b1e53da12710f50e51336b (diff) | |
download | chromium_src-5b9bc3505d5cd23898d0bdb350f33956070cc3a6.zip chromium_src-5b9bc3505d5cd23898d0bdb350f33956070cc3a6.tar.gz chromium_src-5b9bc3505d5cd23898d0bdb350f33956070cc3a6.tar.bz2 |
Move CanonicalCookie into separate files
BUG=137014,112155
TEST=no
TBR=sky@chromium.org, erg@chromium.org, tony@chromium.org, eroman@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10785017
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147222 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/cookies')
-rw-r--r-- | net/cookies/canonical_cookie.cc | 380 | ||||
-rw-r--r-- | net/cookies/canonical_cookie.h | 156 | ||||
-rw-r--r-- | net/cookies/canonical_cookie_unittest.cc | 42 | ||||
-rw-r--r-- | net/cookies/cookie_monster.cc | 453 | ||||
-rw-r--r-- | net/cookies/cookie_monster.h | 140 | ||||
-rw-r--r-- | net/cookies/cookie_monster_perftest.cc | 3 | ||||
-rw-r--r-- | net/cookies/cookie_monster_store_test.cc | 59 | ||||
-rw-r--r-- | net/cookies/cookie_monster_store_test.h | 45 | ||||
-rw-r--r-- | net/cookies/cookie_monster_unittest.cc | 157 | ||||
-rw-r--r-- | net/cookies/cookie_util.cc | 124 | ||||
-rw-r--r-- | net/cookies/cookie_util.h | 4 | ||||
-rw-r--r-- | net/cookies/cookie_util_unittest.cc | 88 |
12 files changed, 877 insertions, 774 deletions
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc new file mode 100644 index 0000000..ebd2428 --- /dev/null +++ b/net/cookies/canonical_cookie.cc @@ -0,0 +1,380 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Portions of this code based on Mozilla: +// (netwerk/cookie/src/nsCookieService.cpp) +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Daniel Witte (dwitte@stanford.edu) + * Michiel van Leeuwen (mvl@exedo.nl) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "net/cookies/canonical_cookie.h" + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "googleurl/src/gurl.h" +#include "googleurl/src/url_canon.h" +#include "net/cookies/cookie_util.h" +#include "net/cookies/parsed_cookie.h" + +using base::Time; +using base::TimeDelta; + +namespace net { + +namespace { + +#if defined(ENABLE_PERSISTENT_SESSION_COOKIES) +const int kPersistentSessionCookieExpiryInDays = 14; +#endif + +// Determine the cookie domain to use for setting the specified cookie. +bool GetCookieDomain(const GURL& url, + const ParsedCookie& pc, + std::string* result) { + std::string domain_string; + if (pc.HasDomain()) + domain_string = pc.Domain(); + return cookie_util::GetCookieDomainWithString(url, domain_string, result); +} + +std::string CanonPathWithString(const GURL& url, + const std::string& path_string) { + // The RFC says the path should be a prefix of the current URL path. + // However, Mozilla allows you to set any path for compatibility with + // broken websites. We unfortunately will mimic this behavior. We try + // to be generous and accept cookies with an invalid path attribute, and + // default the path to something reasonable. + + // The path was supplied in the cookie, we'll take it. + if (!path_string.empty() && path_string[0] == '/') + return path_string; + + // The path was not supplied in the cookie or invalid, we will default + // to the current URL path. + // """Defaults to the path of the request URL that generated the + // Set-Cookie response, up to, but not including, the + // right-most /.""" + // How would this work for a cookie on /? We will include it then. + const std::string& url_path = url.path(); + + size_t idx = url_path.find_last_of('/'); + + // The cookie path was invalid or a single '/'. + if (idx == 0 || idx == std::string::npos) + return std::string("/"); + + // Return up to the rightmost '/'. + return url_path.substr(0, idx); +} + +} // namespace + +CanonicalCookie::CanonicalCookie() + : secure_(false), + httponly_(false) { + SetSessionCookieExpiryTime(); +} + +CanonicalCookie::CanonicalCookie( + const GURL& url, const std::string& name, const std::string& value, + const std::string& domain, const std::string& path, + const std::string& mac_key, const std::string& mac_algorithm, + const base::Time& creation, const base::Time& expiration, + const base::Time& last_access, bool secure, bool httponly) + : source_(GetCookieSourceFromURL(url)), + name_(name), + value_(value), + domain_(domain), + path_(path), + mac_key_(mac_key), + mac_algorithm_(mac_algorithm), + creation_date_(creation), + expiry_date_(expiration), + last_access_date_(last_access), + secure_(secure), + httponly_(httponly) { + if (expiration.is_null()) + SetSessionCookieExpiryTime(); +} + +CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc) + : source_(GetCookieSourceFromURL(url)), + name_(pc.Name()), + value_(pc.Value()), + path_(CanonPath(url, pc)), + mac_key_(pc.MACKey()), + mac_algorithm_(pc.MACAlgorithm()), + creation_date_(Time::Now()), + last_access_date_(Time()), + secure_(pc.IsSecure()), + httponly_(pc.IsHttpOnly()) { + if (pc.HasExpires()) + expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_); + else + SetSessionCookieExpiryTime(); + + // Do the best we can with the domain. + std::string cookie_domain; + std::string domain_string; + if (pc.HasDomain()) { + domain_string = pc.Domain(); + } + bool result + = cookie_util::GetCookieDomainWithString(url, domain_string, + &cookie_domain); + // Caller is responsible for passing in good arguments. + DCHECK(result); + domain_ = cookie_domain; +} + +CanonicalCookie::~CanonicalCookie() { +} + +std::string CanonicalCookie::GetCookieSourceFromURL(const GURL& url) { + if (url.SchemeIsFile()) + return url.spec(); + + url_canon::Replacements<char> replacements; + replacements.ClearPort(); + if (url.SchemeIsSecure()) + replacements.SetScheme("http", url_parse::Component(0, 4)); + + return url.GetOrigin().ReplaceComponents(replacements).spec(); +} + +// static +std::string CanonicalCookie::CanonPath(const GURL& url, + const ParsedCookie& pc) { + std::string path_string; + if (pc.HasPath()) + path_string = pc.Path(); + return CanonPathWithString(url, path_string); +} + +// static +Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc, + const Time& current, + const Time& server_time) { + // First, try the Max-Age attribute. + uint64 max_age = 0; + if (pc.HasMaxAge() && +#ifdef COMPILER_MSVC + sscanf_s( +#else + sscanf( +#endif + pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) { + return current + TimeDelta::FromSeconds(max_age); + } + + // Try the Expires attribute. + if (pc.HasExpires()) { + // Adjust for clock skew between server and host. + return current + (cookie_util::ParseCookieTime(pc.Expires()) - server_time); + } + + // Invalid or no expiration, persistent cookie. + return Time(); +} + +void CanonicalCookie::SetSessionCookieExpiryTime() { +#if defined(ENABLE_PERSISTENT_SESSION_COOKIES) + // Mobile apps can sometimes be shut down without any warning, so the session + // cookie has to be persistent and given a default expiration time. + expiry_date_ = base::Time::Now() + + base::TimeDelta::FromDays(kPersistentSessionCookieExpiryInDays); +#endif +} + +CanonicalCookie* CanonicalCookie::Create(const GURL& url, + const ParsedCookie& pc) { + if (!pc.IsValid()) { + return NULL; + } + + std::string domain_string; + if (!GetCookieDomain(url, pc, &domain_string)) { + return NULL; + } + std::string path_string = CanonPath(url, pc); + std::string mac_key = pc.HasMACKey() ? pc.MACKey() : std::string(); + std::string mac_algorithm = pc.HasMACAlgorithm() ? + pc.MACAlgorithm() : std::string(); + Time creation_time = Time::Now(); + Time expiration_time; + if (pc.HasExpires()) + expiration_time = cookie_util::ParseCookieTime(pc.Expires()); + + return (Create(url, pc.Name(), pc.Value(), domain_string, path_string, + mac_key, mac_algorithm, creation_time, expiration_time, + pc.IsSecure(), pc.IsHttpOnly())); +} + +CanonicalCookie* CanonicalCookie::Create(const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + const std::string& mac_key, + const std::string& mac_algorithm, + const base::Time& creation, + const base::Time& expiration, + bool secure, + bool http_only) { + // Expect valid attribute tokens and values, as defined by the ParsedCookie + // logic, otherwise don't create the cookie. + std::string parsed_name = ParsedCookie::ParseTokenString(name); + if (parsed_name != name) + return NULL; + std::string parsed_value = ParsedCookie::ParseValueString(value); + if (parsed_value != value) + return NULL; + + std::string parsed_domain = ParsedCookie::ParseValueString(domain); + if (parsed_domain != domain) + return NULL; + std::string cookie_domain; + if (!cookie_util::GetCookieDomainWithString(url, parsed_domain, + &cookie_domain)) { + return NULL; + } + + std::string parsed_path = ParsedCookie::ParseValueString(path); + if (parsed_path != path) + return NULL; + + std::string cookie_path = CanonPathWithString(url, parsed_path); + // Expect that the path was either not specified (empty), or is valid. + if (!parsed_path.empty() && cookie_path != parsed_path) + return NULL; + // Canonicalize path again to make sure it escapes characters as needed. + url_parse::Component path_component(0, cookie_path.length()); + url_canon::RawCanonOutputT<char> canon_path; + url_parse::Component canon_path_component; + url_canon::CanonicalizePath(cookie_path.data(), path_component, + &canon_path, &canon_path_component); + cookie_path = std::string(canon_path.data() + canon_path_component.begin, + canon_path_component.len); + + return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain, + cookie_path, mac_key, mac_algorithm, creation, + expiration, creation, secure, http_only); +} + +bool CanonicalCookie::IsOnPath(const std::string& url_path) const { + + // A zero length would be unsafe for our trailing '/' checks, and + // would also make no sense for our prefix match. The code that + // creates a CanonicalCookie should make sure the path is never zero length, + // but we double check anyway. + if (path_.empty()) + return false; + + // The Mozilla code broke this into three cases, based on if the cookie path + // was longer, the same length, or shorter than the length of the url path. + // I think the approach below is simpler. + + // Make sure the cookie path is a prefix of the url path. If the + // url path is shorter than the cookie path, then the cookie path + // can't be a prefix. + if (url_path.find(path_) != 0) + return false; + + // Now we know that url_path is >= cookie_path, and that cookie_path + // is a prefix of url_path. If they are the are the same length then + // they are identical, otherwise we need an additional check: + + // In order to avoid in correctly matching a cookie path of /blah + // with a request path of '/blahblah/', we need to make sure that either + // the cookie path ends in a trailing '/', or that we prefix up to a '/' + // in the url path. Since we know that the url path length is greater + // than the cookie path length, it's safe to index one byte past. + if (path_.length() != url_path.length() && + path_[path_.length() - 1] != '/' && + url_path[path_.length()] != '/') + return false; + + return true; +} + +bool CanonicalCookie::IsDomainMatch(const std::string& scheme, + const std::string& host) const { + // Can domain match in two ways; as a domain cookie (where the cookie + // domain begins with ".") or as a host cookie (where it doesn't). + + // Some consumers of the CookieMonster expect to set cookies on + // URLs like http://.strange.url. To retrieve cookies in this instance, + // we allow matching as a host cookie even when the domain_ starts with + // a period. + if (host == domain_) + return true; + + // Domain cookie must have an initial ".". To match, it must be + // equal to url's host with initial period removed, or a suffix of + // it. + + // Arguably this should only apply to "http" or "https" cookies, but + // extension cookie tests currently use the funtionality, and if we + // ever decide to implement that it should be done by preventing + // such cookies from being set. + if (domain_.empty() || domain_[0] != '.') + return false; + + // The host with a "." prefixed. + if (domain_.compare(1, std::string::npos, host) == 0) + return true; + + // A pure suffix of the host (ok since we know the domain already + // starts with a ".") + return (host.length() > domain_.length() && + host.compare(host.length() - domain_.length(), + domain_.length(), domain_) == 0); +} + +std::string CanonicalCookie::DebugString() const { + return base::StringPrintf( + "name: %s value: %s domain: %s path: %s creation: %" + PRId64, + name_.c_str(), value_.c_str(), + domain_.c_str(), path_.c_str(), + static_cast<int64>(creation_date_.ToTimeT())); +} + +} // namespace diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h new file mode 100644 index 0000000..4a710b2 --- /dev/null +++ b/net/cookies/canonical_cookie.h @@ -0,0 +1,156 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_COOKIES_CANONICAL_COOKIE_H_ +#define NET_COOKIES_CANONICAL_COOKIE_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/time.h" +#include "net/base/net_export.h" + +class GURL; + +namespace net { + +class ParsedCookie; + +class NET_EXPORT CanonicalCookie { + public: + + // These constructors do no validation or canonicalization of their inputs; + // the resulting CanonicalCookies should not be relied on to be canonical + // unless the caller has done appropriate validation and canonicalization + // themselves. + CanonicalCookie(); + CanonicalCookie(const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + const std::string& mac_key, + const std::string& mac_algorithm, + const base::Time& creation, + const base::Time& expiration, + const base::Time& last_access, + bool secure, + bool httponly); + + // This constructor does canonicalization but not validation. + // The result of this constructor should not be relied on in contexts + // in which pre-validation of the ParsedCookie has not been done. + CanonicalCookie(const GURL& url, const ParsedCookie& pc); + + ~CanonicalCookie(); + + // Supports the default copy constructor. + + // Creates a canonical cookie from parsed cookie. + // Canonicalizes and validates inputs. May return NULL if an attribute + // value is invalid. + static CanonicalCookie* Create(const GURL& url, + const ParsedCookie& pc); + + // Creates a canonical cookie from unparsed attribute values. + // Canonicalizes and validates inputs. May return NULL if an attribute + // value is invalid. + static CanonicalCookie* Create(const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + const std::string& mac_key, + const std::string& mac_algorithm, + const base::Time& creation, + const base::Time& expiration, + bool secure, + bool http_only); + + const std::string& Source() const { return source_; } + const std::string& Name() const { return name_; } + const std::string& Value() const { return value_; } + const std::string& Domain() const { return domain_; } + const std::string& Path() const { return path_; } + const std::string& MACKey() const { return mac_key_; } + const std::string& MACAlgorithm() const { return mac_algorithm_; } + const base::Time& CreationDate() const { return creation_date_; } + const base::Time& LastAccessDate() const { return last_access_date_; } + bool IsPersistent() const { return !expiry_date_.is_null(); } + const base::Time& ExpiryDate() const { return expiry_date_; } + bool IsSecure() const { return secure_; } + bool IsHttpOnly() const { return httponly_; } + bool IsDomainCookie() const { + return !domain_.empty() && domain_[0] == '.'; } + bool IsHostCookie() const { return !IsDomainCookie(); } + + bool IsExpired(const base::Time& current) { + return !expiry_date_.is_null() && current >= expiry_date_; + } + + // Are the cookies considered equivalent in the eyes of RFC 2965. + // The RFC says that name must match (case-sensitive), domain must + // match (case insensitive), and path must match (case sensitive). + // For the case insensitive domain compare, we rely on the domain + // having been canonicalized (in + // GetCookieDomainWithString->CanonicalizeHost). + bool IsEquivalent(const CanonicalCookie& ecc) const { + // It seems like it would make sense to take secure and httponly into + // account, but the RFC doesn't specify this. + // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost(). + return (name_ == ecc.Name() && domain_ == ecc.Domain() + && path_ == ecc.Path()); + } + + void SetLastAccessDate(const base::Time& date) { + last_access_date_ = date; + } + + bool IsOnPath(const std::string& url_path) const; + bool IsDomainMatch(const std::string& scheme, const std::string& host) const; + + std::string DebugString() const; + + // Returns the cookie source when cookies are set for |url|. This function + // is public for unit test purposes only. + static std::string GetCookieSourceFromURL(const GURL& url); + static std::string CanonPath(const GURL& url, const ParsedCookie& pc); + static base::Time CanonExpiration(const ParsedCookie& pc, + const base::Time& current, + const base::Time& server_time); + + private: + // Gives the session cookie an expiration time if needed + void SetSessionCookieExpiryTime(); + + // The source member of a canonical cookie is the origin of the URL that tried + // to set this cookie, minus the port number if any. This field is not + // persistent though; its only used in the in-tab cookies dialog to show the + // user the source URL. This is used for both allowed and blocked cookies. + // When a CanonicalCookie is constructed from the backing store (common case) + // this field will be null. CanonicalCookie consumers should not rely on + // this field unless they guarantee that the creator of those + // CanonicalCookies properly initialized the field. + // TODO(abarth): We might need to make this field persistent for MAC cookies. + std::string source_; + std::string name_; + std::string value_; + std::string domain_; + std::string path_; + std::string mac_key_; // TODO(abarth): Persist to disk. + std::string mac_algorithm_; // TODO(abarth): Persist to disk. + base::Time creation_date_; + base::Time expiry_date_; + base::Time last_access_date_; + bool secure_; + bool httponly_; +}; + +class CookieList : public std::vector<CanonicalCookie> { +}; + +} // namespace net + +#endif // NET_COOKIES_CANONICAL_COOKIE_H_ diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc new file mode 100644 index 0000000..9dab3ce --- /dev/null +++ b/net/cookies/canonical_cookie_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cookies/canonical_cookie.h" + +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(CanonicalCookieTest, GetCookieSourceFromURL) { + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com/"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com/test"))); + EXPECT_EQ("file:///tmp/test.html", + CanonicalCookie::GetCookieSourceFromURL( + GURL("file:///tmp/test.html"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com:1234/"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("https://example.com/"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://user:pwd@example.com/"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com/test?foo"))); + EXPECT_EQ("http://example.com/", + CanonicalCookie::GetCookieSourceFromURL( + GURL("http://example.com/test#foo"))); +} + +} // namespace net diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc index 9851c9a..3d6656e6 100644 --- a/net/cookies/cookie_monster.cc +++ b/net/cookies/cookie_monster.cc @@ -50,17 +50,15 @@ #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" -#include "base/format_macros.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/metrics/histogram.h" -#include "base/string_tokenizer.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "googleurl/src/gurl.h" -#include "googleurl/src/url_canon.h" +#include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_util.h" #include "net/cookies/parsed_cookie.h" #include "net/base/registry_controlled_domain.h" @@ -103,7 +101,7 @@ const int CookieMonster::kSafeFromGlobalPurgeDays = 30; namespace { -typedef std::vector<CookieMonster::CanonicalCookie*> CanonicalCookieVector; +typedef std::vector<CanonicalCookie*> CanonicalCookieVector; // Default minimum delay after updating a cookie's LastAccessDate before we // will update it again. @@ -125,15 +123,10 @@ const int kVlogGarbageCollection = 5; const int kVlogSetCookies = 7; const int kVlogGetCookies = 9; -#if defined(ENABLE_PERSISTENT_SESSION_COOKIES) -const int kPersistentSessionCookieExpiryInDays = 14; -#endif - // Mozilla sorts on the path length (longest first), and then it // sorts by creation time (oldest first). // The RFC says the sort order for the domain attribute is undefined. -bool CookieSorter(CookieMonster::CanonicalCookie* cc1, - CookieMonster::CanonicalCookie* cc2) { +bool CookieSorter(CanonicalCookie* cc1, CanonicalCookie* cc2) { if (cc1->Path().length() == cc2->Path().length()) return cc1->CreationDate() < cc2->CreationDate(); return cc1->Path().length() > cc2->Path().length(); @@ -199,69 +192,6 @@ bool GetCookieDomain(const GURL& url, return cookie_util::GetCookieDomainWithString(url, domain_string, result); } -std::string CanonPathWithString(const GURL& url, - const std::string& path_string) { - // The RFC says the path should be a prefix of the current URL path. - // However, Mozilla allows you to set any path for compatibility with - // broken websites. We unfortunately will mimic this behavior. We try - // to be generous and accept cookies with an invalid path attribute, and - // default the path to something reasonable. - - // The path was supplied in the cookie, we'll take it. - if (!path_string.empty() && path_string[0] == '/') - return path_string; - - // The path was not supplied in the cookie or invalid, we will default - // to the current URL path. - // """Defaults to the path of the request URL that generated the - // Set-Cookie response, up to, but not including, the - // right-most /.""" - // How would this work for a cookie on /? We will include it then. - const std::string& url_path = url.path(); - - size_t idx = url_path.find_last_of('/'); - - // The cookie path was invalid or a single '/'. - if (idx == 0 || idx == std::string::npos) - return std::string("/"); - - // Return up to the rightmost '/'. - return url_path.substr(0, idx); -} - -std::string CanonPath(const GURL& url, const ParsedCookie& pc) { - std::string path_string; - if (pc.HasPath()) - path_string = pc.Path(); - return CanonPathWithString(url, path_string); -} - -Time CanonExpiration(const ParsedCookie& pc, - const Time& current, - const Time& server_time) { - // First, try the Max-Age attribute. - uint64 max_age = 0; - if (pc.HasMaxAge() && -#ifdef COMPILER_MSVC - sscanf_s( -#else - sscanf( -#endif - pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) { - return current + TimeDelta::FromSeconds(max_age); - } - - // Try the Expires attribute. - if (pc.HasExpires()) { - // Adjust for clock skew between server and host. - return current + (CookieMonster::ParseCookieTime(pc.Expires()) - - server_time); - } - - // Invalid or no expiration, persistent cookie. - return Time(); -} - // Helper for GarbageCollection. If |cookie_its->size() > num_max|, remove the // |num_max - num_purge| most recently accessed cookies from cookie_its. // (In other words, leave the entries that are candidates for @@ -348,7 +278,7 @@ void BuildCookieInfoList(const CanonicalCookieVector& cookies, std::vector<CookieStore::CookieInfo>* cookie_infos) { for (CanonicalCookieVector::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { - const CookieMonster::CanonicalCookie* cookie = *it; + const CanonicalCookie* cookie = *it; CookieStore::CookieInfo cookie_info; cookie_info.name = cookie->Name(); @@ -395,123 +325,6 @@ CookieMonster::CookieMonster(PersistentCookieStore* store, SetDefaultCookieableSchemes(); } -// Parse a cookie expiration time. We try to be lenient, but we need to -// assume some order to distinguish the fields. The basic rules: -// - The month name must be present and prefix the first 3 letters of the -// full month name (jan for January, jun for June). -// - If the year is <= 2 digits, it must occur after the day of month. -// - The time must be of the format hh:mm:ss. -// An average cookie expiration will look something like this: -// Sat, 15-Apr-17 21:01:22 GMT -Time CookieMonster::ParseCookieTime(const std::string& time_string) { - static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" }; - static const int kMonthsLen = arraysize(kMonths); - // We want to be pretty liberal, and support most non-ascii and non-digit - // characters as a delimiter. We can't treat : as a delimiter, because it - // is the delimiter for hh:mm:ss, and we want to keep this field together. - // We make sure to include - and +, since they could prefix numbers. - // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes - // will be preserved, and we will get them here. So we make sure to include - // quote characters, and also \ for anything that was internally escaped. - static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; - - Time::Exploded exploded = {0}; - - StringTokenizer tokenizer(time_string, kDelimiters); - - bool found_day_of_month = false; - bool found_month = false; - bool found_time = false; - bool found_year = false; - - while (tokenizer.GetNext()) { - const std::string token = tokenizer.token(); - DCHECK(!token.empty()); - bool numerical = IsAsciiDigit(token[0]); - - // String field - if (!numerical) { - if (!found_month) { - for (int i = 0; i < kMonthsLen; ++i) { - // Match prefix, so we could match January, etc - if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { - exploded.month = i + 1; - found_month = true; - break; - } - } - } else { - // If we've gotten here, it means we've already found and parsed our - // month, and we have another string, which we would expect to be the - // the time zone name. According to the RFC and my experiments with - // how sites format their expirations, we don't have much of a reason - // to support timezones. We don't want to ever barf on user input, - // but this DCHECK should pass for well-formed data. - // DCHECK(token == "GMT"); - } - // Numeric field w/ a colon - } else if (token.find(':') != std::string::npos) { - if (!found_time && -#ifdef COMPILER_MSVC - sscanf_s( -#else - sscanf( -#endif - token.c_str(), "%2u:%2u:%2u", &exploded.hour, - &exploded.minute, &exploded.second) == 3) { - found_time = true; - } else { - // We should only ever encounter one time-like thing. If we're here, - // it means we've found a second, which shouldn't happen. We keep - // the first. This check should be ok for well-formed input: - // NOTREACHED(); - } - // Numeric field - } else { - // Overflow with atoi() is unspecified, so we enforce a max length. - if (!found_day_of_month && token.length() <= 2) { - exploded.day_of_month = atoi(token.c_str()); - found_day_of_month = true; - } else if (!found_year && token.length() <= 5) { - exploded.year = atoi(token.c_str()); - found_year = true; - } else { - // If we're here, it means we've either found an extra numeric field, - // or a numeric field which was too long. For well-formed input, the - // following check would be reasonable: - // NOTREACHED(); - } - } - } - - if (!found_day_of_month || !found_month || !found_time || !found_year) { - // We didn't find all of the fields we need. For well-formed input, the - // following check would be reasonable: - // NOTREACHED() << "Cookie parse expiration failed: " << time_string; - return Time(); - } - - // Normalize the year to expand abbreviated years to the full year. - if (exploded.year >= 69 && exploded.year <= 99) - exploded.year += 1900; - if (exploded.year >= 0 && exploded.year <= 68) - exploded.year += 2000; - - // If our values are within their correct ranges, we got our time. - if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && - exploded.month >= 1 && exploded.month <= 12 && - exploded.year >= 1601 && exploded.year <= 30827 && - exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { - return Time::FromUTCExploded(exploded); - } - - // One of our values was out of expected range. For well-formed input, - // the following check would be reasonable: - // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; - - return Time(); -} // Task classes for queueing the coming request. @@ -798,7 +611,7 @@ class CookieMonster::DeleteCanonicalCookieTask public: DeleteCanonicalCookieTask( CookieMonster* cookie_monster, - const CookieMonster::CanonicalCookie& cookie, + const CanonicalCookie& cookie, const CookieMonster::DeleteCookieCallback& callback) : CookieMonsterTask(cookie_monster), cookie_(cookie), @@ -812,7 +625,7 @@ class CookieMonster::DeleteCanonicalCookieTask virtual ~DeleteCanonicalCookieTask() {} private: - CookieMonster::CanonicalCookie cookie_; + CanonicalCookie cookie_; CookieMonster::DeleteCookieCallback callback_; DISALLOW_COPY_AND_ASSIGN(DeleteCanonicalCookieTask); @@ -1211,14 +1024,11 @@ bool CookieMonster::InitializeFrom(const CookieList& list) { InitIfNecessary(); for (net::CookieList::const_iterator iter = list.begin(); iter != list.end(); ++iter) { - scoped_ptr<net::CookieMonster::CanonicalCookie> cookie; - cookie.reset(new net::CookieMonster::CanonicalCookie(*iter)); + scoped_ptr<CanonicalCookie> cookie(new CanonicalCookie(*iter)); net::CookieOptions options; options.set_include_httponly(); - if (!SetCanonicalCookie(&cookie, cookie->CreationDate(), - options)) { + if (!SetCanonicalCookie(&cookie, cookie->CreationDate(), options)) return false; - } } return true; } @@ -1917,13 +1727,14 @@ bool CookieMonster::SetCookieWithCreationTimeAndOptions( return false; } - std::string cookie_path = CanonPath(url, pc); + std::string cookie_path = CanonicalCookie::CanonPath(url, pc); std::string mac_key = pc.HasMACKey() ? pc.MACKey() : std::string(); std::string mac_algorithm = pc.HasMACAlgorithm() ? pc.MACAlgorithm() : std::string(); scoped_ptr<CanonicalCookie> cc; - Time cookie_expires = CanonExpiration(pc, creation_time, server_time); + Time cookie_expires = + CanonicalCookie::CanonExpiration(pc, creation_time, server_time); cc.reset(new CanonicalCookie(url, pc.Name(), pc.Value(), cookie_domain, cookie_path, mac_key, mac_algorithm, @@ -2344,246 +2155,4 @@ Time CookieMonster::CurrentTime() { Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1)); } -CookieMonster::CanonicalCookie::CanonicalCookie() - : secure_(false), - httponly_(false) { - SetSessionCookieExpiryTime(); -} - -CookieMonster::CanonicalCookie::CanonicalCookie( - const GURL& url, const std::string& name, const std::string& value, - const std::string& domain, const std::string& path, - const std::string& mac_key, const std::string& mac_algorithm, - const base::Time& creation, const base::Time& expiration, - const base::Time& last_access, bool secure, bool httponly) - : source_(GetCookieSourceFromURL(url)), - name_(name), - value_(value), - domain_(domain), - path_(path), - mac_key_(mac_key), - mac_algorithm_(mac_algorithm), - creation_date_(creation), - expiry_date_(expiration), - last_access_date_(last_access), - secure_(secure), - httponly_(httponly) { - if (expiration.is_null()) - SetSessionCookieExpiryTime(); -} - -CookieMonster::CanonicalCookie::CanonicalCookie(const GURL& url, - const ParsedCookie& pc) - : source_(GetCookieSourceFromURL(url)), - name_(pc.Name()), - value_(pc.Value()), - path_(CanonPath(url, pc)), - mac_key_(pc.MACKey()), - mac_algorithm_(pc.MACAlgorithm()), - creation_date_(Time::Now()), - last_access_date_(Time()), - secure_(pc.IsSecure()), - httponly_(pc.IsHttpOnly()) { - if (pc.HasExpires()) - expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_); - else - SetSessionCookieExpiryTime(); - - // Do the best we can with the domain. - std::string cookie_domain; - std::string domain_string; - if (pc.HasDomain()) { - domain_string = pc.Domain(); - } - bool result - = cookie_util::GetCookieDomainWithString(url, domain_string, - &cookie_domain); - // Caller is responsible for passing in good arguments. - DCHECK(result); - domain_ = cookie_domain; -} - -CookieMonster::CanonicalCookie::~CanonicalCookie() { -} - -std::string CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - const GURL& url) { - if (url.SchemeIsFile()) - return url.spec(); - - url_canon::Replacements<char> replacements; - replacements.ClearPort(); - if (url.SchemeIsSecure()) - replacements.SetScheme("http", url_parse::Component(0, 4)); - - return url.GetOrigin().ReplaceComponents(replacements).spec(); -} - -void CookieMonster::CanonicalCookie::SetSessionCookieExpiryTime() { -#if defined(ENABLE_PERSISTENT_SESSION_COOKIES) - // Mobile apps can sometimes be shut down without any warning, so the session - // cookie has to be persistent and given a default expiration time. - expiry_date_ = base::Time::Now() + - base::TimeDelta::FromDays(kPersistentSessionCookieExpiryInDays); -#endif -} - -CookieMonster::CanonicalCookie* CookieMonster::CanonicalCookie::Create( - const GURL& url, - const ParsedCookie& pc) { - if (!pc.IsValid()) { - return NULL; - } - - std::string domain_string; - if (!GetCookieDomain(url, pc, &domain_string)) { - return NULL; - } - std::string path_string = CanonPath(url, pc); - std::string mac_key = pc.HasMACKey() ? pc.MACKey() : std::string(); - std::string mac_algorithm = pc.HasMACAlgorithm() ? - pc.MACAlgorithm() : std::string(); - Time creation_time = Time::Now(); - Time expiration_time; - if (pc.HasExpires()) - expiration_time = net::CookieMonster::ParseCookieTime(pc.Expires()); - - return (Create(url, pc.Name(), pc.Value(), domain_string, path_string, - mac_key, mac_algorithm, creation_time, expiration_time, - pc.IsSecure(), pc.IsHttpOnly())); -} - -CookieMonster::CanonicalCookie* CookieMonster::CanonicalCookie::Create( - const GURL& url, - const std::string& name, - const std::string& value, - const std::string& domain, - const std::string& path, - const std::string& mac_key, - const std::string& mac_algorithm, - const base::Time& creation, - const base::Time& expiration, - bool secure, - bool http_only) { - // Expect valid attribute tokens and values, as defined by the ParsedCookie - // logic, otherwise don't create the cookie. - std::string parsed_name = ParsedCookie::ParseTokenString(name); - if (parsed_name != name) - return NULL; - std::string parsed_value = ParsedCookie::ParseValueString(value); - if (parsed_value != value) - return NULL; - - std::string parsed_domain = ParsedCookie::ParseValueString(domain); - if (parsed_domain != domain) - return NULL; - std::string cookie_domain; - if (!cookie_util::GetCookieDomainWithString(url, parsed_domain, - &cookie_domain)) { - return NULL; - } - - std::string parsed_path = ParsedCookie::ParseValueString(path); - if (parsed_path != path) - return NULL; - - std::string cookie_path = CanonPathWithString(url, parsed_path); - // Expect that the path was either not specified (empty), or is valid. - if (!parsed_path.empty() && cookie_path != parsed_path) - return NULL; - // Canonicalize path again to make sure it escapes characters as needed. - url_parse::Component path_component(0, cookie_path.length()); - url_canon::RawCanonOutputT<char> canon_path; - url_parse::Component canon_path_component; - url_canon::CanonicalizePath(cookie_path.data(), path_component, - &canon_path, &canon_path_component); - cookie_path = std::string(canon_path.data() + canon_path_component.begin, - canon_path_component.len); - - return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain, - cookie_path, mac_key, mac_algorithm, creation, - expiration, creation, secure, http_only); -} - -bool CookieMonster::CanonicalCookie::IsOnPath( - const std::string& url_path) const { - - // A zero length would be unsafe for our trailing '/' checks, and - // would also make no sense for our prefix match. The code that - // creates a CanonicalCookie should make sure the path is never zero length, - // but we double check anyway. - if (path_.empty()) - return false; - - // The Mozilla code broke this into three cases, based on if the cookie path - // was longer, the same length, or shorter than the length of the url path. - // I think the approach below is simpler. - - // Make sure the cookie path is a prefix of the url path. If the - // url path is shorter than the cookie path, then the cookie path - // can't be a prefix. - if (url_path.find(path_) != 0) - return false; - - // Now we know that url_path is >= cookie_path, and that cookie_path - // is a prefix of url_path. If they are the are the same length then - // they are identical, otherwise we need an additional check: - - // In order to avoid in correctly matching a cookie path of /blah - // with a request path of '/blahblah/', we need to make sure that either - // the cookie path ends in a trailing '/', or that we prefix up to a '/' - // in the url path. Since we know that the url path length is greater - // than the cookie path length, it's safe to index one byte past. - if (path_.length() != url_path.length() && - path_[path_.length() - 1] != '/' && - url_path[path_.length()] != '/') - return false; - - return true; -} - -bool CookieMonster::CanonicalCookie::IsDomainMatch( - const std::string& scheme, - const std::string& host) const { - // Can domain match in two ways; as a domain cookie (where the cookie - // domain begins with ".") or as a host cookie (where it doesn't). - - // Some consumers of the CookieMonster expect to set cookies on - // URLs like http://.strange.url. To retrieve cookies in this instance, - // we allow matching as a host cookie even when the domain_ starts with - // a period. - if (host == domain_) - return true; - - // Domain cookie must have an initial ".". To match, it must be - // equal to url's host with initial period removed, or a suffix of - // it. - - // Arguably this should only apply to "http" or "https" cookies, but - // extension cookie tests currently use the funtionality, and if we - // ever decide to implement that it should be done by preventing - // such cookies from being set. - if (domain_.empty() || domain_[0] != '.') - return false; - - // The host with a "." prefixed. - if (domain_.compare(1, std::string::npos, host) == 0) - return true; - - // A pure suffix of the host (ok since we know the domain already - // starts with a ".") - return (host.length() > domain_.length() && - host.compare(host.length() - domain_.length(), - domain_.length(), domain_) == 0); -} - -std::string CookieMonster::CanonicalCookie::DebugString() const { - return base::StringPrintf( - "name: %s value: %s domain: %s path: %s creation: %" - PRId64, - name_.c_str(), value_.c_str(), - domain_.c_str(), path_.c_str(), - static_cast<int64>(creation_date_.ToTimeT())); -} - } // namespace diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h index 4561a1c..ef5f9ab 100644 --- a/net/cookies/cookie_monster.h +++ b/net/cookies/cookie_monster.h @@ -34,6 +34,7 @@ class TimeTicks; namespace net { +class CanonicalCookie; class CookieList; class ParsedCookie; @@ -63,7 +64,6 @@ class ParsedCookie; // - Verify that our domain enforcement and non-dotted handling is correct class NET_EXPORT CookieMonster : public CookieStore { public: - class CanonicalCookie; class Delegate; class PersistentCookieStore; @@ -119,9 +119,6 @@ class NET_EXPORT CookieMonster : public CookieStore { Delegate* delegate, int last_access_threshold_milliseconds); - // Parses the string with the cookie time (very forgivingly). - static base::Time ParseCookieTime(const std::string& time_string); - // Helper function that adds all cookies from |list| into this instance. bool InitializeFrom(const CookieList& list); @@ -660,132 +657,6 @@ class NET_EXPORT CookieMonster : public CookieStore { DISALLOW_COPY_AND_ASSIGN(CookieMonster); }; -class NET_EXPORT CookieMonster::CanonicalCookie { - public: - - // These constructors do no validation or canonicalization of their inputs; - // the resulting CanonicalCookies should not be relied on to be canonical - // unless the caller has done appropriate validation and canonicalization - // themselves. - CanonicalCookie(); - CanonicalCookie(const GURL& url, - const std::string& name, - const std::string& value, - const std::string& domain, - const std::string& path, - const std::string& mac_key, - const std::string& mac_algorithm, - const base::Time& creation, - const base::Time& expiration, - const base::Time& last_access, - bool secure, - bool httponly); - - // This constructor does canonicalization but not validation. - // The result of this constructor should not be relied on in contexts - // in which pre-validation of the ParsedCookie has not been done. - CanonicalCookie(const GURL& url, const ParsedCookie& pc); - - ~CanonicalCookie(); - - // Supports the default copy constructor. - - // Creates a canonical cookie from parsed cookie. - // Canonicalizes and validates inputs. May return NULL if an attribute - // value is invalid. - static CanonicalCookie* Create(const GURL& url, - const ParsedCookie& pc); - - // Creates a canonical cookie from unparsed attribute values. - // Canonicalizes and validates inputs. May return NULL if an attribute - // value is invalid. - static CanonicalCookie* Create(const GURL& url, - const std::string& name, - const std::string& value, - const std::string& domain, - const std::string& path, - const std::string& mac_key, - const std::string& mac_algorithm, - const base::Time& creation, - const base::Time& expiration, - bool secure, - bool http_only); - - const std::string& Source() const { return source_; } - const std::string& Name() const { return name_; } - const std::string& Value() const { return value_; } - const std::string& Domain() const { return domain_; } - const std::string& Path() const { return path_; } - const std::string& MACKey() const { return mac_key_; } - const std::string& MACAlgorithm() const { return mac_algorithm_; } - const base::Time& CreationDate() const { return creation_date_; } - const base::Time& LastAccessDate() const { return last_access_date_; } - bool IsPersistent() const { return !expiry_date_.is_null(); } - const base::Time& ExpiryDate() const { return expiry_date_; } - bool IsSecure() const { return secure_; } - bool IsHttpOnly() const { return httponly_; } - bool IsDomainCookie() const { - return !domain_.empty() && domain_[0] == '.'; } - bool IsHostCookie() const { return !IsDomainCookie(); } - - bool IsExpired(const base::Time& current) { - return !expiry_date_.is_null() && current >= expiry_date_; - } - - // Are the cookies considered equivalent in the eyes of RFC 2965. - // The RFC says that name must match (case-sensitive), domain must - // match (case insensitive), and path must match (case sensitive). - // For the case insensitive domain compare, we rely on the domain - // having been canonicalized (in - // GetCookieDomainWithString->CanonicalizeHost). - bool IsEquivalent(const CanonicalCookie& ecc) const { - // It seems like it would make sense to take secure and httponly into - // account, but the RFC doesn't specify this. - // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost(). - return (name_ == ecc.Name() && domain_ == ecc.Domain() - && path_ == ecc.Path()); - } - - void SetLastAccessDate(const base::Time& date) { - last_access_date_ = date; - } - - bool IsOnPath(const std::string& url_path) const; - bool IsDomainMatch(const std::string& scheme, const std::string& host) const; - - std::string DebugString() const; - - // Returns the cookie source when cookies are set for |url|. This function - // is public for unit test purposes only. - static std::string GetCookieSourceFromURL(const GURL& url); - - private: - // Gives the session cookie an expiration time if needed - void SetSessionCookieExpiryTime(); - - // The source member of a canonical cookie is the origin of the URL that tried - // to set this cookie, minus the port number if any. This field is not - // persistent though; its only used in the in-tab cookies dialog to show the - // user the source URL. This is used for both allowed and blocked cookies. - // When a CanonicalCookie is constructed from the backing store (common case) - // this field will be null. CanonicalCookie consumers should not rely on - // this field unless they guarantee that the creator of those - // CanonicalCookies properly initialized the field. - // TODO(abarth): We might need to make this field persistent for MAC cookies. - std::string source_; - std::string name_; - std::string value_; - std::string domain_; - std::string path_; - std::string mac_key_; // TODO(abarth): Persist to disk. - std::string mac_algorithm_; // TODO(abarth): Persist to disk. - base::Time creation_date_; - base::Time expiry_date_; - base::Time last_access_date_; - bool secure_; - bool httponly_; -}; - class CookieMonster::Delegate : public base::RefCountedThreadSafe<CookieMonster::Delegate> { public: @@ -815,7 +686,7 @@ class CookieMonster::Delegate // generating a notification with cause CHANGE_COOKIE_OVERWRITE. Afterwards, // a new cookie is written with the updated values, generating a notification // with cause CHANGE_COOKIE_EXPLICIT. - virtual void OnCookieChanged(const CookieMonster::CanonicalCookie& cookie, + virtual void OnCookieChanged(const CanonicalCookie& cookie, bool removed, ChangeCause cause) = 0; protected: @@ -829,8 +700,8 @@ typedef base::RefCountedThreadSafe<CookieMonster::PersistentCookieStore> class CookieMonster::PersistentCookieStore : public RefcountedPersistentCookieStore { public: - typedef base::Callback<void(const std::vector< - CookieMonster::CanonicalCookie*>&)> LoadedCallback; + typedef base::Callback<void(const std::vector<CanonicalCookie*>&)> + LoadedCallback; // Initializes the store and retrieves the existing cookies. This will be // called only once at startup. The callback will return all the cookies @@ -864,9 +735,6 @@ class CookieMonster::PersistentCookieStore DISALLOW_COPY_AND_ASSIGN(PersistentCookieStore); }; -class CookieList : public std::vector<CookieMonster::CanonicalCookie> { -}; - } // namespace net #endif // NET_COOKIES_COOKIE_MONSTER_H_ diff --git a/net/cookies/cookie_monster_perftest.cc b/net/cookies/cookie_monster_perftest.cc index 2e8aa27..7e755f0 100644 --- a/net/cookies/cookie_monster_perftest.cc +++ b/net/cookies/cookie_monster_perftest.cc @@ -10,6 +10,7 @@ #include "base/string_util.h" #include "base/stringprintf.h" #include "googleurl/src/gurl.h" +#include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_monster_store_test.h" #include "net/cookies/parsed_cookie.h" @@ -324,7 +325,7 @@ TEST_F(CookieMonsterTest, TestDomainLine) { TEST_F(CookieMonsterTest, TestImport) { scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); - std::vector<CookieMonster::CanonicalCookie*> initial_cookies; + std::vector<CanonicalCookie*> initial_cookies; GetCookiesCallback getCookiesCallback; // We want to setup a fairly large backing store, with 300 domains of 50 diff --git a/net/cookies/cookie_monster_store_test.cc b/net/cookies/cookie_monster_store_test.cc index 6724d9d..6e0965d 100644 --- a/net/cookies/cookie_monster_store_test.cc +++ b/net/cookies/cookie_monster_store_test.cc @@ -9,12 +9,13 @@ #include "base/stringprintf.h" #include "base/time.h" #include "googleurl/src/gurl.h" +#include "net/cookies/cookie_util.h" #include "net/cookies/parsed_cookie.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { LoadedCallbackTask::LoadedCallbackTask(LoadedCallback loaded_callback, - std::vector<CookieMonster::CanonicalCookie*> cookies) + std::vector<CanonicalCookie*> cookies) : loaded_callback_(loaded_callback), cookies_(cookies) { } @@ -28,13 +29,13 @@ MockPersistentCookieStore::MockPersistentCookieStore() void MockPersistentCookieStore::SetLoadExpectation( bool return_value, - const std::vector<CookieMonster::CanonicalCookie*>& result) { + const std::vector<CanonicalCookie*>& result) { load_return_value_ = return_value; load_result_ = result; } void MockPersistentCookieStore::Load(const LoadedCallback& loaded_callback) { - std::vector<CookieMonster::CanonicalCookie*> out_cookies; + std::vector<CanonicalCookie*> out_cookies; if (load_return_value_) { out_cookies = load_result_; loaded_ = true; @@ -44,32 +45,31 @@ void MockPersistentCookieStore::Load(const LoadedCallback& loaded_callback) { new LoadedCallbackTask(loaded_callback, out_cookies))); } -void MockPersistentCookieStore::LoadCookiesForKey(const std::string& key, +void MockPersistentCookieStore::LoadCookiesForKey( + const std::string& key, const LoadedCallback& loaded_callback) { if (!loaded_) { Load(loaded_callback); } else { MessageLoop::current()->PostTask(FROM_HERE, - base::Bind(&LoadedCallbackTask::Run, - new LoadedCallbackTask(loaded_callback, - std::vector<CookieMonster::CanonicalCookie*>()))); + base::Bind(&LoadedCallbackTask::Run, + new LoadedCallbackTask(loaded_callback, + std::vector<CanonicalCookie*>()))); } } -void MockPersistentCookieStore::AddCookie( - const CookieMonster::CanonicalCookie& cookie) { +void MockPersistentCookieStore::AddCookie(const CanonicalCookie& cookie) { commands_.push_back( CookieStoreCommand(CookieStoreCommand::ADD, cookie)); } void MockPersistentCookieStore::UpdateCookieAccessTime( - const CookieMonster::CanonicalCookie& cookie) { + const CanonicalCookie& cookie) { commands_.push_back(CookieStoreCommand( CookieStoreCommand::UPDATE_ACCESS_TIME, cookie)); } -void MockPersistentCookieStore::DeleteCookie( - const CookieMonster::CanonicalCookie& cookie) { +void MockPersistentCookieStore::DeleteCookie(const CanonicalCookie& cookie) { commands_.push_back( CookieStoreCommand(CookieStoreCommand::REMOVE, cookie)); } @@ -87,7 +87,7 @@ MockPersistentCookieStore::~MockPersistentCookieStore() {} MockCookieMonsterDelegate::MockCookieMonsterDelegate() {} void MockCookieMonsterDelegate::OnCookieChanged( - const CookieMonster::CanonicalCookie& cookie, + const CanonicalCookie& cookie, bool removed, CookieMonster::Delegate::ChangeCause cause) { CookieNotification notification(cookie, removed); @@ -96,10 +96,9 @@ void MockCookieMonsterDelegate::OnCookieChanged( MockCookieMonsterDelegate::~MockCookieMonsterDelegate() {} -CookieMonster::CanonicalCookie BuildCanonicalCookie( - const std::string& key, - const std::string& cookie_line, - const base::Time& creation_time) { +CanonicalCookie BuildCanonicalCookie(const std::string& key, + const std::string& cookie_line, + const base::Time& creation_time) { // Parse the cookie line. ParsedCookie pc(cookie_line); @@ -111,10 +110,10 @@ CookieMonster::CanonicalCookie BuildCanonicalCookie( EXPECT_FALSE(pc.HasMaxAge()); EXPECT_TRUE(pc.HasPath()); base::Time cookie_expires = pc.HasExpires() ? - CookieMonster::ParseCookieTime(pc.Expires()) : base::Time(); + cookie_util::ParseCookieTime(pc.Expires()) : base::Time(); std::string cookie_path = pc.Path(); - return CookieMonster::CanonicalCookie( + return CanonicalCookie( GURL(), pc.Name(), pc.Value(), key, cookie_path, pc.MACKey(), pc.MACAlgorithm(), creation_time, cookie_expires, creation_time, @@ -125,9 +124,9 @@ void AddCookieToList( const std::string& key, const std::string& cookie_line, const base::Time& creation_time, - std::vector<CookieMonster::CanonicalCookie*>* out_list) { - scoped_ptr<CookieMonster::CanonicalCookie> cookie( - new CookieMonster::CanonicalCookie( + std::vector<CanonicalCookie*>* out_list) { + scoped_ptr<CanonicalCookie> cookie( + new CanonicalCookie( BuildCanonicalCookie(key, cookie_line, creation_time))); out_list->push_back(cookie.release()); @@ -139,12 +138,11 @@ MockSimplePersistentCookieStore::MockSimplePersistentCookieStore() void MockSimplePersistentCookieStore::Load( const LoadedCallback& loaded_callback) { - std::vector<CookieMonster::CanonicalCookie*> out_cookies; + std::vector<CanonicalCookie*> out_cookies; for (CanonicalCookieMap::const_iterator it = cookies_.begin(); it != cookies_.end(); it++) - out_cookies.push_back( - new CookieMonster::CanonicalCookie(it->second)); + out_cookies.push_back(new CanonicalCookie(it->second)); MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&LoadedCallbackTask::Run, @@ -160,26 +158,25 @@ void MockSimplePersistentCookieStore::LoadCookiesForKey(const std::string& key, MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&LoadedCallbackTask::Run, new LoadedCallbackTask(loaded_callback, - std::vector<CookieMonster::CanonicalCookie*>()))); + std::vector<CanonicalCookie*>()))); } } -void MockSimplePersistentCookieStore::AddCookie( - const CookieMonster::CanonicalCookie& cookie) { +void MockSimplePersistentCookieStore::AddCookie(const CanonicalCookie& cookie) { int64 creation_time = cookie.CreationDate().ToInternalValue(); EXPECT_TRUE(cookies_.find(creation_time) == cookies_.end()); cookies_[creation_time] = cookie; } void MockSimplePersistentCookieStore::UpdateCookieAccessTime( - const CookieMonster::CanonicalCookie& cookie) { + const CanonicalCookie& cookie) { int64 creation_time = cookie.CreationDate().ToInternalValue(); ASSERT_TRUE(cookies_.find(creation_time) != cookies_.end()); cookies_[creation_time].SetLastAccessDate(base::Time::Now()); } void MockSimplePersistentCookieStore::DeleteCookie( - const CookieMonster::CanonicalCookie& cookie) { + const CanonicalCookie& cookie) { int64 creation_time = cookie.CreationDate().ToInternalValue(); CanonicalCookieMap::iterator it = cookies_.find(creation_time); ASSERT_TRUE(it != cookies_.end()); @@ -214,7 +211,7 @@ CookieMonster* CreateMonsterFromStoreForGC( std::string mac_key; std::string mac_algorithm; - CookieMonster::CanonicalCookie cc( + CanonicalCookie cc( GURL(), "a", "1", base::StringPrintf("h%05d.izzle", i), "/path", mac_key, mac_algorithm, creation_time, expiration_time, last_access_time, false, false); diff --git a/net/cookies/cookie_monster_store_test.h b/net/cookies/cookie_monster_store_test.h index 265bd77..d7da521 100644 --- a/net/cookies/cookie_monster_store_test.h +++ b/net/cookies/cookie_monster_store_test.h @@ -14,6 +14,7 @@ #include <string> #include <utility> #include <vector> +#include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_monster.h" namespace base { @@ -31,7 +32,7 @@ class LoadedCallbackTask typedef CookieMonster::PersistentCookieStore::LoadedCallback LoadedCallback; LoadedCallbackTask(LoadedCallback loaded_callback, - std::vector<CookieMonster::CanonicalCookie*> cookies); + std::vector<CanonicalCookie*> cookies); void Run() { loaded_callback_.Run(cookies_); @@ -42,7 +43,7 @@ class LoadedCallbackTask ~LoadedCallbackTask(); LoadedCallback loaded_callback_; - std::vector<CookieMonster::CanonicalCookie*> cookies_; + std::vector<CanonicalCookie*> cookies_; DISALLOW_COPY_AND_ASSIGN(LoadedCallbackTask); }; // Wrapper class LoadedCallbackTask @@ -55,13 +56,12 @@ struct CookieStoreCommand { REMOVE, }; - CookieStoreCommand(Type type, - const CookieMonster::CanonicalCookie& cookie) + CookieStoreCommand(Type type, const CanonicalCookie& cookie) : type(type), cookie(cookie) {} Type type; - CookieMonster::CanonicalCookie cookie; + CanonicalCookie cookie; }; // Implementation of PersistentCookieStore that captures the @@ -76,7 +76,7 @@ class MockPersistentCookieStore void SetLoadExpectation( bool return_value, - const std::vector<CookieMonster::CanonicalCookie*>& result); + const std::vector<CanonicalCookie*>& result); const CommandList& commands() const { return commands_; @@ -87,13 +87,13 @@ class MockPersistentCookieStore virtual void LoadCookiesForKey(const std::string& key, const LoadedCallback& loaded_callback) OVERRIDE; - virtual void AddCookie(const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + virtual void AddCookie(const CanonicalCookie& cookie) OVERRIDE; virtual void UpdateCookieAccessTime( - const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + const CanonicalCookie& cookie) OVERRIDE; virtual void DeleteCookie( - const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + const CanonicalCookie& cookie) OVERRIDE; virtual void Flush(const base::Closure& callback) OVERRIDE; @@ -107,7 +107,7 @@ class MockPersistentCookieStore // Deferred result to use when Load() is called. bool load_return_value_; - std::vector<CookieMonster::CanonicalCookie*> load_result_; + std::vector<CanonicalCookie*> load_result_; // Indicates if the store has been fully loaded to avoid returning duplicate // cookies. bool loaded_; @@ -118,7 +118,7 @@ class MockPersistentCookieStore // Mock for CookieMonster::Delegate class MockCookieMonsterDelegate : public CookieMonster::Delegate { public: - typedef std::pair<CookieMonster::CanonicalCookie, bool> + typedef std::pair<CanonicalCookie, bool> CookieNotification; MockCookieMonsterDelegate(); @@ -128,7 +128,7 @@ class MockCookieMonsterDelegate : public CookieMonster::Delegate { void reset() { changes_.clear(); } virtual void OnCookieChanged( - const CookieMonster::CanonicalCookie& cookie, + const CanonicalCookie& cookie, bool removed, CookieMonster::Delegate::ChangeCause cause) OVERRIDE; @@ -141,17 +141,16 @@ class MockCookieMonsterDelegate : public CookieMonster::Delegate { }; // Helper to build a single CanonicalCookie. -CookieMonster::CanonicalCookie BuildCanonicalCookie( - const std::string& key, - const std::string& cookie_line, - const base::Time& creation_time); +CanonicalCookie BuildCanonicalCookie(const std::string& key, + const std::string& cookie_line, + const base::Time& creation_time); // Helper to build a list of CanonicalCookie*s. void AddCookieToList( const std::string& key, const std::string& cookie_line, const base::Time& creation_time, - std::vector<CookieMonster::CanonicalCookie*>* out_list); + std::vector<CanonicalCookie*>* out_list); // Just act like a backing database. Keep cookie information from // Add/Update/Delete and regurgitate it when Load is called. @@ -165,14 +164,11 @@ class MockSimplePersistentCookieStore virtual void LoadCookiesForKey(const std::string& key, const LoadedCallback& loaded_callback) OVERRIDE; - virtual void AddCookie( - const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + virtual void AddCookie(const CanonicalCookie& cookie) OVERRIDE; - virtual void UpdateCookieAccessTime( - const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + virtual void UpdateCookieAccessTime(const CanonicalCookie& cookie) OVERRIDE; - virtual void DeleteCookie( - const CookieMonster::CanonicalCookie& cookie) OVERRIDE; + virtual void DeleteCookie(const CanonicalCookie& cookie) OVERRIDE; virtual void Flush(const base::Closure& callback) OVERRIDE; @@ -182,8 +178,7 @@ class MockSimplePersistentCookieStore virtual ~MockSimplePersistentCookieStore(); private: - typedef std::map<int64, CookieMonster::CanonicalCookie> - CanonicalCookieMap; + typedef std::map<int64, CanonicalCookie> CanonicalCookieMap; CanonicalCookieMap cookies_; diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc index 7c04aeb..24636da 100644 --- a/net/cookies/cookie_monster_unittest.cc +++ b/net/cookies/cookie_monster_unittest.cc @@ -4,7 +4,6 @@ #include "net/cookies/cookie_store_unittest.h" -#include <time.h> #include <string> #include "base/basictypes.h" @@ -18,6 +17,7 @@ #include "base/threading/thread.h" #include "base/time.h" #include "googleurl/src/gurl.h" +#include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_monster_store_test.h" // For CookieStore mock #include "net/cookies/cookie_util.h" @@ -40,10 +40,9 @@ class NewMockPersistentCookieStore MOCK_METHOD1(Load, void(const LoadedCallback& loaded_callback)); MOCK_METHOD2(LoadCookiesForKey, void(const std::string& key, const LoadedCallback& loaded_callback)); - MOCK_METHOD1(AddCookie, void(const CookieMonster::CanonicalCookie& cc)); - MOCK_METHOD1(UpdateCookieAccessTime, - void(const CookieMonster::CanonicalCookie& cc)); - MOCK_METHOD1(DeleteCookie, void(const CookieMonster::CanonicalCookie& cc)); + MOCK_METHOD1(AddCookie, void(const CanonicalCookie& cc)); + MOCK_METHOD1(UpdateCookieAccessTime, void(const CanonicalCookie& cc)); + MOCK_METHOD1(DeleteCookie, void(const CanonicalCookie& cc)); MOCK_METHOD1(Flush, void(const base::Closure& callback)); MOCK_METHOD0(SetForceKeepSessionState, void()); @@ -190,8 +189,7 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> { return callback.num_deleted(); } - bool DeleteCanonicalCookie(CookieMonster*cm, - const CookieMonster::CanonicalCookie& cookie) { + bool DeleteCanonicalCookie(CookieMonster*cm, const CanonicalCookie& cookie) { DCHECK(cm); SetCookieCallback callback; cm->DeleteCanonicalCookieAsync( @@ -586,7 +584,7 @@ class DeferredCookieTaskTest : public CookieMonsterTest { testing::InSequence in_sequence_; // Holds cookies to be returned from PersistentCookieStore::Load or // PersistentCookieStore::LoadCookiesForKey. - std::vector<CookieMonster::CanonicalCookie*> loaded_cookies_; + std::vector<CanonicalCookie*> loaded_cookies_; // Stores the callback passed from the CookieMonster to the // PersistentCookieStore::Load CookieMonster::PersistentCookieStore::LoadedCallback loaded_callback_; @@ -809,8 +807,8 @@ TEST_F(DeferredCookieTaskTest, DeferredDeleteAllForHostCookies) { } TEST_F(DeferredCookieTaskTest, DeferredDeleteCanonicalCookie) { - std::vector<CookieMonster::CanonicalCookie*> cookies; - CookieMonster::CanonicalCookie cookie = BuildCanonicalCookie( + std::vector<CanonicalCookie*> cookies; + CanonicalCookie cookie = BuildCanonicalCookie( "www.google.com", "X=1; path=/", base::Time::Now()); MockDeleteCookieCallback delete_cookie_callback; @@ -881,94 +879,6 @@ TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) { CompleteLoadingAndWait(); } -TEST_F(CookieMonsterTest, TestCookieDateParsing) { - const struct { - const char* str; - const bool valid; - const time_t epoch; - } tests[] = { - { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 }, - { "Thu, 19-Apr-2007 16:00:00 GMT", true, 1176998400 }, - { "Wed, 25 Apr 2007 21:02:13 GMT", true, 1177534933 }, - { "Thu, 19/Apr\\2007 16:00:00 GMT", true, 1176998400 }, - { "Fri, 1 Jan 2010 01:01:50 GMT", true, 1262307710 }, - { "Wednesday, 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, - { ", 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, - { " 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, - { "1-Jan-2003 00:00:00 GMT", true, 1041379200 }, - { "Wed,18-Apr-07 22:50:12 GMT", true, 1176936612 }, - { "WillyWonka , 18-Apr-07 22:50:12 GMT", true, 1176936612 }, - { "WillyWonka , 18-Apr-07 22:50:12", true, 1176936612 }, - { "WillyWonka , 18-apr-07 22:50:12", true, 1176936612 }, - { "Mon, 18-Apr-1977 22:50:13 GMT", true, 230251813 }, - { "Mon, 18-Apr-77 22:50:13 GMT", true, 230251813 }, - // If the cookie came in with the expiration quoted (which in terms of - // the RFC you shouldn't do), we will get string quoted. Bug 1261605. - { "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", true, 1492290082 }, - // Test with full month names and partial names. - { "Partyday, 18- April-07 22:50:12", true, 1176936612 }, - { "Partyday, 18 - Apri-07 22:50:12", true, 1176936612 }, - { "Wednes, 1-Januar-2003 00:00:00 GMT", true, 1041379200 }, - // Test that we always take GMT even with other time zones or bogus - // values. The RFC says everything should be GMT, and in the worst case - // we are 24 hours off because of zone issues. - { "Sat, 15-Apr-17 21:01:22", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 GMT-2", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 GMT BLAH", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 GMT-0400", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)",true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 DST", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 -0400", true, 1492290082 }, - { "Sat, 15-Apr-17 21:01:22 (hello there)", true, 1492290082 }, - // Test that if we encounter multiple : fields, that we take the first - // that correctly parses. - { "Sat, 15-Apr-17 21:01:22 11:22:33", true, 1492290082 }, - { "Sat, 15-Apr-17 ::00 21:01:22", true, 1492290082 }, - { "Sat, 15-Apr-17 boink:z 21:01:22", true, 1492290082 }, - // We take the first, which in this case is invalid. - { "Sat, 15-Apr-17 91:22:33 21:01:22", false, 0 }, - // amazon.com formats their cookie expiration like this. - { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 }, - // Test that hh:mm:ss can occur anywhere. - { "22:50:12 Thu Apr 18 2007 GMT", true, 1176936612 }, - { "Thu 22:50:12 Apr 18 2007 GMT", true, 1176936612 }, - { "Thu Apr 22:50:12 18 2007 GMT", true, 1176936612 }, - { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 }, - { "Thu Apr 18 2007 22:50:12 GMT", true, 1176936612 }, - { "Thu Apr 18 2007 GMT 22:50:12", true, 1176936612 }, - // Test that the day and year can be anywhere if they are unambigious. - { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 }, - { "15-Sat, Apr-17 21:01:22 GMT", true, 1492290082 }, - { "15-Sat, Apr 21:01:22 GMT 17", true, 1492290082 }, - { "15-Sat, Apr 21:01:22 GMT 2017", true, 1492290082 }, - { "15 Apr 21:01:22 2017", true, 1492290082 }, - { "15 17 Apr 21:01:22", true, 1492290082 }, - { "Apr 15 17 21:01:22", true, 1492290082 }, - { "Apr 15 21:01:22 17", true, 1492290082 }, - { "2017 April 15 21:01:22", true, 1492290082 }, - { "15 April 2017 21:01:22", true, 1492290082 }, - // Some invalid dates - { "98 April 17 21:01:22", false, 0 }, - { "Thu, 012-Aug-2008 20:49:07 GMT", false, 0 }, - { "Thu, 12-Aug-31841 20:49:07 GMT", false, 0 }, - { "Thu, 12-Aug-9999999999 20:49:07 GMT", false, 0 }, - { "Thu, 999999999999-Aug-2007 20:49:07 GMT", false, 0 }, - { "Thu, 12-Aug-2007 20:61:99999999999 GMT", false, 0 }, - { "IAintNoDateFool", false, 0 }, - }; - - Time parsed_time; - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { - parsed_time = CookieMonster::ParseCookieTime(tests[i].str); - if (!tests[i].valid) { - EXPECT_FALSE(!parsed_time.is_null()) << tests[i].str; - continue; - } - EXPECT_TRUE(!parsed_time.is_null()) << tests[i].str; - EXPECT_EQ(tests[i].epoch, parsed_time.ToTimeT()) << tests[i].str; - } -} - TEST_F(CookieMonsterTest, TestCookieDeleteAll) { scoped_refptr<MockPersistentCookieStore> store( new MockPersistentCookieStore); @@ -1292,7 +1202,7 @@ TEST_F(CookieMonsterTest, DontImportDuplicateCookies) { // be careful not to have any duplicate creation times at all (as it's a // violation of a CookieMonster invariant) even if Time::Now() doesn't // move between calls. - std::vector<CookieMonster::CanonicalCookie*> initial_cookies; + std::vector<CanonicalCookie*> initial_cookies; // Insert 4 cookies with name "X" on path "/", with varying creation // dates. We expect only the most recent one to be preserved following @@ -1377,7 +1287,7 @@ TEST_F(CookieMonsterTest, DontImportDuplicateCreationTimes) { // four with the earlier time as creation times. We should only get // two cookies remaining, but which two (other than that there should // be one from each set) will be random. - std::vector<CookieMonster::CanonicalCookie*> initial_cookies; + std::vector<CanonicalCookie*> initial_cookies; AddCookieToList("www.google.com", "X=1; path=/", now, &initial_cookies); AddCookieToList("www.google.com", "X=2; path=/", now, &initial_cookies); AddCookieToList("www.google.com", "X=3; path=/", now, &initial_cookies); @@ -1658,7 +1568,7 @@ TEST_F(CookieMonsterTest, UniqueCreationTime) { // Now we check CookieList cookie_list(GetAllCookies(cm)); - typedef std::map<int64, CookieMonster::CanonicalCookie> TimeCookieMap; + typedef std::map<int64, CanonicalCookie> TimeCookieMap; TimeCookieMap check_map; for (CookieList::const_iterator it = cookie_list.begin(); it != cookie_list.end(); it++) { @@ -1758,7 +1668,7 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { for (int output_index = 0; output_index < 2; output_index++) { int input_index = output_index * 2; const CookiesInputInfo* input = &input_info[input_index]; - const CookieMonster::CanonicalCookie* output = &cookies[output_index]; + const CanonicalCookie* output = &cookies[output_index]; EXPECT_EQ(input->name, output->Name()); EXPECT_EQ(input->value, output->Value()); @@ -1933,7 +1843,7 @@ class FlushablePersistentStore : public CookieMonster::PersistentCookieStore { FlushablePersistentStore() : flush_count_(0) {} void Load(const LoadedCallback& loaded_callback) { - std::vector<CookieMonster::CanonicalCookie*> out_cookies; + std::vector<CanonicalCookie*> out_cookies; MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&net::LoadedCallbackTask::Run, new net::LoadedCallbackTask(loaded_callback, out_cookies))); @@ -1944,9 +1854,9 @@ class FlushablePersistentStore : public CookieMonster::PersistentCookieStore { Load(loaded_callback); } - void AddCookie(const CookieMonster::CanonicalCookie&) {} - void UpdateCookieAccessTime(const CookieMonster::CanonicalCookie&) {} - void DeleteCookie(const CookieMonster::CanonicalCookie&) {} + void AddCookie(const CanonicalCookie&) {} + void UpdateCookieAccessTime(const CanonicalCookie&) {} + void DeleteCookie(const CanonicalCookie&) {} void SetForceKeepSessionState() {} void Flush(const base::Closure& callback) { @@ -2039,36 +1949,6 @@ TEST_F(CookieMonsterTest, FlushStore) { ASSERT_EQ(3, counter->callback_count()); } -TEST_F(CookieMonsterTest, GetCookieSourceFromURL) { - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com/"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com/test"))); - EXPECT_EQ("file:///tmp/test.html", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("file:///tmp/test.html"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com:1234/"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("https://example.com/"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://user:pwd@example.com/"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com/test?foo"))); - EXPECT_EQ("http://example.com/", - CookieMonster::CanonicalCookie::GetCookieSourceFromURL( - GURL("http://example.com/test#foo"))); -} - TEST_F(CookieMonsterTest, HistogramCheck) { scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL)); // Should match call in InitializeHistograms, but doesn't really matter @@ -2170,7 +2050,7 @@ class MultiThreadedCookieMonsterTest : public CookieMonsterTest { } void DeleteCanonicalCookieTask(CookieMonster* cm, - const CookieMonster::CanonicalCookie& cookie, + const CanonicalCookie& cookie, SetCookieCallback* callback) { cm->DeleteCanonicalCookieAsync( cookie, @@ -2336,8 +2216,7 @@ TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckDeleteCanonicalCookie) { TEST_F(CookieMonsterTest, InvalidExpiryTime) { ParsedCookie pc(std::string(kValidCookieLine) + "; expires=Blarg arg arg"); - scoped_ptr<CookieMonster::CanonicalCookie> cookie( - CookieMonster::CanonicalCookie::Create(url_google_, pc)); + scoped_ptr<CanonicalCookie> cookie(CanonicalCookie::Create(url_google_, pc)); #if defined(ENABLE_PERSISTENT_SESSION_COOKIES) ASSERT_TRUE(cookie->IsPersistent()); diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc index 6e1833f..bd1019d 100644 --- a/net/cookies/cookie_util.cc +++ b/net/cookies/cookie_util.cc @@ -4,7 +4,13 @@ #include "net/cookies/cookie_util.h" +#include <cstdio> +#include <cstdlib> + #include "base/logging.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "build/build_config.h" #include "googleurl/src/gurl.h" #include "net/base/net_util.h" #include "net/base/registry_controlled_domain.h" @@ -74,6 +80,124 @@ bool GetCookieDomainWithString(const GURL& url, return true; } +// Parse a cookie expiration time. We try to be lenient, but we need to +// assume some order to distinguish the fields. The basic rules: +// - The month name must be present and prefix the first 3 letters of the +// full month name (jan for January, jun for June). +// - If the year is <= 2 digits, it must occur after the day of month. +// - The time must be of the format hh:mm:ss. +// An average cookie expiration will look something like this: +// Sat, 15-Apr-17 21:01:22 GMT +base::Time ParseCookieTime(const std::string& time_string) { + static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" }; + static const int kMonthsLen = arraysize(kMonths); + // We want to be pretty liberal, and support most non-ascii and non-digit + // characters as a delimiter. We can't treat : as a delimiter, because it + // is the delimiter for hh:mm:ss, and we want to keep this field together. + // We make sure to include - and +, since they could prefix numbers. + // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes + // will be preserved, and we will get them here. So we make sure to include + // quote characters, and also \ for anything that was internally escaped. + static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; + + base::Time::Exploded exploded = {0}; + + StringTokenizer tokenizer(time_string, kDelimiters); + + bool found_day_of_month = false; + bool found_month = false; + bool found_time = false; + bool found_year = false; + + while (tokenizer.GetNext()) { + const std::string token = tokenizer.token(); + DCHECK(!token.empty()); + bool numerical = IsAsciiDigit(token[0]); + + // String field + if (!numerical) { + if (!found_month) { + for (int i = 0; i < kMonthsLen; ++i) { + // Match prefix, so we could match January, etc + if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { + exploded.month = i + 1; + found_month = true; + break; + } + } + } else { + // If we've gotten here, it means we've already found and parsed our + // month, and we have another string, which we would expect to be the + // the time zone name. According to the RFC and my experiments with + // how sites format their expirations, we don't have much of a reason + // to support timezones. We don't want to ever barf on user input, + // but this DCHECK should pass for well-formed data. + // DCHECK(token == "GMT"); + } + // Numeric field w/ a colon + } else if (token.find(':') != std::string::npos) { + if (!found_time && +#ifdef COMPILER_MSVC + sscanf_s( +#else + sscanf( +#endif + token.c_str(), "%2u:%2u:%2u", &exploded.hour, + &exploded.minute, &exploded.second) == 3) { + found_time = true; + } else { + // We should only ever encounter one time-like thing. If we're here, + // it means we've found a second, which shouldn't happen. We keep + // the first. This check should be ok for well-formed input: + // NOTREACHED(); + } + // Numeric field + } else { + // Overflow with atoi() is unspecified, so we enforce a max length. + if (!found_day_of_month && token.length() <= 2) { + exploded.day_of_month = atoi(token.c_str()); + found_day_of_month = true; + } else if (!found_year && token.length() <= 5) { + exploded.year = atoi(token.c_str()); + found_year = true; + } else { + // If we're here, it means we've either found an extra numeric field, + // or a numeric field which was too long. For well-formed input, the + // following check would be reasonable: + // NOTREACHED(); + } + } + } + + if (!found_day_of_month || !found_month || !found_time || !found_year) { + // We didn't find all of the fields we need. For well-formed input, the + // following check would be reasonable: + // NOTREACHED() << "Cookie parse expiration failed: " << time_string; + return base::Time(); + } + + // Normalize the year to expand abbreviated years to the full year. + if (exploded.year >= 69 && exploded.year <= 99) + exploded.year += 1900; + if (exploded.year >= 0 && exploded.year <= 68) + exploded.year += 2000; + + // If our values are within their correct ranges, we got our time. + if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && + exploded.month >= 1 && exploded.month <= 12 && + exploded.year >= 1601 && exploded.year <= 30827 && + exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { + return base::Time::FromUTCExploded(exploded); + } + + // One of our values was out of expected range. For well-formed input, + // the following check would be reasonable: + // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; + + return base::Time(); +} + } // namespace cookie_utils } // namespace net diff --git a/net/cookies/cookie_util.h b/net/cookies/cookie_util.h index 39cf786..1475cd5 100644 --- a/net/cookies/cookie_util.h +++ b/net/cookies/cookie_util.h @@ -7,6 +7,7 @@ #include <string> +#include "base/time.h" #include "net/base/net_export.h" class GURL; @@ -33,6 +34,9 @@ NET_EXPORT bool GetCookieDomainWithString(const GURL& url, // i.e. it doesn't begin with a leading '.' character. NET_EXPORT bool DomainIsHostOnly(const std::string& domain_string); +// Parses the string with the cookie time (very forgivingly). +NET_EXPORT base::Time ParseCookieTime(const std::string& time_string); + } // namspace cookie_util } // namespace net diff --git a/net/cookies/cookie_util_unittest.cc b/net/cookies/cookie_util_unittest.cc index 5d2a420..f297afd 100644 --- a/net/cookies/cookie_util_unittest.cc +++ b/net/cookies/cookie_util_unittest.cc @@ -21,3 +21,91 @@ TEST(CookieUtilTest, TestDomainIsHostOnly) { net::cookie_util::DomainIsHostOnly(tests[i].str)); } } + +TEST(CookieUtilTest, TestCookieDateParsing) { + const struct { + const char* str; + const bool valid; + const time_t epoch; + } tests[] = { + { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 }, + { "Thu, 19-Apr-2007 16:00:00 GMT", true, 1176998400 }, + { "Wed, 25 Apr 2007 21:02:13 GMT", true, 1177534933 }, + { "Thu, 19/Apr\\2007 16:00:00 GMT", true, 1176998400 }, + { "Fri, 1 Jan 2010 01:01:50 GMT", true, 1262307710 }, + { "Wednesday, 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, + { ", 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, + { " 1-Jan-2003 00:00:00 GMT", true, 1041379200 }, + { "1-Jan-2003 00:00:00 GMT", true, 1041379200 }, + { "Wed,18-Apr-07 22:50:12 GMT", true, 1176936612 }, + { "WillyWonka , 18-Apr-07 22:50:12 GMT", true, 1176936612 }, + { "WillyWonka , 18-Apr-07 22:50:12", true, 1176936612 }, + { "WillyWonka , 18-apr-07 22:50:12", true, 1176936612 }, + { "Mon, 18-Apr-1977 22:50:13 GMT", true, 230251813 }, + { "Mon, 18-Apr-77 22:50:13 GMT", true, 230251813 }, + // If the cookie came in with the expiration quoted (which in terms of + // the RFC you shouldn't do), we will get string quoted. Bug 1261605. + { "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", true, 1492290082 }, + // Test with full month names and partial names. + { "Partyday, 18- April-07 22:50:12", true, 1176936612 }, + { "Partyday, 18 - Apri-07 22:50:12", true, 1176936612 }, + { "Wednes, 1-Januar-2003 00:00:00 GMT", true, 1041379200 }, + // Test that we always take GMT even with other time zones or bogus + // values. The RFC says everything should be GMT, and in the worst case + // we are 24 hours off because of zone issues. + { "Sat, 15-Apr-17 21:01:22", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 GMT-2", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 GMT BLAH", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 GMT-0400", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)",true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 DST", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 -0400", true, 1492290082 }, + { "Sat, 15-Apr-17 21:01:22 (hello there)", true, 1492290082 }, + // Test that if we encounter multiple : fields, that we take the first + // that correctly parses. + { "Sat, 15-Apr-17 21:01:22 11:22:33", true, 1492290082 }, + { "Sat, 15-Apr-17 ::00 21:01:22", true, 1492290082 }, + { "Sat, 15-Apr-17 boink:z 21:01:22", true, 1492290082 }, + // We take the first, which in this case is invalid. + { "Sat, 15-Apr-17 91:22:33 21:01:22", false, 0 }, + // amazon.com formats their cookie expiration like this. + { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 }, + // Test that hh:mm:ss can occur anywhere. + { "22:50:12 Thu Apr 18 2007 GMT", true, 1176936612 }, + { "Thu 22:50:12 Apr 18 2007 GMT", true, 1176936612 }, + { "Thu Apr 22:50:12 18 2007 GMT", true, 1176936612 }, + { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 }, + { "Thu Apr 18 2007 22:50:12 GMT", true, 1176936612 }, + { "Thu Apr 18 2007 GMT 22:50:12", true, 1176936612 }, + // Test that the day and year can be anywhere if they are unambigious. + { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 }, + { "15-Sat, Apr-17 21:01:22 GMT", true, 1492290082 }, + { "15-Sat, Apr 21:01:22 GMT 17", true, 1492290082 }, + { "15-Sat, Apr 21:01:22 GMT 2017", true, 1492290082 }, + { "15 Apr 21:01:22 2017", true, 1492290082 }, + { "15 17 Apr 21:01:22", true, 1492290082 }, + { "Apr 15 17 21:01:22", true, 1492290082 }, + { "Apr 15 21:01:22 17", true, 1492290082 }, + { "2017 April 15 21:01:22", true, 1492290082 }, + { "15 April 2017 21:01:22", true, 1492290082 }, + // Some invalid dates + { "98 April 17 21:01:22", false, 0 }, + { "Thu, 012-Aug-2008 20:49:07 GMT", false, 0 }, + { "Thu, 12-Aug-31841 20:49:07 GMT", false, 0 }, + { "Thu, 12-Aug-9999999999 20:49:07 GMT", false, 0 }, + { "Thu, 999999999999-Aug-2007 20:49:07 GMT", false, 0 }, + { "Thu, 12-Aug-2007 20:61:99999999999 GMT", false, 0 }, + { "IAintNoDateFool", false, 0 }, + }; + + base::Time parsed_time; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + parsed_time = net::cookie_util::ParseCookieTime(tests[i].str); + if (!tests[i].valid) { + EXPECT_FALSE(!parsed_time.is_null()) << tests[i].str; + continue; + } + EXPECT_TRUE(!parsed_time.is_null()) << tests[i].str; + EXPECT_EQ(tests[i].epoch, parsed_time.ToTimeT()) << tests[i].str; + } +} |