Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / net / cookies / cookie_util.cc
blobb9c7e8d601f8ed2e3c4d8b3eb07c8e1d0c5ffdae
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.
5 #include "net/cookies/cookie_util.h"
7 #include <cstdio>
8 #include <cstdlib>
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"
18 namespace net {
19 namespace cookie_util {
21 bool DomainIsHostOnly(const std::string& domain_string) {
22 return (domain_string.empty() || domain_string[0] != '.');
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);
34 if (!DomainIsHostOnly(host))
35 return host.substr(1);
36 return host;
39 bool GetCookieDomainWithString(const GURL& url,
40 const std::string& domain_string,
41 std::string* result) {
42 const std::string url_host(url.host());
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;
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;
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.
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;
83 *result = cookie_domain;
84 return true;
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 !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
109 base::Time::Exploded exploded = {0};
111 base::StringTokenizer tokenizer(time_string, kDelimiters);
113 bool found_day_of_month = false;
114 bool found_month = false;
115 bool found_time = false;
116 bool found_year = false;
118 while (tokenizer.GetNext()) {
119 const std::string token = tokenizer.token();
120 DCHECK(!token.empty());
121 bool numerical = base::IsAsciiDigit(token[0]);
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::StartsWith(token, base::StringPiece(kMonths[i], 3),
129 base::CompareCase::INSENSITIVE_ASCII)) {
130 exploded.month = i + 1;
131 found_month = true;
132 break;
135 } else {
136 // If we've gotten here, it means we've already found and parsed our
137 // month, and we have another string, which we would expect to be the
138 // the time zone name. According to the RFC and my experiments with
139 // how sites format their expirations, we don't have much of a reason
140 // to support timezones. We don't want to ever barf on user input,
141 // but this DCHECK should pass for well-formed data.
142 // DCHECK(token == "GMT");
144 // Numeric field w/ a colon
145 } else if (token.find(':') != std::string::npos) {
146 if (!found_time &&
147 #ifdef COMPILER_MSVC
148 sscanf_s(
149 #else
150 sscanf(
151 #endif
152 token.c_str(), "%2u:%2u:%2u", &exploded.hour,
153 &exploded.minute, &exploded.second) == 3) {
154 found_time = true;
155 } else {
156 // We should only ever encounter one time-like thing. If we're here,
157 // it means we've found a second, which shouldn't happen. We keep
158 // the first. This check should be ok for well-formed input:
159 // NOTREACHED();
161 // Numeric field
162 } else {
163 // Overflow with atoi() is unspecified, so we enforce a max length.
164 if (!found_day_of_month && token.length() <= 2) {
165 exploded.day_of_month = atoi(token.c_str());
166 found_day_of_month = true;
167 } else if (!found_year && token.length() <= 5) {
168 exploded.year = atoi(token.c_str());
169 found_year = true;
170 } else {
171 // If we're here, it means we've either found an extra numeric field,
172 // or a numeric field which was too long. For well-formed input, the
173 // following check would be reasonable:
174 // NOTREACHED();
179 if (!found_day_of_month || !found_month || !found_time || !found_year) {
180 // We didn't find all of the fields we need. For well-formed input, the
181 // following check would be reasonable:
182 // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
183 return base::Time();
186 // Normalize the year to expand abbreviated years to the full year.
187 if (exploded.year >= 69 && exploded.year <= 99)
188 exploded.year += 1900;
189 if (exploded.year >= 0 && exploded.year <= 68)
190 exploded.year += 2000;
192 // If our values are within their correct ranges, we got our time.
193 if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 &&
194 exploded.month >= 1 && exploded.month <= 12 &&
195 exploded.year >= 1601 && exploded.year <= 30827 &&
196 exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) {
197 return base::Time::FromUTCExploded(exploded);
200 // One of our values was out of expected range. For well-formed input,
201 // the following check would be reasonable:
202 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
204 return base::Time();
207 GURL CookieOriginToURL(const std::string& domain, bool is_https) {
208 if (domain.empty())
209 return GURL();
211 const std::string scheme = is_https ? "https" : "http";
212 const std::string host = domain[0] == '.' ? domain.substr(1) : domain;
213 return GURL(scheme + "://" + host);
216 void ParseRequestCookieLine(const std::string& header_value,
217 ParsedRequestCookies* parsed_cookies) {
218 std::string::const_iterator i = header_value.begin();
219 while (i != header_value.end()) {
220 // Here we are at the beginning of a cookie.
222 // Eat whitespace.
223 while (i != header_value.end() && *i == ' ') ++i;
224 if (i == header_value.end()) return;
226 // Find cookie name.
227 std::string::const_iterator cookie_name_beginning = i;
228 while (i != header_value.end() && *i != '=') ++i;
229 base::StringPiece cookie_name(cookie_name_beginning, i);
231 // Find cookie value.
232 base::StringPiece cookie_value;
233 // Cookies may have no value, in this case '=' may or may not be there.
234 if (i != header_value.end() && i + 1 != header_value.end()) {
235 ++i; // Skip '='.
236 std::string::const_iterator cookie_value_beginning = i;
237 if (*i == '"') {
238 ++i; // Skip '"'.
239 while (i != header_value.end() && *i != '"') ++i;
240 if (i == header_value.end()) return;
241 ++i; // Skip '"'.
242 cookie_value = base::StringPiece(cookie_value_beginning, i);
243 // i points to character after '"', potentially a ';'.
244 } else {
245 while (i != header_value.end() && *i != ';') ++i;
246 cookie_value = base::StringPiece(cookie_value_beginning, i);
247 // i points to ';' or end of string.
250 parsed_cookies->push_back(std::make_pair(cookie_name, cookie_value));
251 // Eat ';'.
252 if (i != header_value.end()) ++i;
256 std::string SerializeRequestCookieLine(
257 const ParsedRequestCookies& parsed_cookies) {
258 std::string buffer;
259 for (ParsedRequestCookies::const_iterator i = parsed_cookies.begin();
260 i != parsed_cookies.end(); ++i) {
261 if (!buffer.empty())
262 buffer.append("; ");
263 buffer.append(i->first.begin(), i->first.end());
264 buffer.push_back('=');
265 buffer.append(i->second.begin(), i->second.end());
267 return buffer;
270 } // namespace cookie_util
271 } // namespace net