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