| Index: net/cookies/cookie_monster.cc
|
| diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
|
| index 9851c9a30fe7f15d9d5a1dd16a53acf1dfbe1a7f..aa58f6e1e290f9624e5a5b524d7215c9010d8b87 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.
|
| @@ -132,8 +130,7 @@ const int kPersistentSessionCookieExpiryInDays = 14;
|
| // 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 +196,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 +282,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 +329,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 +615,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 +629,7 @@ class CookieMonster::DeleteCanonicalCookieTask
|
| virtual ~DeleteCanonicalCookieTask() {}
|
|
|
| private:
|
| - CookieMonster::CanonicalCookie cookie_;
|
| + CanonicalCookie cookie_;
|
| CookieMonster::DeleteCookieCallback callback_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(DeleteCanonicalCookieTask);
|
| @@ -1211,14 +1028,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 +1731,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 +2159,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
|
|
|