OLD | NEW |
(Empty) | |
| 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 <algorithm> |
| 48 #include <set> |
| 49 |
| 50 #include "base/basictypes.h" |
| 51 #include "base/bind.h" |
| 52 #include "base/callback.h" |
| 53 #include "base/format_macros.h" |
| 54 #include "base/logging.h" |
| 55 #include "base/memory/scoped_ptr.h" |
| 56 #include "base/message_loop.h" |
| 57 #include "base/message_loop_proxy.h" |
| 58 #include "base/metrics/histogram.h" |
| 59 #include "base/string_tokenizer.h" |
| 60 #include "base/string_util.h" |
| 61 #include "base/stringprintf.h" |
| 62 #include "googleurl/src/gurl.h" |
| 63 #include "googleurl/src/url_canon.h" |
| 64 #include "net/cookies/cookie_util.h" |
| 65 #include "net/cookies/parsed_cookie.h" |
| 66 #include "net/base/registry_controlled_domain.h" |
| 67 |
| 68 using base::Time; |
| 69 using base::TimeDelta; |
| 70 |
| 71 namespace net { |
| 72 |
| 73 namespace { |
| 74 |
| 75 // Determine the cookie domain to use for setting the specified cookie. |
| 76 bool GetCookieDomain(const GURL& url, |
| 77 const ParsedCookie& pc, |
| 78 std::string* result) { |
| 79 std::string domain_string; |
| 80 if (pc.HasDomain()) |
| 81 domain_string = pc.Domain(); |
| 82 return cookie_util::GetCookieDomainWithString(url, domain_string, result); |
| 83 } |
| 84 |
| 85 std::string CanonPathWithString(const GURL& url, |
| 86 const std::string& path_string) { |
| 87 // The RFC says the path should be a prefix of the current URL path. |
| 88 // However, Mozilla allows you to set any path for compatibility with |
| 89 // broken websites. We unfortunately will mimic this behavior. We try |
| 90 // to be generous and accept cookies with an invalid path attribute, and |
| 91 // default the path to something reasonable. |
| 92 |
| 93 // The path was supplied in the cookie, we'll take it. |
| 94 if (!path_string.empty() && path_string[0] == '/') |
| 95 return path_string; |
| 96 |
| 97 // The path was not supplied in the cookie or invalid, we will default |
| 98 // to the current URL path. |
| 99 // """Defaults to the path of the request URL that generated the |
| 100 // Set-Cookie response, up to, but not including, the |
| 101 // right-most /.""" |
| 102 // How would this work for a cookie on /? We will include it then. |
| 103 const std::string& url_path = url.path(); |
| 104 |
| 105 size_t idx = url_path.find_last_of('/'); |
| 106 |
| 107 // The cookie path was invalid or a single '/'. |
| 108 if (idx == 0 || idx == std::string::npos) |
| 109 return std::string("/"); |
| 110 |
| 111 // Return up to the rightmost '/'. |
| 112 return url_path.substr(0, idx); |
| 113 } |
| 114 |
| 115 } // namespace |
| 116 |
| 117 CanonicalCookie::CanonicalCookie() |
| 118 : secure_(false), |
| 119 httponly_(false) { |
| 120 SetSessionCookieExpiryTime(); |
| 121 } |
| 122 |
| 123 CanonicalCookie::CanonicalCookie( |
| 124 const GURL& url, const std::string& name, const std::string& value, |
| 125 const std::string& domain, const std::string& path, |
| 126 const std::string& mac_key, const std::string& mac_algorithm, |
| 127 const base::Time& creation, const base::Time& expiration, |
| 128 const base::Time& last_access, bool secure, bool httponly) |
| 129 : source_(GetCookieSourceFromURL(url)), |
| 130 name_(name), |
| 131 value_(value), |
| 132 domain_(domain), |
| 133 path_(path), |
| 134 mac_key_(mac_key), |
| 135 mac_algorithm_(mac_algorithm), |
| 136 creation_date_(creation), |
| 137 expiry_date_(expiration), |
| 138 last_access_date_(last_access), |
| 139 secure_(secure), |
| 140 httponly_(httponly) { |
| 141 if (expiration.is_null()) |
| 142 SetSessionCookieExpiryTime(); |
| 143 } |
| 144 |
| 145 CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc) |
| 146 : source_(GetCookieSourceFromURL(url)), |
| 147 name_(pc.Name()), |
| 148 value_(pc.Value()), |
| 149 path_(CanonPath(url, pc)), |
| 150 mac_key_(pc.MACKey()), |
| 151 mac_algorithm_(pc.MACAlgorithm()), |
| 152 creation_date_(Time::Now()), |
| 153 last_access_date_(Time()), |
| 154 secure_(pc.IsSecure()), |
| 155 httponly_(pc.IsHttpOnly()) { |
| 156 if (pc.HasExpires()) |
| 157 expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_); |
| 158 else |
| 159 SetSessionCookieExpiryTime(); |
| 160 |
| 161 // Do the best we can with the domain. |
| 162 std::string cookie_domain; |
| 163 std::string domain_string; |
| 164 if (pc.HasDomain()) { |
| 165 domain_string = pc.Domain(); |
| 166 } |
| 167 bool result |
| 168 = cookie_util::GetCookieDomainWithString(url, domain_string, |
| 169 &cookie_domain); |
| 170 // Caller is responsible for passing in good arguments. |
| 171 DCHECK(result); |
| 172 domain_ = cookie_domain; |
| 173 } |
| 174 |
| 175 CanonicalCookie::~CanonicalCookie() { |
| 176 } |
| 177 |
| 178 std::string CanonicalCookie::GetCookieSourceFromURL(const GURL& url) { |
| 179 if (url.SchemeIsFile()) |
| 180 return url.spec(); |
| 181 |
| 182 url_canon::Replacements<char> replacements; |
| 183 replacements.ClearPort(); |
| 184 if (url.SchemeIsSecure()) |
| 185 replacements.SetScheme("http", url_parse::Component(0, 4)); |
| 186 |
| 187 return url.GetOrigin().ReplaceComponents(replacements).spec(); |
| 188 } |
| 189 |
| 190 // static |
| 191 std::string CanonicalCookie::CanonPath(const GURL& url, |
| 192 const ParsedCookie& pc) { |
| 193 std::string path_string; |
| 194 if (pc.HasPath()) |
| 195 path_string = pc.Path(); |
| 196 return CanonPathWithString(url, path_string); |
| 197 } |
| 198 |
| 199 // static |
| 200 Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc, |
| 201 const Time& current, |
| 202 const Time& server_time) { |
| 203 // First, try the Max-Age attribute. |
| 204 uint64 max_age = 0; |
| 205 if (pc.HasMaxAge() && |
| 206 #ifdef COMPILER_MSVC |
| 207 sscanf_s( |
| 208 #else |
| 209 sscanf( |
| 210 #endif |
| 211 pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) { |
| 212 return current + TimeDelta::FromSeconds(max_age); |
| 213 } |
| 214 |
| 215 // Try the Expires attribute. |
| 216 if (pc.HasExpires()) { |
| 217 // Adjust for clock skew between server and host. |
| 218 return current + (ParsedCookie::ParseCookieTime(pc.Expires()) - |
| 219 server_time); |
| 220 } |
| 221 |
| 222 // Invalid or no expiration, persistent cookie. |
| 223 return Time(); |
| 224 } |
| 225 |
| 226 void CanonicalCookie::SetSessionCookieExpiryTime() { |
| 227 #if defined(ENABLE_PERSISTENT_SESSION_COOKIES) |
| 228 // Mobile apps can sometimes be shut down without any warning, so the session |
| 229 // cookie has to be persistent and given a default expiration time. |
| 230 expiry_date_ = base::Time::Now() + |
| 231 base::TimeDelta::FromDays(kPersistentSessionCookieExpiryInDays); |
| 232 #endif |
| 233 } |
| 234 |
| 235 CanonicalCookie* CanonicalCookie::Create(const GURL& url, |
| 236 const ParsedCookie& pc) { |
| 237 if (!pc.IsValid()) { |
| 238 return NULL; |
| 239 } |
| 240 |
| 241 std::string domain_string; |
| 242 if (!GetCookieDomain(url, pc, &domain_string)) { |
| 243 return NULL; |
| 244 } |
| 245 std::string path_string = CanonPath(url, pc); |
| 246 std::string mac_key = pc.HasMACKey() ? pc.MACKey() : std::string(); |
| 247 std::string mac_algorithm = pc.HasMACAlgorithm() ? |
| 248 pc.MACAlgorithm() : std::string(); |
| 249 Time creation_time = Time::Now(); |
| 250 Time expiration_time; |
| 251 if (pc.HasExpires()) |
| 252 expiration_time = ParsedCookie::ParseCookieTime(pc.Expires()); |
| 253 |
| 254 return (Create(url, pc.Name(), pc.Value(), domain_string, path_string, |
| 255 mac_key, mac_algorithm, creation_time, expiration_time, |
| 256 pc.IsSecure(), pc.IsHttpOnly())); |
| 257 } |
| 258 |
| 259 CanonicalCookie* CanonicalCookie::Create(const GURL& url, |
| 260 const std::string& name, |
| 261 const std::string& value, |
| 262 const std::string& domain, |
| 263 const std::string& path, |
| 264 const std::string& mac_key, |
| 265 const std::string& mac_algorithm, |
| 266 const base::Time& creation, |
| 267 const base::Time& expiration, |
| 268 bool secure, |
| 269 bool http_only) { |
| 270 // Expect valid attribute tokens and values, as defined by the ParsedCookie |
| 271 // logic, otherwise don't create the cookie. |
| 272 std::string parsed_name = ParsedCookie::ParseTokenString(name); |
| 273 if (parsed_name != name) |
| 274 return NULL; |
| 275 std::string parsed_value = ParsedCookie::ParseValueString(value); |
| 276 if (parsed_value != value) |
| 277 return NULL; |
| 278 |
| 279 std::string parsed_domain = ParsedCookie::ParseValueString(domain); |
| 280 if (parsed_domain != domain) |
| 281 return NULL; |
| 282 std::string cookie_domain; |
| 283 if (!cookie_util::GetCookieDomainWithString(url, parsed_domain, |
| 284 &cookie_domain)) { |
| 285 return NULL; |
| 286 } |
| 287 |
| 288 std::string parsed_path = ParsedCookie::ParseValueString(path); |
| 289 if (parsed_path != path) |
| 290 return NULL; |
| 291 |
| 292 std::string cookie_path = CanonPathWithString(url, parsed_path); |
| 293 // Expect that the path was either not specified (empty), or is valid. |
| 294 if (!parsed_path.empty() && cookie_path != parsed_path) |
| 295 return NULL; |
| 296 // Canonicalize path again to make sure it escapes characters as needed. |
| 297 url_parse::Component path_component(0, cookie_path.length()); |
| 298 url_canon::RawCanonOutputT<char> canon_path; |
| 299 url_parse::Component canon_path_component; |
| 300 url_canon::CanonicalizePath(cookie_path.data(), path_component, |
| 301 &canon_path, &canon_path_component); |
| 302 cookie_path = std::string(canon_path.data() + canon_path_component.begin, |
| 303 canon_path_component.len); |
| 304 |
| 305 return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain, |
| 306 cookie_path, mac_key, mac_algorithm, creation, |
| 307 expiration, creation, secure, http_only); |
| 308 } |
| 309 |
| 310 bool CanonicalCookie::IsOnPath(const std::string& url_path) const { |
| 311 |
| 312 // A zero length would be unsafe for our trailing '/' checks, and |
| 313 // would also make no sense for our prefix match. The code that |
| 314 // creates a CanonicalCookie should make sure the path is never zero length, |
| 315 // but we double check anyway. |
| 316 if (path_.empty()) |
| 317 return false; |
| 318 |
| 319 // The Mozilla code broke this into three cases, based on if the cookie path |
| 320 // was longer, the same length, or shorter than the length of the url path. |
| 321 // I think the approach below is simpler. |
| 322 |
| 323 // Make sure the cookie path is a prefix of the url path. If the |
| 324 // url path is shorter than the cookie path, then the cookie path |
| 325 // can't be a prefix. |
| 326 if (url_path.find(path_) != 0) |
| 327 return false; |
| 328 |
| 329 // Now we know that url_path is >= cookie_path, and that cookie_path |
| 330 // is a prefix of url_path. If they are the are the same length then |
| 331 // they are identical, otherwise we need an additional check: |
| 332 |
| 333 // In order to avoid in correctly matching a cookie path of /blah |
| 334 // with a request path of '/blahblah/', we need to make sure that either |
| 335 // the cookie path ends in a trailing '/', or that we prefix up to a '/' |
| 336 // in the url path. Since we know that the url path length is greater |
| 337 // than the cookie path length, it's safe to index one byte past. |
| 338 if (path_.length() != url_path.length() && |
| 339 path_[path_.length() - 1] != '/' && |
| 340 url_path[path_.length()] != '/') |
| 341 return false; |
| 342 |
| 343 return true; |
| 344 } |
| 345 |
| 346 bool CanonicalCookie::IsDomainMatch(const std::string& scheme, |
| 347 const std::string& host) const { |
| 348 // Can domain match in two ways; as a domain cookie (where the cookie |
| 349 // domain begins with ".") or as a host cookie (where it doesn't). |
| 350 |
| 351 // Some consumers of the CookieMonster expect to set cookies on |
| 352 // URLs like http://.strange.url. To retrieve cookies in this instance, |
| 353 // we allow matching as a host cookie even when the domain_ starts with |
| 354 // a period. |
| 355 if (host == domain_) |
| 356 return true; |
| 357 |
| 358 // Domain cookie must have an initial ".". To match, it must be |
| 359 // equal to url's host with initial period removed, or a suffix of |
| 360 // it. |
| 361 |
| 362 // Arguably this should only apply to "http" or "https" cookies, but |
| 363 // extension cookie tests currently use the funtionality, and if we |
| 364 // ever decide to implement that it should be done by preventing |
| 365 // such cookies from being set. |
| 366 if (domain_.empty() || domain_[0] != '.') |
| 367 return false; |
| 368 |
| 369 // The host with a "." prefixed. |
| 370 if (domain_.compare(1, std::string::npos, host) == 0) |
| 371 return true; |
| 372 |
| 373 // A pure suffix of the host (ok since we know the domain already |
| 374 // starts with a ".") |
| 375 return (host.length() > domain_.length() && |
| 376 host.compare(host.length() - domain_.length(), |
| 377 domain_.length(), domain_) == 0); |
| 378 } |
| 379 |
| 380 std::string CanonicalCookie::DebugString() const { |
| 381 return base::StringPrintf( |
| 382 "name: %s value: %s domain: %s path: %s creation: %" |
| 383 PRId64, |
| 384 name_.c_str(), value_.c_str(), |
| 385 domain_.c_str(), path_.c_str(), |
| 386 static_cast<int64>(creation_date_.ToTimeT())); |
| 387 } |
| 388 |
| 389 } // namespace |
OLD | NEW |