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