blob: 595bf06b78d9f0d30b6b55074dc83af42f0a5d6d [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
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"
initial.commit586acc5fe2008-07-26 22:42:5256#include "net/base/net_util.h"
57#include "net/base/registry_controlled_domain.h"
58
59// #define COOKIE_LOGGING_ENABLED
60#ifdef COOKIE_LOGGING_ENABLED
61#define COOKIE_DLOG(severity) DLOG_IF(INFO, 1)
62#else
63#define COOKIE_DLOG(severity) DLOG_IF(INFO, 0)
64#endif
65
[email protected]e1acf6f2008-10-27 20:43:3366using base::Time;
67using base::TimeDelta;
68
[email protected]8ac1a752008-07-31 19:40:3769namespace net {
70
[email protected]e32306c52008-11-06 16:59:0571// Cookie garbage collection thresholds. Based off of the Mozilla defaults.
72// It might seem scary to have a high purge value, but really it's not. You
73// just make sure that you increase the max to cover the increase in purge,
74// and we would have been purging the same amount of cookies. We're just
75// going through the garbage collection process less often.
76static const size_t kNumCookiesPerHost = 70; // ~50 cookies
77static const size_t kNumCookiesPerHostPurge = 20;
[email protected]48234762009-06-03 18:20:1478static const size_t kNumCookiesTotal = 3300; // ~3000 cookies
79static const size_t kNumCookiesTotalPurge = 300;
[email protected]e32306c52008-11-06 16:59:0580
[email protected]77e0a462008-11-01 00:43:3581// Default minimum delay after updating a cookie's LastAccessDate before we
82// will update it again.
83static const int kDefaultAccessUpdateThresholdSeconds = 60;
84
[email protected]8ac1a752008-07-31 19:40:3785// static
86bool CookieMonster::enable_file_scheme_ = false;
initial.commit586acc5fe2008-07-26 22:42:5287
88// static
89void CookieMonster::EnableFileScheme() {
90 enable_file_scheme_ = true;
91}
92
93CookieMonster::CookieMonster()
94 : initialized_(false),
[email protected]77e0a462008-11-01 00:43:3595 store_(NULL),
96 last_access_threshold_(
97 TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)) {
[email protected]47accfd62009-05-14 18:46:2198 SetDefaultCookieableSchemes();
initial.commit586acc5fe2008-07-26 22:42:5299}
100
101CookieMonster::CookieMonster(PersistentCookieStore* store)
102 : initialized_(false),
[email protected]77e0a462008-11-01 00:43:35103 store_(store),
104 last_access_threshold_(
105 TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)) {
[email protected]47accfd62009-05-14 18:46:21106 SetDefaultCookieableSchemes();
initial.commit586acc5fe2008-07-26 22:42:52107}
108
109CookieMonster::~CookieMonster() {
110 DeleteAll(false);
111}
112
113void CookieMonster::InitStore() {
114 DCHECK(store_) << "Store must exist to initialize";
115
116 // Initialize the store and sync in any saved persistent cookies. We don't
117 // care if it's expired, insert it so it can be garbage collected, removed,
118 // and sync'd.
119 std::vector<KeyedCanonicalCookie> cookies;
[email protected]e32306c52008-11-06 16:59:05120 // Reserve space for the maximum amount of cookies a database should have.
121 // This prevents multiple vector growth / copies as we append cookies.
122 cookies.reserve(kNumCookiesTotal);
initial.commit586acc5fe2008-07-26 22:42:52123 store_->Load(&cookies);
124 for (std::vector<KeyedCanonicalCookie>::const_iterator it = cookies.begin();
125 it != cookies.end(); ++it) {
126 InternalInsertCookie(it->first, it->second, false);
127 }
128}
129
[email protected]47accfd62009-05-14 18:46:21130void CookieMonster::SetDefaultCookieableSchemes() {
131 // Note: file must be the last scheme.
132 static const char* kDefaultCookieableSchemes[] = { "http", "https", "file" };
133 int num_schemes = enable_file_scheme_ ? 3 : 2;
134 SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes);
135}
136
initial.commit586acc5fe2008-07-26 22:42:52137// The system resolution is not high enough, so we can have multiple
138// set cookies that result in the same system time. When this happens, we
139// increment by one Time unit. Let's hope computers don't get too fast.
140Time CookieMonster::CurrentTime() {
141 return std::max(Time::Now(),
142 Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1));
143}
144
145// Parse a cookie expiration time. We try to be lenient, but we need to
146// assume some order to distinguish the fields. The basic rules:
147// - The month name must be present and prefix the first 3 letters of the
148// full month name (jan for January, jun for June).
149// - If the year is <= 2 digits, it must occur after the day of month.
150// - The time must be of the format hh:mm:ss.
151// An average cookie expiration will look something like this:
152// Sat, 15-Apr-17 21:01:22 GMT
153Time CookieMonster::ParseCookieTime(const std::string& time_string) {
154 static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun",
155 "jul", "aug", "sep", "oct", "nov", "dec" };
156 static const int kMonthsLen = arraysize(kMonths);
157 // We want to be pretty liberal, and support most non-ascii and non-digit
158 // characters as a delimiter. We can't treat : as a delimiter, because it
159 // is the delimiter for hh:mm:ss, and we want to keep this field together.
160 // We make sure to include - and +, since they could prefix numbers.
161 // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes
162 // will be preserved, and we will get them here. So we make sure to include
163 // quote characters, and also \ for anything that was internally escaped.
164 static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
165
166 Time::Exploded exploded = {0};
167
168 StringTokenizer tokenizer(time_string, kDelimiters);
169
170 bool found_day_of_month = false;
171 bool found_month = false;
172 bool found_time = false;
173 bool found_year = false;
174
175 while (tokenizer.GetNext()) {
176 const std::string token = tokenizer.token();
177 DCHECK(!token.empty());
178 bool numerical = IsAsciiDigit(token[0]);
179
180 // String field
181 if (!numerical) {
182 if (!found_month) {
183 for (int i = 0; i < kMonthsLen; ++i) {
184 // Match prefix, so we could match January, etc
[email protected]7e3dcd92008-12-30 13:13:34185 if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) {
initial.commit586acc5fe2008-07-26 22:42:52186 exploded.month = i + 1;
187 found_month = true;
188 break;
189 }
190 }
191 } else {
192 // If we've gotten here, it means we've already found and parsed our
193 // month, and we have another string, which we would expect to be the
194 // the time zone name. According to the RFC and my experiments with
195 // how sites format their expirations, we don't have much of a reason
196 // to support timezones. We don't want to ever barf on user input,
197 // but this DCHECK should pass for well-formed data.
198 // DCHECK(token == "GMT");
199 }
200 // Numeric field w/ a colon
201 } else if (token.find(':') != std::string::npos) {
202 if (!found_time &&
[email protected]d862fd92008-08-21 18:15:35203#ifdef COMPILER_MSVC
204 sscanf_s(
205#else
206 sscanf(
207#endif
208 token.c_str(), "%2u:%2u:%2u", &exploded.hour,
209 &exploded.minute, &exploded.second) == 3) {
initial.commit586acc5fe2008-07-26 22:42:52210 found_time = true;
211 } else {
212 // We should only ever encounter one time-like thing. If we're here,
213 // it means we've found a second, which shouldn't happen. We keep
214 // the first. This check should be ok for well-formed input:
215 // NOTREACHED();
216 }
217 // Numeric field
218 } else {
219 // Overflow with atoi() is unspecified, so we enforce a max length.
220 if (!found_day_of_month && token.length() <= 2) {
221 exploded.day_of_month = atoi(token.c_str());
222 found_day_of_month = true;
223 } else if (!found_year && token.length() <= 5) {
224 exploded.year = atoi(token.c_str());
225 found_year = true;
226 } else {
227 // If we're here, it means we've either found an extra numeric field,
228 // or a numeric field which was too long. For well-formed input, the
229 // following check would be reasonable:
230 // NOTREACHED();
231 }
232 }
233 }
234
235 if (!found_day_of_month || !found_month || !found_time || !found_year) {
236 // We didn't find all of the fields we need. For well-formed input, the
237 // following check would be reasonable:
238 // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
239 return Time();
240 }
241
242 // Normalize the year to expand abbreviated years to the full year.
243 if (exploded.year >= 69 && exploded.year <= 99)
244 exploded.year += 1900;
245 if (exploded.year >= 0 && exploded.year <= 68)
246 exploded.year += 2000;
247
248 // If our values are within their correct ranges, we got our time.
249 if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 &&
250 exploded.month >= 1 && exploded.month <= 12 &&
251 exploded.year >= 1601 && exploded.year <= 30827 &&
252 exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) {
253 return Time::FromUTCExploded(exploded);
254 }
255
256 // One of our values was out of expected range. For well-formed input,
257 // the following check would be reasonable:
258 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
259
260 return Time();
261}
262
[email protected]69bb5872010-01-12 20:33:52263// Returns the effective TLD+1 for a given host. This only makes sense for http
264// and https schemes. For other schemes, the host will be returned unchanged
265// (minus any leading .).
266static std::string GetEffectiveDomain(const std::string& scheme,
267 const std::string& host) {
268 if (scheme == "http" || scheme == "https")
269 return RegistryControlledDomainService::GetDomainAndRegistry(host);
270
271 if (!host.empty() && host[0] == '.')
272 return host.substr(1);
273 return host;
274}
275
initial.commit586acc5fe2008-07-26 22:42:52276// Determine the cookie domain key to use for setting the specified cookie.
277// On success returns true, and sets cookie_domain_key to either a
278// -host cookie key (ex: "google.com")
279// -domain cookie key (ex: ".google.com")
280static bool GetCookieDomainKey(const GURL& url,
281 const CookieMonster::ParsedCookie& pc,
282 std::string* cookie_domain_key) {
283 const std::string url_host(url.host());
[email protected]c3a756b62009-01-23 10:50:51284
285 // If no domain was specified in the cookie, default to a host cookie.
286 // We match IE/Firefox in allowing a domain=IPADDR if it matches the url
287 // ip address hostname exactly. It should be treated as a host cookie.
288 if (!pc.HasDomain() || pc.Domain().empty() ||
289 (url.HostIsIPAddress() && url_host == pc.Domain())) {
initial.commit586acc5fe2008-07-26 22:42:52290 *cookie_domain_key = url_host;
291 DCHECK((*cookie_domain_key)[0] != '.');
292 return true;
293 }
294
295 // Get the normalized domain specified in cookie line.
296 // Note: The RFC says we can reject a cookie if the domain
297 // attribute does not start with a dot. IE/FF/Safari however, allow a cookie
298 // of the form domain=my.domain.com, treating it the same as
299 // domain=.my.domain.com -- for compatibility we do the same here. Firefox
300 // also treats domain=.....my.domain.com like domain=.my.domain.com, but
301 // neither IE nor Safari do this, and we don't either.
[email protected]01dbd932009-06-23 22:52:42302 url_canon::CanonHostInfo ignored;
303 std::string cookie_domain(net::CanonicalizeHost(pc.Domain(), &ignored));
initial.commit586acc5fe2008-07-26 22:42:52304 if (cookie_domain.empty())
305 return false;
306 if (cookie_domain[0] != '.')
307 cookie_domain = "." + cookie_domain;
308
309 // Ensure |url| and |cookie_domain| have the same domain+registry.
[email protected]69bb5872010-01-12 20:33:52310 const std::string url_scheme(url.scheme());
initial.commit586acc5fe2008-07-26 22:42:52311 const std::string url_domain_and_registry(
[email protected]69bb5872010-01-12 20:33:52312 GetEffectiveDomain(url_scheme, url_host));
initial.commit586acc5fe2008-07-26 22:42:52313 if (url_domain_and_registry.empty())
314 return false; // IP addresses/intranet hosts can't set domain cookies.
315 const std::string cookie_domain_and_registry(
[email protected]69bb5872010-01-12 20:33:52316 GetEffectiveDomain(url_scheme, cookie_domain));
initial.commit586acc5fe2008-07-26 22:42:52317 if (url_domain_and_registry != cookie_domain_and_registry)
318 return false; // Can't set a cookie on a different domain + registry.
319
320 // Ensure |url_host| is |cookie_domain| or one of its subdomains. Given that
321 // we know the domain+registry are the same from the above checks, this is
322 // basically a simple string suffix check.
323 if ((url_host.length() < cookie_domain.length()) ?
324 (cookie_domain != ("." + url_host)) :
325 url_host.compare(url_host.length() - cookie_domain.length(),
326 cookie_domain.length(), cookie_domain))
327 return false;
328
initial.commit586acc5fe2008-07-26 22:42:52329 *cookie_domain_key = cookie_domain;
330 return true;
331}
332
333static std::string CanonPath(const GURL& url,
334 const CookieMonster::ParsedCookie& pc) {
335 // The RFC says the path should be a prefix of the current URL path.
336 // However, Mozilla allows you to set any path for compatibility with
337 // broken websites. We unfortunately will mimic this behavior. We try
338 // to be generous and accept cookies with an invalid path attribute, and
339 // default the path to something reasonable.
340
341 // The path was supplied in the cookie, we'll take it.
342 if (pc.HasPath() && !pc.Path().empty() && pc.Path()[0] == '/')
343 return pc.Path();
344
345 // The path was not supplied in the cookie or invalid, we will default
346 // to the current URL path.
347 // """Defaults to the path of the request URL that generated the
348 // Set-Cookie response, up to, but not including, the
349 // right-most /."""
350 // How would this work for a cookie on /? We will include it then.
351 const std::string& url_path = url.path();
352
[email protected]c890ed192008-10-30 23:45:53353 size_t idx = url_path.find_last_of('/');
initial.commit586acc5fe2008-07-26 22:42:52354
355 // The cookie path was invalid or a single '/'.
356 if (idx == 0 || idx == std::string::npos)
357 return std::string("/");
358
359 // Return up to the rightmost '/'.
360 return url_path.substr(0, idx);
361}
362
363static Time CanonExpiration(const CookieMonster::ParsedCookie& pc,
[email protected]4f79b3f2010-02-05 04:27:47364 const Time& current,
365 const CookieOptions& options) {
366 if (options.force_session())
367 return Time();
368
initial.commit586acc5fe2008-07-26 22:42:52369 // First, try the Max-Age attribute.
370 uint64 max_age = 0;
371 if (pc.HasMaxAge() &&
[email protected]dce5df52009-06-29 17:58:25372#ifdef COMPILER_MSVC
373 sscanf_s(
[email protected]d862fd92008-08-21 18:15:35374#else
[email protected]dce5df52009-06-29 17:58:25375 sscanf(
[email protected]d862fd92008-08-21 18:15:35376#endif
[email protected]dce5df52009-06-29 17:58:25377 pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) {
initial.commit586acc5fe2008-07-26 22:42:52378 return current + TimeDelta::FromSeconds(max_age);
379 }
380
381 // Try the Expires attribute.
382 if (pc.HasExpires())
383 return CookieMonster::ParseCookieTime(pc.Expires());
384
385 // Invalid or no expiration, persistent cookie.
386 return Time();
387}
388
[email protected]47accfd62009-05-14 18:46:21389bool CookieMonster::HasCookieableScheme(const GURL& url) {
initial.commit586acc5fe2008-07-26 22:42:52390 // Make sure the request is on a cookie-able url scheme.
[email protected]47accfd62009-05-14 18:46:21391 for (size_t i = 0; i < cookieable_schemes_.size(); ++i) {
initial.commit586acc5fe2008-07-26 22:42:52392 // We matched a scheme.
[email protected]47accfd62009-05-14 18:46:21393 if (url.SchemeIs(cookieable_schemes_[i].c_str())) {
initial.commit586acc5fe2008-07-26 22:42:52394 // We've matched a supported scheme.
395 return true;
396 }
397 }
398
399 // The scheme didn't match any in our whitelist.
400 COOKIE_DLOG(WARNING) << "Unsupported cookie scheme: " << url.scheme();
401 return false;
402}
403
[email protected]47accfd62009-05-14 18:46:21404void CookieMonster::SetCookieableSchemes(
405 const char* schemes[], size_t num_schemes) {
406 cookieable_schemes_.clear();
407 cookieable_schemes_.insert(cookieable_schemes_.end(),
408 schemes, schemes + num_schemes);
409}
410
[email protected]34602282010-02-03 22:14:15411bool CookieMonster::SetCookieWithCreationTimeAndOptions(
412 const GURL& url,
413 const std::string& cookie_line,
414 const Time& creation_time_or_null,
415 const CookieOptions& options) {
initial.commit586acc5fe2008-07-26 22:42:52416 if (!HasCookieableScheme(url)) {
initial.commit586acc5fe2008-07-26 22:42:52417 return false;
418 }
419
420 AutoLock autolock(lock_);
421 InitIfNecessary();
422
423 COOKIE_DLOG(INFO) << "SetCookie() line: " << cookie_line;
424
[email protected]34602282010-02-03 22:14:15425 Time creation_time = creation_time_or_null;
426 if (creation_time.is_null()) {
427 creation_time = CurrentTime();
428 last_time_seen_ = creation_time;
429 }
430
initial.commit586acc5fe2008-07-26 22:42:52431 // Parse the cookie.
432 ParsedCookie pc(cookie_line);
433
434 if (!pc.IsValid()) {
435 COOKIE_DLOG(WARNING) << "Couldn't parse cookie";
436 return false;
437 }
438
[email protected]3a96c742008-11-19 19:46:27439 if (options.exclude_httponly() && pc.IsHttpOnly()) {
440 COOKIE_DLOG(INFO) << "SetCookie() not setting httponly cookie";
441 return false;
442 }
443
initial.commit586acc5fe2008-07-26 22:42:52444 std::string cookie_domain;
445 if (!GetCookieDomainKey(url, pc, &cookie_domain)) {
446 return false;
447 }
448
449 std::string cookie_path = CanonPath(url, pc);
450
451 scoped_ptr<CanonicalCookie> cc;
[email protected]4f79b3f2010-02-05 04:27:47452 Time cookie_expires = CanonExpiration(pc, creation_time, options);
initial.commit586acc5fe2008-07-26 22:42:52453
454 cc.reset(new CanonicalCookie(pc.Name(), pc.Value(), cookie_path,
455 pc.IsSecure(), pc.IsHttpOnly(),
[email protected]77e0a462008-11-01 00:43:35456 creation_time, creation_time,
457 !cookie_expires.is_null(), cookie_expires));
initial.commit586acc5fe2008-07-26 22:42:52458
459 if (!cc.get()) {
460 COOKIE_DLOG(WARNING) << "Failed to allocate CanonicalCookie";
461 return false;
462 }
463
[email protected]3a96c742008-11-19 19:46:27464 if (DeleteAnyEquivalentCookie(cookie_domain,
465 *cc,
466 options.exclude_httponly())) {
467 COOKIE_DLOG(INFO) << "SetCookie() not clobbering httponly cookie";
468 return false;
469 }
initial.commit586acc5fe2008-07-26 22:42:52470
471 COOKIE_DLOG(INFO) << "SetCookie() cc: " << cc->DebugString();
472
473 // Realize that we might be setting an expired cookie, and the only point
474 // was to delete the cookie which we've already done.
475 if (!cc->IsExpired(creation_time))
476 InternalInsertCookie(cookie_domain, cc.release(), true);
477
478 // We assume that hopefully setting a cookie will be less common than
479 // querying a cookie. Since setting a cookie can put us over our limits,
480 // make sure that we garbage collect... We can also make the assumption that
481 // if a cookie was set, in the common case it will be used soon after,
482 // and we will purge the expired cookies in GetCookies().
483 GarbageCollect(creation_time, cookie_domain);
484
485 return true;
486}
487
initial.commit586acc5fe2008-07-26 22:42:52488void CookieMonster::InternalInsertCookie(const std::string& key,
489 CanonicalCookie* cc,
490 bool sync_to_store) {
491 if (cc->IsPersistent() && store_ && sync_to_store)
492 store_->AddCookie(key, *cc);
493 cookies_.insert(CookieMap::value_type(key, cc));
494}
495
[email protected]77e0a462008-11-01 00:43:35496void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc) {
497 // Based off the Mozilla code. When a cookie has been accessed recently,
498 // don't bother updating its access time again. This reduces the number of
499 // updates we do during pageload, which in turn reduces the chance our storage
500 // backend will hit its batch thresholds and be forced to update.
501 const Time current = Time::Now();
502 if ((current - cc->LastAccessDate()) < last_access_threshold_)
503 return;
504
505 cc->SetLastAccessDate(current);
506 if (cc->IsPersistent() && store_)
507 store_->UpdateCookieAccessTime(*cc);
508}
509
initial.commit586acc5fe2008-07-26 22:42:52510void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
511 bool sync_to_store) {
512 CanonicalCookie* cc = it->second;
513 COOKIE_DLOG(INFO) << "InternalDeleteCookie() cc: " << cc->DebugString();
514 if (cc->IsPersistent() && store_ && sync_to_store)
515 store_->DeleteCookie(*cc);
516 cookies_.erase(it);
517 delete cc;
518}
519
[email protected]3a96c742008-11-19 19:46:27520bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key,
521 const CanonicalCookie& ecc,
522 bool skip_httponly) {
[email protected]c890ed192008-10-30 23:45:53523 bool found_equivalent_cookie = false;
[email protected]3a96c742008-11-19 19:46:27524 bool skipped_httponly = false;
initial.commit586acc5fe2008-07-26 22:42:52525 for (CookieMapItPair its = cookies_.equal_range(key);
526 its.first != its.second; ) {
527 CookieMap::iterator curit = its.first;
528 CanonicalCookie* cc = curit->second;
529 ++its.first;
530
initial.commit586acc5fe2008-07-26 22:42:52531 if (ecc.IsEquivalent(*cc)) {
[email protected]c890ed192008-10-30 23:45:53532 // We should never have more than one equivalent cookie, since they should
533 // overwrite each other.
534 DCHECK(!found_equivalent_cookie) <<
535 "Duplicate equivalent cookies found, cookie store is corrupted.";
[email protected]3a96c742008-11-19 19:46:27536 if (skip_httponly && cc->IsHttpOnly()) {
537 skipped_httponly = true;
538 } else {
539 InternalDeleteCookie(curit, true);
540 }
[email protected]c890ed192008-10-30 23:45:53541 found_equivalent_cookie = true;
initial.commit586acc5fe2008-07-26 22:42:52542#ifdef NDEBUG
[email protected]c890ed192008-10-30 23:45:53543 // Speed optimization: No point looping through the rest of the cookies
544 // since we're only doing it as a consistency check.
initial.commit586acc5fe2008-07-26 22:42:52545 break;
546#endif
547 }
548 }
[email protected]3a96c742008-11-19 19:46:27549 return skipped_httponly;
initial.commit586acc5fe2008-07-26 22:42:52550}
551
initial.commit586acc5fe2008-07-26 22:42:52552int CookieMonster::GarbageCollect(const Time& current,
553 const std::string& key) {
initial.commit586acc5fe2008-07-26 22:42:52554 int num_deleted = 0;
555
556 // Collect garbage for this key.
557 if (cookies_.count(key) > kNumCookiesPerHost) {
558 COOKIE_DLOG(INFO) << "GarbageCollect() key: " << key;
559 num_deleted += GarbageCollectRange(current, cookies_.equal_range(key),
[email protected]c890ed192008-10-30 23:45:53560 kNumCookiesPerHost, kNumCookiesPerHostPurge);
initial.commit586acc5fe2008-07-26 22:42:52561 }
562
563 // Collect garbage for everything.
564 if (cookies_.size() > kNumCookiesTotal) {
565 COOKIE_DLOG(INFO) << "GarbageCollect() everything";
566 num_deleted += GarbageCollectRange(current,
[email protected]c890ed192008-10-30 23:45:53567 CookieMapItPair(cookies_.begin(), cookies_.end()), kNumCookiesTotal,
568 kNumCookiesTotalPurge);
569 }
570
571 return num_deleted;
572}
573
[email protected]77e0a462008-11-01 00:43:35574static bool LRUCookieSorter(const CookieMonster::CookieMap::iterator& it1,
575 const CookieMonster::CookieMap::iterator& it2) {
576 // Cookies accessed less recently should be deleted first.
577 if (it1->second->LastAccessDate() != it2->second->LastAccessDate())
578 return it1->second->LastAccessDate() < it2->second->LastAccessDate();
579
580 // In rare cases we might have two cookies with identical last access times.
581 // To preserve the stability of the sort, in these cases prefer to delete
582 // older cookies over newer ones. CreationDate() is guaranteed to be unique.
[email protected]c890ed192008-10-30 23:45:53583 return it1->second->CreationDate() < it2->second->CreationDate();
584}
585
586int CookieMonster::GarbageCollectRange(const Time& current,
587 const CookieMapItPair& itpair,
588 size_t num_max,
589 size_t num_purge) {
590 // First, delete anything that's expired.
591 std::vector<CookieMap::iterator> cookie_its;
592 int num_deleted = GarbageCollectExpired(current, itpair, &cookie_its);
593
[email protected]77e0a462008-11-01 00:43:35594 // If the range still has too many cookies, delete the least recently used.
[email protected]c890ed192008-10-30 23:45:53595 if (cookie_its.size() > num_max) {
596 COOKIE_DLOG(INFO) << "GarbageCollectRange() Deep Garbage Collect.";
597 // Purge down to (|num_max| - |num_purge|) total cookies.
598 DCHECK(num_purge <= num_max);
599 num_purge += cookie_its.size() - num_max;
600
601 std::partial_sort(cookie_its.begin(), cookie_its.begin() + num_purge,
[email protected]77e0a462008-11-01 00:43:35602 cookie_its.end(), LRUCookieSorter);
[email protected]c890ed192008-10-30 23:45:53603 for (size_t i = 0; i < num_purge; ++i)
604 InternalDeleteCookie(cookie_its[i], true);
605
606 num_deleted += num_purge;
607 }
608
609 return num_deleted;
610}
611
612int CookieMonster::GarbageCollectExpired(
613 const Time& current,
614 const CookieMapItPair& itpair,
615 std::vector<CookieMap::iterator>* cookie_its) {
616 int num_deleted = 0;
617 for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) {
618 CookieMap::iterator curit = it;
619 ++it;
620
621 if (curit->second->IsExpired(current)) {
622 InternalDeleteCookie(curit, true);
623 ++num_deleted;
624 } else if (cookie_its) {
625 cookie_its->push_back(curit);
626 }
initial.commit586acc5fe2008-07-26 22:42:52627 }
628
629 return num_deleted;
630}
631
632int CookieMonster::DeleteAll(bool sync_to_store) {
633 AutoLock autolock(lock_);
634 InitIfNecessary();
635
636 int num_deleted = 0;
637 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
638 CookieMap::iterator curit = it;
639 ++it;
640 InternalDeleteCookie(curit, sync_to_store);
641 ++num_deleted;
642 }
643
644 return num_deleted;
645}
646
647int CookieMonster::DeleteAllCreatedBetween(const Time& delete_begin,
648 const Time& delete_end,
649 bool sync_to_store) {
650 AutoLock autolock(lock_);
651 InitIfNecessary();
652
653 int num_deleted = 0;
654 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
655 CookieMap::iterator curit = it;
656 CanonicalCookie* cc = curit->second;
657 ++it;
658
659 if (cc->CreationDate() >= delete_begin &&
660 (delete_end.is_null() || cc->CreationDate() < delete_end)) {
661 InternalDeleteCookie(curit, sync_to_store);
662 ++num_deleted;
663 }
664 }
665
666 return num_deleted;
667}
668
669int CookieMonster::DeleteAllCreatedAfter(const Time& delete_begin,
670 bool sync_to_store) {
671 return DeleteAllCreatedBetween(delete_begin, Time(), sync_to_store);
672}
673
674bool CookieMonster::DeleteCookie(const std::string& domain,
675 const CanonicalCookie& cookie,
676 bool sync_to_store) {
677 AutoLock autolock(lock_);
678 InitIfNecessary();
679
680 for (CookieMapItPair its = cookies_.equal_range(domain);
681 its.first != its.second; ++its.first) {
682 // The creation date acts as our unique index...
683 if (its.first->second->CreationDate() == cookie.CreationDate()) {
684 InternalDeleteCookie(its.first, sync_to_store);
685 return true;
686 }
687 }
688 return false;
689}
690
691// Mozilla sorts on the path length (longest first), and then it
692// sorts by creation time (oldest first).
693// The RFC says the sort order for the domain attribute is undefined.
694static bool CookieSorter(CookieMonster::CanonicalCookie* cc1,
695 CookieMonster::CanonicalCookie* cc2) {
696 if (cc1->Path().length() == cc2->Path().length())
697 return cc1->CreationDate() < cc2->CreationDate();
698 return cc1->Path().length() > cc2->Path().length();
699}
700
[email protected]34602282010-02-03 22:14:15701bool CookieMonster::SetCookieWithOptions(const GURL& url,
702 const std::string& cookie_line,
703 const CookieOptions& options) {
704 return SetCookieWithCreationTimeAndOptions(url, cookie_line, Time(), options);
705}
706
initial.commit586acc5fe2008-07-26 22:42:52707// Currently our cookie datastructure is based on Mozilla's approach. We have a
708// hash keyed on the cookie's domain, and for any query we walk down the domain
709// components and probe for cookies until we reach the TLD, where we stop.
710// For example, a.b.blah.com, we would probe
711// - a.b.blah.com
712// - .a.b.blah.com (TODO should we check this first or second?)
713// - .b.blah.com
714// - .blah.com
715// There are some alternative datastructures we could try, like a
716// search/prefix trie, where we reverse the hostname and query for all
717// keys that are a prefix of our hostname. I think the hash probing
718// should be fast and simple enough for now.
719std::string CookieMonster::GetCookiesWithOptions(const GURL& url,
[email protected]3a96c742008-11-19 19:46:27720 const CookieOptions& options) {
initial.commit586acc5fe2008-07-26 22:42:52721 if (!HasCookieableScheme(url)) {
initial.commit586acc5fe2008-07-26 22:42:52722 return std::string();
723 }
724
725 // Get the cookies for this host and its domain(s).
726 std::vector<CanonicalCookie*> cookies;
727 FindCookiesForHostAndDomain(url, options, &cookies);
728 std::sort(cookies.begin(), cookies.end(), CookieSorter);
729
730 std::string cookie_line;
731 for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
732 it != cookies.end(); ++it) {
733 if (it != cookies.begin())
734 cookie_line += "; ";
735 // In Mozilla if you set a cookie like AAAA, it will have an empty token
736 // and a value of AAAA. When it sends the cookie back, it will send AAAA,
737 // so we need to avoid sending =AAAA for a blank token value.
738 if (!(*it)->Name().empty())
739 cookie_line += (*it)->Name() + "=";
740 cookie_line += (*it)->Value();
741 }
742
743 COOKIE_DLOG(INFO) << "GetCookies() result: " << cookie_line;
744
745 return cookie_line;
746}
747
[email protected]971713e2009-10-29 16:07:21748void CookieMonster::DeleteCookie(const GURL& url,
749 const std::string& cookie_name) {
750 if (!HasCookieableScheme(url))
751 return;
752
[email protected]e8bac552009-10-30 16:15:39753 CookieOptions options;
754 options.set_include_httponly();
755 // Get the cookies for this host and its domain(s).
756 std::vector<CanonicalCookie*> cookies;
757 FindCookiesForHostAndDomain(url, options, &cookies);
758 std::set<CanonicalCookie*> matching_cookies;
759
760 for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
761 it != cookies.end(); ++it) {
762 if ((*it)->Name() != cookie_name)
763 continue;
764 if (url.path().find((*it)->Path()))
765 continue;
766 matching_cookies.insert(*it);
767 }
768
769 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
770 CookieMap::iterator curit = it;
771 ++it;
772 if (matching_cookies.find(curit->second) != matching_cookies.end())
773 InternalDeleteCookie(curit, true);
[email protected]971713e2009-10-29 16:07:21774 }
775}
776
initial.commit586acc5fe2008-07-26 22:42:52777CookieMonster::CookieList CookieMonster::GetAllCookies() {
778 AutoLock autolock(lock_);
779 InitIfNecessary();
780
[email protected]c890ed192008-10-30 23:45:53781 // This function is being called to scrape the cookie list for management UI
782 // or similar. We shouldn't show expired cookies in this list since it will
783 // just be confusing to users, and this function is called rarely enough (and
784 // is already slow enough) that it's OK to take the time to garbage collect
785 // the expired cookies now.
786 //
787 // Note that this does not prune cookies to be below our limits (if we've
788 // exceeded them) the way that calling GarbageCollect() would.
789 GarbageCollectExpired(Time::Now(),
790 CookieMapItPair(cookies_.begin(), cookies_.end()),
791 NULL);
initial.commit586acc5fe2008-07-26 22:42:52792
[email protected]c890ed192008-10-30 23:45:53793 CookieList cookie_list;
794 for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it)
initial.commit586acc5fe2008-07-26 22:42:52795 cookie_list.push_back(CookieListPair(it->first, *it->second));
initial.commit586acc5fe2008-07-26 22:42:52796
797 return cookie_list;
798}
799
[email protected]34602282010-02-03 22:14:15800CookieMonster::CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) {
[email protected]86d9b0d2010-02-03 18:12:07801 AutoLock autolock(lock_);
802 InitIfNecessary();
803
804 // Do not return removed cookies.
805 GarbageCollectExpired(Time::Now(),
806 CookieMapItPair(cookies_.begin(), cookies_.end()),
807 NULL);
808
[email protected]79a087a2010-02-03 17:08:19809 CookieList cookie_list;
810 if (!HasCookieableScheme(url))
811 return cookie_list;
812
813 bool secure = url.SchemeIsSecure();
814
815 // Query for the full host, For example: 'a.c.blah.com'.
816 std::string key(url.host());
[email protected]bfcaed42010-02-04 17:38:20817 FindRawCookies(key, secure, url.path(), &cookie_list);
[email protected]79a087a2010-02-03 17:08:19818
819 // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
820 const std::string domain(GetEffectiveDomain(url.scheme(), key));
821 if (domain.empty())
822 return cookie_list;
823
824 // Use same logic as in FindCookiesForHostAndDomain.
825 DCHECK_LE(domain.length(), key.length());
826 DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
827 domain));
828 for (key = "." + key; key.length() > domain.length(); ) {
[email protected]bfcaed42010-02-04 17:38:20829 FindRawCookies(key, secure, url.path(), &cookie_list);
[email protected]79a087a2010-02-03 17:08:19830 const size_t next_dot = key.find('.', 1); // Skip over leading dot.
831 key.erase(0, next_dot);
832 }
833 return cookie_list;
834}
835
initial.commit586acc5fe2008-07-26 22:42:52836void CookieMonster::FindCookiesForHostAndDomain(
837 const GURL& url,
[email protected]3a96c742008-11-19 19:46:27838 const CookieOptions& options,
initial.commit586acc5fe2008-07-26 22:42:52839 std::vector<CanonicalCookie*>* cookies) {
840 AutoLock autolock(lock_);
841 InitIfNecessary();
842
843 const Time current_time(CurrentTime());
844
845 // Query for the full host, For example: 'a.c.blah.com'.
846 std::string key(url.host());
847 FindCookiesForKey(key, url, options, current_time, cookies);
848
849 // See if we can search for domain cookies, i.e. if the host has a TLD + 1.
[email protected]69bb5872010-01-12 20:33:52850 const std::string domain(GetEffectiveDomain(url.scheme(), key));
initial.commit586acc5fe2008-07-26 22:42:52851 if (domain.empty())
852 return;
853 DCHECK_LE(domain.length(), key.length());
854 DCHECK_EQ(0, key.compare(key.length() - domain.length(), domain.length(),
855 domain));
856
857 // Walk through the string and query at the dot points (GURL should have
858 // canonicalized the dots, so this should be safe). Stop once we reach the
859 // domain + registry; we can't write cookies past this point, and with some
860 // registrars other domains can, in which case we don't want to read their
861 // cookies.
862 for (key = "." + key; key.length() > domain.length(); ) {
863 FindCookiesForKey(key, url, options, current_time, cookies);
864 const size_t next_dot = key.find('.', 1); // Skip over leading dot.
865 key.erase(0, next_dot);
866 }
867}
868
869void CookieMonster::FindCookiesForKey(
870 const std::string& key,
871 const GURL& url,
[email protected]3a96c742008-11-19 19:46:27872 const CookieOptions& options,
initial.commit586acc5fe2008-07-26 22:42:52873 const Time& current,
874 std::vector<CanonicalCookie*>* cookies) {
875 bool secure = url.SchemeIsSecure();
876
877 for (CookieMapItPair its = cookies_.equal_range(key);
878 its.first != its.second; ) {
879 CookieMap::iterator curit = its.first;
880 CanonicalCookie* cc = curit->second;
881 ++its.first;
882
883 // If the cookie is expired, delete it.
884 if (cc->IsExpired(current)) {
885 InternalDeleteCookie(curit, true);
886 continue;
887 }
888
[email protected]3a96c742008-11-19 19:46:27889 // Filter out HttpOnly cookies, per options.
890 if (options.exclude_httponly() && cc->IsHttpOnly())
initial.commit586acc5fe2008-07-26 22:42:52891 continue;
892
893 // Filter out secure cookies unless we're https.
894 if (!secure && cc->IsSecure())
895 continue;
896
897 if (!cc->IsOnPath(url.path()))
898 continue;
899
[email protected]77e0a462008-11-01 00:43:35900 // Add this cookie to the set of matching cookies. Since we're reading the
901 // cookie, update its last access time.
902 InternalUpdateCookieAccessTime(cc);
initial.commit586acc5fe2008-07-26 22:42:52903 cookies->push_back(cc);
904 }
905}
906
[email protected]79a087a2010-02-03 17:08:19907void CookieMonster::FindRawCookies(const std::string& key,
908 bool include_secure,
[email protected]bfcaed42010-02-04 17:38:20909 const std::string& path,
[email protected]79a087a2010-02-03 17:08:19910 CookieList* list) {
911 for (CookieMapItPair its = cookies_.equal_range(key);
912 its.first != its.second; ++its.first) {
913 CanonicalCookie* cc = its.first->second;
[email protected]bfcaed42010-02-04 17:38:20914 if (!include_secure && cc->IsSecure())
915 continue;
916 if (!cc->IsOnPath(path))
917 continue;
918 list->push_back(CookieListPair(key, *cc));
[email protected]79a087a2010-02-03 17:08:19919 }
920}
921
initial.commit586acc5fe2008-07-26 22:42:52922
923CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line)
924 : is_valid_(false),
925 path_index_(0),
926 domain_index_(0),
927 expires_index_(0),
928 maxage_index_(0),
929 secure_index_(0),
930 httponly_index_(0) {
931
932 if (cookie_line.size() > kMaxCookieSize) {
933 LOG(INFO) << "Not parsing cookie, too large: " << cookie_line.size();
934 return;
935 }
936
937 ParseTokenValuePairs(cookie_line);
938 if (pairs_.size() > 0) {
939 is_valid_ = true;
940 SetupAttributes();
941 }
942}
943
944// Returns true if |c| occurs in |chars|
945// TODO maybe make this take an iterator, could check for end also?
946static inline bool CharIsA(const char c, const char* chars) {
947 return strchr(chars, c) != NULL;
948}
949// Seek the iterator to the first occurrence of a character in |chars|.
950// Returns true if it hit the end, false otherwise.
951static inline bool SeekTo(std::string::const_iterator* it,
952 const std::string::const_iterator& end,
953 const char* chars) {
954 for (; *it != end && !CharIsA(**it, chars); ++(*it));
955 return *it == end;
956}
957// Seek the iterator to the first occurrence of a character not in |chars|.
958// Returns true if it hit the end, false otherwise.
959static inline bool SeekPast(std::string::const_iterator* it,
960 const std::string::const_iterator& end,
961 const char* chars) {
962 for (; *it != end && CharIsA(**it, chars); ++(*it));
963 return *it == end;
964}
965static inline bool SeekBackPast(std::string::const_iterator* it,
966 const std::string::const_iterator& end,
967 const char* chars) {
968 for (; *it != end && CharIsA(**it, chars); --(*it));
969 return *it == end;
970}
971
972// Parse all token/value pairs and populate pairs_.
973void CookieMonster::ParsedCookie::ParseTokenValuePairs(
974 const std::string& cookie_line) {
975 static const char kTerminator[] = "\n\r\0";
976 static const int kTerminatorLen = sizeof(kTerminator) - 1;
977 static const char kWhitespace[] = " \t";
initial.commit586acc5fe2008-07-26 22:42:52978 static const char kValueSeparator[] = ";";
979 static const char kTokenSeparator[] = ";=";
980
981 pairs_.clear();
982
983 // Ok, here we go. We should be expecting to be starting somewhere
984 // before the cookie line, not including any header name...
985 std::string::const_iterator start = cookie_line.begin();
986 std::string::const_iterator end = cookie_line.end();
987 std::string::const_iterator it = start;
988
989 // TODO Make sure we're stripping \r\n in the network code. Then we
990 // can log any unexpected terminators.
[email protected]c890ed192008-10-30 23:45:53991 size_t term_pos =
992 cookie_line.find_first_of(std::string(kTerminator, kTerminatorLen));
initial.commit586acc5fe2008-07-26 22:42:52993 if (term_pos != std::string::npos) {
994 // We found a character we should treat as an end of string.
995 end = start + term_pos;
996 }
997
998 for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
999 TokenValuePair pair;
1000 std::string::const_iterator token_start, token_real_end, token_end;
1001
1002 // Seek past any whitespace before the "token" (the name).
1003 // token_start should point at the first character in the token
1004 if (SeekPast(&it, end, kWhitespace))
1005 break; // No token, whitespace or empty.
1006 token_start = it;
1007
1008 // Seek over the token, to the token separator.
1009 // token_real_end should point at the token separator, i.e. '='.
1010 // If it == end after the seek, we probably have a token-value.
1011 SeekTo(&it, end, kTokenSeparator);
1012 token_real_end = it;
1013
1014 // Ignore any whitespace between the token and the token separator.
1015 // token_end should point after the last interesting token character,
1016 // pointing at either whitespace, or at '=' (and equal to token_real_end).
1017 if (it != token_start) { // We could have an empty token name.
1018 --it; // Go back before the token separator.
1019 // Skip over any whitespace to the first non-whitespace character.
1020 SeekBackPast(&it, token_start, kWhitespace);
1021 // Point after it.
1022 ++it;
1023 }
1024 token_end = it;
1025
1026 // Seek us back to the end of the token.
1027 it = token_real_end;
1028
1029 if (it == end || *it != '=') {
1030 // We have a token-value, we didn't have any token name.
1031 if (pair_num == 0) {
1032 // For the first time around, we want to treat single values
1033 // as a value with an empty name. (Mozilla bug 169091).
1034 // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
1035 // set 2 different cookies, and setting "BBB" will then replace "AAA".
1036 pair.first = "";
1037 // Rewind to the beginning of what we thought was the token name,
1038 // and let it get parsed as a value.
1039 it = token_start;
1040 } else {
1041 // Any not-first attribute we want to treat a value as a
1042 // name with an empty value... This is so something like
1043 // "secure;" will get parsed as a Token name, and not a value.
1044 pair.first = std::string(token_start, token_end);
1045 }
1046 } else {
1047 // We have a TOKEN=VALUE.
1048 pair.first = std::string(token_start, token_end);
1049 ++it; // Skip past the '='.
1050 }
1051
1052 // OK, now try to parse a value.
1053 std::string::const_iterator value_start, value_end;
1054
1055 // Seek past any whitespace that might in-between the token and value.
1056 SeekPast(&it, end, kWhitespace);
1057 // value_start should point at the first character of the value.
1058 value_start = it;
1059
[email protected]7d927f8c2009-01-09 18:24:561060 // It is unclear exactly how quoted string values should be handled.
1061 // Major browsers do different things, for example, Firefox supports
1062 // semicolons embedded in a quoted value, while IE does not. Looking at
1063 // the specs, RFC 2109 and 2965 allow for a quoted-string as the value.
1064 // However, these specs were apparently written after browsers had
1065 // implemented cookies, and they seem very distant from the reality of
1066 // what is actually implemented and used on the web. The original spec
1067 // from Netscape is possibly what is closest to the cookies used today.
1068 // This spec didn't have explicit support for double quoted strings, and
1069 // states that ; is not allowed as part of a value. We had originally
1070 // implement the Firefox behavior (A="B;C"; -> A="B;C";). However, since
1071 // there is no standard that makes sense, we decided to follow the behavior
1072 // of IE and Safari, which is closer to the original Netscape proposal.
1073 // This means that A="B;C" -> A="B;. This also makes the code much simpler
1074 // and reduces the possibility for invalid cookies, where other browsers
1075 // like Opera currently reject those invalid cookies (ex A="B" "C";).
initial.commit586acc5fe2008-07-26 22:42:521076
[email protected]7d927f8c2009-01-09 18:24:561077 // Just look for ';' to terminate ('=' allowed).
1078 // We can hit the end, maybe they didn't terminate.
1079 SeekTo(&it, end, kValueSeparator);
initial.commit586acc5fe2008-07-26 22:42:521080
[email protected]7d927f8c2009-01-09 18:24:561081 // Will be pointed at the ; seperator or the end.
1082 value_end = it;
initial.commit586acc5fe2008-07-26 22:42:521083
[email protected]7d927f8c2009-01-09 18:24:561084 // Ignore any unwanted whitespace after the value.
1085 if (value_end != value_start) { // Could have an empty value
1086 --value_end;
1087 SeekBackPast(&value_end, value_start, kWhitespace);
1088 ++value_end;
initial.commit586acc5fe2008-07-26 22:42:521089 }
1090
1091 // OK, we're finished with a Token/Value.
1092 pair.second = std::string(value_start, value_end);
1093 // From RFC2109: "Attributes (names) (attr) are case-insensitive."
1094 if (pair_num != 0)
1095 StringToLowerASCII(&pair.first);
1096 pairs_.push_back(pair);
1097
1098 // We've processed a token/value pair, we're either at the end of
1099 // the string or a ValueSeparator like ';', which we want to skip.
1100 if (it != end)
1101 ++it;
1102 }
1103}
1104
1105void CookieMonster::ParsedCookie::SetupAttributes() {
1106 static const char kPathTokenName[] = "path";
1107 static const char kDomainTokenName[] = "domain";
1108 static const char kExpiresTokenName[] = "expires";
1109 static const char kMaxAgeTokenName[] = "max-age";
1110 static const char kSecureTokenName[] = "secure";
1111 static const char kHttpOnlyTokenName[] = "httponly";
1112
1113 // We skip over the first token/value, the user supplied one.
1114 for (size_t i = 1; i < pairs_.size(); ++i) {
1115 if (pairs_[i].first == kPathTokenName)
1116 path_index_ = i;
1117 else if (pairs_[i].first == kDomainTokenName)
1118 domain_index_ = i;
1119 else if (pairs_[i].first == kExpiresTokenName)
1120 expires_index_ = i;
1121 else if (pairs_[i].first == kMaxAgeTokenName)
1122 maxage_index_ = i;
1123 else if (pairs_[i].first == kSecureTokenName)
1124 secure_index_ = i;
1125 else if (pairs_[i].first == kHttpOnlyTokenName)
1126 httponly_index_ = i;
1127 else { /* some attribute we don't know or don't care about. */ }
1128 }
1129}
1130
1131// Create a cookie-line for the cookie. For debugging only!
1132// If we want to use this for something more than debugging, we
1133// should rewrite it better...
1134std::string CookieMonster::ParsedCookie::DebugString() const {
1135 std::string out;
1136 for (PairList::const_iterator it = pairs_.begin();
1137 it != pairs_.end(); ++it) {
1138 out.append(it->first);
1139 out.append("=");
1140 out.append(it->second);
1141 out.append("; ");
1142 }
1143 return out;
1144}
1145
1146bool CookieMonster::CanonicalCookie::IsOnPath(
1147 const std::string& url_path) const {
1148
1149 // A zero length would be unsafe for our trailing '/' checks, and
1150 // would also make no sense for our prefix match. The code that
1151 // creates a CanonicalCookie should make sure the path is never zero length,
1152 // but we double check anyway.
1153 if (path_.empty())
1154 return false;
1155
1156 // The Mozilla code broke it into 3 cases, if it's strings lengths
1157 // are less than, equal, or greater. I think this is simpler:
1158
1159 // Make sure the cookie path is a prefix of the url path. If the
1160 // url path is shorter than the cookie path, then the cookie path
1161 // can't be a prefix.
1162 if (url_path.find(path_) != 0)
1163 return false;
1164
1165 // Now we know that url_path is >= cookie_path, and that cookie_path
1166 // is a prefix of url_path. If they are the are the same length then
1167 // they are identical, otherwise we need an additional check:
1168
1169 // In order to avoid in correctly matching a cookie path of /blah
1170 // with a request path of '/blahblah/', we need to make sure that either
1171 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
1172 // in the url path. Since we know that the url path length is greater
1173 // than the cookie path length, it's safe to index one byte past.
1174 if (path_.length() != url_path.length() &&
1175 path_[path_.length() - 1] != '/' &&
1176 url_path[path_.length()] != '/')
1177 return false;
1178
1179 return true;
1180}
1181
1182std::string CookieMonster::CanonicalCookie::DebugString() const {
[email protected]34b2b002009-11-20 06:53:281183 return StringPrintf("name: %s value: %s path: %s creation: %" PRId64,
initial.commit586acc5fe2008-07-26 22:42:521184 name_.c_str(), value_.c_str(), path_.c_str(),
[email protected]34b2b002009-11-20 06:53:281185 static_cast<int64>(creation_date_.ToTimeT()));
initial.commit586acc5fe2008-07-26 22:42:521186}
[email protected]8ac1a752008-07-31 19:40:371187
1188} // namespace