blob: e79cf47664a27fee1f68dd072cc1439a210a2356 [file] [log] [blame]
[email protected]297a4ed02010-02-12 08:12:521// Copyright (c) 2010 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit586acc5fe2008-07-26 22:42:524
5// Portions of this code based on Mozilla:
6// (netwerk/cookie/src/nsCookieService.cpp)
7/* ***** BEGIN LICENSE BLOCK *****
8 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
9 *
10 * The contents of this file are subject to the Mozilla Public License Version
11 * 1.1 (the "License"); you may not use this file except in compliance with
12 * the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS" basis,
16 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
17 * for the specific language governing rights and limitations under the
18 * License.
19 *
20 * The Original Code is mozilla.org code.
21 *
22 * The Initial Developer of the Original Code is
23 * Netscape Communications Corporation.
24 * Portions created by the Initial Developer are Copyright (C) 2003
25 * the Initial Developer. All Rights Reserved.
26 *
27 * Contributor(s):
28 * Daniel Witte ([email protected])
29 * Michiel van Leeuwen ([email protected])
30 *
31 * Alternatively, the contents of this file may be used under the terms of
32 * either the GNU General Public License Version 2 or later (the "GPL"), or
33 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 * in which case the provisions of the GPL or the LGPL are applicable instead
35 * of those above. If you wish to allow use of your version of this file only
36 * under the terms of either the GPL or the LGPL, and not to allow others to
37 * use your version of this file under the terms of the MPL, indicate your
38 * decision by deleting the provisions above and replace them with the notice
39 * and other provisions required by the GPL or the LGPL. If you do not delete
40 * the provisions above, a recipient may use your version of this file under
41 * the terms of any one of the MPL, the GPL or the LGPL.
42 *
43 * ***** END LICENSE BLOCK ***** */
44
45#include "net/base/cookie_monster.h"
46
47#include <algorithm>
48
49#include "base/basictypes.h"
[email protected]dce5df52009-06-29 17:58:2550#include "base/format_macros.h"
initial.commit586acc5fe2008-07-26 22:42:5251#include "base/logging.h"
52#include "base/scoped_ptr.h"
53#include "base/string_tokenizer.h"
54#include "base/string_util.h"
55#include "googleurl/src/gurl.h"
[email protected]f325f1e12010-04-30 22:38:5556#include "googleurl/src/url_canon.h"
initial.commit586acc5fe2008-07-26 22:42:5257#include "net/base/net_util.h"
58#include "net/base/registry_controlled_domain.h"
59
60// #define COOKIE_LOGGING_ENABLED
61#ifdef COOKIE_LOGGING_ENABLED
62#define COOKIE_DLOG(severity) DLOG_IF(INFO, 1)
63#else
64#define COOKIE_DLOG(severity) DLOG_IF(INFO, 0)
65#endif
66
[email protected]e1acf6f2008-10-27 20:43:3367using base::Time;
68using base::TimeDelta;
69
[email protected]c4058fb2010-06-22 17:25:2670static const int kMinutesInTenYears = 10 * 365 * 24 * 60;
71
[email protected]8ac1a752008-07-31 19:40:3772namespace net {
73
[email protected]297a4ed02010-02-12 08:12:5274namespace {
75
[email protected]e32306c52008-11-06 16:59:0576// Cookie garbage collection thresholds. Based off of the Mozilla defaults.
77// It might seem scary to have a high purge value, but really it's not. You
78// just make sure that you increase the max to cover the increase in purge,
79// and we would have been purging the same amount of cookies. We're just
80// going through the garbage collection process less often.
[email protected]297a4ed02010-02-12 08:12:5281const size_t kNumCookiesPerHost = 70; // ~50 cookies
82const size_t kNumCookiesPerHostPurge = 20;
83const size_t kNumCookiesTotal = 3300; // ~3000 cookies
84const size_t kNumCookiesTotalPurge = 300;
[email protected]e32306c52008-11-06 16:59:0585
[email protected]77e0a462008-11-01 00:43:3586// Default minimum delay after updating a cookie's LastAccessDate before we
87// will update it again.
[email protected]297a4ed02010-02-12 08:12:5288const int kDefaultAccessUpdateThresholdSeconds = 60;
89
90// Comparator to sort cookies from highest creation date to lowest
91// creation date.
92struct OrderByCreationTimeDesc {
93 bool operator()(const CookieMonster::CookieMap::iterator& a,
94 const CookieMonster::CookieMap::iterator& b) const {
95 return a->second->CreationDate() > b->second->CreationDate();
96 }
97};
98
99} // namespace
[email protected]77e0a462008-11-01 00:43:35100
[email protected]8ac1a752008-07-31 19:40:37101// static
102bool CookieMonster::enable_file_scheme_ = false;
initial.commit586acc5fe2008-07-26 22:42:52103
104// static
105void CookieMonster::EnableFileScheme() {
106 enable_file_scheme_ = true;
107}
108
[email protected]0f7066e2010-03-25 08:31:47109CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate)
initial.commit586acc5fe2008-07-26 22:42:52110 : initialized_(false),
[email protected]77e0a462008-11-01 00:43:35111 store_(store),
112 last_access_threshold_(
[email protected]0f7066e2010-03-25 08:31:47113 TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)),
[email protected]c4058fb2010-06-22 17:25:26114 delegate_(delegate),
115 last_statistic_record_time_(Time::Now()) {
[email protected]374f58b2010-07-20 15:29:26116 InitializeHistograms();
[email protected]47accfd62009-05-14 18:46:21117 SetDefaultCookieableSchemes();
initial.commit586acc5fe2008-07-26 22:42:52118}
119
120CookieMonster::~CookieMonster() {
121 DeleteAll(false);
122}
123
[email protected]374f58b2010-07-20 15:29:26124// Initialize all histogram counter variables used in this class.
125//
126// Normal histogram usage involves using the macros defined in
127// histogram.h, which automatically takes care of declaring these
128// variables (as statics), initializing them, and accumulating into
129// them, all from a single entry point. Unfortunately, that solution
130// doesn't work for the CookieMonster, as it's vulnerable to races between
131// separate threads executing the same functions and hence initializing the
132// same static variables. There isn't a race danger in the histogram
133// accumulation calls; they are written to be resilient to simultaneous
134// calls from multiple threads.
135//
136// The solution taken here is to have per-CookieMonster instance
137// variables that are constructed during CookieMonster construction.
138// Note that these variables refer to the same underlying histogram,
139// so we still race (but safely) with other CookieMonster instances
140// for accumulation.
141//
142// To do this we've expanded out the individual histogram macros calls,
143// with declarations of the variables in the class decl, initialization here
144// (done from the class constructor) and direct calls to the accumulation
145// methods where needed. The specific histogram macro calls on which the
146// initialization is based are included in comments below.
147void CookieMonster::InitializeHistograms() {
148 // From UMA_HISTOGRAM_CUSTOM_COUNTS
149 histogram_expiration_duration_minutes_ = Histogram::FactoryGet(
150 "net.CookieExpirationDurationMinutes",
151 1, kMinutesInTenYears, 50,
152 Histogram::kUmaTargetedHistogramFlag);
153 histogram_between_access_interval_minutes_ = Histogram::FactoryGet(
154 "net.CookieBetweenAccessIntervalMinutes",
155 1, kMinutesInTenYears, 50,
156 Histogram::kUmaTargetedHistogramFlag);
157 histogram_evicted_last_access_minutes_ = Histogram::FactoryGet(
158 "net.CookieEvictedLastAccessMinutes",
159 1, kMinutesInTenYears, 50,
160 Histogram::kUmaTargetedHistogramFlag);
161 histogram_count_ = Histogram::FactoryGet(
162 "net.CookieCount", 1, 4000, 50,
163 Histogram::kUmaTargetedHistogramFlag);
164
165 // From UMA_HISTOGRAM_COUNTS_10000 & UMA_HISTOGRAM_CUSTOM_COUNTS
166 histogram_number_duplicate_db_cookies_ = Histogram::FactoryGet(
167 "Net.NumDuplicateCookiesInDb", 1, 10000, 50,
168 Histogram::kUmaTargetedHistogramFlag);
169
170 // From UMA_HISTOGRAM_ENUMERATION
171 histogram_cookie_deletion_cause_ = LinearHistogram::FactoryGet(
172 "net.CookieDeletionCause", 1,
173 DELETE_COOKIE_LAST_ENTRY, DELETE_COOKIE_LAST_ENTRY + 1,
174 Histogram::kUmaTargetedHistogramFlag);
175}
176
initial.commit586acc5fe2008-07-26 22:42:52177void CookieMonster::InitStore() {
178 DCHECK(store_) << "Store must exist to initialize";
179
180 // Initialize the store and sync in any saved persistent cookies. We don't
181 // care if it's expired, insert it so it can be garbage collected, removed,
182 // and sync'd.
183 std::vector<KeyedCanonicalCookie> cookies;
[email protected]e32306c52008-11-06 16:59:05184 // Reserve space for the maximum amount of cookies a database should have.
185 // This prevents multiple vector growth / copies as we append cookies.
186 cookies.reserve(kNumCookiesTotal);
initial.commit586acc5fe2008-07-26 22:42:52187 store_->Load(&cookies);
188 for (std::vector<KeyedCanonicalCookie>::const_iterator it = cookies.begin();
189 it != cookies.end(); ++it) {
190 InternalInsertCookie(it->first, it->second, false);
191 }
[email protected]297a4ed02010-02-12 08:12:52192
193 // After importing cookies from the PersistentCookieStore, verify that
194 // none of our constraints are violated.
195 //
196 // In particular, the backing store might have given us duplicate cookies.
197 EnsureCookiesMapIsValid();
198}
199
200void CookieMonster::EnsureCookiesMapIsValid() {
[email protected]bb8905722010-05-21 17:29:04201 lock_.AssertAcquired();
202
[email protected]297a4ed02010-02-12 08:12:52203 int num_duplicates_trimmed = 0;
204
205 // Iterate through all the of the cookies, grouped by host.
206 CookieMap::iterator prev_range_end = cookies_.begin();
207 while (prev_range_end != cookies_.end()) {
208 CookieMap::iterator cur_range_begin = prev_range_end;
209 const std::string key = cur_range_begin->first; // Keep a copy.
210 CookieMap::iterator cur_range_end = cookies_.upper_bound(key);
211 prev_range_end = cur_range_end;
212
213 // Ensure no equivalent cookies for this host.
214 num_duplicates_trimmed +=
215 TrimDuplicateCookiesForHost(key, cur_range_begin, cur_range_end);
216 }
217
218 // Record how many duplicates were found in the database.
[email protected]374f58b2010-07-20 15:29:26219 // See InitializeHistograms() for details.
220 histogram_cookie_deletion_cause_->Add(num_duplicates_trimmed);
[email protected]297a4ed02010-02-12 08:12:52221
222 // TODO(eroman): Should also verify that there are no cookies with the same
223 // creation time, since that is assumed to be unique by the rest of the code.
224}
225
226// Our strategy to find duplicates is:
227// (1) Build a map from (cookiename, cookiepath) to
228// {list of cookies with this signature, sorted by creation time}.
229// (2) For each list with more than 1 entry, keep the cookie having the
230// most recent creation time, and delete the others.
[email protected]1655ba342010-07-14 18:17:42231namespace {
232// Two cookies are considered equivalent if they have the same domain,
233// name, and path.
234struct CookieSignature {
235 public:
236 CookieSignature(const std::string& name, const std::string& domain,
237 const std::string& path)
238 : name(name),
239 domain(domain),
240 path(path) {}
241
242 // To be a key for a map this class needs to be assignable, copyable,
243 // and have an operator<. The default assignment operator
244 // and copy constructor are exactly what we want.
245
246 bool operator<(const CookieSignature& cs) const {
247 // Name compare dominates, then domain, then path.
248 int diff = name.compare(cs.name);
249 if (diff != 0)
250 return diff < 0;
251
252 diff = domain.compare(cs.domain);
253 if (diff != 0)
254 return diff < 0;
255
256 return path.compare(cs.path) < 0;
257 }
258
259 std::string name;
260 std::string domain;
261 std::string path;
262};
263}
264
[email protected]297a4ed02010-02-12 08:12:52265int CookieMonster::TrimDuplicateCookiesForHost(
266 const std::string& key,
267 CookieMap::iterator begin,
268 CookieMap::iterator end) {
[email protected]bb8905722010-05-21 17:29:04269 lock_.AssertAcquired();
[email protected]297a4ed02010-02-12 08:12:52270
[email protected]65781e92010-07-21 15:29:57271 // Set of cookies ordered by creation time.
272 typedef std::set<CookieMap::iterator, OrderByCreationTimeDesc> CookieSet;
[email protected]297a4ed02010-02-12 08:12:52273
274 // Helper map we populate to find the duplicates.
[email protected]65781e92010-07-21 15:29:57275 typedef std::map<CookieSignature, CookieSet> EquivalenceMap;
[email protected]297a4ed02010-02-12 08:12:52276 EquivalenceMap equivalent_cookies;
277
278 // The number of duplicate cookies that have been found.
279 int num_duplicates = 0;
280
281 // Iterate through all of the cookies in our range, and insert them into
282 // the equivalence map.
283 for (CookieMap::iterator it = begin; it != end; ++it) {
284 DCHECK_EQ(key, it->first);
285 CanonicalCookie* cookie = it->second;
286
[email protected]1655ba342010-07-14 18:17:42287 CookieSignature signature(cookie->Name(), cookie->Domain(),
288 cookie->Path());
[email protected]65781e92010-07-21 15:29:57289 CookieSet& list = equivalent_cookies[signature];
[email protected]297a4ed02010-02-12 08:12:52290
291 // We found a duplicate!
292 if (!list.empty())
293 num_duplicates++;
294
295 // We save the iterator into |cookies_| rather than the actual cookie
296 // pointer, since we may need to delete it later.
297 list.insert(it);
298 }
299
300 // If there were no duplicates, we are done!
301 if (num_duplicates == 0)
302 return 0;
303
304 // Otherwise, delete all the duplicate cookies, both from our in-memory store
305 // and from the backing store.
306 for (EquivalenceMap::iterator it = equivalent_cookies.begin();
307 it != equivalent_cookies.end();
308 ++it) {
309 const CookieSignature& signature = it->first;
[email protected]65781e92010-07-21 15:29:57310 CookieSet& dupes = it->second;
[email protected]297a4ed02010-02-12 08:12:52311
312 if (dupes.size() <= 1)
313 continue; // This cookiename/path has no duplicates.
314
315 // Since |dups| is sorted by creation time (descending), the first cookie
316 // is the most recent one, so we will keep it. The rest are duplicates.
317 dupes.erase(dupes.begin());
318
319 LOG(ERROR) << StringPrintf("Found %d duplicate cookies for host='%s', "
[email protected]1655ba342010-07-14 18:17:42320 "with {name='%s', domain='%s', path='%s'}",
[email protected]297a4ed02010-02-12 08:12:52321 static_cast<int>(dupes.size()),
322 key.c_str(),
[email protected]1655ba342010-07-14 18:17:42323 signature.name.c_str(),
324 signature.domain.c_str(),
325 signature.path.c_str());
[email protected]297a4ed02010-02-12 08:12:52326
327 // Remove all the cookies identified by |dupes|. It is valid to delete our
328 // list of iterators one at a time, since |cookies_| is a multimap (they
329 // don't invalidate existing iterators following deletion).
[email protected]65781e92010-07-21 15:29:57330 for (CookieSet::iterator dupes_it = dupes.begin();
[email protected]297a4ed02010-02-12 08:12:52331 dupes_it != dupes.end();
332 ++dupes_it) {
[email protected]c4058fb2010-06-22 17:25:26333 InternalDeleteCookie(*dupes_it, true /*sync_to_store*/,
[email protected]2f3f3592010-07-07 20:11:51334 DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE);
[email protected]297a4ed02010-02-12 08:12:52335 }
336 }
337
338 return num_duplicates;
initial.commit586acc5fe2008-07-26 22:42:52339}
340
[email protected]47accfd62009-05-14 18:46:21341void CookieMonster::SetDefaultCookieableSchemes() {
342 // Note: file must be the last scheme.
343 static const char* kDefaultCookieableSchemes[] = { "http", "https", "file" };
344 int num_schemes = enable_file_scheme_ ? 3 : 2;
345 SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes);
346}
347
initial.commit586acc5fe2008-07-26 22:42:52348// The system resolution is not high enough, so we can have multiple
349// set cookies that result in the same system time. When this happens, we
350// increment by one Time unit. Let's hope computers don't get too fast.
351Time CookieMonster::CurrentTime() {
352 return std::max(Time::Now(),
353 Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1));
354}
355
356// Parse a cookie expiration time. We try to be lenient, but we need to
357// assume some order to distinguish the fields. The basic rules:
358// - The month name must be present and prefix the first 3 letters of the
359// full month name (jan for January, jun for June).
360// - If the year is <= 2 digits, it must occur after the day of month.
361// - The time must be of the format hh:mm:ss.
362// An average cookie expiration will look something like this:
363// Sat, 15-Apr-17 21:01:22 GMT
364Time CookieMonster::ParseCookieTime(const std::string& time_string) {
365 static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun",
366 "jul", "aug", "sep", "oct", "nov", "dec" };
367 static const int kMonthsLen = arraysize(kMonths);
368 // We want to be pretty liberal, and support most non-ascii and non-digit
369 // characters as a delimiter. We can't treat : as a delimiter, because it
370 // is the delimiter for hh:mm:ss, and we want to keep this field together.
371 // We make sure to include - and +, since they could prefix numbers.
372 // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes
373 // will be preserved, and we will get them here. So we make sure to include
374 // quote characters, and also \ for anything that was internally escaped.
375 static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
376
377 Time::Exploded exploded = {0};
378
379 StringTokenizer tokenizer(time_string, kDelimiters);
380
381 bool found_day_of_month = false;
382 bool found_month = false;
383 bool found_time = false;
384 bool found_year = false;
385
386 while (tokenizer.GetNext()) {
387 const std::string token = tokenizer.token();
388 DCHECK(!token.empty());
389 bool numerical = IsAsciiDigit(token[0]);
390
391 // String field
392 if (!numerical) {
393 if (!found_month) {
394 for (int i = 0; i < kMonthsLen; ++i) {
395 // Match prefix, so we could match January, etc
[email protected]7e3dcd92008-12-30 13:13:34396 if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) {
initial.commit586acc5fe2008-07-26 22:42:52397 exploded.month = i + 1;
398 found_month = true;
399 break;
400 }
401 }
402 } else {
403 // If we've gotten here, it means we've already found and parsed our
404 // month, and we have another string, which we would expect to be the
405 // the time zone name. According to the RFC and my experiments with
406 // how sites format their expirations, we don't have much of a reason
407 // to support timezones. We don't want to ever barf on user input,
408 // but this DCHECK should pass for well-formed data.
409 // DCHECK(token == "GMT");
410 }
411 // Numeric field w/ a colon
412 } else if (token.find(':') != std::string::npos) {
413 if (!found_time &&
[email protected]d862fd92008-08-21 18:15:35414#ifdef COMPILER_MSVC
415 sscanf_s(
416#else
417 sscanf(
418#endif
419 token.c_str(), "%2u:%2u:%2u", &exploded.hour,
420 &exploded.minute, &exploded.second) == 3) {
initial.commit586acc5fe2008-07-26 22:42:52421 found_time = true;
422 } else {
423 // We should only ever encounter one time-like thing. If we're here,
424 // it means we've found a second, which shouldn't happen. We keep
425 // the first. This check should be ok for well-formed input:
426 // NOTREACHED();
427 }
428 // Numeric field
429 } else {
430 // Overflow with atoi() is unspecified, so we enforce a max length.
431 if (!found_day_of_month && token.length() <= 2) {
432 exploded.day_of_month = atoi(token.c_str());
433 found_day_of_month = true;
434 } else if (!found_year && token.length() <= 5) {
435 exploded.year = atoi(token.c_str());
436 found_year = true;
437 } else {
438 // If we're here, it means we've either found an extra numeric field,
439 // or a numeric field which was too long. For well-formed input, the
440 // following check would be reasonable:
441 // NOTREACHED();
442 }
443 }
444 }
445
446 if (!found_day_of_month || !found_month || !found_time || !found_year) {
447 // We didn't find all of the fields we need. For well-formed input, the
448 // following check would be reasonable:
449 // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
450 return Time();
451 }
452
453 // Normalize the year to expand abbreviated years to the full year.
454 if (exploded.year >= 69 && exploded.year <= 99)
455 exploded.year += 1900;
456 if (exploded.year >= 0 && exploded.year <= 68)
457 exploded.year += 2000;
458
459 // If our values are within their correct ranges, we got our time.
460 if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 &&
461 exploded.month >= 1 && exploded.month <= 12 &&
462 exploded.year >= 1601 && exploded.year <= 30827 &&
463 exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) {
464 return Time::FromUTCExploded(exploded);
465 }
466
467 // One of our values was out of expected range. For well-formed input,
468 // the following check would be reasonable:
469 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
470
471 return Time();
472}
473
[email protected]f325f1e12010-04-30 22:38:55474bool CookieMonster::DomainIsHostOnly(const std::string& domain_string) {
475 return (domain_string.empty() || domain_string[0] != '.');
476}
477
[email protected]69bb5872010-01-12 20:33:52478// Returns the effective TLD+1 for a given host. This only makes sense for http
479// and https schemes. For other schemes, the host will be returned unchanged
480// (minus any leading .).
481static std::string GetEffectiveDomain(const std::string& scheme,
482 const std::string& host) {
483 if (scheme == "http" || scheme == "https")
484 return RegistryControlledDomainService::GetDomainAndRegistry(host);
485
[email protected]f325f1e12010-04-30 22:38:55486 if (!CookieMonster::DomainIsHostOnly(host))
[email protected]69bb5872010-01-12 20:33:52487 return host.substr(1);
488 return host;
489}
490
[email protected]f325f1e12010-04-30 22:38:55491// Determine the cookie domain key to use for setting a cookie with the
492// specified domain attribute string.
initial.commit586acc5fe2008-07-26 22:42:52493// On success returns true, and sets cookie_domain_key to either a
494// -host cookie key (ex: "google.com")
495// -domain cookie key (ex: ".google.com")
[email protected]f325f1e12010-04-30 22:38:55496static bool GetCookieDomainKeyWithString(const GURL& url,
497 const std::string& domain_string,
498 std::string* cookie_domain_key) {
initial.commit586acc5fe2008-07-26 22:42:52499 const std::string url_host(url.host());
[email protected]c3a756b62009-01-23 10:50:51500
[email protected]f325f1e12010-04-30 22:38:55501 // If no domain was specified in the domain string, default to a host cookie.
[email protected]c3a756b62009-01-23 10:50:51502 // We match IE/Firefox in allowing a domain=IPADDR if it matches the url
503 // ip address hostname exactly. It should be treated as a host cookie.
[email protected]f325f1e12010-04-30 22:38:55504 if (domain_string.empty() ||
505 (url.HostIsIPAddress() && url_host == domain_string)) {
initial.commit586acc5fe2008-07-26 22:42:52506 *cookie_domain_key = url_host;
[email protected]f325f1e12010-04-30 22:38:55507 DCHECK(CookieMonster::DomainIsHostOnly(*cookie_domain_key));
initial.commit586acc5fe2008-07-26 22:42:52508 return true;
509 }
510
511 // Get the normalized domain specified in cookie line.
512 // Note: The RFC says we can reject a cookie if the domain
513 // attribute does not start with a dot. IE/FF/Safari however, allow a cookie
514 // of the form domain=my.domain.com, treating it the same as
515 // domain=.my.domain.com -- for compatibility we do the same here. Firefox
516 // also treats domain=.....my.domain.com like domain=.my.domain.com, but
517 // neither IE nor Safari do this, and we don't either.
[email protected]01dbd932009-06-23 22:52:42518 url_canon::CanonHostInfo ignored;
[email protected]f325f1e12010-04-30 22:38:55519 std::string cookie_domain(net::CanonicalizeHost(domain_string, &ignored));
initial.commit586acc5fe2008-07-26 22:42:52520 if (cookie_domain.empty())
521 return false;
522 if (cookie_domain[0] != '.')
523 cookie_domain = "." + cookie_domain;
524
525 // Ensure |url| and |cookie_domain| have the same domain+registry.
[email protected]69bb5872010-01-12 20:33:52526 const std::string url_scheme(url.scheme());
initial.commit586acc5fe2008-07-26 22:42:52527 const std::string url_domain_and_registry(
[email protected]69bb5872010-01-12 20:33:52528 GetEffectiveDomain(url_scheme, url_host));
initial.commit586acc5fe2008-07-26 22:42:52529 if (url_domain_and_registry.empty())
530 return false; // IP addresses/intranet hosts can't set domain cookies.
531 const std::string cookie_domain_and_registry(
[email protected]69bb5872010-01-12 20:33:52532 GetEffectiveDomain(url_scheme, cookie_domain));
initial.commit586acc5fe2008-07-26 22:42:52533 if (url_domain_and_registry != cookie_domain_and_registry)
534 return false; // Can't set a cookie on a different domain + registry.
535
536 // Ensure |url_host| is |cookie_domain| or one of its subdomains. Given that
537 // we know the domain+registry are the same from the above checks, this is
538 // basically a simple string suffix check.
539 if ((url_host.length() < cookie_domain.length()) ?
540 (cookie_domain != ("." + url_host)) :
541 url_host.compare(url_host.length() - cookie_domain.length(),
542 cookie_domain.length(), cookie_domain))
543 return false;
544
initial.commit586acc5fe2008-07-26 22:42:52545 *cookie_domain_key = cookie_domain;
546 return true;
547}
548
[email protected]f325f1e12010-04-30 22:38:55549// Determine the cookie domain key to use for setting the specified cookie.
550static bool GetCookieDomainKey(const GURL& url,
551 const CookieMonster::ParsedCookie& pc,
552 std::string* cookie_domain_key) {
553 std::string domain_string;
554 if (pc.HasDomain())
555 domain_string = pc.Domain();
556 return GetCookieDomainKeyWithString(url, domain_string, cookie_domain_key);
557}
558
559static std::string CanonPathWithString(const GURL& url,
560 const std::string& path_string) {
initial.commit586acc5fe2008-07-26 22:42:52561 // The RFC says the path should be a prefix of the current URL path.
562 // However, Mozilla allows you to set any path for compatibility with
563 // broken websites. We unfortunately will mimic this behavior. We try
564 // to be generous and accept cookies with an invalid path attribute, and
565 // default the path to something reasonable.
566
567 // The path was supplied in the cookie, we'll take it.
[email protected]f325f1e12010-04-30 22:38:55568 if (!path_string.empty() && path_string[0] == '/')
569 return path_string;
initial.commit586acc5fe2008-07-26 22:42:52570
571 // The path was not supplied in the cookie or invalid, we will default
572 // to the current URL path.
573 // """Defaults to the path of the request URL that generated the
574 // Set-Cookie response, up to, but not including, the
575 // right-most /."""
576 // How would this work for a cookie on /? We will include it then.
577 const std::string& url_path = url.path();
578
[email protected]c890ed192008-10-30 23:45:53579 size_t idx = url_path.find_last_of('/');
initial.commit586acc5fe2008-07-26 22:42:52580
581 // The cookie path was invalid or a single '/'.
582 if (idx == 0 || idx == std::string::npos)
583 return std::string("/");
584
585 // Return up to the rightmost '/'.
586 return url_path.substr(0, idx);
587}
588
[email protected]f325f1e12010-04-30 22:38:55589static std::string CanonPath(const GURL& url,
590 const CookieMonster::ParsedCookie& pc) {
591 std::string path_string;
592 if (pc.HasPath())
593 path_string = pc.Path();
594 return CanonPathWithString(url, path_string);
595}
596
initial.commit586acc5fe2008-07-26 22:42:52597static Time CanonExpiration(const CookieMonster::ParsedCookie& pc,
[email protected]4f79b3f2010-02-05 04:27:47598 const Time& current,
599 const CookieOptions& options) {
600 if (options.force_session())
601 return Time();
602
initial.commit586acc5fe2008-07-26 22:42:52603 // First, try the Max-Age attribute.
604 uint64 max_age = 0;
605 if (pc.HasMaxAge() &&
[email protected]dce5df52009-06-29 17:58:25606#ifdef COMPILER_MSVC
607 sscanf_s(
[email protected]d862fd92008-08-21 18:15:35608#else
[email protected]dce5df52009-06-29 17:58:25609 sscanf(
[email protected]d862fd92008-08-21 18:15:35610#endif
[email protected]dce5df52009-06-29 17:58:25611 pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) {
initial.commit586acc5fe2008-07-26 22:42:52612 return current + TimeDelta::FromSeconds(max_age);
613 }
614
615 // Try the Expires attribute.
616 if (pc.HasExpires())
617 return CookieMonster::ParseCookieTime(pc.Expires());
618
619 // Invalid or no expiration, persistent cookie.
620 return Time();
621}
622
[email protected]47accfd62009-05-14 18:46:21623bool CookieMonster::HasCookieableScheme(const GURL& url) {
[email protected]bb8905722010-05-21 17:29:04624 lock_.AssertAcquired();
625
initial.commit586acc5fe2008-07-26 22:42:52626 // Make sure the request is on a cookie-able url scheme.
[email protected]47accfd62009-05-14 18:46:21627 for (size_t i = 0; i < cookieable_schemes_.size(); ++i) {
initial.commit586acc5fe2008-07-26 22:42:52628 // We matched a scheme.
[email protected]47accfd62009-05-14 18:46:21629 if (url.SchemeIs(cookieable_schemes_[i].c_str())) {
initial.commit586acc5fe2008-07-26 22:42:52630 // We've matched a supported scheme.
631 return true;
632 }
633 }
634
635 // The scheme didn't match any in our whitelist.
636 COOKIE_DLOG(WARNING) << "Unsupported cookie scheme: " << url.scheme();
637 return false;
638}
639
[email protected]47accfd62009-05-14 18:46:21640void CookieMonster::SetCookieableSchemes(
641 const char* schemes[], size_t num_schemes) {
[email protected]bb8905722010-05-21 17:29:04642 AutoLock autolock(lock_);
643
[email protected]cf12bd12010-06-17 14:41:30644 // Cookieable Schemes must be set before first use of function.
645 DCHECK(!initialized_);
646
[email protected]47accfd62009-05-14 18:46:21647 cookieable_schemes_.clear();
648 cookieable_schemes_.insert(cookieable_schemes_.end(),
649 schemes, schemes + num_schemes);
650}
651
[email protected]34602282010-02-03 22:14:15652bool CookieMonster::SetCookieWithCreationTimeAndOptions(
653 const GURL& url,
654 const std::string& cookie_line,
655 const Time& creation_time_or_null,
656 const CookieOptions& options) {
[email protected]bb8905722010-05-21 17:29:04657 AutoLock autolock(lock_);
658
initial.commit586acc5fe2008-07-26 22:42:52659 if (!HasCookieableScheme(url)) {
initial.commit586acc5fe2008-07-26 22:42:52660 return false;
661 }
662
initial.commit586acc5fe2008-07-26 22:42:52663 InitIfNecessary();
664
665 COOKIE_DLOG(INFO) << "SetCookie() line: " << cookie_line;
666
[email protected]34602282010-02-03 22:14:15667 Time creation_time = creation_time_or_null;
668 if (creation_time.is_null()) {
669 creation_time = CurrentTime();
670 last_time_seen_ = creation_time;
671 }
672
initial.commit586acc5fe2008-07-26 22:42:52673 // Parse the cookie.
674 ParsedCookie pc(cookie_line);
675
676 if (!pc.IsValid()) {
677 COOKIE_DLOG(WARNING) << "Couldn't parse cookie";
678 return false;
679 }
680
[email protected]3a96c742008-11-19 19:46:27681 if (options.exclude_httponly() && pc.IsHttpOnly()) {
682 COOKIE_DLOG(INFO) << "SetCookie() not setting httponly cookie";
683 return false;
684 }
685
initial.commit586acc5fe2008-07-26 22:42:52686 std::string cookie_domain;
687 if (!GetCookieDomainKey(url, pc, &cookie_domain)) {
688 return false;
689 }
690
691 std::string cookie_path = CanonPath(url, pc);
692
693 scoped_ptr<CanonicalCookie> cc;
[email protected]4f79b3f2010-02-05 04:27:47694 Time cookie_expires = CanonExpiration(pc, creation_time, options);
initial.commit586acc5fe2008-07-26 22:42:52695
[email protected]1655ba342010-07-14 18:17:42696 cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_domain,
697 cookie_path,
initial.commit586acc5fe2008-07-26 22:42:52698 pc.IsSecure(), pc.IsHttpOnly(),
[email protected]77e0a462008-11-01 00:43:35699 creation_time, creation_time,
700 !cookie_expires.is_null(), cookie_expires));
initial.commit586acc5fe2008-07-26 22:42:52701
702 if (!cc.get()) {
703 COOKIE_DLOG(WARNING) << "Failed to allocate CanonicalCookie";
704 return false;
705 }
[email protected]f325f1e12010-04-30 22:38:55706 return SetCanonicalCookie(&cc, cookie_domain, creation_time, options);
707}
initial.commit586acc5fe2008-07-26 22:42:52708
[email protected]f325f1e12010-04-30 22:38:55709bool CookieMonster::SetCookieWithDetails(
710 const GURL& url, const std::string& name, const std::string& value,
711 const std::string& domain, const std::string& path,
712 const base::Time& expiration_time, bool secure, bool http_only) {
[email protected]f325f1e12010-04-30 22:38:55713
[email protected]f325f1e12010-04-30 22:38:55714 AutoLock autolock(lock_);
[email protected]bb8905722010-05-21 17:29:04715
716 if (!HasCookieableScheme(url))
717 return false;
718
[email protected]f325f1e12010-04-30 22:38:55719 InitIfNecessary();
720
721 Time creation_time = CurrentTime();
722 last_time_seen_ = creation_time;
723
724 scoped_ptr<CanonicalCookie> cc;
725 cc.reset(CanonicalCookie::Create(
[email protected]1655ba342010-07-14 18:17:42726 url, name, value, domain, path,
727 creation_time, expiration_time,
[email protected]f325f1e12010-04-30 22:38:55728 secure, http_only));
729
730 if (!cc.get())
731 return false;
732
733 CookieOptions options;
734 options.set_include_httponly();
[email protected]1655ba342010-07-14 18:17:42735 return SetCanonicalCookie(&cc, cc->Domain(), creation_time, options);
[email protected]f325f1e12010-04-30 22:38:55736}
737
738bool CookieMonster::SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc,
739 const std::string& cookie_domain,
740 const Time& creation_time,
741 const CookieOptions& options) {
742 if (DeleteAnyEquivalentCookie(cookie_domain, **cc,
[email protected]3a96c742008-11-19 19:46:27743 options.exclude_httponly())) {
744 COOKIE_DLOG(INFO) << "SetCookie() not clobbering httponly cookie";
745 return false;
746 }
initial.commit586acc5fe2008-07-26 22:42:52747
[email protected]f325f1e12010-04-30 22:38:55748 COOKIE_DLOG(INFO) << "SetCookie() cc: " << (*cc)->DebugString();
initial.commit586acc5fe2008-07-26 22:42:52749
750 // Realize that we might be setting an expired cookie, and the only point
751 // was to delete the cookie which we've already done.
[email protected]c4058fb2010-06-22 17:25:26752 if (!(*cc)->IsExpired(creation_time)) {
[email protected]374f58b2010-07-20 15:29:26753 // See InitializeHistograms() for details.
754 histogram_expiration_duration_minutes_->Add(
755 ((*cc)->ExpiryDate() - creation_time).InMinutes());
[email protected]f325f1e12010-04-30 22:38:55756 InternalInsertCookie(cookie_domain, cc->release(), true);
[email protected]c4058fb2010-06-22 17:25:26757 }
initial.commit586acc5fe2008-07-26 22:42:52758
759 // We assume that hopefully setting a cookie will be less common than
760 // querying a cookie. Since setting a cookie can put us over our limits,
761 // make sure that we garbage collect... We can also make the assumption that
762 // if a cookie was set, in the common case it will be used soon after,
763 // and we will purge the expired cookies in GetCookies().
764 GarbageCollect(creation_time, cookie_domain);
765
766 return true;
767}
768
initial.commit586acc5fe2008-07-26 22:42:52769void CookieMonster::InternalInsertCookie(const std::string& key,
770 CanonicalCookie* cc,
771 bool sync_to_store) {
[email protected]bb8905722010-05-21 17:29:04772 lock_.AssertAcquired();
773
initial.commit586acc5fe2008-07-26 22:42:52774 if (cc->IsPersistent() && store_ && sync_to_store)
775 store_->AddCookie(key, *cc);
776 cookies_.insert(CookieMap::value_type(key, cc));
[email protected]0f7066e2010-03-25 08:31:47777 if (delegate_.get())
[email protected]65781e92010-07-21 15:29:57778 delegate_->OnCookieChanged(*cc, false);
initial.commit586acc5fe2008-07-26 22:42:52779}
780
[email protected]77e0a462008-11-01 00:43:35781void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc) {
[email protected]bb8905722010-05-21 17:29:04782 lock_.AssertAcquired();
783
[email protected]77e0a462008-11-01 00:43:35784 // Based off the Mozilla code. When a cookie has been accessed recently,
785 // don't bother updating its access time again. This reduces the number of
786 // updates we do during pageload, which in turn reduces the chance our storage
787 // backend will hit its batch thresholds and be forced to update.
788 const Time current = Time::Now();
789 if ((current - cc->LastAccessDate()) < last_access_threshold_)
790 return;
791
[email protected]374f58b2010-07-20 15:29:26792 // See InitializeHistograms() for details.
793 histogram_between_access_interval_minutes_->Add(
794 (current - cc->LastAccessDate()).InMinutes());
[email protected]c4058fb2010-06-22 17:25:26795
[email protected]77e0a462008-11-01 00:43:35796 cc->SetLastAccessDate(current);
797 if (cc->IsPersistent() && store_)
798 store_->UpdateCookieAccessTime(*cc);
799}
800
initial.commit586acc5fe2008-07-26 22:42:52801void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
[email protected]c4058fb2010-06-22 17:25:26802 bool sync_to_store,
803 DeletionCause deletion_cause) {
[email protected]bb8905722010-05-21 17:29:04804 lock_.AssertAcquired();
805
[email protected]374f58b2010-07-20 15:29:26806 // See InitializeHistograms() for details.
807 histogram_cookie_deletion_cause_->Add(deletion_cause);
[email protected]c4058fb2010-06-22 17:25:26808
initial.commit586acc5fe2008-07-26 22:42:52809 CanonicalCookie* cc = it->second;
810 COOKIE_DLOG(INFO) << "InternalDeleteCookie() cc: " << cc->DebugString();
811 if (cc->IsPersistent() && store_ && sync_to_store)
812 store_->DeleteCookie(*cc);
[email protected]0f7066e2010-03-25 08:31:47813 if (delegate_.get())
[email protected]65781e92010-07-21 15:29:57814 delegate_->OnCookieChanged(*cc, true);
initial.commit586acc5fe2008-07-26 22:42:52815 cookies_.erase(it);
816 delete cc;
817}
818
[email protected]3a96c742008-11-19 19:46:27819bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key,
820 const CanonicalCookie& ecc,
821 bool skip_httponly) {
[email protected]bb8905722010-05-21 17:29:04822 lock_.AssertAcquired();
823
[email protected]c890ed192008-10-30 23:45:53824 bool found_equivalent_cookie = false;
[email protected]3a96c742008-11-19 19:46:27825 bool skipped_httponly = false;
initial.commit586acc5fe2008-07-26 22:42:52826 for (CookieMapItPair its = cookies_.equal_range(key);
827 its.first != its.second; ) {
828 CookieMap::iterator curit = its.first;
829 CanonicalCookie* cc = curit->second;
830 ++its.first;
831
initial.commit586acc5fe2008-07-26 22:42:52832 if (ecc.IsEquivalent(*cc)) {
[email protected]c890ed192008-10-30 23:45:53833 // We should never have more than one equivalent cookie, since they should
834 // overwrite each other.
[email protected]297a4ed02010-02-12 08:12:52835 CHECK(!found_equivalent_cookie) <<
[email protected]c890ed192008-10-30 23:45:53836 "Duplicate equivalent cookies found, cookie store is corrupted.";
[email protected]3a96c742008-11-19 19:46:27837 if (skip_httponly && cc->IsHttpOnly()) {
838 skipped_httponly = true;
839 } else {
[email protected]2f3f3592010-07-07 20:11:51840 InternalDeleteCookie(curit, true, DELETE_COOKIE_OVERWRITE);
[email protected]3a96c742008-11-19 19:46:27841 }
[email protected]c890ed192008-10-30 23:45:53842 found_equivalent_cookie = true;
initial.commit586acc5fe2008-07-26 22:42:52843 }
844 }
[email protected]3a96c742008-11-19 19:46:27845 return skipped_httponly;
initial.commit586acc5fe2008-07-26 22:42:52846}
847
initial.commit586acc5fe2008-07-26 22:42:52848int CookieMonster::GarbageCollect(const Time& current,
849 const std::string& key) {
[email protected]bb8905722010-05-21 17:29:04850 lock_.AssertAcquired();
851
initial.commit586acc5fe2008-07-26 22:42:52852 int num_deleted = 0;
853
854 // Collect garbage for this key.
855 if (cookies_.count(key) > kNumCookiesPerHost) {
856 COOKIE_DLOG(INFO) << "GarbageCollect() key: " << key;
857 num_deleted += GarbageCollectRange(current, cookies_.equal_range(key),
[email protected]c890ed192008-10-30 23:45:53858 kNumCookiesPerHost, kNumCookiesPerHostPurge);
initial.commit586acc5fe2008-07-26 22:42:52859 }
860
861 // Collect garbage for everything.
862 if (cookies_.size() > kNumCookiesTotal) {
863 COOKIE_DLOG(INFO) << "GarbageCollect() everything";
864 num_deleted += GarbageCollectRange(current,
[email protected]c890ed192008-10-30 23:45:53865 CookieMapItPair(cookies_.begin(), cookies_.end()), kNumCookiesTotal,
866 kNumCookiesTotalPurge);
867 }
868
869 return num_deleted;
870}
871
[email protected]77e0a462008-11-01 00:43:35872static bool LRUCookieSorter(const CookieMonster::CookieMap::iterator& it1,
873 const CookieMonster::CookieMap::iterator& it2) {
874 // Cookies accessed less recently should be deleted first.
875 if (it1->second->LastAccessDate() != it2->second->LastAccessDate())
876 return it1->second->LastAccessDate() < it2->second->LastAccessDate();
877
878 // In rare cases we might have two cookies with identical last access times.
879 // To preserve the stability of the sort, in these cases prefer to delete
880 // older cookies over newer ones. CreationDate() is guaranteed to be unique.
[email protected]c890ed192008-10-30 23:45:53881 return it1->second->CreationDate() < it2->second->CreationDate();
882}
883
884int CookieMonster::GarbageCollectRange(const Time& current,
885 const CookieMapItPair& itpair,
886 size_t num_max,
887 size_t num_purge) {
[email protected]bb8905722010-05-21 17:29:04888 lock_.AssertAcquired();
889
[email protected]c890ed192008-10-30 23:45:53890 // First, delete anything that's expired.
891 std::vector<CookieMap::iterator> cookie_its;
892 int num_deleted = GarbageCollectExpired(current, itpair, &cookie_its);
893
[email protected]77e0a462008-11-01 00:43:35894 // If the range still has too many cookies, delete the least recently used.
[email protected]c890ed192008-10-30 23:45:53895 if (cookie_its.size() > num_max) {
896 COOKIE_DLOG(INFO) << "GarbageCollectRange() Deep Garbage Collect.";
897 // Purge down to (|num_max| - |num_purge|) total cookies.
898 DCHECK(num_purge <= num_max);
899 num_purge += cookie_its.size() - num_max;
900
901 std::partial_sort(cookie_its.begin(), cookie_its.begin() + num_purge,
[email protected]77e0a462008-11-01 00:43:35902 cookie_its.end(), LRUCookieSorter);
[email protected]c4058fb2010-06-22 17:25:26903 for (size_t i = 0; i < num_purge; ++i) {
[email protected]374f58b2010-07-20 15:29:26904 // See InitializeHistograms() for details.
905 histogram_evicted_last_access_minutes_->Add(
906 (current - cookie_its[i]->second->LastAccessDate()).InMinutes());
[email protected]2f3f3592010-07-07 20:11:51907 InternalDeleteCookie(cookie_its[i], true, DELETE_COOKIE_EVICTED);
[email protected]c4058fb2010-06-22 17:25:26908 }
[email protected]c890ed192008-10-30 23:45:53909
910 num_deleted += num_purge;
911 }
912
913 return num_deleted;
914}
915
916int CookieMonster::GarbageCollectExpired(
917 const Time& current,
918 const CookieMapItPair& itpair,
919 std::vector<CookieMap::iterator>* cookie_its) {
[email protected]bb8905722010-05-21 17:29:04920 lock_.AssertAcquired();
921
[email protected]c890ed192008-10-30 23:45:53922 int num_deleted = 0;
923 for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) {
924 CookieMap::iterator curit = it;
925 ++it;
926
927 if (curit->second->IsExpired(current)) {
[email protected]2f3f3592010-07-07 20:11:51928 InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
[email protected]c890ed192008-10-30 23:45:53929 ++num_deleted;
930 } else if (cookie_its) {
931 cookie_its->push_back(curit);
932 }
initial.commit586acc5fe2008-07-26 22:42:52933 }
934
935 return num_deleted;
936}
937
938int CookieMonster::DeleteAll(bool sync_to_store) {
939 AutoLock autolock(lock_);
940 InitIfNecessary();
941
942 int num_deleted = 0;
943 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
944 CookieMap::iterator curit = it;
945 ++it;
[email protected]c4058fb2010-06-22 17:25:26946 InternalDeleteCookie(curit, sync_to_store,
[email protected]2f3f3592010-07-07 20:11:51947 sync_to_store ? DELETE_COOKIE_EXPLICIT :
948 DELETE_COOKIE_DONT_RECORD /* Destruction. */);
initial.commit586acc5fe2008-07-26 22:42:52949 ++num_deleted;
950 }
951
952 return num_deleted;
953}
954
955int CookieMonster::DeleteAllCreatedBetween(const Time& delete_begin,
956 const Time& delete_end,
957 bool sync_to_store) {
958 AutoLock autolock(lock_);
959 InitIfNecessary();
960
961 int num_deleted = 0;
962 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
963 CookieMap::iterator curit = it;
964 CanonicalCookie* cc = curit->second;
965 ++it;
966
967 if (cc->CreationDate() >= delete_begin &&
968 (delete_end.is_null() || cc->CreationDate() < delete_end)) {
[email protected]2f3f3592010-07-07 20:11:51969 InternalDeleteCookie(curit, sync_to_store, DELETE_COOKIE_EXPLICIT);
initial.commit586acc5fe2008-07-26 22:42:52970 ++num_deleted;
971 }
972 }
973
974 return num_deleted;
975}
976
977int CookieMonster::DeleteAllCreatedAfter(const Time& delete_begin,
978 bool sync_to_store) {
979 return DeleteAllCreatedBetween(delete_begin, Time(), sync_to_store);
980}
981
[email protected]c8c7d8a2010-07-07 19:58:36982int CookieMonster::DeleteAllForHost(const GURL& url) {
[email protected]c10da4b02010-03-25 14:38:32983 AutoLock autolock(lock_);
984 InitIfNecessary();
[email protected]bb8905722010-05-21 17:29:04985
[email protected]c8c7d8a2010-07-07 19:58:36986 if (!HasCookieableScheme(url))
987 return 0;
988
989 // We store host cookies in the store by their canonical host name;
990 // domain cookies are stored with a leading ".". So this is a pretty
991 // simple lookup and per-cookie delete.
[email protected]c10da4b02010-03-25 14:38:32992 int num_deleted = 0;
[email protected]c8c7d8a2010-07-07 19:58:36993 for (CookieMapItPair its = cookies_.equal_range(url.host());
994 its.first != its.second;) {
995 CookieMap::iterator curit = its.first;
996 ++its.first;
997 num_deleted++;
998
[email protected]2f3f3592010-07-07 20:11:51999 InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
[email protected]c10da4b02010-03-25 14:38:321000 }
1001 return num_deleted;
1002}
1003
initial.commit586acc5fe2008-07-26 22:42:521004bool CookieMonster::DeleteCookie(const std::string& domain,
1005 const CanonicalCookie& cookie,
1006 bool sync_to_store) {
1007 AutoLock autolock(lock_);
1008 InitIfNecessary();
1009
1010 for (CookieMapItPair its = cookies_.equal_range(domain);
1011 its.first != its.second; ++its.first) {
1012 // The creation date acts as our unique index...
1013 if (its.first->second->CreationDate() == cookie.CreationDate()) {
[email protected]2f3f3592010-07-07 20:11:511014 InternalDeleteCookie(its.first, sync_to_store, DELETE_COOKIE_EXPLICIT);
initial.commit586acc5fe2008-07-26 22:42:521015 return true;
1016 }
1017 }
1018 return false;
1019}
1020
1021// Mozilla sorts on the path length (longest first), and then it
1022// sorts by creation time (oldest first).
1023// The RFC says the sort order for the domain attribute is undefined.
1024static bool CookieSorter(CookieMonster::CanonicalCookie* cc1,
1025 CookieMonster::CanonicalCookie* cc2) {
1026 if (cc1->Path().length() == cc2->Path().length())
1027 return cc1->CreationDate() < cc2->CreationDate();
1028 return cc1->Path().length() > cc2->Path().length();
1029}
1030
[email protected]34602282010-02-03 22:14:151031bool CookieMonster::SetCookieWithOptions(const GURL& url,
1032 const std::string& cookie_line,
1033 const CookieOptions& options) {
1034 return SetCookieWithCreationTimeAndOptions(url, cookie_line, Time(), options);
1035}
1036
initial.commit586acc5fe2008-07-26 22:42:521037// Currently our cookie datastructure is based on Mozilla's approach. We have a
1038// hash keyed on the cookie's domain, and for any query we walk down the domain
1039// components and probe for cookies until we reach the TLD, where we stop.
1040// For example, a.b.blah.com, we would probe
1041// - a.b.blah.com
1042// - .a.b.blah.com (TODO should we check this first or second?)
1043// - .b.blah.com
1044// - .blah.com
1045// There are some alternative datastructures we could try, like a
1046// search/prefix trie, where we reverse the hostname and query for all
1047// keys that are a prefix of our hostname. I think the hash probing
1048// should be fast and simple enough for now.
1049std::string CookieMonster::GetCookiesWithOptions(const GURL& url,
[email protected]3a96c742008-11-19 19:46:271050 const CookieOptions& options) {
[email protected]bb8905722010-05-21 17:29:041051 AutoLock autolock(lock_);
1052 InitIfNecessary();
1053
initial.commit586acc5fe2008-07-26 22:42:521054 if (!HasCookieableScheme(url)) {
initial.commit586acc5fe2008-07-26 22:42:521055 return std::string();
1056 }
1057
1058 // Get the cookies for this host and its domain(s).
1059 std::vector<CanonicalCookie*> cookies;
1060 FindCookiesForHostAndDomain(url, options, &cookies);
1061 std::sort(cookies.begin(), cookies.end(), CookieSorter);
1062
1063 std::string cookie_line;
1064 for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
1065 it != cookies.end(); ++it) {
1066 if (it != cookies.begin())
1067 cookie_line += "; ";
1068 // In Mozilla if you set a cookie like AAAA, it will have an empty token
1069 // and a value of AAAA. When it sends the cookie back, it will send AAAA,
1070 // so we need to avoid sending =AAAA for a blank token value.
1071 if (!(*it)->Name().empty())
1072 cookie_line += (*it)->Name() + "=";
1073 cookie_line += (*it)->Value();
1074 }
1075
1076 COOKIE_DLOG(INFO) << "GetCookies() result: " << cookie_line;
1077
1078 return cookie_line;
1079}
1080
[email protected]971713e2009-10-29 16:07:211081void CookieMonster::DeleteCookie(const GURL& url,
1082 const std::string& cookie_name) {
[email protected]bb8905722010-05-21 17:29:041083 AutoLock autolock(lock_);
1084 InitIfNecessary();
1085
[email protected]971713e2009-10-29 16:07:211086 if (!HasCookieableScheme(url))
1087 return;
1088
[email protected]e8bac552009-10-30 16:15:391089 CookieOptions options;
1090 options.set_include_httponly();
1091 // Get the cookies for this host and its domain(s).
1092 std::vector<CanonicalCookie*> cookies;
1093 FindCookiesForHostAndDomain(url, options, &cookies);
1094 std::set<CanonicalCookie*> matching_cookies;
1095
1096 for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
1097 it != cookies.end(); ++it) {
1098 if ((*it)->Name() != cookie_name)
1099 continue;
1100 if (url.path().find((*it)->Path()))
1101 continue;
1102 matching_cookies.insert(*it);
1103 }
1104
1105 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
1106 CookieMap::iterator curit = it;
1107 ++it;
[email protected]c4058fb2010-06-22 17:25:261108 if (matching_cookies.find(curit->second) != matching_cookies.end()) {
[email protected]2f3f3592010-07-07 20:11:511109 InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
[email protected]c4058fb2010-06-22 17:25:261110 }
[email protected]971713e2009-10-29 16:07:211111 }
1112}
1113
initial.commit586acc5fe2008-07-26 22:42:521114CookieMonster::CookieList CookieMonster::GetAllCookies() {
1115 AutoLock autolock(lock_);
1116 InitIfNecessary();
1117
[email protected]c890ed192008-10-30 23:45:531118 // This function is being called to scrape the cookie list for management UI
1119 // or similar. We shouldn't show expired cookies in this list since it will
1120 // just be confusing to users, and this function is called rarely enough (and
1121 // is already slow enough) that it's OK to take the time to garbage collect
1122 // the expired cookies now.
1123 //
1124 // Note that this does not prune cookies to be below our limits (if we've
1125 // exceeded them) the way that calling GarbageCollect() would.
1126 GarbageCollectExpired(Time::Now(),
1127 CookieMapItPair(cookies_.begin(), cookies_.end()),
1128 NULL);
initial.commit586acc5fe2008-07-26 22:42:521129
[email protected]c890ed192008-10-30 23:45:531130 CookieList cookie_list;
1131 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it)
[email protected]65781e92010-07-21 15:29:571132 cookie_list.push_back(*it->second);
initial.commit586acc5fe2008-07-26 22:42:521133
1134 return cookie_list;
1135}
1136
[email protected]34602282010-02-03 22:14:151137CookieMonster::CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) {
[email protected]86d9b0d2010-02-03 18:12:071138 AutoLock autolock(lock_);
1139 InitIfNecessary();
[email protected]bb8905722010-05-21 17:29:041140
[email protected]c10da4b02010-03-25 14:38:321141 return InternalGetAllCookiesForURL(url);
[email protected]79a087a2010-02-03 17:08:191142}
1143
initial.commit586acc5fe2008-07-26 22:42:521144void CookieMonster::FindCookiesForHostAndDomain(
1145 const GURL& url,
[email protected]3a96c742008-11-19 19:46:271146 const CookieOptions& options,
initial.commit586acc5fe2008-07-26 22:42:521147 std::vector<CanonicalCookie*>* cookies) {
[email protected]bb8905722010-05-21 17:29:041148 lock_.AssertAcquired();
initial.commit586acc5fe2008-07-26 22:42:521149
1150 const Time current_time(CurrentTime());
1151
[email protected]c4058fb2010-06-22 17:25:261152 // Probe to save statistics relatively frequently. We do it here rather
1153 // than in the set path as many websites won't set cookies, and we
1154 // want to collect statistics whenever the browser's being used.
1155 RecordPeriodicStats(current_time);
1156
initial.commit586acc5fe2008-07-26 22:42:521157 // Query for the full host, For example: 'a.c.blah.com'.
1158 std::string key(url.host());
1159 FindCookiesForKey(key, url, options, current_time, cookies);
1160
1161 // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
[email protected]69bb5872010-01-12 20:33:521162 const std::string domain(GetEffectiveDomain(url.scheme(), key));
initial.commit586acc5fe2008-07-26 22:42:521163 if (domain.empty())
1164 return;
1165 DCHECK_LE(domain.length(), key.length());
1166 DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
1167 domain));
1168
1169 // Walk through the string and query at the dot points (GURL should have
1170 // canonicalized the dots, so this should be safe). Stop once we reach the
1171 // domain + registry; we can't write cookies past this point, and with some
1172 // registrars other domains can, in which case we don't want to read their
1173 // cookies.
1174 for (key = "." + key; key.length() > domain.length(); ) {
1175 FindCookiesForKey(key, url, options, current_time, cookies);
1176 const size_t next_dot = key.find('.', 1); // Skip over leading dot.
1177 key.erase(0, next_dot);
1178 }
1179}
1180
1181void CookieMonster::FindCookiesForKey(
1182 const std::string& key,
1183 const GURL& url,
[email protected]3a96c742008-11-19 19:46:271184 const CookieOptions& options,
initial.commit586acc5fe2008-07-26 22:42:521185 const Time& current,
1186 std::vector<CanonicalCookie*>* cookies) {
[email protected]bb8905722010-05-21 17:29:041187 lock_.AssertAcquired();
1188
initial.commit586acc5fe2008-07-26 22:42:521189 bool secure = url.SchemeIsSecure();
1190
1191 for (CookieMapItPair its = cookies_.equal_range(key);
1192 its.first != its.second; ) {
1193 CookieMap::iterator curit = its.first;
1194 CanonicalCookie* cc = curit->second;
1195 ++its.first;
1196
1197 // If the cookie is expired, delete it.
1198 if (cc->IsExpired(current)) {
[email protected]2f3f3592010-07-07 20:11:511199 InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
initial.commit586acc5fe2008-07-26 22:42:521200 continue;
1201 }
1202
[email protected]3a96c742008-11-19 19:46:271203 // Filter out HttpOnly cookies, per options.
1204 if (options.exclude_httponly() && cc->IsHttpOnly())
initial.commit586acc5fe2008-07-26 22:42:521205 continue;
1206
1207 // Filter out secure cookies unless we're https.
1208 if (!secure && cc->IsSecure())
1209 continue;
1210
1211 if (!cc->IsOnPath(url.path()))
1212 continue;
1213
[email protected]77e0a462008-11-01 00:43:351214 // Add this cookie to the set of matching cookies. Since we're reading the
1215 // cookie, update its last access time.
1216 InternalUpdateCookieAccessTime(cc);
initial.commit586acc5fe2008-07-26 22:42:521217 cookies->push_back(cc);
1218 }
1219}
1220
[email protected]79a087a2010-02-03 17:08:191221void CookieMonster::FindRawCookies(const std::string& key,
1222 bool include_secure,
[email protected]bfcaed42010-02-04 17:38:201223 const std::string& path,
[email protected]79a087a2010-02-03 17:08:191224 CookieList* list) {
[email protected]bb8905722010-05-21 17:29:041225 lock_.AssertAcquired();
1226
[email protected]79a087a2010-02-03 17:08:191227 for (CookieMapItPair its = cookies_.equal_range(key);
1228 its.first != its.second; ++its.first) {
1229 CanonicalCookie* cc = its.first->second;
[email protected]bfcaed42010-02-04 17:38:201230 if (!include_secure && cc->IsSecure())
1231 continue;
1232 if (!cc->IsOnPath(path))
1233 continue;
[email protected]65781e92010-07-21 15:29:571234 list->push_back(*cc);
[email protected]79a087a2010-02-03 17:08:191235 }
1236}
1237
[email protected]c10da4b02010-03-25 14:38:321238CookieMonster::CookieList CookieMonster::InternalGetAllCookiesForURL(
1239 const GURL& url) {
[email protected]bb8905722010-05-21 17:29:041240 lock_.AssertAcquired();
1241
[email protected]c10da4b02010-03-25 14:38:321242 // Do not return removed cookies.
1243 GarbageCollectExpired(Time::Now(),
1244 CookieMapItPair(cookies_.begin(), cookies_.end()),
1245 NULL);
1246
1247 CookieList cookie_list;
1248 if (!HasCookieableScheme(url))
1249 return cookie_list;
1250
1251 bool secure = url.SchemeIsSecure();
1252
1253 // Query for the full host, For example: 'a.c.blah.com'.
1254 std::string key(url.host());
1255 FindRawCookies(key, secure, url.path(), &cookie_list);
1256
1257 // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
1258 const std::string domain(GetEffectiveDomain(url.scheme(), key));
1259 if (domain.empty())
1260 return cookie_list;
1261
1262 // Use same logic as in FindCookiesForHostAndDomain.
1263 DCHECK_LE(domain.length(), key.length());
1264 DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
1265 domain));
1266 for (key = "." + key; key.length() > domain.length(); ) {
1267 FindRawCookies(key, secure, url.path(), &cookie_list);
1268 const size_t next_dot = key.find('.', 1); // Skip over leading dot.
1269 key.erase(0, next_dot);
1270 }
1271 return cookie_list;
1272}
initial.commit586acc5fe2008-07-26 22:42:521273
[email protected]c4058fb2010-06-22 17:25:261274// Test to see if stats should be recorded, and record them if so.
1275// The goal here is to get sampling for the average browser-hour of
1276// activity. We won't take samples when the web isn't being surfed,
1277// and when the web is being surfed, we'll take samples about every
1278// kRecordStatisticsIntervalSeconds.
1279// last_statistic_record_time_ is initialized to Now() rather than null
1280// in the constructor so that we won't take statistics right after
1281// startup, to avoid bias from browsers that are started but not used.
1282void CookieMonster::RecordPeriodicStats(const base::Time& current_time) {
1283 const base::TimeDelta kRecordStatisticsIntervalTime(
1284 base::TimeDelta::FromSeconds(kRecordStatisticsIntervalSeconds));
1285
1286 if (current_time - last_statistic_record_time_ >
1287 kRecordStatisticsIntervalTime) {
[email protected]374f58b2010-07-20 15:29:261288 // See InitializeHistograms() for details.
1289 histogram_count_->Add(cookies_.size());
[email protected]c4058fb2010-06-22 17:25:261290 last_statistic_record_time_ = current_time;
1291 }
1292}
1293
initial.commit586acc5fe2008-07-26 22:42:521294CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line)
1295 : is_valid_(false),
1296 path_index_(0),
1297 domain_index_(0),
1298 expires_index_(0),
1299 maxage_index_(0),
1300 secure_index_(0),
1301 httponly_index_(0) {
1302
1303 if (cookie_line.size() > kMaxCookieSize) {
1304 LOG(INFO) << "Not parsing cookie, too large: " << cookie_line.size();
1305 return;
1306 }
1307
1308 ParseTokenValuePairs(cookie_line);
1309 if (pairs_.size() > 0) {
1310 is_valid_ = true;
1311 SetupAttributes();
1312 }
1313}
1314
1315// Returns true if |c| occurs in |chars|
1316// TODO maybe make this take an iterator, could check for end also?
1317static inline bool CharIsA(const char c, const char* chars) {
1318 return strchr(chars, c) != NULL;
1319}
1320// Seek the iterator to the first occurrence of a character in |chars|.
1321// Returns true if it hit the end, false otherwise.
1322static inline bool SeekTo(std::string::const_iterator* it,
1323 const std::string::const_iterator& end,
1324 const char* chars) {
[email protected]f07467d2010-06-16 14:28:301325 for (; *it != end && !CharIsA(**it, chars); ++(*it)) {}
initial.commit586acc5fe2008-07-26 22:42:521326 return *it == end;
1327}
1328// Seek the iterator to the first occurrence of a character not in |chars|.
1329// Returns true if it hit the end, false otherwise.
1330static inline bool SeekPast(std::string::const_iterator* it,
1331 const std::string::const_iterator& end,
1332 const char* chars) {
[email protected]f07467d2010-06-16 14:28:301333 for (; *it != end && CharIsA(**it, chars); ++(*it)) {}
initial.commit586acc5fe2008-07-26 22:42:521334 return *it == end;
1335}
1336static inline bool SeekBackPast(std::string::const_iterator* it,
1337 const std::string::const_iterator& end,
1338 const char* chars) {
[email protected]f07467d2010-06-16 14:28:301339 for (; *it != end && CharIsA(**it, chars); --(*it)) {}
initial.commit586acc5fe2008-07-26 22:42:521340 return *it == end;
1341}
1342
[email protected]f325f1e12010-04-30 22:38:551343const char CookieMonster::ParsedCookie::kTerminator[] = "\n\r\0";
1344const int CookieMonster::ParsedCookie::kTerminatorLen =
1345 sizeof(kTerminator) - 1;
1346const char CookieMonster::ParsedCookie::kWhitespace[] = " \t";
1347const char CookieMonster::ParsedCookie::kValueSeparator[] = ";";
1348const char CookieMonster::ParsedCookie::kTokenSeparator[] = ";=";
1349
1350std::string::const_iterator CookieMonster::ParsedCookie::FindFirstTerminator(
1351 const std::string& s) {
1352 std::string::const_iterator end = s.end();
1353 size_t term_pos =
1354 s.find_first_of(std::string(kTerminator, kTerminatorLen));
1355 if (term_pos != std::string::npos) {
1356 // We found a character we should treat as an end of string.
1357 end = s.begin() + term_pos;
1358 }
1359 return end;
1360}
1361
1362bool CookieMonster::ParsedCookie::ParseToken(
1363 std::string::const_iterator* it,
1364 const std::string::const_iterator& end,
1365 std::string::const_iterator* token_start,
1366 std::string::const_iterator* token_end) {
1367 DCHECK(it && token_start && token_end);
1368 std::string::const_iterator token_real_end;
1369
1370 // Seek past any whitespace before the "token" (the name).
1371 // token_start should point at the first character in the token
1372 if (SeekPast(it, end, kWhitespace))
1373 return false; // No token, whitespace or empty.
1374 *token_start = *it;
1375
1376 // Seek over the token, to the token separator.
1377 // token_real_end should point at the token separator, i.e. '='.
1378 // If it == end after the seek, we probably have a token-value.
1379 SeekTo(it, end, kTokenSeparator);
1380 token_real_end = *it;
1381
1382 // Ignore any whitespace between the token and the token separator.
1383 // token_end should point after the last interesting token character,
1384 // pointing at either whitespace, or at '=' (and equal to token_real_end).
1385 if (*it != *token_start) { // We could have an empty token name.
1386 --(*it); // Go back before the token separator.
1387 // Skip over any whitespace to the first non-whitespace character.
1388 SeekBackPast(it, *token_start, kWhitespace);
1389 // Point after it.
1390 ++(*it);
1391 }
1392 *token_end = *it;
1393
1394 // Seek us back to the end of the token.
1395 *it = token_real_end;
1396 return true;
1397}
1398
1399void CookieMonster::ParsedCookie::ParseValue(
1400 std::string::const_iterator* it,
1401 const std::string::const_iterator& end,
1402 std::string::const_iterator* value_start,
1403 std::string::const_iterator* value_end) {
1404 DCHECK(it && value_start && value_end);
1405
1406 // Seek past any whitespace that might in-between the token and value.
1407 SeekPast(it, end, kWhitespace);
1408 // value_start should point at the first character of the value.
1409 *value_start = *it;
1410
1411 // It is unclear exactly how quoted string values should be handled.
1412 // Major browsers do different things, for example, Firefox supports
1413 // semicolons embedded in a quoted value, while IE does not. Looking at
1414 // the specs, RFC 2109 and 2965 allow for a quoted-string as the value.
1415 // However, these specs were apparently written after browsers had
1416 // implemented cookies, and they seem very distant from the reality of
1417 // what is actually implemented and used on the web. The original spec
1418 // from Netscape is possibly what is closest to the cookies used today.
1419 // This spec didn't have explicit support for double quoted strings, and
1420 // states that ; is not allowed as part of a value. We had originally
1421 // implement the Firefox behavior (A="B;C"; -> A="B;C";). However, since
1422 // there is no standard that makes sense, we decided to follow the behavior
1423 // of IE and Safari, which is closer to the original Netscape proposal.
1424 // This means that A="B;C" -> A="B;. This also makes the code much simpler
1425 // and reduces the possibility for invalid cookies, where other browsers
1426 // like Opera currently reject those invalid cookies (ex A="B" "C";).
1427
1428 // Just look for ';' to terminate ('=' allowed).
1429 // We can hit the end, maybe they didn't terminate.
1430 SeekTo(it, end, kValueSeparator);
1431
1432 // Will be pointed at the ; seperator or the end.
1433 *value_end = *it;
1434
1435 // Ignore any unwanted whitespace after the value.
1436 if (*value_end != *value_start) { // Could have an empty value
1437 --(*value_end);
1438 SeekBackPast(value_end, *value_start, kWhitespace);
1439 ++(*value_end);
1440 }
1441}
1442
1443std::string CookieMonster::ParsedCookie::ParseTokenString(
1444 const std::string& token) {
1445 std::string::const_iterator it = token.begin();
1446 std::string::const_iterator end = FindFirstTerminator(token);
1447
1448 std::string::const_iterator token_start, token_end;
1449 if (ParseToken(&it, end, &token_start, &token_end))
1450 return std::string(token_start, token_end);
1451 return std::string();
1452}
1453
1454std::string CookieMonster::ParsedCookie::ParseValueString(
1455 const std::string& value) {
1456 std::string::const_iterator it = value.begin();
1457 std::string::const_iterator end = FindFirstTerminator(value);
1458
1459 std::string::const_iterator value_start, value_end;
1460 ParseValue(&it, end, &value_start, &value_end);
1461 return std::string(value_start, value_end);
1462}
1463
initial.commit586acc5fe2008-07-26 22:42:521464// Parse all token/value pairs and populate pairs_.
1465void CookieMonster::ParsedCookie::ParseTokenValuePairs(
1466 const std::string& cookie_line) {
initial.commit586acc5fe2008-07-26 22:42:521467 pairs_.clear();
1468
1469 // Ok, here we go. We should be expecting to be starting somewhere
1470 // before the cookie line, not including any header name...
1471 std::string::const_iterator start = cookie_line.begin();
initial.commit586acc5fe2008-07-26 22:42:521472 std::string::const_iterator it = start;
1473
1474 // TODO Make sure we're stripping \r\n in the network code. Then we
1475 // can log any unexpected terminators.
[email protected]f325f1e12010-04-30 22:38:551476 std::string::const_iterator end = FindFirstTerminator(cookie_line);
initial.commit586acc5fe2008-07-26 22:42:521477
1478 for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
1479 TokenValuePair pair;
initial.commit586acc5fe2008-07-26 22:42:521480
[email protected]f325f1e12010-04-30 22:38:551481 std::string::const_iterator token_start, token_end;
1482 if (!ParseToken(&it, end, &token_start, &token_end))
1483 break;
initial.commit586acc5fe2008-07-26 22:42:521484
1485 if (it == end || *it != '=') {
1486 // We have a token-value, we didn't have any token name.
1487 if (pair_num == 0) {
1488 // For the first time around, we want to treat single values
1489 // as a value with an empty name. (Mozilla bug 169091).
1490 // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
1491 // set 2 different cookies, and setting "BBB" will then replace "AAA".
1492 pair.first = "";
1493 // Rewind to the beginning of what we thought was the token name,
1494 // and let it get parsed as a value.
1495 it = token_start;
1496 } else {
1497 // Any not-first attribute we want to treat a value as a
1498 // name with an empty value... This is so something like
1499 // "secure;" will get parsed as a Token name, and not a value.
1500 pair.first = std::string(token_start, token_end);
1501 }
1502 } else {
1503 // We have a TOKEN=VALUE.
1504 pair.first = std::string(token_start, token_end);
1505 ++it; // Skip past the '='.
1506 }
1507
1508 // OK, now try to parse a value.
1509 std::string::const_iterator value_start, value_end;
[email protected]f325f1e12010-04-30 22:38:551510 ParseValue(&it, end, &value_start, &value_end);
initial.commit586acc5fe2008-07-26 22:42:521511 // OK, we're finished with a Token/Value.
1512 pair.second = std::string(value_start, value_end);
[email protected]f325f1e12010-04-30 22:38:551513
initial.commit586acc5fe2008-07-26 22:42:521514 // From RFC2109: "Attributes (names) (attr) are case-insensitive."
1515 if (pair_num != 0)
1516 StringToLowerASCII(&pair.first);
1517 pairs_.push_back(pair);
1518
1519 // We've processed a token/value pair, we're either at the end of
1520 // the string or a ValueSeparator like ';', which we want to skip.
1521 if (it != end)
1522 ++it;
1523 }
1524}
1525
1526void CookieMonster::ParsedCookie::SetupAttributes() {
1527 static const char kPathTokenName[] = "path";
1528 static const char kDomainTokenName[] = "domain";
1529 static const char kExpiresTokenName[] = "expires";
1530 static const char kMaxAgeTokenName[] = "max-age";
1531 static const char kSecureTokenName[] = "secure";
1532 static const char kHttpOnlyTokenName[] = "httponly";
1533
1534 // We skip over the first token/value, the user supplied one.
1535 for (size_t i = 1; i < pairs_.size(); ++i) {
[email protected]f325f1e12010-04-30 22:38:551536 if (pairs_[i].first == kPathTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521537 path_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551538 } else if (pairs_[i].first == kDomainTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521539 domain_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551540 } else if (pairs_[i].first == kExpiresTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521541 expires_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551542 } else if (pairs_[i].first == kMaxAgeTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521543 maxage_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551544 } else if (pairs_[i].first == kSecureTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521545 secure_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551546 } else if (pairs_[i].first == kHttpOnlyTokenName) {
initial.commit586acc5fe2008-07-26 22:42:521547 httponly_index_ = i;
[email protected]f325f1e12010-04-30 22:38:551548 } else {
1549 /* some attribute we don't know or don't care about. */
1550 }
initial.commit586acc5fe2008-07-26 22:42:521551 }
1552}
1553
1554// Create a cookie-line for the cookie. For debugging only!
1555// If we want to use this for something more than debugging, we
1556// should rewrite it better...
1557std::string CookieMonster::ParsedCookie::DebugString() const {
1558 std::string out;
1559 for (PairList::const_iterator it = pairs_.begin();
1560 it != pairs_.end(); ++it) {
1561 out.append(it->first);
1562 out.append("=");
1563 out.append(it->second);
1564 out.append("; ");
1565 }
1566 return out;
1567}
1568
[email protected]a986cc32010-02-12 23:55:471569CookieMonster::CanonicalCookie::CanonicalCookie(const GURL& url,
1570 const ParsedCookie& pc)
1571 : name_(pc.Name()),
1572 value_(pc.Value()),
1573 path_(CanonPath(url, pc)),
1574 creation_date_(Time::Now()),
1575 last_access_date_(Time()),
1576 has_expires_(pc.HasExpires()),
1577 secure_(pc.IsSecure()),
1578 httponly_(pc.IsHttpOnly()) {
1579 if (has_expires_)
1580 expiry_date_ = CanonExpiration(pc, creation_date_, CookieOptions());
[email protected]1655ba342010-07-14 18:17:421581
1582 // Do the best we can with the domain.
1583 std::string cookie_domain;
1584 std::string domain_string;
1585 if (pc.HasDomain()) {
1586 domain_string = pc.Domain();
1587 }
1588 bool result
1589 = GetCookieDomainKeyWithString(url, domain_string,
1590 &cookie_domain);
1591 // Caller is responsible for passing in good arguments.
1592 DCHECK(result);
1593 domain_ = cookie_domain;
[email protected]a986cc32010-02-12 23:55:471594}
1595
[email protected]f325f1e12010-04-30 22:38:551596CookieMonster::CanonicalCookie* CookieMonster::CanonicalCookie::Create(
1597 const GURL& url, const std::string& name, const std::string& value,
[email protected]1655ba342010-07-14 18:17:421598 const std::string& domain, const std::string& path,
1599 const base::Time& creation_time, const base::Time& expiration_time,
1600 bool secure, bool http_only) {
[email protected]f325f1e12010-04-30 22:38:551601 // Expect valid attribute tokens and values, as defined by the ParsedCookie
1602 // logic, otherwise don't create the cookie.
1603 std::string parsed_name = ParsedCookie::ParseTokenString(name);
1604 if (parsed_name != name)
1605 return NULL;
1606 std::string parsed_value = ParsedCookie::ParseValueString(value);
1607 if (parsed_value != value)
1608 return NULL;
[email protected]1655ba342010-07-14 18:17:421609
1610 std::string parsed_domain = ParsedCookie::ParseValueString(domain);
1611 if (parsed_domain != domain)
1612 return NULL;
1613 std::string cookie_domain;
1614 if (!GetCookieDomainKeyWithString(url, parsed_domain, &cookie_domain))
1615 return false;
1616
[email protected]f325f1e12010-04-30 22:38:551617 std::string parsed_path = ParsedCookie::ParseValueString(path);
1618 if (parsed_path != path)
1619 return NULL;
1620
1621 std::string cookie_path = CanonPathWithString(url, parsed_path);
1622 // Expect that the path was either not specified (empty), or is valid.
1623 if (!parsed_path.empty() && cookie_path != parsed_path)
1624 return NULL;
1625 // Canonicalize path again to make sure it escapes characters as needed.
1626 url_parse::Component path_component(0, cookie_path.length());
1627 url_canon::RawCanonOutputT<char> canon_path;
1628 url_parse::Component canon_path_component;
1629 url_canon::CanonicalizePath(cookie_path.data(), path_component,
1630 &canon_path, &canon_path_component);
1631 cookie_path = std::string(canon_path.data() + canon_path_component.begin,
1632 canon_path_component.len);
1633
[email protected]1655ba342010-07-14 18:17:421634 return new CanonicalCookie(parsed_name, parsed_value, cookie_domain,
1635 cookie_path, secure, http_only,
1636 creation_time, creation_time,
[email protected]f325f1e12010-04-30 22:38:551637 !expiration_time.is_null(), expiration_time);
1638}
1639
initial.commit586acc5fe2008-07-26 22:42:521640bool CookieMonster::CanonicalCookie::IsOnPath(
1641 const std::string& url_path) const {
1642
1643 // A zero length would be unsafe for our trailing '/' checks, and
1644 // would also make no sense for our prefix match. The code that
1645 // creates a CanonicalCookie should make sure the path is never zero length,
1646 // but we double check anyway.
1647 if (path_.empty())
1648 return false;
1649
1650 // The Mozilla code broke it into 3 cases, if it's strings lengths
1651 // are less than, equal, or greater. I think this is simpler:
1652
1653 // Make sure the cookie path is a prefix of the url path. If the
1654 // url path is shorter than the cookie path, then the cookie path
1655 // can't be a prefix.
1656 if (url_path.find(path_) != 0)
1657 return false;
1658
1659 // Now we know that url_path is >= cookie_path, and that cookie_path
1660 // is a prefix of url_path. If they are the are the same length then
1661 // they are identical, otherwise we need an additional check:
1662
1663 // In order to avoid in correctly matching a cookie path of /blah
1664 // with a request path of '/blahblah/', we need to make sure that either
1665 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
1666 // in the url path. Since we know that the url path length is greater
1667 // than the cookie path length, it's safe to index one byte past.
1668 if (path_.length() != url_path.length() &&
1669 path_[path_.length() - 1] != '/' &&
1670 url_path[path_.length()] != '/')
1671 return false;
1672
1673 return true;
1674}
1675
1676std::string CookieMonster::CanonicalCookie::DebugString() const {
[email protected]1655ba342010-07-14 18:17:421677 return StringPrintf("name: %s value: %s domain: %s path: %s creation: %"
1678 PRId64,
1679 name_.c_str(), value_.c_str(),
1680 domain_.c_str(), path_.c_str(),
[email protected]34b2b002009-11-20 06:53:281681 static_cast<int64>(creation_date_.ToTimeT()));
initial.commit586acc5fe2008-07-26 22:42:521682}
[email protected]8ac1a752008-07-31 19:40:371683
1684} // namespace
[email protected]65781e92010-07-21 15:29:571685