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
);
107 CanonicalCookie::CanonicalCookie()
112 CanonicalCookie::CanonicalCookie(const GURL
& url
,
113 const std::string
& name
,
114 const std::string
& value
,
115 const std::string
& domain
,
116 const std::string
& path
,
117 const base::Time
& creation
,
118 const base::Time
& expiration
,
119 const base::Time
& last_access
,
123 CookiePriority priority
)
124 : source_(GetCookieSourceFromURL(url
)),
129 creation_date_(creation
),
130 expiry_date_(expiration
),
131 last_access_date_(last_access
),
134 first_party_only_(firstpartyonly
),
135 priority_(priority
) {
138 CanonicalCookie::CanonicalCookie(const GURL
& url
, const ParsedCookie
& pc
)
139 : source_(GetCookieSourceFromURL(url
)),
142 path_(CanonPath(url
, pc
)),
143 creation_date_(Time::Now()),
144 last_access_date_(Time()),
145 secure_(pc
.IsSecure()),
146 httponly_(pc
.IsHttpOnly()),
147 first_party_only_(pc
.IsFirstPartyOnly()),
148 priority_(pc
.Priority()) {
150 expiry_date_
= CanonExpiration(pc
, creation_date_
, creation_date_
);
152 // Do the best we can with the domain.
153 std::string cookie_domain
;
154 std::string domain_string
;
155 if (pc
.HasDomain()) {
156 domain_string
= pc
.Domain();
159 = cookie_util::GetCookieDomainWithString(url
, domain_string
,
161 // Caller is responsible for passing in good arguments.
163 domain_
= cookie_domain
;
166 CanonicalCookie::~CanonicalCookie() {
169 std::string
CanonicalCookie::GetCookieSourceFromURL(const GURL
& url
) {
170 if (url
.SchemeIsFile())
173 url::Replacements
<char> replacements
;
174 replacements
.ClearPort();
175 if (url
.SchemeIsSecure())
176 replacements
.SetScheme("http", url::Component(0, 4));
178 return url
.GetOrigin().ReplaceComponents(replacements
).spec();
182 std::string
CanonicalCookie::CanonPath(const GURL
& url
,
183 const ParsedCookie
& pc
) {
184 std::string path_string
;
186 path_string
= pc
.Path();
187 return CanonPathWithString(url
, path_string
);
191 Time
CanonicalCookie::CanonExpiration(const ParsedCookie
& pc
,
193 const Time
& server_time
) {
194 // First, try the Max-Age attribute.
196 if (pc
.HasMaxAge() &&
202 pc
.MaxAge().c_str(), " %" PRIu64
, &max_age
) == 1) {
203 return current
+ TimeDelta::FromSeconds(max_age
);
206 // Try the Expires attribute.
207 if (pc
.HasExpires() && !pc
.Expires().empty()) {
208 // Adjust for clock skew between server and host.
209 base::Time parsed_expiry
= cookie_util::ParseCookieTime(pc
.Expires());
210 if (!parsed_expiry
.is_null())
211 return parsed_expiry
+ (current
- server_time
);
214 // Invalid or no expiration, persistent cookie.
219 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
220 const std::string
& cookie_line
,
221 const base::Time
& creation_time
,
222 const CookieOptions
& options
) {
223 ParsedCookie
parsed_cookie(cookie_line
);
225 if (!parsed_cookie
.IsValid()) {
226 VLOG(kVlogSetCookies
) << "WARNING: Couldn't parse cookie";
230 if (options
.exclude_httponly() && parsed_cookie
.IsHttpOnly()) {
231 VLOG(kVlogSetCookies
) << "Create() is not creating a httponly cookie";
235 std::string cookie_domain
;
236 if (!GetCookieDomain(url
, parsed_cookie
, &cookie_domain
)) {
240 std::string cookie_path
= CanonicalCookie::CanonPath(url
, parsed_cookie
);
241 Time
server_time(creation_time
);
242 if (options
.has_server_time())
243 server_time
= options
.server_time();
245 Time cookie_expires
= CanonicalCookie::CanonExpiration(parsed_cookie
,
249 return new CanonicalCookie(
250 url
, parsed_cookie
.Name(), parsed_cookie
.Value(), cookie_domain
,
251 cookie_path
, creation_time
, cookie_expires
, creation_time
,
252 parsed_cookie
.IsSecure(), parsed_cookie
.IsHttpOnly(),
253 parsed_cookie
.IsFirstPartyOnly(), parsed_cookie
.Priority());
256 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
257 const std::string
& name
,
258 const std::string
& value
,
259 const std::string
& domain
,
260 const std::string
& path
,
261 const base::Time
& creation
,
262 const base::Time
& expiration
,
265 bool first_party_only
,
266 CookiePriority priority
) {
267 // Expect valid attribute tokens and values, as defined by the ParsedCookie
268 // logic, otherwise don't create the cookie.
269 std::string parsed_name
= ParsedCookie::ParseTokenString(name
);
270 if (parsed_name
!= name
)
272 std::string parsed_value
= ParsedCookie::ParseValueString(value
);
273 if (parsed_value
!= value
)
276 std::string parsed_domain
= ParsedCookie::ParseValueString(domain
);
277 if (parsed_domain
!= domain
)
279 std::string cookie_domain
;
280 if (!cookie_util::GetCookieDomainWithString(url
, parsed_domain
,
285 std::string parsed_path
= ParsedCookie::ParseValueString(path
);
286 if (parsed_path
!= path
)
289 std::string cookie_path
= CanonPathWithString(url
, parsed_path
);
290 // Expect that the path was either not specified (empty), or is valid.
291 if (!parsed_path
.empty() && cookie_path
!= parsed_path
)
293 // Canonicalize path again to make sure it escapes characters as needed.
294 url::Component
path_component(0, cookie_path
.length());
295 url::RawCanonOutputT
<char> canon_path
;
296 url::Component canon_path_component
;
297 url::CanonicalizePath(cookie_path
.data(), path_component
, &canon_path
,
298 &canon_path_component
);
299 cookie_path
= std::string(canon_path
.data() + canon_path_component
.begin
,
300 canon_path_component
.len
);
302 return new CanonicalCookie(url
, parsed_name
, parsed_value
, cookie_domain
,
303 cookie_path
, creation
, expiration
, creation
,
304 secure
, http_only
, first_party_only
, priority
);
307 bool CanonicalCookie::IsOnPath(const std::string
& url_path
) const {
309 // A zero length would be unsafe for our trailing '/' checks, and
310 // would also make no sense for our prefix match. The code that
311 // creates a CanonicalCookie should make sure the path is never zero length,
312 // but we double check anyway.
316 // The Mozilla code broke this into three cases, based on if the cookie path
317 // was longer, the same length, or shorter than the length of the url path.
318 // I think the approach below is simpler.
320 // Make sure the cookie path is a prefix of the url path. If the
321 // url path is shorter than the cookie path, then the cookie path
322 // can't be a prefix.
323 if (url_path
.find(path_
) != 0)
326 // Now we know that url_path is >= cookie_path, and that cookie_path
327 // is a prefix of url_path. If they are the are the same length then
328 // they are identical, otherwise we need an additional check:
330 // In order to avoid in correctly matching a cookie path of /blah
331 // with a request path of '/blahblah/', we need to make sure that either
332 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
333 // in the url path. Since we know that the url path length is greater
334 // than the cookie path length, it's safe to index one byte past.
335 if (path_
.length() != url_path
.length() &&
336 path_
[path_
.length() - 1] != '/' &&
337 url_path
[path_
.length()] != '/')
343 bool CanonicalCookie::IsDomainMatch(const std::string
& host
) const {
344 // Can domain match in two ways; as a domain cookie (where the cookie
345 // domain begins with ".") or as a host cookie (where it doesn't).
347 // Some consumers of the CookieMonster expect to set cookies on
348 // URLs like http://.strange.url. To retrieve cookies in this instance,
349 // we allow matching as a host cookie even when the domain_ starts with
354 // Domain cookie must have an initial ".". To match, it must be
355 // equal to url's host with initial period removed, or a suffix of
358 // Arguably this should only apply to "http" or "https" cookies, but
359 // extension cookie tests currently use the funtionality, and if we
360 // ever decide to implement that it should be done by preventing
361 // such cookies from being set.
362 if (domain_
.empty() || domain_
[0] != '.')
365 // The host with a "." prefixed.
366 if (domain_
.compare(1, std::string::npos
, host
) == 0)
369 // A pure suffix of the host (ok since we know the domain already
370 // starts with a ".")
371 return (host
.length() > domain_
.length() &&
372 host
.compare(host
.length() - domain_
.length(),
373 domain_
.length(), domain_
) == 0);
376 bool CanonicalCookie::IncludeForRequestURL(const GURL
& url
,
377 const CookieOptions
& options
) const {
378 // Filter out HttpOnly cookies, per options.
379 if (options
.exclude_httponly() && IsHttpOnly())
381 // Secure cookies should not be included in requests for URLs with an
383 if (IsSecure() && !url
.SchemeIsSecure())
385 // Don't include cookies for requests that don't apply to the cookie domain.
386 if (!IsDomainMatch(url
.host()))
388 // Don't include cookies for requests with a url path that does not path
389 // match the cookie-path.
390 if (!IsOnPath(url
.path()))
393 // Include first-party-only cookies iff |options| tells us to include all of
394 // them, or if a first-party URL is set and its origin matches the origin of
396 if (IsFirstPartyOnly() && !options
.include_first_party_only() &&
397 options
.first_party_url().GetOrigin() != url
.GetOrigin()) {
404 std::string
CanonicalCookie::DebugString() const {
405 return base::StringPrintf(
406 "name: %s value: %s domain: %s path: %s creation: %"
408 name_
.c_str(), value_
.c_str(),
409 domain_
.c_str(), path_
.c_str(),
410 static_cast<int64
>(creation_date_
.ToTimeT()));
413 CanonicalCookie
* CanonicalCookie::Duplicate() const {
414 CanonicalCookie
* cc
= new CanonicalCookie();
415 cc
->source_
= source_
;
418 cc
->domain_
= domain_
;
420 cc
->creation_date_
= creation_date_
;
421 cc
->expiry_date_
= expiry_date_
;
422 cc
->last_access_date_
= last_access_date_
;
423 cc
->secure_
= secure_
;
424 cc
->httponly_
= httponly_
;
425 cc
->first_party_only_
= first_party_only_
;
426 cc
->priority_
= priority_
;