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) { | |
erikwright (departed)
2012/07/18 17:20:32
If you're concerned about these being ugly/repetit
| |
200 if (path.empty()) { | |
201 ClearAttributePair(path_index_); | |
202 return true; | |
203 } else { | |
204 return SetAttributePair(&path_index_, kPathTokenName, path); | |
205 } | |
206 } | |
207 | |
208 bool ParsedCookie::SetDomain(const std::string& domain) { | |
209 if (domain.empty()) { | |
210 ClearAttributePair(domain_index_); | |
211 return true; | |
212 } else { | |
213 return SetAttributePair(&domain_index_, kDomainTokenName, domain); | |
214 } | |
215 } | |
216 | |
217 bool ParsedCookie::SetMACKey(const std::string& mac_key) { | |
218 if (mac_key.empty()) { | |
219 ClearAttributePair(mac_key_index_); | |
220 return true; | |
221 } else { | |
222 return SetAttributePair(&mac_key_index_, kMACKeyTokenName, mac_key); | |
223 } | |
224 } | |
225 | |
226 bool ParsedCookie::SetMACAlgorithm(const std::string& mac_algorithm) { | |
227 if (mac_algorithm.empty()) { | |
228 ClearAttributePair(mac_algorithm_index_); | |
229 return true; | |
230 } else { | |
231 return SetAttributePair(&mac_algorithm_index_, kMACAlgorithmTokenName, | |
232 mac_algorithm); | |
233 } | |
234 } | |
235 | |
236 bool ParsedCookie::SetExpires(const std::string& expires) { | |
237 if (expires.empty()) { | |
238 ClearAttributePair(expires_index_); | |
239 return true; | |
240 } else { | |
241 return SetAttributePair(&expires_index_, kExpiresTokenName, expires); | |
242 } | |
243 } | |
244 | |
245 bool ParsedCookie::SetMaxAge(const std::string& maxage) { | |
246 if (maxage.empty()) { | |
247 ClearAttributePair(maxage_index_); | |
248 return true; | |
249 } else { | |
250 return SetAttributePair(&maxage_index_, kMaxAgeTokenName, maxage); | |
251 } | |
252 } | |
253 | |
254 bool ParsedCookie::SetIsSecure(bool is_secure) { | |
255 if (is_secure) { | |
256 return SetAttributePair(&secure_index_, kSecureTokenName, ""); | |
257 } else { | |
258 ClearAttributePair(secure_index_); | |
259 return true; | |
260 } | |
261 } | |
262 | |
263 bool ParsedCookie::SetIsHttpOnly(bool is_http_only) { | |
264 if (is_http_only) { | |
265 return SetAttributePair(&httponly_index_, kHttpOnlyTokenName, ""); | |
266 } else { | |
267 ClearAttributePair(httponly_index_); | |
268 return true; | |
269 } | |
270 } | |
271 | |
272 std::string ParsedCookie::ToCookieLine() const { | |
116 std::string out; | 273 std::string out; |
117 for (PairList::const_iterator it = pairs_.begin(); | 274 for (PairList::const_iterator it = pairs_.begin(); |
118 it != pairs_.end(); ++it) { | 275 it != pairs_.end(); ++it) { |
276 if (!out.empty()) | |
277 out.append("; "); | |
119 out.append(it->first); | 278 out.append(it->first); |
120 out.append("="); | 279 if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName) { |
121 out.append(it->second); | 280 out.append("="); |
122 out.append("; "); | 281 out.append(it->second); |
282 } | |
123 } | 283 } |
124 return out; | 284 return out; |
125 } | 285 } |
126 | 286 |
127 std::string::const_iterator ParsedCookie::FindFirstTerminator( | 287 std::string::const_iterator ParsedCookie::FindFirstTerminator( |
128 const std::string& s) { | 288 const std::string& s) { |
129 std::string::const_iterator end = s.end(); | 289 std::string::const_iterator end = s.end(); |
130 size_t term_pos = | 290 size_t term_pos = |
131 s.find_first_of(std::string(kTerminator, kTerminatorLen)); | 291 s.find_first_of(std::string(kTerminator, kTerminatorLen)); |
132 if (term_pos != std::string::npos) { | 292 if (term_pos != std::string::npos) { |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
272 pairs_.push_back(pair); | 432 pairs_.push_back(pair); |
273 | 433 |
274 // We've processed a token/value pair, we're either at the end of | 434 // 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. | 435 // the string or a ValueSeparator like ';', which we want to skip. |
276 if (it != end) | 436 if (it != end) |
277 ++it; | 437 ++it; |
278 } | 438 } |
279 } | 439 } |
280 | 440 |
281 void ParsedCookie::SetupAttributes() { | 441 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. | 442 // We skip over the first token/value, the user supplied one. |
292 for (size_t i = 1; i < pairs_.size(); ++i) { | 443 for (size_t i = 1; i < pairs_.size(); ++i) { |
293 if (pairs_[i].first == kPathTokenName) { | 444 if (pairs_[i].first == kPathTokenName) { |
294 path_index_ = i; | 445 path_index_ = i; |
295 } else if (pairs_[i].first == kDomainTokenName) { | 446 } else if (pairs_[i].first == kDomainTokenName) { |
296 domain_index_ = i; | 447 domain_index_ = i; |
297 } else if (pairs_[i].first == kMACKeyTokenName) { | 448 } else if (pairs_[i].first == kMACKeyTokenName) { |
298 mac_key_index_ = i; | 449 mac_key_index_ = i; |
299 } else if (pairs_[i].first == kMACAlgorithmTokenName) { | 450 } else if (pairs_[i].first == kMACAlgorithmTokenName) { |
300 mac_algorithm_index_ = i; | 451 mac_algorithm_index_ = i; |
301 } else if (pairs_[i].first == kExpiresTokenName) { | 452 } else if (pairs_[i].first == kExpiresTokenName) { |
302 expires_index_ = i; | 453 expires_index_ = i; |
303 } else if (pairs_[i].first == kMaxAgeTokenName) { | 454 } else if (pairs_[i].first == kMaxAgeTokenName) { |
304 maxage_index_ = i; | 455 maxage_index_ = i; |
305 } else if (pairs_[i].first == kSecureTokenName) { | 456 } else if (pairs_[i].first == kSecureTokenName) { |
306 secure_index_ = i; | 457 secure_index_ = i; |
307 } else if (pairs_[i].first == kHttpOnlyTokenName) { | 458 } else if (pairs_[i].first == kHttpOnlyTokenName) { |
308 httponly_index_ = i; | 459 httponly_index_ = i; |
309 } else { | 460 } else { |
310 /* some attribute we don't know or don't care about. */ | 461 /* some attribute we don't know or don't care about. */ |
311 } | 462 } |
312 } | 463 } |
313 } | 464 } |
465 bool ParsedCookie::SetAttributePair(size_t* index, | |
466 const std::string& key, | |
467 const std::string& value) { | |
468 if (!IsValidToken(key) || !IsValidCookieAttributeValue(value)) | |
469 return false; | |
470 if (!IsValid()) | |
471 return false; | |
472 if (*index) { | |
473 pairs_[*index].second = value; | |
474 } else { | |
475 pairs_.push_back(std::make_pair(key, value)); | |
476 *index = pairs_.size() - 1; | |
477 } | |
478 return true; | |
479 } | |
480 | |
481 void ParsedCookie::ClearAttributePair(size_t index) { | |
482 // The first pair (name/value of cookie at pairs_[0]) cannot be cleared. | |
483 // Cookie attributes that don't have a value at the moment, are represented | |
484 // with an index being equal to 0. | |
485 if (index == 0) | |
486 return; | |
487 | |
488 size_t* indexes[] = {&path_index_, &domain_index_, &mac_key_index_, | |
489 &mac_algorithm_index_, &expires_index_, &maxage_index_, &secure_index_, | |
490 &httponly_index_}; | |
491 for (size_t i = 0; i < arraysize(indexes); ++i) { | |
492 if (*indexes[i] == index) | |
493 *indexes[i] = 0; | |
494 else if (*indexes[i] > index) | |
495 --*indexes[i]; | |
496 } | |
497 pairs_.erase(pairs_.begin() + index); | |
498 } | |
314 | 499 |
315 } // namespace | 500 } // namespace |
OLD | NEW |