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