1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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
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/
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
20 * The Original Code is mozilla.org code.
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.
28 * Daniel Witte (dwitte@stanford.edu)
29 * Michiel van Leeuwen (mvl@exedo.nl)
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.
43 * ***** END LICENSE BLOCK ***** */
45 #include "net/cookies/canonical_cookie.h"
47 #include "base/basictypes.h"
48 #include "base/format_macros.h"
49 #include "base/logging.h"
50 #include "base/strings/stringprintf.h"
51 #include "net/cookies/cookie_util.h"
52 #include "net/cookies/parsed_cookie.h"
54 #include "url/url_canon.h"
57 using base::TimeDelta
;
63 const int kVlogSetCookies
= 7;
65 // Determine the cookie domain to use for setting the specified cookie.
66 bool GetCookieDomain(const GURL
& url
,
67 const ParsedCookie
& pc
,
68 std::string
* result
) {
69 std::string domain_string
;
71 domain_string
= pc
.Domain();
72 return cookie_util::GetCookieDomainWithString(url
, domain_string
, result
);
75 std::string
CanonPathWithString(const GURL
& url
,
76 const std::string
& path_string
) {
77 // The RFC says the path should be a prefix of the current URL path.
78 // However, Mozilla allows you to set any path for compatibility with
79 // broken websites. We unfortunately will mimic this behavior. We try
80 // to be generous and accept cookies with an invalid path attribute, and
81 // default the path to something reasonable.
83 // The path was supplied in the cookie, we'll take it.
84 if (!path_string
.empty() && path_string
[0] == '/')
87 // The path was not supplied in the cookie or invalid, we will default
88 // to the current URL path.
89 // """Defaults to the path of the request URL that generated the
90 // Set-Cookie response, up to, but not including, the
92 // How would this work for a cookie on /? We will include it then.
93 const std::string
& url_path
= url
.path();
95 size_t idx
= url_path
.find_last_of('/');
97 // The cookie path was invalid or a single '/'.
98 if (idx
== 0 || idx
== std::string::npos
)
99 return std::string("/");
101 // Return up to the rightmost '/'.
102 return url_path
.substr(0, idx
);
105 // Compares cookies using name, domain and path, so that "equivalent" cookies
106 // (per RFC 2965) are equal to each other.
107 int PartialCookieOrdering(const CanonicalCookie
& a
, const CanonicalCookie
& b
) {
108 int diff
= a
.Name().compare(b
.Name());
112 diff
= a
.Domain().compare(b
.Domain());
116 return a
.Path().compare(b
.Path());
121 CanonicalCookie::CanonicalCookie()
126 CanonicalCookie::CanonicalCookie(const GURL
& url
,
127 const std::string
& name
,
128 const std::string
& value
,
129 const std::string
& domain
,
130 const std::string
& path
,
131 const base::Time
& creation
,
132 const base::Time
& expiration
,
133 const base::Time
& last_access
,
137 CookiePriority priority
)
138 : source_(GetCookieSourceFromURL(url
)),
143 creation_date_(creation
),
144 expiry_date_(expiration
),
145 last_access_date_(last_access
),
148 first_party_only_(firstpartyonly
),
149 priority_(priority
) {
152 CanonicalCookie::CanonicalCookie(const GURL
& url
, const ParsedCookie
& pc
)
153 : source_(GetCookieSourceFromURL(url
)),
156 path_(CanonPath(url
, pc
)),
157 creation_date_(Time::Now()),
158 last_access_date_(Time()),
159 secure_(pc
.IsSecure()),
160 httponly_(pc
.IsHttpOnly()),
161 first_party_only_(pc
.IsFirstPartyOnly()),
162 priority_(pc
.Priority()) {
164 expiry_date_
= CanonExpiration(pc
, creation_date_
, creation_date_
);
166 // Do the best we can with the domain.
167 std::string cookie_domain
;
168 std::string domain_string
;
169 if (pc
.HasDomain()) {
170 domain_string
= pc
.Domain();
173 = cookie_util::GetCookieDomainWithString(url
, domain_string
,
175 // Caller is responsible for passing in good arguments.
177 domain_
= cookie_domain
;
180 CanonicalCookie::~CanonicalCookie() {
183 std::string
CanonicalCookie::GetCookieSourceFromURL(const GURL
& url
) {
184 if (url
.SchemeIsFile())
187 url::Replacements
<char> replacements
;
188 replacements
.ClearPort();
189 if (url
.SchemeIsCryptographic())
190 replacements
.SetScheme("http", url::Component(0, 4));
192 return url
.GetOrigin().ReplaceComponents(replacements
).spec();
196 std::string
CanonicalCookie::CanonPath(const GURL
& url
,
197 const ParsedCookie
& pc
) {
198 std::string path_string
;
200 path_string
= pc
.Path();
201 return CanonPathWithString(url
, path_string
);
205 Time
CanonicalCookie::CanonExpiration(const ParsedCookie
& pc
,
207 const Time
& server_time
) {
208 // First, try the Max-Age attribute.
210 if (pc
.HasMaxAge() &&
216 pc
.MaxAge().c_str(), " %" PRIu64
, &max_age
) == 1) {
217 return current
+ TimeDelta::FromSeconds(max_age
);
220 // Try the Expires attribute.
221 if (pc
.HasExpires() && !pc
.Expires().empty()) {
222 // Adjust for clock skew between server and host.
223 base::Time parsed_expiry
= cookie_util::ParseCookieTime(pc
.Expires());
224 if (!parsed_expiry
.is_null())
225 return parsed_expiry
+ (current
- server_time
);
228 // Invalid or no expiration, persistent cookie.
233 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
234 const std::string
& cookie_line
,
235 const base::Time
& creation_time
,
236 const CookieOptions
& options
) {
237 ParsedCookie
parsed_cookie(cookie_line
);
239 if (!parsed_cookie
.IsValid()) {
240 VLOG(kVlogSetCookies
) << "WARNING: Couldn't parse cookie";
244 if (options
.exclude_httponly() && parsed_cookie
.IsHttpOnly()) {
245 VLOG(kVlogSetCookies
) << "Create() is not creating a httponly cookie";
249 std::string cookie_domain
;
250 if (!GetCookieDomain(url
, parsed_cookie
, &cookie_domain
)) {
254 std::string cookie_path
= CanonicalCookie::CanonPath(url
, parsed_cookie
);
255 Time
server_time(creation_time
);
256 if (options
.has_server_time())
257 server_time
= options
.server_time();
259 Time cookie_expires
= CanonicalCookie::CanonExpiration(parsed_cookie
,
263 return new CanonicalCookie(
264 url
, parsed_cookie
.Name(), parsed_cookie
.Value(), cookie_domain
,
265 cookie_path
, creation_time
, cookie_expires
, creation_time
,
266 parsed_cookie
.IsSecure(), parsed_cookie
.IsHttpOnly(),
267 parsed_cookie
.IsFirstPartyOnly(), parsed_cookie
.Priority());
270 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
271 const std::string
& name
,
272 const std::string
& value
,
273 const std::string
& domain
,
274 const std::string
& path
,
275 const base::Time
& creation
,
276 const base::Time
& expiration
,
279 bool first_party_only
,
280 CookiePriority priority
) {
281 // Expect valid attribute tokens and values, as defined by the ParsedCookie
282 // logic, otherwise don't create the cookie.
283 std::string parsed_name
= ParsedCookie::ParseTokenString(name
);
284 if (parsed_name
!= name
)
286 std::string parsed_value
= ParsedCookie::ParseValueString(value
);
287 if (parsed_value
!= value
)
290 std::string parsed_domain
= ParsedCookie::ParseValueString(domain
);
291 if (parsed_domain
!= domain
)
293 std::string cookie_domain
;
294 if (!cookie_util::GetCookieDomainWithString(url
, parsed_domain
,
299 std::string parsed_path
= ParsedCookie::ParseValueString(path
);
300 if (parsed_path
!= path
)
303 std::string cookie_path
= CanonPathWithString(url
, parsed_path
);
304 // Expect that the path was either not specified (empty), or is valid.
305 if (!parsed_path
.empty() && cookie_path
!= parsed_path
)
307 // Canonicalize path again to make sure it escapes characters as needed.
308 url::Component
path_component(0, cookie_path
.length());
309 url::RawCanonOutputT
<char> canon_path
;
310 url::Component canon_path_component
;
311 url::CanonicalizePath(cookie_path
.data(), path_component
, &canon_path
,
312 &canon_path_component
);
313 cookie_path
= std::string(canon_path
.data() + canon_path_component
.begin
,
314 canon_path_component
.len
);
316 return new CanonicalCookie(url
, parsed_name
, parsed_value
, cookie_domain
,
317 cookie_path
, creation
, expiration
, creation
,
318 secure
, http_only
, first_party_only
, priority
);
321 bool CanonicalCookie::IsOnPath(const std::string
& url_path
) const {
323 // A zero length would be unsafe for our trailing '/' checks, and
324 // would also make no sense for our prefix match. The code that
325 // creates a CanonicalCookie should make sure the path is never zero length,
326 // but we double check anyway.
330 // The Mozilla code broke this into three cases, based on if the cookie path
331 // was longer, the same length, or shorter than the length of the url path.
332 // I think the approach below is simpler.
334 // Make sure the cookie path is a prefix of the url path. If the
335 // url path is shorter than the cookie path, then the cookie path
336 // can't be a prefix.
337 if (url_path
.find(path_
) != 0)
340 // Now we know that url_path is >= cookie_path, and that cookie_path
341 // is a prefix of url_path. If they are the are the same length then
342 // they are identical, otherwise we need an additional check:
344 // In order to avoid in correctly matching a cookie path of /blah
345 // with a request path of '/blahblah/', we need to make sure that either
346 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
347 // in the url path. Since we know that the url path length is greater
348 // than the cookie path length, it's safe to index one byte past.
349 if (path_
.length() != url_path
.length() &&
350 path_
[path_
.length() - 1] != '/' &&
351 url_path
[path_
.length()] != '/')
357 bool CanonicalCookie::IsDomainMatch(const std::string
& host
) const {
358 // Can domain match in two ways; as a domain cookie (where the cookie
359 // domain begins with ".") or as a host cookie (where it doesn't).
361 // Some consumers of the CookieMonster expect to set cookies on
362 // URLs like http://.strange.url. To retrieve cookies in this instance,
363 // we allow matching as a host cookie even when the domain_ starts with
368 // Domain cookie must have an initial ".". To match, it must be
369 // equal to url's host with initial period removed, or a suffix of
372 // Arguably this should only apply to "http" or "https" cookies, but
373 // extension cookie tests currently use the funtionality, and if we
374 // ever decide to implement that it should be done by preventing
375 // such cookies from being set.
376 if (domain_
.empty() || domain_
[0] != '.')
379 // The host with a "." prefixed.
380 if (domain_
.compare(1, std::string::npos
, host
) == 0)
383 // A pure suffix of the host (ok since we know the domain already
384 // starts with a ".")
385 return (host
.length() > domain_
.length() &&
386 host
.compare(host
.length() - domain_
.length(),
387 domain_
.length(), domain_
) == 0);
390 bool CanonicalCookie::IncludeForRequestURL(const GURL
& url
,
391 const CookieOptions
& options
) const {
392 // Filter out HttpOnly cookies, per options.
393 if (options
.exclude_httponly() && IsHttpOnly())
395 // Secure cookies should not be included in requests for URLs with an
397 if (IsSecure() && !url
.SchemeIsCryptographic())
399 // Don't include cookies for requests that don't apply to the cookie domain.
400 if (!IsDomainMatch(url
.host()))
402 // Don't include cookies for requests with a url path that does not path
403 // match the cookie-path.
404 if (!IsOnPath(url
.path()))
407 // Include first-party-only cookies iff |options| tells us to include all of
408 // them, or if a first-party URL is set and its origin matches the origin of
410 if (IsFirstPartyOnly() && !options
.include_first_party_only() &&
411 options
.first_party_url().GetOrigin() != url
.GetOrigin()) {
418 std::string
CanonicalCookie::DebugString() const {
419 return base::StringPrintf(
420 "name: %s value: %s domain: %s path: %s creation: %"
422 name_
.c_str(), value_
.c_str(),
423 domain_
.c_str(), path_
.c_str(),
424 static_cast<int64
>(creation_date_
.ToTimeT()));
427 bool CanonicalCookie::PartialCompare(const CanonicalCookie
& other
) const {
428 return PartialCookieOrdering(*this, other
) < 0;
431 bool CanonicalCookie::FullCompare(const CanonicalCookie
& other
) const {
432 // Do the partial comparison first.
433 int diff
= PartialCookieOrdering(*this, other
);
437 DCHECK(IsEquivalent(other
));
439 // Compare other fields.
440 diff
= Value().compare(other
.Value());
444 if (CreationDate() != other
.CreationDate())
445 return CreationDate() < other
.CreationDate();
447 if (ExpiryDate() != other
.ExpiryDate())
448 return ExpiryDate() < other
.ExpiryDate();
450 if (LastAccessDate() != other
.LastAccessDate())
451 return LastAccessDate() < other
.LastAccessDate();
453 if (IsSecure() != other
.IsSecure())
456 if (IsHttpOnly() != other
.IsHttpOnly())
459 return Priority() < other
.Priority();