canonical_cookie.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/stringprintf.h"
51#include "googleurl/src/gurl.h"
52#include "googleurl/src/url_canon.h"
53#include "net/cookies/cookie_util.h"
54#include "net/cookies/parsed_cookie.h"
55
56using base::Time;
57using base::TimeDelta;
58
59namespace net {
60
61namespace {
62
63// Determine the cookie domain to use for setting the specified cookie.
64bool GetCookieDomain(const GURL& url,
65                     const ParsedCookie& pc,
66                     std::string* result) {
67  std::string domain_string;
68  if (pc.HasDomain())
69    domain_string = pc.Domain();
70  return cookie_util::GetCookieDomainWithString(url, domain_string, result);
71}
72
73std::string CanonPathWithString(const GURL& url,
74                                const std::string& path_string) {
75  // The RFC says the path should be a prefix of the current URL path.
76  // However, Mozilla allows you to set any path for compatibility with
77  // broken websites.  We unfortunately will mimic this behavior.  We try
78  // to be generous and accept cookies with an invalid path attribute, and
79  // default the path to something reasonable.
80
81  // The path was supplied in the cookie, we'll take it.
82  if (!path_string.empty() && path_string[0] == '/')
83    return path_string;
84
85  // The path was not supplied in the cookie or invalid, we will default
86  // to the current URL path.
87  // """Defaults to the path of the request URL that generated the
88  //    Set-Cookie response, up to, but not including, the
89  //    right-most /."""
90  // How would this work for a cookie on /?  We will include it then.
91  const std::string& url_path = url.path();
92
93  size_t idx = url_path.find_last_of('/');
94
95  // The cookie path was invalid or a single '/'.
96  if (idx == 0 || idx == std::string::npos)
97    return std::string("/");
98
99  // Return up to the rightmost '/'.
100  return url_path.substr(0, idx);
101}
102
103}  // namespace
104
105CanonicalCookie::CanonicalCookie()
106    : secure_(false),
107      httponly_(false) {
108}
109
110CanonicalCookie::CanonicalCookie(
111    const GURL& url, const std::string& name, const std::string& value,
112    const std::string& domain, const std::string& path,
113    const std::string& mac_key, const std::string& mac_algorithm,
114    const base::Time& creation, const base::Time& expiration,
115    const base::Time& last_access, bool secure, bool httponly)
116    : source_(GetCookieSourceFromURL(url)),
117      name_(name),
118      value_(value),
119      domain_(domain),
120      path_(path),
121      mac_key_(mac_key),
122      mac_algorithm_(mac_algorithm),
123      creation_date_(creation),
124      expiry_date_(expiration),
125      last_access_date_(last_access),
126      secure_(secure),
127      httponly_(httponly) {
128}
129
130CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc)
131    : source_(GetCookieSourceFromURL(url)),
132      name_(pc.Name()),
133      value_(pc.Value()),
134      path_(CanonPath(url, pc)),
135      mac_key_(pc.MACKey()),
136      mac_algorithm_(pc.MACAlgorithm()),
137      creation_date_(Time::Now()),
138      last_access_date_(Time()),
139      secure_(pc.IsSecure()),
140      httponly_(pc.IsHttpOnly()) {
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_canon::Replacements<char> replacements;
166  replacements.ClearPort();
167  if (url.SchemeIsSecure())
168    replacements.SetScheme("http", url_parse::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()) {
200    // Adjust for clock skew between server and host.
201    return current + (cookie_util::ParseCookieTime(pc.Expires()) - server_time);
202  }
203
204  // Invalid or no expiration, persistent cookie.
205  return Time();
206}
207
208CanonicalCookie* CanonicalCookie::Create(const GURL& url,
209                                         const ParsedCookie& pc) {
210  if (!pc.IsValid()) {
211    return NULL;
212  }
213
214  std::string domain_string;
215  if (!GetCookieDomain(url, pc, &domain_string)) {
216    return NULL;
217  }
218  std::string path_string = CanonPath(url, pc);
219  std::string mac_key = pc.HasMACKey() ? pc.MACKey() : std::string();
220  std::string mac_algorithm = pc.HasMACAlgorithm() ?
221      pc.MACAlgorithm() : std::string();
222  Time creation_time = Time::Now();
223  Time expiration_time;
224  if (pc.HasExpires())
225    expiration_time =  cookie_util::ParseCookieTime(pc.Expires());
226
227  return (Create(url, pc.Name(), pc.Value(), domain_string, path_string,
228                 mac_key, mac_algorithm, creation_time, expiration_time,
229                 pc.IsSecure(), pc.IsHttpOnly()));
230}
231
232CanonicalCookie* CanonicalCookie::Create(const GURL& url,
233                                         const std::string& name,
234                                         const std::string& value,
235                                         const std::string& domain,
236                                         const std::string& path,
237                                         const std::string& mac_key,
238                                         const std::string& mac_algorithm,
239                                         const base::Time& creation,
240                                         const base::Time& expiration,
241                                         bool secure,
242                                         bool http_only) {
243  // Expect valid attribute tokens and values, as defined by the ParsedCookie
244  // logic, otherwise don't create the cookie.
245  std::string parsed_name = ParsedCookie::ParseTokenString(name);
246  if (parsed_name != name)
247    return NULL;
248  std::string parsed_value = ParsedCookie::ParseValueString(value);
249  if (parsed_value != value)
250    return NULL;
251
252  std::string parsed_domain = ParsedCookie::ParseValueString(domain);
253  if (parsed_domain != domain)
254    return NULL;
255  std::string cookie_domain;
256  if (!cookie_util::GetCookieDomainWithString(url, parsed_domain,
257                                               &cookie_domain)) {
258    return NULL;
259  }
260
261  std::string parsed_path = ParsedCookie::ParseValueString(path);
262  if (parsed_path != path)
263    return NULL;
264
265  std::string cookie_path = CanonPathWithString(url, parsed_path);
266  // Expect that the path was either not specified (empty), or is valid.
267  if (!parsed_path.empty() && cookie_path != parsed_path)
268    return NULL;
269  // Canonicalize path again to make sure it escapes characters as needed.
270  url_parse::Component path_component(0, cookie_path.length());
271  url_canon::RawCanonOutputT<char> canon_path;
272  url_parse::Component canon_path_component;
273  url_canon::CanonicalizePath(cookie_path.data(), path_component,
274                              &canon_path, &canon_path_component);
275  cookie_path = std::string(canon_path.data() + canon_path_component.begin,
276                            canon_path_component.len);
277
278  return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain,
279                             cookie_path, mac_key, mac_algorithm, creation,
280                             expiration, creation, secure, http_only);
281}
282
283bool CanonicalCookie::IsOnPath(const std::string& url_path) const {
284
285  // A zero length would be unsafe for our trailing '/' checks, and
286  // would also make no sense for our prefix match.  The code that
287  // creates a CanonicalCookie should make sure the path is never zero length,
288  // but we double check anyway.
289  if (path_.empty())
290    return false;
291
292  // The Mozilla code broke this into three cases, based on if the cookie path
293  // was longer, the same length, or shorter than the length of the url path.
294  // I think the approach below is simpler.
295
296  // Make sure the cookie path is a prefix of the url path.  If the
297  // url path is shorter than the cookie path, then the cookie path
298  // can't be a prefix.
299  if (url_path.find(path_) != 0)
300    return false;
301
302  // Now we know that url_path is >= cookie_path, and that cookie_path
303  // is a prefix of url_path.  If they are the are the same length then
304  // they are identical, otherwise we need an additional check:
305
306  // In order to avoid in correctly matching a cookie path of /blah
307  // with a request path of '/blahblah/', we need to make sure that either
308  // the cookie path ends in a trailing '/', or that we prefix up to a '/'
309  // in the url path.  Since we know that the url path length is greater
310  // than the cookie path length, it's safe to index one byte past.
311  if (path_.length() != url_path.length() &&
312      path_[path_.length() - 1] != '/' &&
313      url_path[path_.length()] != '/')
314    return false;
315
316  return true;
317}
318
319bool CanonicalCookie::IsDomainMatch(const std::string& scheme,
320                                    const std::string& host) const {
321  // Can domain match in two ways; as a domain cookie (where the cookie
322  // domain begins with ".") or as a host cookie (where it doesn't).
323
324  // Some consumers of the CookieMonster expect to set cookies on
325  // URLs like http://.strange.url.  To retrieve cookies in this instance,
326  // we allow matching as a host cookie even when the domain_ starts with
327  // a period.
328  if (host == domain_)
329    return true;
330
331  // Domain cookie must have an initial ".".  To match, it must be
332  // equal to url's host with initial period removed, or a suffix of
333  // it.
334
335  // Arguably this should only apply to "http" or "https" cookies, but
336  // extension cookie tests currently use the funtionality, and if we
337  // ever decide to implement that it should be done by preventing
338  // such cookies from being set.
339  if (domain_.empty() || domain_[0] != '.')
340    return false;
341
342  // The host with a "." prefixed.
343  if (domain_.compare(1, std::string::npos, host) == 0)
344    return true;
345
346  // A pure suffix of the host (ok since we know the domain already
347  // starts with a ".")
348  return (host.length() > domain_.length() &&
349          host.compare(host.length() - domain_.length(),
350                       domain_.length(), domain_) == 0);
351}
352
353std::string CanonicalCookie::DebugString() const {
354  return base::StringPrintf(
355      "name: %s value: %s domain: %s path: %s creation: %"
356      PRId64,
357      name_.c_str(), value_.c_str(),
358      domain_.c_str(), path_.c_str(),
359      static_cast<int64>(creation_date_.ToTimeT()));
360}
361
362}  // namespace net
363