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_(url
.SchemeIsFile() ? url
: url
.GetOrigin()),
143 creation_date_(creation
),
144 expiry_date_(expiration
),
145 last_access_date_(last_access
),
148 first_party_only_(firstpartyonly
),
149 priority_(priority
) {}
151 CanonicalCookie::CanonicalCookie(const GURL
& url
, const ParsedCookie
& pc
)
152 : source_(url
.SchemeIsFile() ? url
: url
.GetOrigin()),
155 path_(CanonPath(url
, pc
)),
156 creation_date_(Time::Now()),
157 last_access_date_(Time()),
158 secure_(pc
.IsSecure()),
159 httponly_(pc
.IsHttpOnly()),
160 first_party_only_(pc
.IsFirstPartyOnly()),
161 priority_(pc
.Priority()) {
163 expiry_date_
= CanonExpiration(pc
, creation_date_
, creation_date_
);
165 // Do the best we can with the domain.
166 std::string cookie_domain
;
167 std::string domain_string
;
168 if (pc
.HasDomain()) {
169 domain_string
= pc
.Domain();
172 = cookie_util::GetCookieDomainWithString(url
, domain_string
,
174 // Caller is responsible for passing in good arguments.
176 domain_
= cookie_domain
;
179 CanonicalCookie::~CanonicalCookie() {
183 std::string
CanonicalCookie::CanonPath(const GURL
& url
,
184 const ParsedCookie
& pc
) {
185 std::string path_string
;
187 path_string
= pc
.Path();
188 return CanonPathWithString(url
, path_string
);
192 Time
CanonicalCookie::CanonExpiration(const ParsedCookie
& pc
,
194 const Time
& server_time
) {
195 // First, try the Max-Age attribute.
197 if (pc
.HasMaxAge() &&
203 pc
.MaxAge().c_str(), " %" PRIu64
, &max_age
) == 1) {
204 return current
+ TimeDelta::FromSeconds(max_age
);
207 // Try the Expires attribute.
208 if (pc
.HasExpires() && !pc
.Expires().empty()) {
209 // Adjust for clock skew between server and host.
210 base::Time parsed_expiry
= cookie_util::ParseCookieTime(pc
.Expires());
211 if (!parsed_expiry
.is_null())
212 return parsed_expiry
+ (current
- server_time
);
215 // Invalid or no expiration, persistent cookie.
220 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
221 const std::string
& cookie_line
,
222 const base::Time
& creation_time
,
223 const CookieOptions
& options
) {
224 ParsedCookie
parsed_cookie(cookie_line
);
226 if (!parsed_cookie
.IsValid()) {
227 VLOG(kVlogSetCookies
) << "WARNING: Couldn't parse cookie";
231 if (options
.exclude_httponly() && parsed_cookie
.IsHttpOnly()) {
232 VLOG(kVlogSetCookies
) << "Create() is not creating a httponly cookie";
236 std::string cookie_domain
;
237 if (!GetCookieDomain(url
, parsed_cookie
, &cookie_domain
)) {
241 std::string cookie_path
= CanonicalCookie::CanonPath(url
, parsed_cookie
);
242 Time
server_time(creation_time
);
243 if (options
.has_server_time())
244 server_time
= options
.server_time();
246 Time cookie_expires
= CanonicalCookie::CanonExpiration(parsed_cookie
,
250 return new CanonicalCookie(
251 url
, parsed_cookie
.Name(), parsed_cookie
.Value(), cookie_domain
,
252 cookie_path
, creation_time
, cookie_expires
, creation_time
,
253 parsed_cookie
.IsSecure(), parsed_cookie
.IsHttpOnly(),
254 parsed_cookie
.IsFirstPartyOnly(), parsed_cookie
.Priority());
257 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
258 const std::string
& name
,
259 const std::string
& value
,
260 const std::string
& domain
,
261 const std::string
& path
,
262 const base::Time
& creation
,
263 const base::Time
& expiration
,
266 bool first_party_only
,
267 CookiePriority priority
) {
268 // Expect valid attribute tokens and values, as defined by the ParsedCookie
269 // logic, otherwise don't create the cookie.
270 std::string parsed_name
= ParsedCookie::ParseTokenString(name
);
271 if (parsed_name
!= name
)
273 std::string parsed_value
= ParsedCookie::ParseValueString(value
);
274 if (parsed_value
!= value
)
277 std::string parsed_domain
= ParsedCookie::ParseValueString(domain
);
278 if (parsed_domain
!= domain
)
280 std::string cookie_domain
;
281 if (!cookie_util::GetCookieDomainWithString(url
, parsed_domain
,
286 std::string parsed_path
= ParsedCookie::ParseValueString(path
);
287 if (parsed_path
!= path
)
290 std::string cookie_path
= CanonPathWithString(url
, parsed_path
);
291 // Expect that the path was either not specified (empty), or is valid.
292 if (!parsed_path
.empty() && cookie_path
!= parsed_path
)
294 // Canonicalize path again to make sure it escapes characters as needed.
295 url::Component
path_component(0, cookie_path
.length());
296 url::RawCanonOutputT
<char> canon_path
;
297 url::Component canon_path_component
;
298 url::CanonicalizePath(cookie_path
.data(), path_component
, &canon_path
,
299 &canon_path_component
);
300 cookie_path
= std::string(canon_path
.data() + canon_path_component
.begin
,
301 canon_path_component
.len
);
303 return new CanonicalCookie(url
, parsed_name
, parsed_value
, cookie_domain
,
304 cookie_path
, creation
, expiration
, creation
,
305 secure
, http_only
, first_party_only
, priority
);
308 bool CanonicalCookie::IsOnPath(const std::string
& url_path
) const {
310 // A zero length would be unsafe for our trailing '/' checks, and
311 // would also make no sense for our prefix match. The code that
312 // creates a CanonicalCookie should make sure the path is never zero length,
313 // but we double check anyway.
317 // The Mozilla code broke this into three cases, based on if the cookie path
318 // was longer, the same length, or shorter than the length of the url path.
319 // I think the approach below is simpler.
321 // Make sure the cookie path is a prefix of the url path. If the
322 // url path is shorter than the cookie path, then the cookie path
323 // can't be a prefix.
324 if (url_path
.find(path_
) != 0)
327 // Now we know that url_path is >= cookie_path, and that cookie_path
328 // is a prefix of url_path. If they are the are the same length then
329 // they are identical, otherwise we need an additional check:
331 // In order to avoid in correctly matching a cookie path of /blah
332 // with a request path of '/blahblah/', we need to make sure that either
333 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
334 // in the url path. Since we know that the url path length is greater
335 // than the cookie path length, it's safe to index one byte past.
336 if (path_
.length() != url_path
.length() &&
337 path_
[path_
.length() - 1] != '/' &&
338 url_path
[path_
.length()] != '/')
344 bool CanonicalCookie::IsDomainMatch(const std::string
& host
) const {
345 // Can domain match in two ways; as a domain cookie (where the cookie
346 // domain begins with ".") or as a host cookie (where it doesn't).
348 // Some consumers of the CookieMonster expect to set cookies on
349 // URLs like http://.strange.url. To retrieve cookies in this instance,
350 // we allow matching as a host cookie even when the domain_ starts with
355 // Domain cookie must have an initial ".". To match, it must be
356 // equal to url's host with initial period removed, or a suffix of
359 // Arguably this should only apply to "http" or "https" cookies, but
360 // extension cookie tests currently use the funtionality, and if we
361 // ever decide to implement that it should be done by preventing
362 // such cookies from being set.
363 if (domain_
.empty() || domain_
[0] != '.')
366 // The host with a "." prefixed.
367 if (domain_
.compare(1, std::string::npos
, host
) == 0)
370 // A pure suffix of the host (ok since we know the domain already
371 // starts with a ".")
372 return (host
.length() > domain_
.length() &&
373 host
.compare(host
.length() - domain_
.length(),
374 domain_
.length(), domain_
) == 0);
377 bool CanonicalCookie::IncludeForRequestURL(const GURL
& url
,
378 const CookieOptions
& options
) const {
379 // Filter out HttpOnly cookies, per options.
380 if (options
.exclude_httponly() && IsHttpOnly())
382 // Secure cookies should not be included in requests for URLs with an
384 if (IsSecure() && !url
.SchemeIsCryptographic())
386 // Don't include cookies for requests that don't apply to the cookie domain.
387 if (!IsDomainMatch(url
.host()))
389 // Don't include cookies for requests with a url path that does not path
390 // match the cookie-path.
391 if (!IsOnPath(url
.path()))
394 // Include first-party-only cookies iff |options| tells us to include all of
395 // them, or if a first-party URL is set and its origin matches the origin of
397 if (IsFirstPartyOnly() && !options
.include_first_party_only() &&
398 options
.first_party_url().GetOrigin() != url
.GetOrigin()) {
405 std::string
CanonicalCookie::DebugString() const {
406 return base::StringPrintf(
407 "name: %s value: %s domain: %s path: %s creation: %"
409 name_
.c_str(), value_
.c_str(),
410 domain_
.c_str(), path_
.c_str(),
411 static_cast<int64
>(creation_date_
.ToTimeT()));
414 bool CanonicalCookie::PartialCompare(const CanonicalCookie
& other
) const {
415 return PartialCookieOrdering(*this, other
) < 0;
418 bool CanonicalCookie::FullCompare(const CanonicalCookie
& other
) const {
419 // Do the partial comparison first.
420 int diff
= PartialCookieOrdering(*this, other
);
424 DCHECK(IsEquivalent(other
));
426 // Compare other fields.
427 diff
= Value().compare(other
.Value());
431 if (CreationDate() != other
.CreationDate())
432 return CreationDate() < other
.CreationDate();
434 if (ExpiryDate() != other
.ExpiryDate())
435 return ExpiryDate() < other
.ExpiryDate();
437 if (LastAccessDate() != other
.LastAccessDate())
438 return LastAccessDate() < other
.LastAccessDate();
440 if (IsSecure() != other
.IsSecure())
443 if (IsHttpOnly() != other
.IsHttpOnly())
446 return Priority() < other
.Priority();