summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-02 13:37:41 +0000
committerbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-02 13:37:41 +0000
commit64527a5d86547911117883a9a442c21830b37260 (patch)
tree320bbe3ed9a18ecdd639e93c708e934ddb8d3367 /net
parent4f05741acc8aca31a1ad7d0a2793cac9cf4e37a6 (diff)
downloadchromium_src-64527a5d86547911117883a9a442c21830b37260.zip
chromium_src-64527a5d86547911117883a9a442c21830b37260.tar.gz
chromium_src-64527a5d86547911117883a9a442c21830b37260.tar.bz2
Add a mutable version of CookieMonster::ParsedCookie
This CL implements CookieMonster::MutableParsedCookie which is derived from CookieMonster::ParsedCookie. This allows parsing a cookie and modifying it afterwards. BUG=112155 TEST=no Review URL: https://chromiumcodereview.appspot.com/10697035 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149622 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/cookies/parsed_cookie.cc266
-rw-r--r--net/cookies/parsed_cookie.h54
-rw-r--r--net/cookies/parsed_cookie_unittest.cc138
3 files changed, 399 insertions, 59 deletions
diff --git a/net/cookies/parsed_cookie.cc b/net/cookies/parsed_cookie.cc
index f9af3c9..704de04 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,79 @@ 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) {
+ return SetString(&path_index_, kPathTokenName, path);
}
-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::SetDomain(const std::string& domain) {
+ return SetString(&domain_index_, kDomainTokenName, domain);
+}
+
+bool ParsedCookie::SetMACKey(const std::string& mac_key) {
+ return SetString(&mac_key_index_, kMACKeyTokenName, mac_key);
+}
+
+bool ParsedCookie::SetMACAlgorithm(const std::string& mac_algorithm) {
+ return SetString(&mac_algorithm_index_, kMACAlgorithmTokenName,
+ mac_algorithm);
+}
-// 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 {
+bool ParsedCookie::SetExpires(const std::string& expires) {
+ return SetString(&expires_index_, kExpiresTokenName, expires);
+}
+
+bool ParsedCookie::SetMaxAge(const std::string& maxage) {
+ return SetString(&maxage_index_, kMaxAgeTokenName, maxage);
+}
+
+bool ParsedCookie::SetIsSecure(bool is_secure) {
+ return SetBool(&secure_index_, kSecureTokenName, is_secure);
+}
+
+bool ParsedCookie::SetIsHttpOnly(bool is_http_only) {
+ return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only);
+}
+
+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 +399,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) {
@@ -312,4 +423,61 @@ void ParsedCookie::SetupAttributes() {
}
}
+bool ParsedCookie::SetString(size_t* index,
+ const std::string& key,
+ const std::string& value) {
+ if (value.empty()) {
+ ClearAttributePair(*index);
+ return true;
+ } else {
+ return SetAttributePair(index, key, value);
+ }
+}
+
+bool ParsedCookie::SetBool(size_t* index,
+ const std::string& key,
+ bool value) {
+ if (!value) {
+ ClearAttributePair(*index);
+ return true;
+ } else {
+ return SetAttributePair(index, key, "");
+ }
+}
+
+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
diff --git a/net/cookies/parsed_cookie.h b/net/cookies/parsed_cookie.h
index 6f8e4ef..694d724 100644
--- a/net/cookies/parsed_cookie.h
+++ b/net/cookies/parsed_cookie.h
@@ -27,8 +27,9 @@ class NET_EXPORT ParsedCookie {
ParsedCookie(const std::string& cookie_line);
~ParsedCookie();
- // You should not call any other methods on the class if !IsValid
- bool IsValid() const { return is_valid_; }
+ // You should not call any other methods except for SetName/SetValue on the
+ // class if !IsValid.
+ bool IsValid() const;
const std::string& Name() const { return pairs_[0].first; }
const std::string& Token() const { return Name(); }
@@ -55,8 +56,24 @@ class NET_EXPORT ParsedCookie {
// "BLAH=hah; path=/; domain=.google.com"
size_t NumberOfAttributes() const { return pairs_.size() - 1; }
- // For debugging only!
- std::string DebugString() const;
+ // These functions set the respective properties of the cookie. If the
+ // parameters are empty, the respective properties are cleared.
+ // The functions return false in case an error occurred.
+ // The cookie needs to be assigned a name/value before setting the other
+ // attributes.
+ bool SetName(const std::string& name);
+ bool SetValue(const std::string& value);
+ bool SetPath(const std::string& path);
+ bool SetDomain(const std::string& domain);
+ bool SetMACKey(const std::string& mac_key);
+ bool SetMACAlgorithm(const std::string& mac_algorithm);
+ bool SetExpires(const std::string& expires);
+ bool SetMaxAge(const std::string& maxage);
+ bool SetIsSecure(bool is_secure);
+ bool SetIsHttpOnly(bool is_http_only);
+
+ // Returns the cookie description as it appears in a HTML response header.
+ std::string ToCookieLine() const;
// Returns an iterator pointing to the first terminator character found in
// the given string.
@@ -87,15 +104,32 @@ class NET_EXPORT ParsedCookie {
static std::string ParseValueString(const std::string& value);
private:
- static const char kTerminator[];
- static const int kTerminatorLen;
- static const char kWhitespace[];
- static const char kValueSeparator[];
- static const char kTokenSeparator[];
-
void ParseTokenValuePairs(const std::string& cookie_line);
void SetupAttributes();
+ // Sets a key/value pair for a cookie. |index| has to point to one of the
+ // |*_index_| fields in ParsedCookie and is updated to the position where
+ // the key/value pair is set in |pairs_|. Accordingly, |key| has to correspond
+ // to the token matching |index|. If |value| contains invalid characters, the
+ // cookie parameter is not changed and the function returns false.
+ // If |value| is empty/false the key/value pair is removed.
+ bool SetString(size_t* index,
+ const std::string& key,
+ const std::string& value);
+ bool SetBool(size_t* index,
+ const std::string& key,
+ bool value);
+
+ // Helper function for SetString and SetBool handling the case that the
+ // key/value pair shall not be removed.
+ bool SetAttributePair(size_t* index,
+ const std::string& key,
+ const std::string& value);
+
+ // Removes the key/value pair from a cookie that is identified by |index|.
+ // |index| refers to a position in |pairs_|.
+ void ClearAttributePair(size_t index);
+
PairList pairs_;
bool is_valid_;
// These will default to 0, but that should never be valid since the
diff --git a/net/cookies/parsed_cookie_unittest.cc b/net/cookies/parsed_cookie_unittest.cc
index 7b6d562..68ead27 100644
--- a/net/cookies/parsed_cookie_unittest.cc
+++ b/net/cookies/parsed_cookie_unittest.cc
@@ -275,4 +275,142 @@ TEST(ParsedCookieTest, ParseTokensAndValues) {
ParsedCookie::ParseValueString("A=B=C;D=E"));
}
+TEST(ParsedCookieTest, SerializeCookieLine) {
+ const char input[] = "ANCUUID=zohNumRKgI0oxyhSsV3Z7D ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; ";
+ const char output[] = "ANCUUID=zohNumRKgI0oxyhSsV3Z7D; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; "
+ "path=/";
+ ParsedCookie pc(input);
+ EXPECT_EQ(output, pc.ToCookieLine());
+}
+
+
+TEST(ParsedCookieTest, SetNameAndValue) {
+ ParsedCookie empty("");
+ EXPECT_FALSE(empty.IsValid());
+ EXPECT_FALSE(empty.SetDomain("foobar.com"));
+ EXPECT_TRUE(empty.SetName("name"));
+ EXPECT_TRUE(empty.SetValue("value"));
+ EXPECT_EQ("name=value", empty.ToCookieLine());
+ EXPECT_TRUE(empty.IsValid());
+
+ // We don't test
+ // ParsedCookie invalid("@foo=bar");
+ // EXPECT_FALSE(invalid.IsValid());
+ // here because we are slightly more tolerant to invalid cookie names and
+ // values that are set by webservers. We only enforce a correct name and
+ // value if set via SetName() and SetValue().
+
+ ParsedCookie pc("name=value");
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set invalid name / value.
+ EXPECT_FALSE(pc.SetName("@foobar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetName(""));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetValue("foo bar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetValue("\"foobar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set valid name / value
+ EXPECT_TRUE(pc.SetName("test"));
+ EXPECT_EQ("test=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_TRUE(pc.SetValue("\"foobar\""));
+ EXPECT_EQ("test=\"foobar\"", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_TRUE(pc.SetValue(""));
+ EXPECT_EQ("test=", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, SetAttributes) {
+ ParsedCookie pc("name=value");
+ EXPECT_TRUE(pc.IsValid());
+
+ // Clear an unset attribute.
+ EXPECT_TRUE(pc.SetDomain(""));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set a string containing an invalid character
+ EXPECT_FALSE(pc.SetDomain("foo;bar"));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set all other attributes and check that they are appended in order.
+ EXPECT_TRUE(pc.SetDomain("domain.com"));
+ EXPECT_TRUE(pc.SetPath("/"));
+ EXPECT_TRUE(pc.SetMACKey("mackey"));
+ EXPECT_TRUE(pc.SetMACAlgorithm("\"macalgorithm\""));
+ EXPECT_TRUE(pc.SetExpires("Sun, 18-Apr-2027 21:06:29 GMT"));
+ EXPECT_TRUE(pc.SetMaxAge("12345"));
+ EXPECT_TRUE(pc.SetIsSecure(true));
+ EXPECT_TRUE(pc.SetIsHttpOnly(true));
+ EXPECT_EQ("name=value; domain=domain.com; path=/; mac-key=mackey; "
+ "mac-algorithm=\"macalgorithm\"; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; "
+ "httponly",
+ pc.ToCookieLine());
+ EXPECT_TRUE(pc.HasDomain());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_TRUE(pc.HasMACKey());
+ EXPECT_TRUE(pc.HasMACAlgorithm());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasMaxAge());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+
+ // Clear one attribute from the middle.
+ EXPECT_TRUE(pc.SetMACAlgorithm(""));
+ EXPECT_TRUE(pc.HasDomain());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_TRUE(pc.HasMACKey());
+ EXPECT_FALSE(pc.HasMACAlgorithm());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasMaxAge());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ("name=value; domain=domain.com; path=/; mac-key=mackey; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; "
+ "httponly",
+ pc.ToCookieLine());
+
+ // Clear the rest and change the name and value.
+ EXPECT_TRUE(pc.SetDomain(""));
+ EXPECT_TRUE(pc.SetPath(""));
+ EXPECT_TRUE(pc.SetMACKey(""));
+ EXPECT_TRUE(pc.SetMACAlgorithm(""));
+ EXPECT_TRUE(pc.SetExpires(""));
+ EXPECT_TRUE(pc.SetMaxAge(""));
+ EXPECT_TRUE(pc.SetIsSecure(false));
+ EXPECT_TRUE(pc.SetIsHttpOnly(false));
+ EXPECT_TRUE(pc.SetName("name2"));
+ EXPECT_TRUE(pc.SetValue("value2"));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasMACKey());
+ EXPECT_FALSE(pc.HasMACAlgorithm());
+ EXPECT_FALSE(pc.HasExpires());
+ EXPECT_FALSE(pc.HasMaxAge());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_FALSE(pc.IsHttpOnly());
+ EXPECT_EQ("name2=value2", pc.ToCookieLine());
+}
+
} // namespace net