Home | History | Annotate | Download | only in cookies
      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 (at) stanford.edu)
     29  *   Michiel van Leeuwen (mvl (at) 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 
     56 using base::Time;
     57 using base::TimeDelta;
     58 
     59 namespace net {
     60 
     61 namespace {
     62 
     63 const int kVlogSetCookies = 7;
     64 
     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;
     70   if (pc.HasDomain())
     71     domain_string = pc.Domain();
     72   return cookie_util::GetCookieDomainWithString(url, domain_string, result);
     73 }
     74 
     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.
     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 
    107 CanonicalCookie::CanonicalCookie()
    108     : secure_(false),
    109       httponly_(false) {
    110 }
    111 
    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)),
    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 
    131 CanonicalCookie::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 
    158 CanonicalCookie::~CanonicalCookie() {
    159 }
    160 
    161 std::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
    174 std::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
    183 Time 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
    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);
    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 
    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,
    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_parse::Component path_component(0, cookie_path.length());
    287   url_canon::RawCanonOutputT<char> canon_path;
    288   url_parse::Component canon_path_component;
    289   url_canon::CanonicalizePath(cookie_path.data(), path_component,
    290                               &canon_path, &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 
    299 bool 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 
    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).
    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 
    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())
    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 
    388 std::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 
    397 }  // namespace net
    398