OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // Portions of this code based on Mozilla: | 5 // Portions of this code based on Mozilla: |
6 // (netwerk/cookie/src/nsCookieService.cpp) | 6 // (netwerk/cookie/src/nsCookieService.cpp) |
7 /* ***** BEGIN LICENSE BLOCK ***** | 7 /* ***** BEGIN LICENSE BLOCK ***** |
8 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 | 8 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
9 * | 9 * |
10 * The contents of this file are subject to the Mozilla Public License Version | 10 * The contents of this file are subject to the Mozilla Public License Version |
(...skipping 27 matching lines...) Expand all Loading... |
38 * decision by deleting the provisions above and replace them with the notice | 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 | 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 | 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. | 41 * the terms of any one of the MPL, the GPL or the LGPL. |
42 * | 42 * |
43 * ***** END LICENSE BLOCK ***** */ | 43 * ***** END LICENSE BLOCK ***** */ |
44 | 44 |
45 #include "net/cookies/parsed_cookie.h" | 45 #include "net/cookies/parsed_cookie.h" |
46 | 46 |
47 #include "base/logging.h" | 47 #include "base/logging.h" |
| 48 #include "base/string_tokenizer.h" |
48 #include "base/string_util.h" | 49 #include "base/string_util.h" |
49 | 50 |
| 51 using base::Time; |
| 52 |
50 namespace net { | 53 namespace net { |
51 | 54 |
52 ParsedCookie::ParsedCookie(const std::string& cookie_line) | 55 ParsedCookie::ParsedCookie(const std::string& cookie_line) |
53 : is_valid_(false), | 56 : is_valid_(false), |
54 path_index_(0), | 57 path_index_(0), |
55 domain_index_(0), | 58 domain_index_(0), |
56 mac_key_index_(0), | 59 mac_key_index_(0), |
57 mac_algorithm_index_(0), | 60 mac_algorithm_index_(0), |
58 expires_index_(0), | 61 expires_index_(0), |
59 maxage_index_(0), | 62 maxage_index_(0), |
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
271 StringToLowerASCII(&pair.first); | 274 StringToLowerASCII(&pair.first); |
272 pairs_.push_back(pair); | 275 pairs_.push_back(pair); |
273 | 276 |
274 // We've processed a token/value pair, we're either at the end of | 277 // We've processed a token/value pair, we're either at the end of |
275 // the string or a ValueSeparator like ';', which we want to skip. | 278 // the string or a ValueSeparator like ';', which we want to skip. |
276 if (it != end) | 279 if (it != end) |
277 ++it; | 280 ++it; |
278 } | 281 } |
279 } | 282 } |
280 | 283 |
| 284 // Parse a cookie expiration time. We try to be lenient, but we need to |
| 285 // assume some order to distinguish the fields. The basic rules: |
| 286 // - The month name must be present and prefix the first 3 letters of the |
| 287 // full month name (jan for January, jun for June). |
| 288 // - If the year is <= 2 digits, it must occur after the day of month. |
| 289 // - The time must be of the format hh:mm:ss. |
| 290 // An average cookie expiration will look something like this: |
| 291 // Sat, 15-Apr-17 21:01:22 GMT |
| 292 // |
| 293 // static |
| 294 Time ParsedCookie::ParseCookieTime(const std::string& time_string) { |
| 295 static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun", |
| 296 "jul", "aug", "sep", "oct", "nov", "dec" }; |
| 297 static const int kMonthsLen = arraysize(kMonths); |
| 298 // We want to be pretty liberal, and support most non-ascii and non-digit |
| 299 // characters as a delimiter. We can't treat : as a delimiter, because it |
| 300 // is the delimiter for hh:mm:ss, and we want to keep this field together. |
| 301 // We make sure to include - and +, since they could prefix numbers. |
| 302 // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes |
| 303 // will be preserved, and we will get them here. So we make sure to include |
| 304 // quote characters, and also \ for anything that was internally escaped. |
| 305 static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~"; |
| 306 |
| 307 Time::Exploded exploded = {0}; |
| 308 |
| 309 StringTokenizer tokenizer(time_string, kDelimiters); |
| 310 |
| 311 bool found_day_of_month = false; |
| 312 bool found_month = false; |
| 313 bool found_time = false; |
| 314 bool found_year = false; |
| 315 |
| 316 while (tokenizer.GetNext()) { |
| 317 const std::string token = tokenizer.token(); |
| 318 DCHECK(!token.empty()); |
| 319 bool numerical = IsAsciiDigit(token[0]); |
| 320 |
| 321 // String field |
| 322 if (!numerical) { |
| 323 if (!found_month) { |
| 324 for (int i = 0; i < kMonthsLen; ++i) { |
| 325 // Match prefix, so we could match January, etc |
| 326 if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) { |
| 327 exploded.month = i + 1; |
| 328 found_month = true; |
| 329 break; |
| 330 } |
| 331 } |
| 332 } else { |
| 333 // If we've gotten here, it means we've already found and parsed our |
| 334 // month, and we have another string, which we would expect to be the |
| 335 // the time zone name. According to the RFC and my experiments with |
| 336 // how sites format their expirations, we don't have much of a reason |
| 337 // to support timezones. We don't want to ever barf on user input, |
| 338 // but this DCHECK should pass for well-formed data. |
| 339 // DCHECK(token == "GMT"); |
| 340 } |
| 341 // Numeric field w/ a colon |
| 342 } else if (token.find(':') != std::string::npos) { |
| 343 if (!found_time && |
| 344 #ifdef COMPILER_MSVC |
| 345 sscanf_s( |
| 346 #else |
| 347 sscanf( |
| 348 #endif |
| 349 token.c_str(), "%2u:%2u:%2u", &exploded.hour, |
| 350 &exploded.minute, &exploded.second) == 3) { |
| 351 found_time = true; |
| 352 } else { |
| 353 // We should only ever encounter one time-like thing. If we're here, |
| 354 // it means we've found a second, which shouldn't happen. We keep |
| 355 // the first. This check should be ok for well-formed input: |
| 356 // NOTREACHED(); |
| 357 } |
| 358 // Numeric field |
| 359 } else { |
| 360 // Overflow with atoi() is unspecified, so we enforce a max length. |
| 361 if (!found_day_of_month && token.length() <= 2) { |
| 362 exploded.day_of_month = atoi(token.c_str()); |
| 363 found_day_of_month = true; |
| 364 } else if (!found_year && token.length() <= 5) { |
| 365 exploded.year = atoi(token.c_str()); |
| 366 found_year = true; |
| 367 } else { |
| 368 // If we're here, it means we've either found an extra numeric field, |
| 369 // or a numeric field which was too long. For well-formed input, the |
| 370 // following check would be reasonable: |
| 371 // NOTREACHED(); |
| 372 } |
| 373 } |
| 374 } |
| 375 |
| 376 if (!found_day_of_month || !found_month || !found_time || !found_year) { |
| 377 // We didn't find all of the fields we need. For well-formed input, the |
| 378 // following check would be reasonable: |
| 379 // NOTREACHED() << "Cookie parse expiration failed: " << time_string; |
| 380 return Time(); |
| 381 } |
| 382 |
| 383 // Normalize the year to expand abbreviated years to the full year. |
| 384 if (exploded.year >= 69 && exploded.year <= 99) |
| 385 exploded.year += 1900; |
| 386 if (exploded.year >= 0 && exploded.year <= 68) |
| 387 exploded.year += 2000; |
| 388 |
| 389 // If our values are within their correct ranges, we got our time. |
| 390 if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 && |
| 391 exploded.month >= 1 && exploded.month <= 12 && |
| 392 exploded.year >= 1601 && exploded.year <= 30827 && |
| 393 exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) { |
| 394 return Time::FromUTCExploded(exploded); |
| 395 } |
| 396 |
| 397 // One of our values was out of expected range. For well-formed input, |
| 398 // the following check would be reasonable: |
| 399 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string; |
| 400 |
| 401 return Time(); |
| 402 } |
| 403 |
281 void ParsedCookie::SetupAttributes() { | 404 void ParsedCookie::SetupAttributes() { |
282 static const char kPathTokenName[] = "path"; | 405 static const char kPathTokenName[] = "path"; |
283 static const char kDomainTokenName[] = "domain"; | 406 static const char kDomainTokenName[] = "domain"; |
284 static const char kMACKeyTokenName[] = "mac-key"; | 407 static const char kMACKeyTokenName[] = "mac-key"; |
285 static const char kMACAlgorithmTokenName[] = "mac-algorithm"; | 408 static const char kMACAlgorithmTokenName[] = "mac-algorithm"; |
286 static const char kExpiresTokenName[] = "expires"; | 409 static const char kExpiresTokenName[] = "expires"; |
287 static const char kMaxAgeTokenName[] = "max-age"; | 410 static const char kMaxAgeTokenName[] = "max-age"; |
288 static const char kSecureTokenName[] = "secure"; | 411 static const char kSecureTokenName[] = "secure"; |
289 static const char kHttpOnlyTokenName[] = "httponly"; | 412 static const char kHttpOnlyTokenName[] = "httponly"; |
290 | 413 |
(...skipping 15 matching lines...) Expand all Loading... |
306 secure_index_ = i; | 429 secure_index_ = i; |
307 } else if (pairs_[i].first == kHttpOnlyTokenName) { | 430 } else if (pairs_[i].first == kHttpOnlyTokenName) { |
308 httponly_index_ = i; | 431 httponly_index_ = i; |
309 } else { | 432 } else { |
310 /* some attribute we don't know or don't care about. */ | 433 /* some attribute we don't know or don't care about. */ |
311 } | 434 } |
312 } | 435 } |
313 } | 436 } |
314 | 437 |
315 } // namespace | 438 } // namespace |
OLD | NEW |