parsed_cookie.cc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  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. // Portions of this code based on Mozilla:
  5. // (netwerk/cookie/src/nsCookieService.cpp)
  6. /* ***** BEGIN LICENSE BLOCK *****
  7. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  8. *
  9. * The contents of this file are subject to the Mozilla Public License Version
  10. * 1.1 (the "License"); you may not use this file except in compliance with
  11. * the License. You may obtain a copy of the License at
  12. * http://www.mozilla.org/MPL/
  13. *
  14. * Software distributed under the License is distributed on an "AS IS" basis,
  15. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  16. * for the specific language governing rights and limitations under the
  17. * License.
  18. *
  19. * The Original Code is mozilla.org code.
  20. *
  21. * The Initial Developer of the Original Code is
  22. * Netscape Communications Corporation.
  23. * Portions created by the Initial Developer are Copyright (C) 2003
  24. * the Initial Developer. All Rights Reserved.
  25. *
  26. * Contributor(s):
  27. * Daniel Witte (dwitte@stanford.edu)
  28. * Michiel van Leeuwen (mvl@exedo.nl)
  29. *
  30. * Alternatively, the contents of this file may be used under the terms of
  31. * either the GNU General Public License Version 2 or later (the "GPL"), or
  32. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  33. * in which case the provisions of the GPL or the LGPL are applicable instead
  34. * of those above. If you wish to allow use of your version of this file only
  35. * under the terms of either the GPL or the LGPL, and not to allow others to
  36. * use your version of this file under the terms of the MPL, indicate your
  37. * decision by deleting the provisions above and replace them with the notice
  38. * and other provisions required by the GPL or the LGPL. If you do not delete
  39. * the provisions above, a recipient may use your version of this file under
  40. * the terms of any one of the MPL, the GPL or the LGPL.
  41. *
  42. * ***** END LICENSE BLOCK ***** */
  43. #include "net/cookies/parsed_cookie.h"
  44. #include "base/logging.h"
  45. #include "base/metrics/histogram_macros.h"
  46. #include "base/numerics/checked_math.h"
  47. #include "base/strings/string_util.h"
  48. #include "net/base/features.h"
  49. #include "net/cookies/cookie_constants.h"
  50. #include "net/cookies/cookie_inclusion_status.h"
  51. #include "net/http/http_util.h"
  52. namespace {
  53. const char kPathTokenName[] = "path";
  54. const char kDomainTokenName[] = "domain";
  55. const char kExpiresTokenName[] = "expires";
  56. const char kMaxAgeTokenName[] = "max-age";
  57. const char kSecureTokenName[] = "secure";
  58. const char kHttpOnlyTokenName[] = "httponly";
  59. const char kSameSiteTokenName[] = "samesite";
  60. const char kPriorityTokenName[] = "priority";
  61. const char kSamePartyTokenName[] = "sameparty";
  62. const char kPartitionedTokenName[] = "partitioned";
  63. const char kTerminator[] = "\n\r\0";
  64. const int kTerminatorLen = sizeof(kTerminator) - 1;
  65. const char kWhitespace[] = " \t";
  66. const char kValueSeparator = ';';
  67. const char kTokenSeparator[] = ";=";
  68. // Returns true if |c| occurs in |chars|
  69. // TODO(erikwright): maybe make this take an iterator, could check for end also?
  70. inline bool CharIsA(const char c, const char* chars) {
  71. return strchr(chars, c) != nullptr;
  72. }
  73. // Seek the iterator to the first occurrence of |character|.
  74. // Returns true if it hits the end, false otherwise.
  75. inline bool SeekToCharacter(std::string::const_iterator* it,
  76. const std::string::const_iterator& end,
  77. const char character) {
  78. for (; *it != end && **it != character; ++(*it)) {
  79. }
  80. return *it == end;
  81. }
  82. // Seek the iterator to the first occurrence of a character in |chars|.
  83. // Returns true if it hit the end, false otherwise.
  84. inline bool SeekTo(std::string::const_iterator* it,
  85. const std::string::const_iterator& end,
  86. const char* chars) {
  87. for (; *it != end && !CharIsA(**it, chars); ++(*it)) {
  88. }
  89. return *it == end;
  90. }
  91. // Seek the iterator to the first occurrence of a character not in |chars|.
  92. // Returns true if it hit the end, false otherwise.
  93. inline bool SeekPast(std::string::const_iterator* it,
  94. const std::string::const_iterator& end,
  95. const char* chars) {
  96. for (; *it != end && CharIsA(**it, chars); ++(*it)) {
  97. }
  98. return *it == end;
  99. }
  100. inline bool SeekBackPast(std::string::const_iterator* it,
  101. const std::string::const_iterator& end,
  102. const char* chars) {
  103. for (; *it != end && CharIsA(**it, chars); --(*it)) {
  104. }
  105. return *it == end;
  106. }
  107. // Validate value, which may be according to RFC 6265
  108. // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
  109. // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
  110. // ; US-ASCII characters excluding CTLs,
  111. // ; whitespace DQUOTE, comma, semicolon,
  112. // ; and backslash
  113. // Note: This function is being replaced by
  114. // ParsedCookie::IsValidCookieValue() but remains here while we test that
  115. // removing this functionality doesn't break things.
  116. // TODO(crbug.com/1243852) Remove this when kExtraCookieValidityChecks
  117. // gets removed (assuming the associated changes cause no issues).
  118. bool IsValidCookieValueLegacy(const std::string& value) {
  119. // Number of characters to skip in validation at beginning and end of string.
  120. size_t skip = 0;
  121. if (value.size() >= 2 && *value.begin() == '"' && *(value.end() - 1) == '"')
  122. skip = 1;
  123. for (std::string::const_iterator i = value.begin() + skip;
  124. i != value.end() - skip; ++i) {
  125. bool valid_octet =
  126. (*i == 0x21 || (*i >= 0x23 && *i <= 0x2B) ||
  127. (*i >= 0x2D && *i <= 0x3A) || (*i >= 0x3C && *i <= 0x5B) ||
  128. (*i >= 0x5D && *i <= 0x7E));
  129. if (!valid_octet)
  130. return false;
  131. }
  132. return true;
  133. }
  134. // Returns the string piece within |value| that is a valid cookie value.
  135. base::StringPiece ValidStringPieceForValue(const std::string& value) {
  136. std::string::const_iterator it = value.begin();
  137. std::string::const_iterator end =
  138. net::ParsedCookie::FindFirstTerminator(value);
  139. std::string::const_iterator value_start;
  140. std::string::const_iterator value_end;
  141. net::ParsedCookie::ParseValue(&it, end, &value_start, &value_end);
  142. return base::MakeStringPiece(value_start, value_end);
  143. }
  144. } // namespace
  145. namespace net {
  146. ParsedCookie::ParsedCookie(const std::string& cookie_line,
  147. CookieInclusionStatus* status_out) {
  148. // Put a pointer on the stack so the rest of the function can assign to it if
  149. // the default nullptr is passed in.
  150. CookieInclusionStatus blank_status;
  151. if (status_out == nullptr) {
  152. status_out = &blank_status;
  153. }
  154. *status_out = CookieInclusionStatus();
  155. if ((!base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) &&
  156. cookie_line.size() > kMaxCookieSize) {
  157. DVLOG(1) << "Not parsing cookie, too large: " << cookie_line.size();
  158. status_out->AddExclusionReason(
  159. CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE);
  160. return;
  161. }
  162. ParseTokenValuePairs(cookie_line, *status_out);
  163. if (IsValid()) {
  164. SetupAttributes();
  165. } else if (status_out->IsInclude()) {
  166. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  167. status_out->AddExclusionReason(
  168. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  169. }
  170. // Status should indicate exclusion if the resulting ParsedCookie is invalid.
  171. DCHECK(IsValid() || !status_out->IsInclude());
  172. }
  173. ParsedCookie::~ParsedCookie() = default;
  174. bool ParsedCookie::IsValid() const {
  175. return !pairs_.empty();
  176. }
  177. CookieSameSite ParsedCookie::SameSite(
  178. CookieSameSiteString* samesite_string) const {
  179. CookieSameSite samesite = CookieSameSite::UNSPECIFIED;
  180. if (same_site_index_ != 0) {
  181. samesite = StringToCookieSameSite(pairs_[same_site_index_].second,
  182. samesite_string);
  183. } else if (samesite_string) {
  184. *samesite_string = CookieSameSiteString::kUnspecified;
  185. }
  186. return samesite;
  187. }
  188. CookiePriority ParsedCookie::Priority() const {
  189. return (priority_index_ == 0)
  190. ? COOKIE_PRIORITY_DEFAULT
  191. : StringToCookiePriority(pairs_[priority_index_].second);
  192. }
  193. bool ParsedCookie::SetName(const std::string& name) {
  194. if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) {
  195. const std::string& value = pairs_.empty() ? "" : pairs_[0].second;
  196. // Ensure there are no invalid characters in `name`. This should be done
  197. // before calling ParseTokenString because we want terminating characters
  198. // ('\r', '\n', and '\0') and '=' in `name` to cause a rejection instead of
  199. // truncation.
  200. // TODO(crbug.com/1233602) Once we change logic more broadly to reject
  201. // cookies containing these characters, we should be able to simplify this
  202. // logic since IsValidCookieNameValuePair() also calls IsValidCookieName().
  203. // Also, this check will currently fail if `name` has a tab character in the
  204. // leading or trailing whitespace, which is inconsistent with what happens
  205. // when parsing a cookie line in the constructor (but the old logic for
  206. // SetName() behaved this way as well).
  207. if (!IsValidCookieName(name)) {
  208. return false;
  209. }
  210. // Use the same whitespace trimming code as the constructor.
  211. const std::string& parsed_name = ParseTokenString(name);
  212. if (!IsValidCookieNameValuePair(parsed_name, value)) {
  213. return false;
  214. }
  215. if (pairs_.empty())
  216. pairs_.push_back(std::make_pair("", ""));
  217. pairs_[0].first = parsed_name;
  218. } else {
  219. if (!name.empty() && !HttpUtil::IsToken(name))
  220. return false;
  221. // Fail if we'd be creating a cookie with an empty name and value.
  222. if (name.empty() && (pairs_.empty() || pairs_[0].second.empty()))
  223. return false;
  224. if (pairs_.empty())
  225. pairs_.push_back(std::make_pair("", ""));
  226. pairs_[0].first = name;
  227. }
  228. return true;
  229. }
  230. bool ParsedCookie::SetValue(const std::string& value) {
  231. if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) {
  232. const std::string& name = pairs_.empty() ? "" : pairs_[0].first;
  233. // Ensure there are no invalid characters in `value`. This should be done
  234. // before calling ParseValueString because we want terminating characters
  235. // ('\r', '\n', and '\0') in `value` to cause a rejection instead of
  236. // truncation.
  237. // TODO(crbug.com/1233602) Once we change logic more broadly to reject
  238. // cookies containing these characters, we should be able to simplify this
  239. // logic since IsValidCookieNameValuePair() also calls IsValidCookieValue().
  240. // Also, this check will currently fail if `value` has a tab character in
  241. // the leading or trailing whitespace, which is inconsistent with what
  242. // happens when parsing a cookie line in the constructor (but the old logic
  243. // for SetValue() behaved this way as well).
  244. if (!IsValidCookieValue(value)) {
  245. return false;
  246. }
  247. // Use the same whitespace trimming code as the constructor.
  248. const std::string& parsed_value = ParseValueString(value);
  249. if (!IsValidCookieNameValuePair(name, parsed_value)) {
  250. return false;
  251. }
  252. if (pairs_.empty())
  253. pairs_.push_back(std::make_pair("", ""));
  254. pairs_[0].second = parsed_value;
  255. } else {
  256. if (!IsValidCookieValueLegacy(value))
  257. return false;
  258. // Fail if we'd be creating a cookie with an empty name and value.
  259. if (value.empty() && (pairs_.empty() || pairs_[0].first.empty()))
  260. return false;
  261. if (pairs_.empty())
  262. pairs_.push_back(std::make_pair("", ""));
  263. pairs_[0].second = value;
  264. }
  265. return true;
  266. }
  267. bool ParsedCookie::SetPath(const std::string& path) {
  268. return SetString(&path_index_, kPathTokenName, path);
  269. }
  270. bool ParsedCookie::SetDomain(const std::string& domain) {
  271. return SetString(&domain_index_, kDomainTokenName, domain);
  272. }
  273. bool ParsedCookie::SetExpires(const std::string& expires) {
  274. return SetString(&expires_index_, kExpiresTokenName, expires);
  275. }
  276. bool ParsedCookie::SetMaxAge(const std::string& maxage) {
  277. return SetString(&maxage_index_, kMaxAgeTokenName, maxage);
  278. }
  279. bool ParsedCookie::SetIsSecure(bool is_secure) {
  280. return SetBool(&secure_index_, kSecureTokenName, is_secure);
  281. }
  282. bool ParsedCookie::SetIsHttpOnly(bool is_http_only) {
  283. return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only);
  284. }
  285. bool ParsedCookie::SetSameSite(const std::string& same_site) {
  286. return SetString(&same_site_index_, kSameSiteTokenName, same_site);
  287. }
  288. bool ParsedCookie::SetPriority(const std::string& priority) {
  289. return SetString(&priority_index_, kPriorityTokenName, priority);
  290. }
  291. bool ParsedCookie::SetIsSameParty(bool is_same_party) {
  292. return SetBool(&same_party_index_, kSamePartyTokenName, is_same_party);
  293. }
  294. bool ParsedCookie::SetIsPartitioned(bool is_partitioned) {
  295. return SetBool(&partitioned_index_, kPartitionedTokenName, is_partitioned);
  296. }
  297. std::string ParsedCookie::ToCookieLine() const {
  298. std::string out;
  299. for (auto it = pairs_.begin(); it != pairs_.end(); ++it) {
  300. if (!out.empty())
  301. out.append("; ");
  302. out.append(it->first);
  303. // Determine whether to emit the pair's value component. We should always
  304. // print it for the first pair(see crbug.com/977619). After the first pair,
  305. // we need to consider whether the name component is a special token.
  306. if (it == pairs_.begin() ||
  307. (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName &&
  308. it->first != kSamePartyTokenName &&
  309. it->first != kPartitionedTokenName)) {
  310. out.append("=");
  311. out.append(it->second);
  312. }
  313. }
  314. return out;
  315. }
  316. // static
  317. std::string::const_iterator ParsedCookie::FindFirstTerminator(
  318. const std::string& s) {
  319. std::string::const_iterator end = s.end();
  320. size_t term_pos = s.find_first_of(std::string(kTerminator, kTerminatorLen));
  321. if (term_pos != std::string::npos) {
  322. // We found a character we should treat as an end of string.
  323. end = s.begin() + term_pos;
  324. }
  325. return end;
  326. }
  327. // static
  328. bool ParsedCookie::ParseToken(std::string::const_iterator* it,
  329. const std::string::const_iterator& end,
  330. std::string::const_iterator* token_start,
  331. std::string::const_iterator* token_end) {
  332. DCHECK(it && token_start && token_end);
  333. std::string::const_iterator token_real_end;
  334. // Seek past any whitespace before the "token" (the name).
  335. // token_start should point at the first character in the token
  336. if (SeekPast(it, end, kWhitespace))
  337. return false; // No token, whitespace or empty.
  338. *token_start = *it;
  339. // Seek over the token, to the token separator.
  340. // token_real_end should point at the token separator, i.e. '='.
  341. // If it == end after the seek, we probably have a token-value.
  342. SeekTo(it, end, kTokenSeparator);
  343. token_real_end = *it;
  344. // Ignore any whitespace between the token and the token separator.
  345. // token_end should point after the last interesting token character,
  346. // pointing at either whitespace, or at '=' (and equal to token_real_end).
  347. if (*it != *token_start) { // We could have an empty token name.
  348. --(*it); // Go back before the token separator.
  349. // Skip over any whitespace to the first non-whitespace character.
  350. SeekBackPast(it, *token_start, kWhitespace);
  351. // Point after it.
  352. ++(*it);
  353. }
  354. *token_end = *it;
  355. // Seek us back to the end of the token.
  356. *it = token_real_end;
  357. return true;
  358. }
  359. // static
  360. void ParsedCookie::ParseValue(std::string::const_iterator* it,
  361. const std::string::const_iterator& end,
  362. std::string::const_iterator* value_start,
  363. std::string::const_iterator* value_end) {
  364. DCHECK(it && value_start && value_end);
  365. // Seek past any whitespace that might be in-between the token and value.
  366. SeekPast(it, end, kWhitespace);
  367. // value_start should point at the first character of the value.
  368. *value_start = *it;
  369. // Just look for ';' to terminate ('=' allowed).
  370. // We can hit the end, maybe they didn't terminate.
  371. SeekToCharacter(it, end, kValueSeparator);
  372. // Will point at the ; separator or the end.
  373. *value_end = *it;
  374. // Ignore any unwanted whitespace after the value.
  375. if (*value_end != *value_start) { // Could have an empty value
  376. --(*value_end);
  377. // Skip over any whitespace to the first non-whitespace character.
  378. SeekBackPast(value_end, *value_start, kWhitespace);
  379. // Point after it.
  380. ++(*value_end);
  381. }
  382. }
  383. // static
  384. std::string ParsedCookie::ParseTokenString(const std::string& token) {
  385. std::string::const_iterator it = token.begin();
  386. std::string::const_iterator end = FindFirstTerminator(token);
  387. std::string::const_iterator token_start, token_end;
  388. if (ParseToken(&it, end, &token_start, &token_end))
  389. return std::string(token_start, token_end);
  390. return std::string();
  391. }
  392. // static
  393. std::string ParsedCookie::ParseValueString(const std::string& value) {
  394. return std::string(ValidStringPieceForValue(value));
  395. }
  396. // static
  397. bool ParsedCookie::ValueMatchesParsedValue(const std::string& value) {
  398. // ValidStringPieceForValue() returns a valid substring of |value|.
  399. // If |value| can be fully parsed the result will have the same length
  400. // as |value|.
  401. return ValidStringPieceForValue(value).length() == value.length();
  402. }
  403. // static
  404. bool ParsedCookie::IsValidCookieName(const std::string& name) {
  405. // IsValidCookieName() returns whether a string matches the following
  406. // grammar:
  407. //
  408. // cookie-name = *cookie-name-octet
  409. // cookie-name-octet = %x20-3A / %x3C / %x3E-7E / %x80-FF
  410. // ; octets excluding CTLs, ";", and "="
  411. //
  412. // This can be used to determine whether cookie names and cookie attribute
  413. // names contain any invalid characters.
  414. //
  415. // Note that RFC6265bis section 4.1.1 suggests a stricter grammar for
  416. // parsing cookie names, but we choose to allow a wider range of characters
  417. // than what's allowed by that grammar (while still conforming to the
  418. // requirements of the parsing algorithm defined in section 5.2).
  419. //
  420. // For reference, see:
  421. // - https://crbug.com/238041
  422. for (char i : name) {
  423. if (HttpUtil::IsControlChar(i) || i == ';' || i == '=')
  424. return false;
  425. }
  426. return true;
  427. }
  428. // static
  429. bool ParsedCookie::IsValidCookieValue(const std::string& value) {
  430. // IsValidCookieValue() returns whether a string matches the following
  431. // grammar:
  432. //
  433. // cookie-value = *cookie-value-octet
  434. // cookie-value-octet = %x20-3A / %x3C-7E / %x80-FF
  435. // ; octets excluding CTLs and ";"
  436. //
  437. // This can be used to determine whether cookie values contain any invalid
  438. // characters.
  439. //
  440. // Note that RFC6265bis section 4.1.1 suggests a stricter grammar for
  441. // parsing cookie values, but we choose to allow a wider range of characters
  442. // than what's allowed by that grammar (while still conforming to the
  443. // requirements of the parsing algorithm defined in section 5.2).
  444. //
  445. // For reference, see:
  446. // - https://crbug.com/238041
  447. for (char i : value) {
  448. if (HttpUtil::IsControlChar(i) || i == ';')
  449. return false;
  450. }
  451. return true;
  452. }
  453. // static
  454. bool ParsedCookie::IsValidCookieAttributeValueLegacy(const std::string& value) {
  455. // This legacy method only checks the character set, so use
  456. // CookieAttributeValueHasValidCharSet()
  457. return CookieAttributeValueHasValidCharSet(value);
  458. }
  459. // static
  460. bool ParsedCookie::CookieAttributeValueHasValidCharSet(
  461. const std::string& value) {
  462. // A cookie attribute value has the same character set restrictions as cookie
  463. // values, so re-use the validation function for that.
  464. return IsValidCookieValue(value);
  465. }
  466. // static
  467. bool ParsedCookie::CookieAttributeValueHasValidSize(const std::string& value) {
  468. return (value.size() <= kMaxCookieAttributeValueSize);
  469. }
  470. // static
  471. bool ParsedCookie::IsValidCookieNameValuePair(
  472. const std::string& name,
  473. const std::string& value,
  474. CookieInclusionStatus* status_out) {
  475. // Ignore cookies with neither name nor value.
  476. if (name.empty() && value.empty()) {
  477. if (status_out != nullptr) {
  478. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  479. status_out->AddExclusionReason(
  480. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  481. }
  482. // TODO(crbug.com/1228815) Note - if the exclusion reasons change to no
  483. // longer be the same, we'll need to not return right away and evaluate all
  484. // of the checks.
  485. return false;
  486. }
  487. // Enforce a length limit for name + value per RFC6265bis.
  488. base::CheckedNumeric<size_t> name_value_pair_size = name.size();
  489. name_value_pair_size += value.size();
  490. if (!name_value_pair_size.IsValid() ||
  491. (name_value_pair_size.ValueOrDie() > kMaxCookieNamePlusValueSize)) {
  492. if (status_out != nullptr) {
  493. status_out->AddExclusionReason(
  494. CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE);
  495. }
  496. return false;
  497. }
  498. // Ignore Set-Cookie directives containing control characters. See
  499. // http://crbug.com/238041.
  500. if (!IsValidCookieName(name) || !IsValidCookieValue(value)) {
  501. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  502. if (status_out != nullptr) {
  503. status_out->AddExclusionReason(
  504. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  505. }
  506. return false;
  507. }
  508. return true;
  509. }
  510. // Parse all token/value pairs and populate pairs_.
  511. void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line,
  512. CookieInclusionStatus& status_out) {
  513. pairs_.clear();
  514. // Ok, here we go. We should be expecting to be starting somewhere
  515. // before the cookie line, not including any header name...
  516. std::string::const_iterator start = cookie_line.begin();
  517. std::string::const_iterator it = start;
  518. // TODO(erikwright): Make sure we're stripping \r\n in the network code.
  519. // Then we can log any unexpected terminators.
  520. std::string::const_iterator end = FindFirstTerminator(cookie_line);
  521. // For metrics on truncating character presence in the cookie line.
  522. if (end < cookie_line.end()) {
  523. switch (*end) {
  524. case '\0':
  525. truncating_char_in_cookie_string_type_ =
  526. TruncatingCharacterInCookieStringType::kTruncatingCharNull;
  527. break;
  528. case '\r':
  529. truncating_char_in_cookie_string_type_ =
  530. TruncatingCharacterInCookieStringType::kTruncatingCharNewline;
  531. break;
  532. case '\n':
  533. truncating_char_in_cookie_string_type_ =
  534. TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed;
  535. break;
  536. default:
  537. NOTREACHED();
  538. }
  539. }
  540. // Exit early for an empty cookie string.
  541. if (it == end) {
  542. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  543. status_out.AddExclusionReason(
  544. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  545. return;
  546. }
  547. for (int pair_num = 0; it != end; ++pair_num) {
  548. TokenValuePair pair;
  549. std::string::const_iterator token_start, token_end;
  550. if (!ParseToken(&it, end, &token_start, &token_end)) {
  551. // Allow first token to be treated as empty-key if unparsable
  552. if (pair_num != 0)
  553. break;
  554. // If parsing failed, start the value parsing at the very beginning.
  555. token_start = start;
  556. }
  557. if (it == end || *it != '=') {
  558. // We have a token-value, we didn't have any token name.
  559. if (pair_num == 0) {
  560. // For the first time around, we want to treat single values
  561. // as a value with an empty name. (Mozilla bug 169091).
  562. // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
  563. // set 2 different cookies, and setting "BBB" will then replace "AAA".
  564. pair.first = "";
  565. // Rewind to the beginning of what we thought was the token name,
  566. // and let it get parsed as a value.
  567. it = token_start;
  568. } else {
  569. // Any not-first attribute we want to treat a value as a
  570. // name with an empty value... This is so something like
  571. // "secure;" will get parsed as a Token name, and not a value.
  572. pair.first = std::string(token_start, token_end);
  573. }
  574. } else {
  575. // We have a TOKEN=VALUE.
  576. pair.first = std::string(token_start, token_end);
  577. ++it; // Skip past the '='.
  578. }
  579. // OK, now try to parse a value.
  580. std::string::const_iterator value_start, value_end;
  581. ParseValue(&it, end, &value_start, &value_end);
  582. // OK, we're finished with a Token/Value.
  583. pair.second = std::string(value_start, value_end);
  584. if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) {
  585. bool ignore_pair = false;
  586. if (pair_num == 0) {
  587. if (!IsValidCookieNameValuePair(pair.first, pair.second, &status_out)) {
  588. pairs_.clear();
  589. break;
  590. }
  591. } else {
  592. // From RFC2109: "Attributes (names) (attr) are case-insensitive."
  593. pair.first = base::ToLowerASCII(pair.first);
  594. // Attribute names have the same character set limitations as cookie
  595. // names, but only a handful of values are allowed. We don't check that
  596. // this attribute name is one of the allowed ones here, so just re-use
  597. // the cookie name check.
  598. if (!IsValidCookieName(pair.first)) {
  599. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  600. status_out.AddExclusionReason(
  601. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  602. pairs_.clear();
  603. break;
  604. }
  605. if (!CookieAttributeValueHasValidCharSet(pair.second)) {
  606. // If the attribute value contains invalid characters, the whole
  607. // cookie should be ignored.
  608. status_out.AddExclusionReason(
  609. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  610. pairs_.clear();
  611. break;
  612. }
  613. if (!CookieAttributeValueHasValidSize(pair.second)) {
  614. // If the attribute value is too large, it should be ignored.
  615. ignore_pair = true;
  616. status_out.AddWarningReason(
  617. CookieInclusionStatus::WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
  618. }
  619. }
  620. if (!ignore_pair) {
  621. pairs_.push_back(pair);
  622. }
  623. } else {
  624. // Ignore cookies with neither name nor value.
  625. if (pair_num == 0 && (pair.first.empty() && pair.second.empty())) {
  626. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  627. status_out.AddExclusionReason(
  628. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  629. pairs_.clear();
  630. break;
  631. }
  632. // From RFC2109: "Attributes (names) (attr) are case-insensitive."
  633. if (pair_num != 0)
  634. pair.first = base::ToLowerASCII(pair.first);
  635. // Ignore Set-Cookie directives containing control characters. See
  636. // http://crbug.com/238041.
  637. if (!IsValidCookieAttributeValueLegacy(pair.first) ||
  638. !IsValidCookieAttributeValueLegacy(pair.second)) {
  639. // TODO(crbug.com/1228815): Apply more specific exclusion reasons.
  640. status_out.AddExclusionReason(
  641. CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  642. pairs_.clear();
  643. break;
  644. }
  645. pairs_.push_back(pair);
  646. }
  647. // We've processed a token/value pair, we're either at the end of
  648. // the string or a ValueSeparator like ';', which we want to skip.
  649. if (it != end)
  650. ++it;
  651. }
  652. // For metrics on name/value truncation.
  653. //
  654. // If we stopped before the (real) end of the string and the leftovers
  655. // include something other than terminating characters or whitespace then
  656. // this cookie was truncated due to a terminating CTL.
  657. //
  658. // We only care about the name or value being truncated which means we
  659. // only care about the first pair. If the pairs_.size() > 1 then that
  660. // means the truncation happened after name/value parsing which we're not
  661. // concerned about for metrics.
  662. using std::string_literals::operator""s;
  663. if (pairs_.size() == 1 &&
  664. cookie_line.find_first_not_of("\n\r\0 \t"s, end - start) !=
  665. std::string::npos) {
  666. truncated_name_or_value_ = true;
  667. }
  668. }
  669. void ParsedCookie::SetupAttributes() {
  670. // We skip over the first token/value, the user supplied one.
  671. for (size_t i = 1; i < pairs_.size(); ++i) {
  672. if (pairs_[i].first == kPathTokenName) {
  673. path_index_ = i;
  674. } else if (pairs_[i].first == kDomainTokenName) {
  675. domain_index_ = i;
  676. } else if (pairs_[i].first == kExpiresTokenName) {
  677. expires_index_ = i;
  678. } else if (pairs_[i].first == kMaxAgeTokenName) {
  679. maxage_index_ = i;
  680. } else if (pairs_[i].first == kSecureTokenName) {
  681. secure_index_ = i;
  682. } else if (pairs_[i].first == kHttpOnlyTokenName) {
  683. httponly_index_ = i;
  684. } else if (pairs_[i].first == kSameSiteTokenName) {
  685. same_site_index_ = i;
  686. } else if (pairs_[i].first == kPriorityTokenName) {
  687. priority_index_ = i;
  688. } else if (pairs_[i].first == kSamePartyTokenName) {
  689. same_party_index_ = i;
  690. } else if (pairs_[i].first == kPartitionedTokenName) {
  691. partitioned_index_ = i;
  692. } else {
  693. /* some attribute we don't know or don't care about. */
  694. }
  695. }
  696. }
  697. bool ParsedCookie::SetString(size_t* index,
  698. const std::string& key,
  699. const std::string& untrusted_value) {
  700. // This function should do equivalent input validation to the
  701. // constructor. Otherwise, the Set* functions can put this ParsedCookie in a
  702. // state where parsing the output of ToCookieLine() produces a different
  703. // ParsedCookie.
  704. //
  705. // Without input validation, invoking pc.SetPath(" baz ") would result in
  706. // pc.ToCookieLine() == "path= baz ". Parsing the "path= baz " string would
  707. // produce a cookie with "path" attribute equal to "baz" (no spaces). We
  708. // should not produce cookie lines that parse to different key/value pairs!
  709. // Inputs containing invalid characters or attribute value strings that are
  710. // too large should be ignored. Note that we check the attribute value size
  711. // after removing leading and trailing whitespace.
  712. if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) {
  713. if (!CookieAttributeValueHasValidCharSet(untrusted_value))
  714. return false;
  715. } else {
  716. if (!IsValidCookieAttributeValueLegacy(untrusted_value))
  717. return false;
  718. }
  719. // Use the same whitespace trimming code as the constructor.
  720. const std::string parsed_value = ParseValueString(untrusted_value);
  721. if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) {
  722. if (!CookieAttributeValueHasValidSize(parsed_value))
  723. return false;
  724. }
  725. if (parsed_value.empty()) {
  726. ClearAttributePair(*index);
  727. return true;
  728. } else {
  729. return SetAttributePair(index, key, parsed_value);
  730. }
  731. }
  732. bool ParsedCookie::SetBool(size_t* index, const std::string& key, bool value) {
  733. if (!value) {
  734. ClearAttributePair(*index);
  735. return true;
  736. } else {
  737. return SetAttributePair(index, key, std::string());
  738. }
  739. }
  740. bool ParsedCookie::SetAttributePair(size_t* index,
  741. const std::string& key,
  742. const std::string& value) {
  743. if (!HttpUtil::IsToken(key))
  744. return false;
  745. if (!IsValid())
  746. return false;
  747. if (*index) {
  748. pairs_[*index].second = value;
  749. } else {
  750. pairs_.push_back(std::make_pair(key, value));
  751. *index = pairs_.size() - 1;
  752. }
  753. return true;
  754. }
  755. void ParsedCookie::ClearAttributePair(size_t index) {
  756. // The first pair (name/value of cookie at pairs_[0]) cannot be cleared.
  757. // Cookie attributes that don't have a value at the moment, are
  758. // represented with an index being equal to 0.
  759. if (index == 0)
  760. return;
  761. size_t* indexes[] = {&path_index_, &domain_index_, &expires_index_,
  762. &maxage_index_, &secure_index_, &httponly_index_,
  763. &same_site_index_, &priority_index_, &same_party_index_,
  764. &partitioned_index_};
  765. for (size_t* attribute_index : indexes) {
  766. if (*attribute_index == index)
  767. *attribute_index = 0;
  768. else if (*attribute_index > index)
  769. --(*attribute_index);
  770. }
  771. pairs_.erase(pairs_.begin() + index);
  772. }
  773. } // namespace net