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 net::CanonicalCookie
& a
,
108 const net::CanonicalCookie
& b
) {
109 int diff
= a
.Name().compare(b
.Name());
113 diff
= a
.Domain().compare(b
.Domain());
117 return a
.Path().compare(b
.Path());
122 CanonicalCookie::CanonicalCookie()
127 CanonicalCookie::CanonicalCookie(const GURL
& url
,
128 const std::string
& name
,
129 const std::string
& value
,
130 const std::string
& domain
,
131 const std::string
& path
,
132 const base::Time
& creation
,
133 const base::Time
& expiration
,
134 const base::Time
& last_access
,
138 CookiePriority priority
)
139 : source_(GetCookieSourceFromURL(url
)),
144 creation_date_(creation
),
145 expiry_date_(expiration
),
146 last_access_date_(last_access
),
149 first_party_only_(firstpartyonly
),
150 priority_(priority
) {
153 CanonicalCookie::CanonicalCookie(const GURL
& url
, const ParsedCookie
& pc
)
154 : source_(GetCookieSourceFromURL(url
)),
157 path_(CanonPath(url
, pc
)),
158 creation_date_(Time::Now()),
159 last_access_date_(Time()),
160 secure_(pc
.IsSecure()),
161 httponly_(pc
.IsHttpOnly()),
162 first_party_only_(pc
.IsFirstPartyOnly()),
163 priority_(pc
.Priority()) {
165 expiry_date_
= CanonExpiration(pc
, creation_date_
, creation_date_
);
167 // Do the best we can with the domain.
168 std::string cookie_domain
;
169 std::string domain_string
;
170 if (pc
.HasDomain()) {
171 domain_string
= pc
.Domain();
174 = cookie_util::GetCookieDomainWithString(url
, domain_string
,
176 // Caller is responsible for passing in good arguments.
178 domain_
= cookie_domain
;
181 CanonicalCookie::~CanonicalCookie() {
184 std::string
CanonicalCookie::GetCookieSourceFromURL(const GURL
& url
) {
185 if (url
.SchemeIsFile())
188 url::Replacements
<char> replacements
;
189 replacements
.ClearPort();
190 if (url
.SchemeIsSecure())
191 replacements
.SetScheme("http", url::Component(0, 4));
193 return url
.GetOrigin().ReplaceComponents(replacements
).spec();
197 std::string
CanonicalCookie::CanonPath(const GURL
& url
,
198 const ParsedCookie
& pc
) {
199 std::string path_string
;
201 path_string
= pc
.Path();
202 return CanonPathWithString(url
, path_string
);
206 Time
CanonicalCookie::CanonExpiration(const ParsedCookie
& pc
,
208 const Time
& server_time
) {
209 // First, try the Max-Age attribute.
211 if (pc
.HasMaxAge() &&
217 pc
.MaxAge().c_str(), " %" PRIu64
, &max_age
) == 1) {
218 return current
+ TimeDelta::FromSeconds(max_age
);
221 // Try the Expires attribute.
222 if (pc
.HasExpires() && !pc
.Expires().empty()) {
223 // Adjust for clock skew between server and host.
224 base::Time parsed_expiry
= cookie_util::ParseCookieTime(pc
.Expires());
225 if (!parsed_expiry
.is_null())
226 return parsed_expiry
+ (current
- server_time
);
229 // Invalid or no expiration, persistent cookie.
234 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
235 const std::string
& cookie_line
,
236 const base::Time
& creation_time
,
237 const CookieOptions
& options
) {
238 ParsedCookie
parsed_cookie(cookie_line
);
240 if (!parsed_cookie
.IsValid()) {
241 VLOG(kVlogSetCookies
) << "WARNING: Couldn't parse cookie";
245 if (options
.exclude_httponly() && parsed_cookie
.IsHttpOnly()) {
246 VLOG(kVlogSetCookies
) << "Create() is not creating a httponly cookie";
250 std::string cookie_domain
;
251 if (!GetCookieDomain(url
, parsed_cookie
, &cookie_domain
)) {
255 std::string cookie_path
= CanonicalCookie::CanonPath(url
, parsed_cookie
);
256 Time
server_time(creation_time
);
257 if (options
.has_server_time())
258 server_time
= options
.server_time();
260 Time cookie_expires
= CanonicalCookie::CanonExpiration(parsed_cookie
,
264 return new CanonicalCookie(
265 url
, parsed_cookie
.Name(), parsed_cookie
.Value(), cookie_domain
,
266 cookie_path
, creation_time
, cookie_expires
, creation_time
,
267 parsed_cookie
.IsSecure(), parsed_cookie
.IsHttpOnly(),
268 parsed_cookie
.IsFirstPartyOnly(), parsed_cookie
.Priority());
271 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
272 const std::string
& name
,
273 const std::string
& value
,
274 const std::string
& domain
,
275 const std::string
& path
,
276 const base::Time
& creation
,
277 const base::Time
& expiration
,
280 bool first_party_only
,
281 CookiePriority priority
) {
282 // Expect valid attribute tokens and values, as defined by the ParsedCookie
283 // logic, otherwise don't create the cookie.
284 std::string parsed_name
= ParsedCookie::ParseTokenString(name
);
285 if (parsed_name
!= name
)
287 std::string parsed_value
= ParsedCookie::ParseValueString(value
);
288 if (parsed_value
!= value
)
291 std::string parsed_domain
= ParsedCookie::ParseValueString(domain
);
292 if (parsed_domain
!= domain
)
294 std::string cookie_domain
;
295 if (!cookie_util::GetCookieDomainWithString(url
, parsed_domain
,
300 std::string parsed_path
= ParsedCookie::ParseValueString(path
);
301 if (parsed_path
!= path
)
304 std::string cookie_path
= CanonPathWithString(url
, parsed_path
);
305 // Expect that the path was either not specified (empty), or is valid.
306 if (!parsed_path
.empty() && cookie_path
!= parsed_path
)
308 // Canonicalize path again to make sure it escapes characters as needed.
309 url::Component
path_component(0, cookie_path
.length());
310 url::RawCanonOutputT
<char> canon_path
;
311 url::Component canon_path_component
;
312 url::CanonicalizePath(cookie_path
.data(), path_component
, &canon_path
,
313 &canon_path_component
);
314 cookie_path
= std::string(canon_path
.data() + canon_path_component
.begin
,
315 canon_path_component
.len
);
317 return new CanonicalCookie(url
, parsed_name
, parsed_value
, cookie_domain
,
318 cookie_path
, creation
, expiration
, creation
,
319 secure
, http_only
, first_party_only
, priority
);
322 bool CanonicalCookie::IsOnPath(const std::string
& url_path
) const {
324 // A zero length would be unsafe for our trailing '/' checks, and
325 // would also make no sense for our prefix match. The code that
326 // creates a CanonicalCookie should make sure the path is never zero length,
327 // but we double check anyway.
331 // The Mozilla code broke this into three cases, based on if the cookie path
332 // was longer, the same length, or shorter than the length of the url path.
333 // I think the approach below is simpler.
335 // Make sure the cookie path is a prefix of the url path. If the
336 // url path is shorter than the cookie path, then the cookie path
337 // can't be a prefix.
338 if (url_path
.find(path_
) != 0)
341 // Now we know that url_path is >= cookie_path, and that cookie_path
342 // is a prefix of url_path. If they are the are the same length then
343 // they are identical, otherwise we need an additional check:
345 // In order to avoid in correctly matching a cookie path of /blah
346 // with a request path of '/blahblah/', we need to make sure that either
347 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
348 // in the url path. Since we know that the url path length is greater
349 // than the cookie path length, it's safe to index one byte past.
350 if (path_
.length() != url_path
.length() &&
351 path_
[path_
.length() - 1] != '/' &&
352 url_path
[path_
.length()] != '/')
358 bool CanonicalCookie::IsDomainMatch(const std::string
& host
) const {
359 // Can domain match in two ways; as a domain cookie (where the cookie
360 // domain begins with ".") or as a host cookie (where it doesn't).
362 // Some consumers of the CookieMonster expect to set cookies on
363 // URLs like http://.strange.url. To retrieve cookies in this instance,
364 // we allow matching as a host cookie even when the domain_ starts with
369 // Domain cookie must have an initial ".". To match, it must be
370 // equal to url's host with initial period removed, or a suffix of
373 // Arguably this should only apply to "http" or "https" cookies, but
374 // extension cookie tests currently use the funtionality, and if we
375 // ever decide to implement that it should be done by preventing
376 // such cookies from being set.
377 if (domain_
.empty() || domain_
[0] != '.')
380 // The host with a "." prefixed.
381 if (domain_
.compare(1, std::string::npos
, host
) == 0)
384 // A pure suffix of the host (ok since we know the domain already
385 // starts with a ".")
386 return (host
.length() > domain_
.length() &&
387 host
.compare(host
.length() - domain_
.length(),
388 domain_
.length(), domain_
) == 0);
391 bool CanonicalCookie::IncludeForRequestURL(const GURL
& url
,
392 const CookieOptions
& options
) const {
393 // Filter out HttpOnly cookies, per options.
394 if (options
.exclude_httponly() && IsHttpOnly())
396 // Secure cookies should not be included in requests for URLs with an
398 if (IsSecure() && !url
.SchemeIsSecure())
400 // Don't include cookies for requests that don't apply to the cookie domain.
401 if (!IsDomainMatch(url
.host()))
403 // Don't include cookies for requests with a url path that does not path
404 // match the cookie-path.
405 if (!IsOnPath(url
.path()))
408 // Include first-party-only cookies iff |options| tells us to include all of
409 // them, or if a first-party URL is set and its origin matches the origin of
411 if (IsFirstPartyOnly() && !options
.include_first_party_only() &&
412 options
.first_party_url().GetOrigin() != url
.GetOrigin()) {
419 std::string
CanonicalCookie::DebugString() const {
420 return base::StringPrintf(
421 "name: %s value: %s domain: %s path: %s creation: %"
423 name_
.c_str(), value_
.c_str(),
424 domain_
.c_str(), path_
.c_str(),
425 static_cast<int64
>(creation_date_
.ToTimeT()));
428 bool CanonicalCookie::PartialCompare(const CanonicalCookie
& other
) const {
429 return PartialCookieOrdering(*this, other
) < 0;
432 bool CanonicalCookie::FullCompare(const CanonicalCookie
& other
) const {
433 // Do the partial comparison first.
434 int diff
= PartialCookieOrdering(*this, other
);
438 DCHECK(IsEquivalent(other
));
440 // Compare other fields.
441 diff
= Value().compare(other
.Value());
445 if (CreationDate() != other
.CreationDate())
446 return CreationDate() < other
.CreationDate();
448 if (ExpiryDate() != other
.ExpiryDate())
449 return ExpiryDate() < other
.ExpiryDate();
451 if (LastAccessDate() != other
.LastAccessDate())
452 return LastAccessDate() < other
.LastAccessDate();
454 if (IsSecure() != other
.IsSecure())
457 if (IsHttpOnly() != other
.IsHttpOnly())
460 return Priority() < other
.Priority();