| 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 #include "net/cookies/cookie_util.h" | |
| 6 | |
| 7 #include <cstdio> | |
| 8 #include <cstdlib> | |
| 9 | |
| 10 #include "base/logging.h" | |
| 11 #include "base/strings/string_tokenizer.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "build/build_config.h" | |
| 14 #include "net/base/net_util.h" | |
| 15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | |
| 16 #include "url/gurl.h" | |
| 17 | |
| 18 namespace net { | |
| 19 namespace cookie_util { | |
| 20 | |
| 21 bool DomainIsHostOnly(const std::string& domain_string) { | |
| 22 return (domain_string.empty() || domain_string[0] != '.'); | |
| 23 } | |
| 24 | |
| 25 std::string GetEffectiveDomain(const std::string& scheme, | |
| 26 const std::string& host) { | |
| 27 if (scheme == "http" || scheme == "https" || scheme == "ws" || | |
| 28 scheme == "wss") { | |
| 29 return registry_controlled_domains::GetDomainAndRegistry( | |
| 30 host, | |
| 31 registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); | |
| 32 } | |
| 33 | |
| 34 if (!DomainIsHostOnly(host)) | |
| 35 return host.substr(1); | |
| 36 return host; | |
| 37 } | |
| 38 | |
| 39 bool GetCookieDomainWithString(const GURL& url, | |
| 40 const std::string& domain_string, | |
| 41 std::string* result) { | |
| 42 const std::string url_host(url.host()); | |
| 43 | |
| 44 // If no domain was specified in the domain string, default to a host cookie. | |
| 45 // We match IE/Firefox in allowing a domain=IPADDR if it matches the url | |
| 46 // ip address hostname exactly. It should be treated as a host cookie. | |
| 47 if (domain_string.empty() || | |
| 48 (url.HostIsIPAddress() && url_host == domain_string)) { | |
| 49 *result = url_host; | |
| 50 DCHECK(DomainIsHostOnly(*result)); | |
| 51 return true; | |
| 52 } | |
| 53 | |
| 54 // Get the normalized domain specified in cookie line. | |
| 55 url::CanonHostInfo ignored; | |
| 56 std::string cookie_domain(CanonicalizeHost(domain_string, &ignored)); | |
| 57 if (cookie_domain.empty()) | |
| 58 return false; | |
| 59 if (cookie_domain[0] != '.') | |
| 60 cookie_domain = "." + cookie_domain; | |
| 61 | |
| 62 // Ensure |url| and |cookie_domain| have the same domain+registry. | |
| 63 const std::string url_scheme(url.scheme()); | |
| 64 const std::string url_domain_and_registry( | |
| 65 GetEffectiveDomain(url_scheme, url_host)); | |
| 66 if (url_domain_and_registry.empty()) | |
| 67 return false; // IP addresses/intranet hosts can't set domain cookies. | |
| 68 const std::string cookie_domain_and_registry( | |
| 69 GetEffectiveDomain(url_scheme, cookie_domain)); | |
| 70 if (url_domain_and_registry != cookie_domain_and_registry) | |
| 71 return false; // Can't set a cookie on a different domain + registry. | |
| 72 | |
| 73 // Ensure |url_host| is |cookie_domain| or one of its subdomains. Given that | |
| 74 // we know the domain+registry are the same from the above checks, this is | |
| 75 // basically a simple string suffix check. | |
| 76 const bool is_suffix = (url_host.length() < cookie_domain.length()) ? | |
| 77 (cookie_domain != ("." + url_host)) : | |
| 78 (url_host.compare(url_host.length() - cookie_domain.length(), | |
| 79 cookie_domain.length(), cookie_domain) != 0); | |
| 80 if (is_suffix) | |
| 81 return false; | |
| 82 | |
| 83 *result = cookie_domain; | |
| 84 return true; | |
| 85 } | |
| 86 | |
| 87 // Parse a cookie expiration time. We try to be lenient, but we need to | |
| 88 // assume some order to distinguish the fields. The basic rules: | |
| 89 // - The month name must be present and prefix the first 3 letters of the | |
| 90 // full month name (jan for January, jun for June). | |
| 91 // - If the year is <= 2 digits, it must occur after the day of month. | |
| 92 // - The time must be of the format hh:mm:ss. | |
| 93 // An average cookie expiration will look something like this: | |
| 94 // Sat, 15-Apr-17 21:01:22 GMT | |
| 95 base::Time ParseCookieTime(const std::string& time_string) { | |
| 96 static const char* const kMonths[] = { | |
| 97 "jan", "feb", "mar", "apr", "may", "jun", | |
| 98 "jul", "aug", "sep", "oct", "nov", "dec" }; | |
| 99 static const int kMonthsLen = arraysize(kMonths); | |
| 100 // We want to be pretty liberal, and support most non-ascii and non-digit | |
| 101 // characters as a delimiter. We can't treat : as a delimiter, because it | |
| 102 // is the delimiter for hh:mm:ss, and we want to keep this field together. | |
| 103 // We make sure to include - and +, since they could prefix numbers. | |
| 104 // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes | |
| 105 // will be preserved, and we will get them here. So we make sure to include | |
| 106 // quote characters, and also \ for anything that was internally escaped. | |
| 107 static const char kDelimiters[] = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; | |
| 108 | |
| 109 base::Time::Exploded exploded = {0}; | |
| 110 | |
| 111 base::StringTokenizer tokenizer(time_string, kDelimiters); | |
| 112 | |
| 113 bool found_day_of_month = false; | |
| 114 bool found_month = false; | |
| 115 bool found_time = false; | |
| 116 bool found_year = false; | |
| 117 | |
| 118 while (tokenizer.GetNext()) { | |
| 119 const std::string token = tokenizer.token(); | |
| 120 DCHECK(!token.empty()); | |
| 121 bool numerical = IsAsciiDigit(token[0]); | |
| 122 | |
| 123 // String field | |
| 124 if (!numerical) { | |
| 125 if (!found_month) { | |
| 126 for (int i = 0; i < kMonthsLen; ++i) { | |
| 127 // Match prefix, so we could match January, etc | |
| 128 if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { | |
| 129 exploded.month = i + 1; | |
| 130 found_month = true; | |
| 131 break; | |
| 132 } | |
| 133 } | |
| 134 } else { | |
| 135 // If we've gotten here, it means we've already found and parsed our | |
| 136 // month, and we have another string, which we would expect to be the | |
| 137 // the time zone name. According to the RFC and my experiments with | |
| 138 // how sites format their expirations, we don't have much of a reason | |
| 139 // to support timezones. We don't want to ever barf on user input, | |
| 140 // but this DCHECK should pass for well-formed data. | |
| 141 // DCHECK(token == "GMT"); | |
| 142 } | |
| 143 // Numeric field w/ a colon | |
| 144 } else if (token.find(':') != std::string::npos) { | |
| 145 if (!found_time && | |
| 146 #ifdef COMPILER_MSVC | |
| 147 sscanf_s( | |
| 148 #else | |
| 149 sscanf( | |
| 150 #endif | |
| 151 token.c_str(), "%2u:%2u:%2u", &exploded.hour, | |
| 152 &exploded.minute, &exploded.second) == 3) { | |
| 153 found_time = true; | |
| 154 } else { | |
| 155 // We should only ever encounter one time-like thing. If we're here, | |
| 156 // it means we've found a second, which shouldn't happen. We keep | |
| 157 // the first. This check should be ok for well-formed input: | |
| 158 // NOTREACHED(); | |
| 159 } | |
| 160 // Numeric field | |
| 161 } else { | |
| 162 // Overflow with atoi() is unspecified, so we enforce a max length. | |
| 163 if (!found_day_of_month && token.length() <= 2) { | |
| 164 exploded.day_of_month = atoi(token.c_str()); | |
| 165 found_day_of_month = true; | |
| 166 } else if (!found_year && token.length() <= 5) { | |
| 167 exploded.year = atoi(token.c_str()); | |
| 168 found_year = true; | |
| 169 } else { | |
| 170 // If we're here, it means we've either found an extra numeric field, | |
| 171 // or a numeric field which was too long. For well-formed input, the | |
| 172 // following check would be reasonable: | |
| 173 // NOTREACHED(); | |
| 174 } | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 if (!found_day_of_month || !found_month || !found_time || !found_year) { | |
| 179 // We didn't find all of the fields we need. For well-formed input, the | |
| 180 // following check would be reasonable: | |
| 181 // NOTREACHED() << "Cookie parse expiration failed: " << time_string; | |
| 182 return base::Time(); | |
| 183 } | |
| 184 | |
| 185 // Normalize the year to expand abbreviated years to the full year. | |
| 186 if (exploded.year >= 69 && exploded.year <= 99) | |
| 187 exploded.year += 1900; | |
| 188 if (exploded.year >= 0 && exploded.year <= 68) | |
| 189 exploded.year += 2000; | |
| 190 | |
| 191 // If our values are within their correct ranges, we got our time. | |
| 192 if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && | |
| 193 exploded.month >= 1 && exploded.month <= 12 && | |
| 194 exploded.year >= 1601 && exploded.year <= 30827 && | |
| 195 exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { | |
| 196 return base::Time::FromUTCExploded(exploded); | |
| 197 } | |
| 198 | |
| 199 // One of our values was out of expected range. For well-formed input, | |
| 200 // the following check would be reasonable: | |
| 201 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; | |
| 202 | |
| 203 return base::Time(); | |
| 204 } | |
| 205 | |
| 206 GURL CookieOriginToURL(const std::string& domain, bool is_https) { | |
| 207 if (domain.empty()) | |
| 208 return GURL(); | |
| 209 | |
| 210 const std::string scheme = is_https ? "https" : "http"; | |
| 211 const std::string host = domain[0] == '.' ? domain.substr(1) : domain; | |
| 212 return GURL(scheme + "://" + host); | |
| 213 } | |
| 214 | |
| 215 void ParseRequestCookieLine(const std::string& header_value, | |
| 216 ParsedRequestCookies* parsed_cookies) { | |
| 217 std::string::const_iterator i = header_value.begin(); | |
| 218 while (i != header_value.end()) { | |
| 219 // Here we are at the beginning of a cookie. | |
| 220 | |
| 221 // Eat whitespace. | |
| 222 while (i != header_value.end() && *i == ' ') ++i; | |
| 223 if (i == header_value.end()) return; | |
| 224 | |
| 225 // Find cookie name. | |
| 226 std::string::const_iterator cookie_name_beginning = i; | |
| 227 while (i != header_value.end() && *i != '=') ++i; | |
| 228 base::StringPiece cookie_name(cookie_name_beginning, i); | |
| 229 | |
| 230 // Find cookie value. | |
| 231 base::StringPiece cookie_value; | |
| 232 // Cookies may have no value, in this case '=' may or may not be there. | |
| 233 if (i != header_value.end() && i + 1 != header_value.end()) { | |
| 234 ++i; // Skip '='. | |
| 235 std::string::const_iterator cookie_value_beginning = i; | |
| 236 if (*i == '"') { | |
| 237 ++i; // Skip '"'. | |
| 238 while (i != header_value.end() && *i != '"') ++i; | |
| 239 if (i == header_value.end()) return; | |
| 240 ++i; // Skip '"'. | |
| 241 cookie_value = base::StringPiece(cookie_value_beginning, i); | |
| 242 // i points to character after '"', potentially a ';'. | |
| 243 } else { | |
| 244 while (i != header_value.end() && *i != ';') ++i; | |
| 245 cookie_value = base::StringPiece(cookie_value_beginning, i); | |
| 246 // i points to ';' or end of string. | |
| 247 } | |
| 248 } | |
| 249 parsed_cookies->push_back(std::make_pair(cookie_name, cookie_value)); | |
| 250 // Eat ';'. | |
| 251 if (i != header_value.end()) ++i; | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 std::string SerializeRequestCookieLine( | |
| 256 const ParsedRequestCookies& parsed_cookies) { | |
| 257 std::string buffer; | |
| 258 for (ParsedRequestCookies::const_iterator i = parsed_cookies.begin(); | |
| 259 i != parsed_cookies.end(); ++i) { | |
| 260 if (!buffer.empty()) | |
| 261 buffer.append("; "); | |
| 262 buffer.append(i->first.begin(), i->first.end()); | |
| 263 buffer.push_back('='); | |
| 264 buffer.append(i->second.begin(), i->second.end()); | |
| 265 } | |
| 266 return buffer; | |
| 267 } | |
| 268 | |
| 269 } // namespace cookie_util | |
| 270 } // namespace net | |
| OLD | NEW |