Chromium Code Reviews| 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 29 matching lines...) Expand all Loading... | |
| 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_util.h" | 48 #include "base/string_util.h" |
| 49 | 49 |
| 50 namespace { | |
| 51 | |
| 52 const char kPathTokenName[] = "path"; | |
| 53 const char kDomainTokenName[] = "domain"; | |
| 54 const char kMACKeyTokenName[] = "mac-key"; | |
| 55 const char kMACAlgorithmTokenName[] = "mac-algorithm"; | |
| 56 const char kExpiresTokenName[] = "expires"; | |
| 57 const char kMaxAgeTokenName[] = "max-age"; | |
| 58 const char kSecureTokenName[] = "secure"; | |
| 59 const char kHttpOnlyTokenName[] = "httponly"; | |
| 60 | |
| 61 const char kTerminator[] = "\n\r\0"; | |
| 62 const int kTerminatorLen = sizeof(kTerminator) - 1; | |
| 63 const char kWhitespace[] = " \t"; | |
| 64 const char kValueSeparator[] = ";"; | |
| 65 const char kTokenSeparator[] = ";="; | |
| 66 | |
| 67 } // namespace | |
| 68 | |
| 50 namespace net { | 69 namespace net { |
| 51 | 70 |
| 52 ParsedCookie::ParsedCookie(const std::string& cookie_line) | 71 ParsedCookie::ParsedCookie(const std::string& cookie_line) |
| 53 : is_valid_(false), | 72 : is_valid_(false), |
| 54 path_index_(0), | 73 path_index_(0), |
| 55 domain_index_(0), | 74 domain_index_(0), |
| 56 mac_key_index_(0), | 75 mac_key_index_(0), |
| 57 mac_algorithm_index_(0), | 76 mac_algorithm_index_(0), |
| 58 expires_index_(0), | 77 expires_index_(0), |
| 59 maxage_index_(0), | 78 maxage_index_(0), |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 70 is_valid_ = true; | 89 is_valid_ = true; |
| 71 SetupAttributes(); | 90 SetupAttributes(); |
| 72 } | 91 } |
| 73 } | 92 } |
| 74 | 93 |
| 75 ParsedCookie::~ParsedCookie() { | 94 ParsedCookie::~ParsedCookie() { |
| 76 } | 95 } |
| 77 | 96 |
| 78 // Returns true if |c| occurs in |chars| | 97 // Returns true if |c| occurs in |chars| |
| 79 // TODO(erikwright): maybe make this take an iterator, could check for end also? | 98 // TODO(erikwright): maybe make this take an iterator, could check for end also? |
| 80 static inline bool CharIsA(const char c, const char* chars) { | 99 static inline bool CharIsA(const char c, const char* chars) { |
|
erikwright (departed)
2012/07/12 15:50:57
I guess all of these should go into an anonymous n
battre
2012/07/12 18:03:10
Done.
| |
| 81 return strchr(chars, c) != NULL; | 100 return strchr(chars, c) != NULL; |
| 82 } | 101 } |
| 83 // Seek the iterator to the first occurrence of a character in |chars|. | 102 // Seek the iterator to the first occurrence of a character in |chars|. |
| 84 // Returns true if it hit the end, false otherwise. | 103 // Returns true if it hit the end, false otherwise. |
| 85 static inline bool SeekTo(std::string::const_iterator* it, | 104 static inline bool SeekTo(std::string::const_iterator* it, |
| 86 const std::string::const_iterator& end, | 105 const std::string::const_iterator& end, |
| 87 const char* chars) { | 106 const char* chars) { |
| 88 for (; *it != end && !CharIsA(**it, chars); ++(*it)) {} | 107 for (; *it != end && !CharIsA(**it, chars); ++(*it)) {} |
| 89 return *it == end; | 108 return *it == end; |
| 90 } | 109 } |
| 91 // Seek the iterator to the first occurrence of a character not in |chars|. | 110 // Seek the iterator to the first occurrence of a character not in |chars|. |
| 92 // Returns true if it hit the end, false otherwise. | 111 // Returns true if it hit the end, false otherwise. |
| 93 static inline bool SeekPast(std::string::const_iterator* it, | 112 static inline bool SeekPast(std::string::const_iterator* it, |
| 94 const std::string::const_iterator& end, | 113 const std::string::const_iterator& end, |
| 95 const char* chars) { | 114 const char* chars) { |
| 96 for (; *it != end && CharIsA(**it, chars); ++(*it)) {} | 115 for (; *it != end && CharIsA(**it, chars); ++(*it)) {} |
| 97 return *it == end; | 116 return *it == end; |
| 98 } | 117 } |
| 99 static inline bool SeekBackPast(std::string::const_iterator* it, | 118 static inline bool SeekBackPast(std::string::const_iterator* it, |
| 100 const std::string::const_iterator& end, | 119 const std::string::const_iterator& end, |
| 101 const char* chars) { | 120 const char* chars) { |
| 102 for (; *it != end && CharIsA(**it, chars); --(*it)) {} | 121 for (; *it != end && CharIsA(**it, chars); --(*it)) {} |
| 103 return *it == end; | 122 return *it == end; |
| 104 } | 123 } |
| 105 | 124 |
| 106 const char ParsedCookie::kTerminator[] = "\n\r\0"; | 125 // Validate whether |value| is a valid token according to [RFC2616], |
| 107 const int ParsedCookie::kTerminatorLen = sizeof(kTerminator) - 1; | 126 // Section 2.2. |
| 108 const char ParsedCookie::kWhitespace[] = " \t"; | 127 static bool IsValidToken(const std::string& value) { |
| 109 const char ParsedCookie::kValueSeparator[] = ";"; | 128 if (value.empty()) |
| 110 const char ParsedCookie::kTokenSeparator[] = ";="; | 129 return false; |
| 111 | 130 |
| 112 // Create a cookie-line for the cookie. For debugging only! | 131 // Check that |value| has no separators. |
| 113 // If we want to use this for something more than debugging, we | 132 std::string separators = "()<>@,;:\\\"/[]?={} \t"; |
| 114 // should rewrite it better... | 133 if (value.find_first_of(separators) != std::string::npos) |
| 115 std::string ParsedCookie::DebugString() const { | 134 return false; |
| 135 | |
| 136 // Check that |value| has no CTLs. | |
| 137 for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { | |
| 138 if ((*i >= 0 && *i <= 31) || *i >= 127) | |
| 139 return false; | |
| 140 } | |
| 141 | |
| 142 return true; | |
| 143 } | |
| 144 | |
| 145 bool ParsedCookie::SetName(const std::string& name) { | |
| 146 if (!IsValidToken(name)) | |
| 147 return false; | |
| 148 if (pairs_.empty()) | |
| 149 pairs_.push_back(std::make_pair("", "")); | |
| 150 pairs_[0].first = name; | |
| 151 is_valid_ = true; | |
| 152 return true; | |
| 153 } | |
| 154 | |
| 155 // Validate value, which may be according to RFC 6265 | |
| 156 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | |
| 157 // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E | |
| 158 // ; US-ASCII characters excluding CTLs, | |
| 159 // ; whitespace DQUOTE, comma, semicolon, | |
| 160 // ; and backslash | |
| 161 static bool IsValidCookieValue(const std::string& value) { | |
| 162 // Number of characters to skip in validation at beginning and end of string. | |
| 163 size_t skip = 0; | |
| 164 if (value.size() >= 2 && *value.begin() == '"' && *(value.end()-1) == '"') | |
| 165 skip = 1; | |
| 166 for (std::string::const_iterator i = value.begin() + skip; | |
| 167 i != value.end() - skip; ++i) { | |
| 168 bool valid_octet = | |
| 169 (*i == 0x21 || | |
| 170 (*i >= 0x23 && *i <= 0x2B) || | |
| 171 (*i >= 0x2D && *i <= 0x3A) || | |
| 172 (*i >= 0x3C && *i <= 0x5B) || | |
| 173 (*i >= 0x5D && *i <= 0x7E)); | |
| 174 if (!valid_octet) | |
| 175 return false; | |
| 176 } | |
| 177 return true; | |
| 178 } | |
| 179 | |
| 180 bool ParsedCookie::SetValue(const std::string& value) { | |
| 181 if (!IsValidCookieValue(value)) | |
| 182 return false; | |
| 183 if (pairs_.empty()) | |
| 184 pairs_.push_back(std::make_pair("", "")); | |
| 185 pairs_[0].second = value; | |
| 186 is_valid_ = true; | |
| 187 return true; | |
| 188 } | |
| 189 | |
| 190 bool ParsedCookie::SetPath(const std::string& path) { | |
| 191 if (path.empty()) | |
| 192 return ClearAttributePair(path_index_); | |
| 193 else | |
| 194 return SetAttributePair(&path_index_, kPathTokenName, path); | |
| 195 } | |
| 196 | |
| 197 bool ParsedCookie::SetDomain(const std::string& domain) { | |
| 198 if (domain.empty()) | |
| 199 return ClearAttributePair(domain_index_); | |
| 200 else | |
| 201 return SetAttributePair(&domain_index_, kDomainTokenName, domain); | |
| 202 } | |
| 203 | |
| 204 bool ParsedCookie::SetMACKey(const std::string& mac_key) { | |
| 205 if (mac_key.empty()) | |
| 206 return ClearAttributePair(mac_key_index_); | |
| 207 else | |
| 208 return SetAttributePair(&mac_key_index_, kMACKeyTokenName, mac_key); | |
| 209 } | |
| 210 | |
| 211 bool ParsedCookie::SetMACAlgorithm(const std::string& mac_algorithm) { | |
| 212 if (mac_algorithm.empty()) { | |
| 213 return ClearAttributePair(mac_algorithm_index_); | |
| 214 } else { | |
| 215 return SetAttributePair(&mac_algorithm_index_, kMACAlgorithmTokenName, | |
| 216 mac_algorithm); | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 bool ParsedCookie::SetExpires(const std::string& expires) { | |
| 221 if (expires.empty()) | |
| 222 return ClearAttributePair(expires_index_); | |
| 223 else | |
| 224 return SetAttributePair(&expires_index_, kExpiresTokenName, expires); | |
| 225 } | |
| 226 | |
| 227 bool ParsedCookie::SetMaxAge(const std::string& maxage) { | |
| 228 if (maxage.empty()) | |
| 229 return ClearAttributePair(maxage_index_); | |
| 230 else | |
| 231 return SetAttributePair(&maxage_index_, kMaxAgeTokenName, maxage); | |
| 232 } | |
| 233 | |
| 234 bool ParsedCookie::SetIsSecure(bool is_secure) { | |
| 235 if (is_secure) | |
| 236 return SetAttributePair(&secure_index_, kSecureTokenName, ""); | |
| 237 else | |
| 238 return ClearAttributePair(secure_index_); | |
| 239 } | |
| 240 | |
| 241 bool ParsedCookie::SetIsHttpOnly(bool is_http_only) { | |
| 242 if (is_http_only) | |
| 243 return SetAttributePair(&httponly_index_, kHttpOnlyTokenName, ""); | |
| 244 else | |
| 245 return ClearAttributePair(httponly_index_); | |
| 246 } | |
| 247 | |
| 248 std::string ParsedCookie::ToCookieLine() const { | |
| 116 std::string out; | 249 std::string out; |
| 117 for (PairList::const_iterator it = pairs_.begin(); | 250 for (PairList::const_iterator it = pairs_.begin(); |
| 118 it != pairs_.end(); ++it) { | 251 it != pairs_.end(); ++it) { |
| 252 if (!out.empty()) | |
| 253 out.append("; "); | |
| 119 out.append(it->first); | 254 out.append(it->first); |
| 120 out.append("="); | 255 if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName) { |
| 121 out.append(it->second); | 256 out.append("="); |
| 122 out.append("; "); | 257 out.append(it->second); |
| 258 } | |
| 123 } | 259 } |
| 124 return out; | 260 return out; |
| 125 } | 261 } |
| 126 | 262 |
| 127 std::string::const_iterator ParsedCookie::FindFirstTerminator( | 263 std::string::const_iterator ParsedCookie::FindFirstTerminator( |
| 128 const std::string& s) { | 264 const std::string& s) { |
| 129 std::string::const_iterator end = s.end(); | 265 std::string::const_iterator end = s.end(); |
| 130 size_t term_pos = | 266 size_t term_pos = |
| 131 s.find_first_of(std::string(kTerminator, kTerminatorLen)); | 267 s.find_first_of(std::string(kTerminator, kTerminatorLen)); |
| 132 if (term_pos != std::string::npos) { | 268 if (term_pos != std::string::npos) { |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 272 pairs_.push_back(pair); | 408 pairs_.push_back(pair); |
| 273 | 409 |
| 274 // We've processed a token/value pair, we're either at the end of | 410 // 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. | 411 // the string or a ValueSeparator like ';', which we want to skip. |
| 276 if (it != end) | 412 if (it != end) |
| 277 ++it; | 413 ++it; |
| 278 } | 414 } |
| 279 } | 415 } |
| 280 | 416 |
| 281 void ParsedCookie::SetupAttributes() { | 417 void ParsedCookie::SetupAttributes() { |
| 282 static const char kPathTokenName[] = "path"; | |
| 283 static const char kDomainTokenName[] = "domain"; | |
| 284 static const char kMACKeyTokenName[] = "mac-key"; | |
| 285 static const char kMACAlgorithmTokenName[] = "mac-algorithm"; | |
| 286 static const char kExpiresTokenName[] = "expires"; | |
| 287 static const char kMaxAgeTokenName[] = "max-age"; | |
| 288 static const char kSecureTokenName[] = "secure"; | |
| 289 static const char kHttpOnlyTokenName[] = "httponly"; | |
| 290 | |
| 291 // We skip over the first token/value, the user supplied one. | 418 // We skip over the first token/value, the user supplied one. |
| 292 for (size_t i = 1; i < pairs_.size(); ++i) { | 419 for (size_t i = 1; i < pairs_.size(); ++i) { |
| 293 if (pairs_[i].first == kPathTokenName) { | 420 if (pairs_[i].first == kPathTokenName) { |
| 294 path_index_ = i; | 421 path_index_ = i; |
| 295 } else if (pairs_[i].first == kDomainTokenName) { | 422 } else if (pairs_[i].first == kDomainTokenName) { |
| 296 domain_index_ = i; | 423 domain_index_ = i; |
| 297 } else if (pairs_[i].first == kMACKeyTokenName) { | 424 } else if (pairs_[i].first == kMACKeyTokenName) { |
| 298 mac_key_index_ = i; | 425 mac_key_index_ = i; |
| 299 } else if (pairs_[i].first == kMACAlgorithmTokenName) { | 426 } else if (pairs_[i].first == kMACAlgorithmTokenName) { |
| 300 mac_algorithm_index_ = i; | 427 mac_algorithm_index_ = i; |
| 301 } else if (pairs_[i].first == kExpiresTokenName) { | 428 } else if (pairs_[i].first == kExpiresTokenName) { |
| 302 expires_index_ = i; | 429 expires_index_ = i; |
| 303 } else if (pairs_[i].first == kMaxAgeTokenName) { | 430 } else if (pairs_[i].first == kMaxAgeTokenName) { |
| 304 maxage_index_ = i; | 431 maxage_index_ = i; |
| 305 } else if (pairs_[i].first == kSecureTokenName) { | 432 } else if (pairs_[i].first == kSecureTokenName) { |
| 306 secure_index_ = i; | 433 secure_index_ = i; |
| 307 } else if (pairs_[i].first == kHttpOnlyTokenName) { | 434 } else if (pairs_[i].first == kHttpOnlyTokenName) { |
| 308 httponly_index_ = i; | 435 httponly_index_ = i; |
| 309 } else { | 436 } else { |
| 310 /* some attribute we don't know or don't care about. */ | 437 /* some attribute we don't know or don't care about. */ |
| 311 } | 438 } |
| 312 } | 439 } |
| 313 } | 440 } |
| 314 | 441 |
| 442 static bool IsValidCookieAttributeValue(const std::string& value) { | |
| 443 // The greatest common denominator of cookie attribute values is | |
| 444 // <any CHAR except CTLs or ";"> according to RFC 6265. | |
| 445 for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { | |
| 446 if ((*i >= 0 && *i <= 31) || *i == ';') | |
| 447 return false; | |
| 448 } | |
| 449 return true; | |
| 450 } | |
| 451 | |
| 452 bool ParsedCookie::SetAttributePair(size_t* index, | |
| 453 const std::string& key, | |
| 454 const std::string& value) { | |
| 455 if (!IsValidToken(key) || !IsValidCookieAttributeValue(value)) | |
| 456 return false; | |
| 457 if (*index) { | |
| 458 pairs_[*index].second = value; | |
| 459 } else { | |
| 460 // Reserve first spot for cooie name and value. | |
|
erikwright (departed)
2012/07/12 15:50:57
Seems to me it would be better to fail if there is
battre
2012/07/12 18:03:10
Done.
| |
| 461 if (pairs_.empty()) | |
| 462 pairs_.push_back(std::make_pair("", "")); | |
| 463 pairs_.push_back(std::make_pair(key, value)); | |
| 464 *index = pairs_.size() - 1; | |
| 465 } | |
| 466 is_valid_ = true; | |
| 467 return true; | |
| 468 } | |
| 469 | |
| 470 bool ParsedCookie::ClearAttributePair(size_t index) { | |
| 471 // The first pair (name/value of cookie at pairs_[0]) cannot be cleared. | |
| 472 // Cookie attributes that don't have a value at the moment, are represented | |
| 473 // with an index being equal to 0. | |
| 474 if (index == 0) | |
| 475 return true; | |
| 476 | |
| 477 size_t* indexes[] = {&path_index_, &domain_index_, &mac_key_index_, | |
| 478 &mac_algorithm_index_, &expires_index_, &maxage_index_, &secure_index_, | |
| 479 &httponly_index_}; | |
| 480 for (size_t i = 0; i < arraysize(indexes); ++i) { | |
| 481 if (*indexes[i] == index) | |
| 482 *indexes[i] = 0; | |
| 483 else if (*indexes[i] > index) | |
| 484 --*indexes[i]; | |
| 485 } | |
| 486 pairs_.erase(pairs_.begin() + index); | |
| 487 is_valid_ = pairs_.empty(); | |
|
erikwright (departed)
2012/07/12 15:50:57
It seems that is_valid_ can never be mutated by th
battre
2012/07/12 18:03:10
I have redefined IsValid() as you have suggested.
| |
| 488 return true; | |
|
erikwright (departed)
2012/07/12 15:50:57
If you won't ever return false, change return type
battre
2012/07/12 18:03:10
Done, even thought this comes at the cost of makin
| |
| 489 } | |
| 490 | |
| 315 } // namespace | 491 } // namespace |
| OLD | NEW |