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(
113 const GURL
& url
, const std::string
& name
, const std::string
& value
,
114 const std::string
& domain
, const std::string
& path
,
115 const base::Time
& creation
, const base::Time
& expiration
,
116 const base::Time
& last_access
, bool secure
, bool httponly
,
117 CookiePriority priority
)
118 : source_(GetCookieSourceFromURL(url
)),
123 creation_date_(creation
),
124 expiry_date_(expiration
),
125 last_access_date_(last_access
),
128 priority_(priority
) {
131 CanonicalCookie::CanonicalCookie(const GURL
& url
, const ParsedCookie
& pc
)
132 : source_(GetCookieSourceFromURL(url
)),
135 path_(CanonPath(url
, pc
)),
136 creation_date_(Time::Now()),
137 last_access_date_(Time()),
138 secure_(pc
.IsSecure()),
139 httponly_(pc
.IsHttpOnly()),
140 priority_(pc
.Priority()) {
142 expiry_date_
= CanonExpiration(pc
, creation_date_
, creation_date_
);
144 // Do the best we can with the domain.
145 std::string cookie_domain
;
146 std::string domain_string
;
147 if (pc
.HasDomain()) {
148 domain_string
= pc
.Domain();
151 = cookie_util::GetCookieDomainWithString(url
, domain_string
,
153 // Caller is responsible for passing in good arguments.
155 domain_
= cookie_domain
;
158 CanonicalCookie::~CanonicalCookie() {
161 std::string
CanonicalCookie::GetCookieSourceFromURL(const GURL
& url
) {
162 if (url
.SchemeIsFile())
165 url::Replacements
<char> replacements
;
166 replacements
.ClearPort();
167 if (url
.SchemeIsSecure())
168 replacements
.SetScheme("http", url::Component(0, 4));
170 return url
.GetOrigin().ReplaceComponents(replacements
).spec();
174 std::string
CanonicalCookie::CanonPath(const GURL
& url
,
175 const ParsedCookie
& pc
) {
176 std::string path_string
;
178 path_string
= pc
.Path();
179 return CanonPathWithString(url
, path_string
);
183 Time
CanonicalCookie::CanonExpiration(const ParsedCookie
& pc
,
185 const Time
& server_time
) {
186 // First, try the Max-Age attribute.
188 if (pc
.HasMaxAge() &&
194 pc
.MaxAge().c_str(), " %" PRIu64
, &max_age
) == 1) {
195 return current
+ TimeDelta::FromSeconds(max_age
);
198 // Try the Expires attribute.
199 if (pc
.HasExpires() && !pc
.Expires().empty()) {
200 // Adjust for clock skew between server and host.
201 base::Time parsed_expiry
= cookie_util::ParseCookieTime(pc
.Expires());
202 if (!parsed_expiry
.is_null())
203 return parsed_expiry
+ (current
- server_time
);
206 // Invalid or no expiration, persistent cookie.
211 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
212 const std::string
& cookie_line
,
213 const base::Time
& creation_time
,
214 const CookieOptions
& options
) {
215 ParsedCookie
parsed_cookie(cookie_line
);
217 if (!parsed_cookie
.IsValid()) {
218 VLOG(kVlogSetCookies
) << "WARNING: Couldn't parse cookie";
222 if (options
.exclude_httponly() && parsed_cookie
.IsHttpOnly()) {
223 VLOG(kVlogSetCookies
) << "Create() is not creating a httponly cookie";
227 std::string cookie_domain
;
228 if (!GetCookieDomain(url
, parsed_cookie
, &cookie_domain
)) {
232 std::string cookie_path
= CanonicalCookie::CanonPath(url
, parsed_cookie
);
233 Time
server_time(creation_time
);
234 if (options
.has_server_time())
235 server_time
= options
.server_time();
237 Time cookie_expires
= CanonicalCookie::CanonExpiration(parsed_cookie
,
241 return new CanonicalCookie(url
, parsed_cookie
.Name(), parsed_cookie
.Value(),
242 cookie_domain
, cookie_path
, creation_time
,
243 cookie_expires
, creation_time
,
244 parsed_cookie
.IsSecure(),
245 parsed_cookie
.IsHttpOnly(),
246 parsed_cookie
.Priority());
249 CanonicalCookie
* CanonicalCookie::Create(const GURL
& url
,
250 const std::string
& name
,
251 const std::string
& value
,
252 const std::string
& domain
,
253 const std::string
& path
,
254 const base::Time
& creation
,
255 const base::Time
& expiration
,
258 CookiePriority priority
) {
259 // Expect valid attribute tokens and values, as defined by the ParsedCookie
260 // logic, otherwise don't create the cookie.
261 std::string parsed_name
= ParsedCookie::ParseTokenString(name
);
262 if (parsed_name
!= name
)
264 std::string parsed_value
= ParsedCookie::ParseValueString(value
);
265 if (parsed_value
!= value
)
268 std::string parsed_domain
= ParsedCookie::ParseValueString(domain
);
269 if (parsed_domain
!= domain
)
271 std::string cookie_domain
;
272 if (!cookie_util::GetCookieDomainWithString(url
, parsed_domain
,
277 std::string parsed_path
= ParsedCookie::ParseValueString(path
);
278 if (parsed_path
!= path
)
281 std::string cookie_path
= CanonPathWithString(url
, parsed_path
);
282 // Expect that the path was either not specified (empty), or is valid.
283 if (!parsed_path
.empty() && cookie_path
!= parsed_path
)
285 // Canonicalize path again to make sure it escapes characters as needed.
286 url::Component
path_component(0, cookie_path
.length());
287 url::RawCanonOutputT
<char> canon_path
;
288 url::Component canon_path_component
;
289 url::CanonicalizePath(cookie_path
.data(), path_component
, &canon_path
,
290 &canon_path_component
);
291 cookie_path
= std::string(canon_path
.data() + canon_path_component
.begin
,
292 canon_path_component
.len
);
294 return new CanonicalCookie(url
, parsed_name
, parsed_value
, cookie_domain
,
295 cookie_path
, creation
, expiration
, creation
,
296 secure
, http_only
, priority
);
299 bool CanonicalCookie::IsOnPath(const std::string
& url_path
) const {
301 // A zero length would be unsafe for our trailing '/' checks, and
302 // would also make no sense for our prefix match. The code that
303 // creates a CanonicalCookie should make sure the path is never zero length,
304 // but we double check anyway.
308 // The Mozilla code broke this into three cases, based on if the cookie path
309 // was longer, the same length, or shorter than the length of the url path.
310 // I think the approach below is simpler.
312 // Make sure the cookie path is a prefix of the url path. If the
313 // url path is shorter than the cookie path, then the cookie path
314 // can't be a prefix.
315 if (url_path
.find(path_
) != 0)
318 // Now we know that url_path is >= cookie_path, and that cookie_path
319 // is a prefix of url_path. If they are the are the same length then
320 // they are identical, otherwise we need an additional check:
322 // In order to avoid in correctly matching a cookie path of /blah
323 // with a request path of '/blahblah/', we need to make sure that either
324 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
325 // in the url path. Since we know that the url path length is greater
326 // than the cookie path length, it's safe to index one byte past.
327 if (path_
.length() != url_path
.length() &&
328 path_
[path_
.length() - 1] != '/' &&
329 url_path
[path_
.length()] != '/')
335 bool CanonicalCookie::IsDomainMatch(const std::string
& host
) const {
336 // Can domain match in two ways; as a domain cookie (where the cookie
337 // domain begins with ".") or as a host cookie (where it doesn't).
339 // Some consumers of the CookieMonster expect to set cookies on
340 // URLs like http://.strange.url. To retrieve cookies in this instance,
341 // we allow matching as a host cookie even when the domain_ starts with
346 // Domain cookie must have an initial ".". To match, it must be
347 // equal to url's host with initial period removed, or a suffix of
350 // Arguably this should only apply to "http" or "https" cookies, but
351 // extension cookie tests currently use the funtionality, and if we
352 // ever decide to implement that it should be done by preventing
353 // such cookies from being set.
354 if (domain_
.empty() || domain_
[0] != '.')
357 // The host with a "." prefixed.
358 if (domain_
.compare(1, std::string::npos
, host
) == 0)
361 // A pure suffix of the host (ok since we know the domain already
362 // starts with a ".")
363 return (host
.length() > domain_
.length() &&
364 host
.compare(host
.length() - domain_
.length(),
365 domain_
.length(), domain_
) == 0);
368 bool CanonicalCookie::IncludeForRequestURL(const GURL
& url
,
369 const CookieOptions
& options
) const {
370 // Filter out HttpOnly cookies, per options.
371 if (options
.exclude_httponly() && IsHttpOnly())
373 // Secure cookies should not be included in requests for URLs with an
375 if (IsSecure() && !url
.SchemeIsSecure())
377 // Don't include cookies for requests that don't apply to the cookie domain.
378 if (!IsDomainMatch(url
.host()))
380 // Don't include cookies for requests with a url path that does not path
381 // match the cookie-path.
382 if (!IsOnPath(url
.path()))
388 std::string
CanonicalCookie::DebugString() const {
389 return base::StringPrintf(
390 "name: %s value: %s domain: %s path: %s creation: %"
392 name_
.c_str(), value_
.c_str(),
393 domain_
.c_str(), path_
.c_str(),
394 static_cast<int64
>(creation_date_
.ToTimeT()));
397 CanonicalCookie
* CanonicalCookie::Duplicate() {
398 CanonicalCookie
* cc
= new CanonicalCookie();
399 cc
->source_
= source_
;
402 cc
->domain_
= domain_
;
404 cc
->creation_date_
= creation_date_
;
405 cc
->expiry_date_
= expiry_date_
;
406 cc
->last_access_date_
= last_access_date_
;
407 cc
->secure_
= secure_
;
408 cc
->httponly_
= httponly_
;
409 cc
->priority_
= priority_
;