Index: net/cookies/parsed_cookie.cc |
diff --git a/net/cookies/parsed_cookie.cc b/net/cookies/parsed_cookie.cc |
index f9af3c9fa94046bc9c6f6e4d8b27209a2508c38a..06cf1d24fed163740d899dcf5b784af29db1036a 100644 |
--- a/net/cookies/parsed_cookie.cc |
+++ b/net/cookies/parsed_cookie.cc |
@@ -47,11 +47,112 @@ |
#include "base/logging.h" |
#include "base/string_util.h" |
+namespace { |
+ |
+const char kPathTokenName[] = "path"; |
+const char kDomainTokenName[] = "domain"; |
+const char kMACKeyTokenName[] = "mac-key"; |
+const char kMACAlgorithmTokenName[] = "mac-algorithm"; |
+const char kExpiresTokenName[] = "expires"; |
+const char kMaxAgeTokenName[] = "max-age"; |
+const char kSecureTokenName[] = "secure"; |
+const char kHttpOnlyTokenName[] = "httponly"; |
+ |
+const char kTerminator[] = "\n\r\0"; |
+const int kTerminatorLen = sizeof(kTerminator) - 1; |
+const char kWhitespace[] = " \t"; |
+const char kValueSeparator[] = ";"; |
+const char kTokenSeparator[] = ";="; |
+ |
+// Returns true if |c| occurs in |chars| |
+// TODO(erikwright): maybe make this take an iterator, could check for end also? |
+inline bool CharIsA(const char c, const char* chars) { |
+ return strchr(chars, c) != NULL; |
+} |
+// Seek the iterator to the first occurrence of a character in |chars|. |
+// Returns true if it hit the end, false otherwise. |
+inline bool SeekTo(std::string::const_iterator* it, |
+ const std::string::const_iterator& end, |
+ const char* chars) { |
+ for (; *it != end && !CharIsA(**it, chars); ++(*it)) {} |
+ return *it == end; |
+} |
+// Seek the iterator to the first occurrence of a character not in |chars|. |
+// Returns true if it hit the end, false otherwise. |
+inline bool SeekPast(std::string::const_iterator* it, |
+ const std::string::const_iterator& end, |
+ const char* chars) { |
+ for (; *it != end && CharIsA(**it, chars); ++(*it)) {} |
+ return *it == end; |
+} |
+inline bool SeekBackPast(std::string::const_iterator* it, |
+ const std::string::const_iterator& end, |
+ const char* chars) { |
+ for (; *it != end && CharIsA(**it, chars); --(*it)) {} |
+ return *it == end; |
+} |
+ |
+// Validate whether |value| is a valid token according to [RFC2616], |
+// Section 2.2. |
+bool IsValidToken(const std::string& value) { |
+ if (value.empty()) |
+ return false; |
+ |
+ // Check that |value| has no separators. |
+ std::string separators = "()<>@,;:\\\"/[]?={} \t"; |
+ if (value.find_first_of(separators) != std::string::npos) |
+ return false; |
+ |
+ // Check that |value| has no CTLs. |
+ for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { |
+ if ((*i >= 0 && *i <= 31) || *i >= 127) |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+// Validate value, which may be according to RFC 6265 |
+// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) |
+// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E |
+// ; US-ASCII characters excluding CTLs, |
+// ; whitespace DQUOTE, comma, semicolon, |
+// ; and backslash |
+bool IsValidCookieValue(const std::string& value) { |
+ // Number of characters to skip in validation at beginning and end of string. |
+ size_t skip = 0; |
+ if (value.size() >= 2 && *value.begin() == '"' && *(value.end()-1) == '"') |
+ skip = 1; |
+ for (std::string::const_iterator i = value.begin() + skip; |
+ i != value.end() - skip; ++i) { |
+ bool valid_octet = |
+ (*i == 0x21 || |
+ (*i >= 0x23 && *i <= 0x2B) || |
+ (*i >= 0x2D && *i <= 0x3A) || |
+ (*i >= 0x3C && *i <= 0x5B) || |
+ (*i >= 0x5D && *i <= 0x7E)); |
+ if (!valid_octet) |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool IsValidCookieAttributeValue(const std::string& value) { |
+ // The greatest common denominator of cookie attribute values is |
+ // <any CHAR except CTLs or ";"> according to RFC 6265. |
+ for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { |
+ if ((*i >= 0 && *i <= 31) || *i == ';') |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+} // namespace |
+ |
namespace net { |
ParsedCookie::ParsedCookie(const std::string& cookie_line) |
- : is_valid_(false), |
- path_index_(0), |
+ : path_index_(0), |
domain_index_(0), |
mac_key_index_(0), |
mac_algorithm_index_(0), |
@@ -66,60 +167,119 @@ ParsedCookie::ParsedCookie(const std::string& cookie_line) |
} |
ParseTokenValuePairs(cookie_line); |
- if (!pairs_.empty()) { |
- is_valid_ = true; |
+ if (!pairs_.empty()) |
SetupAttributes(); |
- } |
} |
ParsedCookie::~ParsedCookie() { |
} |
-// Returns true if |c| occurs in |chars| |
-// TODO(erikwright): maybe make this take an iterator, could check for end also? |
-static inline bool CharIsA(const char c, const char* chars) { |
- return strchr(chars, c) != NULL; |
+bool ParsedCookie::IsValid() const { |
+ return !pairs_.empty(); |
} |
-// Seek the iterator to the first occurrence of a character in |chars|. |
-// Returns true if it hit the end, false otherwise. |
-static inline bool SeekTo(std::string::const_iterator* it, |
- const std::string::const_iterator& end, |
- const char* chars) { |
- for (; *it != end && !CharIsA(**it, chars); ++(*it)) {} |
- return *it == end; |
+ |
+bool ParsedCookie::SetName(const std::string& name) { |
+ if (!IsValidToken(name)) |
+ return false; |
+ if (pairs_.empty()) |
+ pairs_.push_back(std::make_pair("", "")); |
+ pairs_[0].first = name; |
+ return true; |
} |
-// Seek the iterator to the first occurrence of a character not in |chars|. |
-// Returns true if it hit the end, false otherwise. |
-static inline bool SeekPast(std::string::const_iterator* it, |
- const std::string::const_iterator& end, |
- const char* chars) { |
- for (; *it != end && CharIsA(**it, chars); ++(*it)) {} |
- return *it == end; |
+ |
+bool ParsedCookie::SetValue(const std::string& value) { |
+ if (!IsValidCookieValue(value)) |
+ return false; |
+ if (pairs_.empty()) |
+ pairs_.push_back(std::make_pair("", "")); |
+ pairs_[0].second = value; |
+ return true; |
} |
-static inline bool SeekBackPast(std::string::const_iterator* it, |
- const std::string::const_iterator& end, |
- const char* chars) { |
- for (; *it != end && CharIsA(**it, chars); --(*it)) {} |
- return *it == end; |
+ |
+bool ParsedCookie::SetPath(const std::string& path) { |
erikwright (departed)
2012/07/18 17:20:32
If you're concerned about these being ugly/repetit
|
+ if (path.empty()) { |
+ ClearAttributePair(path_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&path_index_, kPathTokenName, path); |
+ } |
+} |
+ |
+bool ParsedCookie::SetDomain(const std::string& domain) { |
+ if (domain.empty()) { |
+ ClearAttributePair(domain_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&domain_index_, kDomainTokenName, domain); |
+ } |
+} |
+ |
+bool ParsedCookie::SetMACKey(const std::string& mac_key) { |
+ if (mac_key.empty()) { |
+ ClearAttributePair(mac_key_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&mac_key_index_, kMACKeyTokenName, mac_key); |
+ } |
+} |
+ |
+bool ParsedCookie::SetMACAlgorithm(const std::string& mac_algorithm) { |
+ if (mac_algorithm.empty()) { |
+ ClearAttributePair(mac_algorithm_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&mac_algorithm_index_, kMACAlgorithmTokenName, |
+ mac_algorithm); |
+ } |
} |
-const char ParsedCookie::kTerminator[] = "\n\r\0"; |
-const int ParsedCookie::kTerminatorLen = sizeof(kTerminator) - 1; |
-const char ParsedCookie::kWhitespace[] = " \t"; |
-const char ParsedCookie::kValueSeparator[] = ";"; |
-const char ParsedCookie::kTokenSeparator[] = ";="; |
+bool ParsedCookie::SetExpires(const std::string& expires) { |
+ if (expires.empty()) { |
+ ClearAttributePair(expires_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&expires_index_, kExpiresTokenName, expires); |
+ } |
+} |
+ |
+bool ParsedCookie::SetMaxAge(const std::string& maxage) { |
+ if (maxage.empty()) { |
+ ClearAttributePair(maxage_index_); |
+ return true; |
+ } else { |
+ return SetAttributePair(&maxage_index_, kMaxAgeTokenName, maxage); |
+ } |
+} |
+ |
+bool ParsedCookie::SetIsSecure(bool is_secure) { |
+ if (is_secure) { |
+ return SetAttributePair(&secure_index_, kSecureTokenName, ""); |
+ } else { |
+ ClearAttributePair(secure_index_); |
+ return true; |
+ } |
+} |
+ |
+bool ParsedCookie::SetIsHttpOnly(bool is_http_only) { |
+ if (is_http_only) { |
+ return SetAttributePair(&httponly_index_, kHttpOnlyTokenName, ""); |
+ } else { |
+ ClearAttributePair(httponly_index_); |
+ return true; |
+ } |
+} |
-// Create a cookie-line for the cookie. For debugging only! |
-// If we want to use this for something more than debugging, we |
-// should rewrite it better... |
-std::string ParsedCookie::DebugString() const { |
+std::string ParsedCookie::ToCookieLine() const { |
std::string out; |
for (PairList::const_iterator it = pairs_.begin(); |
it != pairs_.end(); ++it) { |
+ if (!out.empty()) |
+ out.append("; "); |
out.append(it->first); |
- out.append("="); |
- out.append(it->second); |
- out.append("; "); |
+ if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName) { |
+ out.append("="); |
+ out.append(it->second); |
+ } |
} |
return out; |
} |
@@ -279,15 +439,6 @@ void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line) { |
} |
void ParsedCookie::SetupAttributes() { |
- static const char kPathTokenName[] = "path"; |
- static const char kDomainTokenName[] = "domain"; |
- static const char kMACKeyTokenName[] = "mac-key"; |
- static const char kMACAlgorithmTokenName[] = "mac-algorithm"; |
- static const char kExpiresTokenName[] = "expires"; |
- static const char kMaxAgeTokenName[] = "max-age"; |
- static const char kSecureTokenName[] = "secure"; |
- static const char kHttpOnlyTokenName[] = "httponly"; |
- |
// We skip over the first token/value, the user supplied one. |
for (size_t i = 1; i < pairs_.size(); ++i) { |
if (pairs_[i].first == kPathTokenName) { |
@@ -311,5 +462,39 @@ void ParsedCookie::SetupAttributes() { |
} |
} |
} |
+bool ParsedCookie::SetAttributePair(size_t* index, |
+ const std::string& key, |
+ const std::string& value) { |
+ if (!IsValidToken(key) || !IsValidCookieAttributeValue(value)) |
+ return false; |
+ if (!IsValid()) |
+ return false; |
+ if (*index) { |
+ pairs_[*index].second = value; |
+ } else { |
+ pairs_.push_back(std::make_pair(key, value)); |
+ *index = pairs_.size() - 1; |
+ } |
+ return true; |
+} |
+ |
+void ParsedCookie::ClearAttributePair(size_t index) { |
+ // The first pair (name/value of cookie at pairs_[0]) cannot be cleared. |
+ // Cookie attributes that don't have a value at the moment, are represented |
+ // with an index being equal to 0. |
+ if (index == 0) |
+ return; |
+ |
+ size_t* indexes[] = {&path_index_, &domain_index_, &mac_key_index_, |
+ &mac_algorithm_index_, &expires_index_, &maxage_index_, &secure_index_, |
+ &httponly_index_}; |
+ for (size_t i = 0; i < arraysize(indexes); ++i) { |
+ if (*indexes[i] == index) |
+ *indexes[i] = 0; |
+ else if (*indexes[i] > index) |
+ --*indexes[i]; |
+ } |
+ pairs_.erase(pairs_.begin() + index); |
+} |
} // namespace |