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.
4
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
9 *
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/
14 *
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
18 * License.
19 *
20 * The Original Code is mozilla.org code.
21 *
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.
26 *
27 * Contributor(s):
28 *   Daniel Witte (dwitte@stanford.edu)
29 *   Michiel van Leeuwen (mvl@exedo.nl)
30 *
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.
42 *
43 * ***** END LICENSE BLOCK ***** */
44
45#include "net/cookies/canonical_cookie.h"
46
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"
53#include "url/gurl.h"
54#include "url/url_canon.h"
55
56using base::Time;
57using base::TimeDelta;
58
59namespace net {
60
61namespace {
62
63const int kVlogSetCookies = 7;
64
65// Determine the cookie domain to use for setting the specified cookie.
66bool GetCookieDomain(const GURL& url,
67                     const ParsedCookie& pc,
68                     std::string* result) {
69  std::string domain_string;
70  if (pc.HasDomain())
71    domain_string = pc.Domain();
72  return cookie_util::GetCookieDomainWithString(url, domain_string, result);
73}
74
75std::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.
82
83  // The path was supplied in the cookie, we'll take it.
84  if (!path_string.empty() && path_string[0] == '/')
85    return path_string;
86
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
91  //    right-most /."""
92  // How would this work for a cookie on /?  We will include it then.
93  const std::string& url_path = url.path();
94
95  size_t idx = url_path.find_last_of('/');
96
97  // The cookie path was invalid or a single '/'.
98  if (idx == 0 || idx == std::string::npos)
99    return std::string("/");
100
101  // Return up to the rightmost '/'.
102  return url_path.substr(0, idx);
103}
104
105}  // namespace
106
107CanonicalCookie::CanonicalCookie()
108    : secure_(false),
109      httponly_(false) {
110}
111
112CanonicalCookie::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)),
119      name_(name),
120      value_(value),
121      domain_(domain),
122      path_(path),
123      creation_date_(creation),
124      expiry_date_(expiration),
125      last_access_date_(last_access),
126      secure_(secure),
127      httponly_(httponly),
128      priority_(priority) {
129}
130
131CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc)
132    : source_(GetCookieSourceFromURL(url)),
133      name_(pc.Name()),
134      value_(pc.Value()),
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()) {
141  if (pc.HasExpires())
142    expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_);
143
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();
149  }
150  bool result
151      = cookie_util::GetCookieDomainWithString(url, domain_string,
152                                                &cookie_domain);
153  // Caller is responsible for passing in good arguments.
154  DCHECK(result);
155  domain_ = cookie_domain;
156}
157
158CanonicalCookie::~CanonicalCookie() {
159}
160
161std::string CanonicalCookie::GetCookieSourceFromURL(const GURL& url) {
162  if (url.SchemeIsFile())
163    return url.spec();
164
165  url::Replacements<char> replacements;
166  replacements.ClearPort();
167  if (url.SchemeIsSecure())
168    replacements.SetScheme("http", url::Component(0, 4));
169
170  return url.GetOrigin().ReplaceComponents(replacements).spec();
171}
172
173// static
174std::string CanonicalCookie::CanonPath(const GURL& url,
175                                       const ParsedCookie& pc) {
176  std::string path_string;
177  if (pc.HasPath())
178    path_string = pc.Path();
179  return CanonPathWithString(url, path_string);
180}
181
182// static
183Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc,
184                                      const Time& current,
185                                      const Time& server_time) {
186  // First, try the Max-Age attribute.
187  uint64 max_age = 0;
188  if (pc.HasMaxAge() &&
189#ifdef COMPILER_MSVC
190      sscanf_s(
191#else
192      sscanf(
193#endif
194             pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) {
195    return current + TimeDelta::FromSeconds(max_age);
196  }
197
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);
204  }
205
206  // Invalid or no expiration, persistent cookie.
207  return Time();
208}
209
210// static
211CanonicalCookie* 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);
216
217  if (!parsed_cookie.IsValid()) {
218    VLOG(kVlogSetCookies) << "WARNING: Couldn't parse cookie";
219    return NULL;
220  }
221
222  if (options.exclude_httponly() && parsed_cookie.IsHttpOnly()) {
223    VLOG(kVlogSetCookies) << "Create() is not creating a httponly cookie";
224    return NULL;
225  }
226
227  std::string cookie_domain;
228  if (!GetCookieDomain(url, parsed_cookie, &cookie_domain)) {
229    return NULL;
230  }
231
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();
236
237  Time cookie_expires = CanonicalCookie::CanonExpiration(parsed_cookie,
238                                                         creation_time,
239                                                         server_time);
240
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());
247}
248
249CanonicalCookie* 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,
256                                         bool secure,
257                                         bool http_only,
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)
263    return NULL;
264  std::string parsed_value = ParsedCookie::ParseValueString(value);
265  if (parsed_value != value)
266    return NULL;
267
268  std::string parsed_domain = ParsedCookie::ParseValueString(domain);
269  if (parsed_domain != domain)
270    return NULL;
271  std::string cookie_domain;
272  if (!cookie_util::GetCookieDomainWithString(url, parsed_domain,
273                                               &cookie_domain)) {
274    return NULL;
275  }
276
277  std::string parsed_path = ParsedCookie::ParseValueString(path);
278  if (parsed_path != path)
279    return NULL;
280
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)
284    return NULL;
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);
293
294  return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain,
295                             cookie_path, creation, expiration, creation,
296                             secure, http_only, priority);
297}
298
299bool CanonicalCookie::IsOnPath(const std::string& url_path) const {
300
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.
305  if (path_.empty())
306    return false;
307
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.
311
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)
316    return false;
317
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:
321
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()] != '/')
330    return false;
331
332  return true;
333}
334
335bool 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).
338
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
342  // a period.
343  if (host == domain_)
344    return true;
345
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
348  // it.
349
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] != '.')
355    return false;
356
357  // The host with a "." prefixed.
358  if (domain_.compare(1, std::string::npos, host) == 0)
359    return true;
360
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);
366}
367
368bool CanonicalCookie::IncludeForRequestURL(const GURL& url,
369                                           const CookieOptions& options) const {
370  // Filter out HttpOnly cookies, per options.
371  if (options.exclude_httponly() && IsHttpOnly())
372    return false;
373  // Secure cookies should not be included in requests for URLs with an
374  // insecure scheme.
375  if (IsSecure() && !url.SchemeIsSecure())
376    return false;
377  // Don't include cookies for requests that don't apply to the cookie domain.
378  if (!IsDomainMatch(url.host()))
379    return false;
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()))
383    return false;
384
385  return true;
386}
387
388std::string CanonicalCookie::DebugString() const {
389  return base::StringPrintf(
390      "name: %s value: %s domain: %s path: %s creation: %"
391      PRId64,
392      name_.c_str(), value_.c_str(),
393      domain_.c_str(), path_.c_str(),
394      static_cast<int64>(creation_date_.ToTimeT()));
395}
396
397CanonicalCookie* CanonicalCookie::Duplicate() {
398  CanonicalCookie* cc = new CanonicalCookie();
399  cc->source_ = source_;
400  cc->name_ = name_;
401  cc->value_ = value_;
402  cc->domain_ = domain_;
403  cc->path_ = path_;
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_;
410  return cc;
411}
412
413}  // namespace net
414