summaryrefslogtreecommitdiffstats
path: root/net/cookies
diff options
context:
space:
mode:
authorbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-12 12:21:41 +0000
committerbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-12 12:21:41 +0000
commitebfe3173617e62ccbda071f8ca367a50a5ba4ee0 (patch)
treedc4d1b827721341b8329859e1c2242370c31ea29 /net/cookies
parent5a76f9b471e5ef367280fb92662bdef9f83cfd57 (diff)
downloadchromium_src-ebfe3173617e62ccbda071f8ca367a50a5ba4ee0.zip
chromium_src-ebfe3173617e62ccbda071f8ca367a50a5ba4ee0.tar.gz
chromium_src-ebfe3173617e62ccbda071f8ca367a50a5ba4ee0.tar.bz2
Extract ParsedCookie into a top level class
BUG=137014,112155 TEST=no TBR=sky@chromium.org, rdsmith@chromium.org, jochen@chromium.org Review URL: https://chromiumcodereview.appspot.com/10689158 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@146324 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/cookies')
-rw-r--r--net/cookies/cookie_monster.cc278
-rw-r--r--net/cookies/cookie_monster.h103
-rw-r--r--net/cookies/cookie_monster_perftest.cc5
-rw-r--r--net/cookies/cookie_monster_store_test.cc3
-rw-r--r--net/cookies/cookie_monster_unittest.cc279
-rw-r--r--net/cookies/parsed_cookie.cc315
-rw-r--r--net/cookies/parsed_cookie.h119
-rw-r--r--net/cookies/parsed_cookie_unittest.cc278
8 files changed, 724 insertions, 656 deletions
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index bdac04c..e44ba18 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -62,6 +62,7 @@
#include "googleurl/src/gurl.h"
#include "googleurl/src/url_canon.h"
#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
#include "net/base/registry_controlled_domain.h"
using base::Time;
@@ -190,7 +191,7 @@ struct CookieSignature {
// Determine the cookie domain to use for setting the specified cookie.
bool GetCookieDomain(const GURL& url,
- const CookieMonster::ParsedCookie& pc,
+ const ParsedCookie& pc,
std::string* result) {
std::string domain_string;
if (pc.HasDomain())
@@ -228,16 +229,14 @@ std::string CanonPathWithString(const GURL& url,
return url_path.substr(0, idx);
}
-std::string CanonPath(const GURL& url,
- const CookieMonster::ParsedCookie& pc) {
+std::string CanonPath(const GURL& url, const ParsedCookie& pc) {
std::string path_string;
if (pc.HasPath())
path_string = pc.Path();
return CanonPathWithString(url, path_string);
}
-Time CanonExpiration(const CookieMonster::ParsedCookie& pc,
- const Time& current) {
+Time CanonExpiration(const ParsedCookie& pc, const Time& current) {
// First, try the Max-Age attribute.
uint64 max_age = 0;
if (pc.HasMaxAge() &&
@@ -2335,275 +2334,6 @@ Time CookieMonster::CurrentTime() {
Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1));
}
-CookieMonster::ParsedCookie::ParsedCookie(const std::string& cookie_line)
- : is_valid_(false),
- path_index_(0),
- domain_index_(0),
- mac_key_index_(0),
- mac_algorithm_index_(0),
- expires_index_(0),
- maxage_index_(0),
- secure_index_(0),
- httponly_index_(0) {
-
- if (cookie_line.size() > kMaxCookieSize) {
- VLOG(1) << "Not parsing cookie, too large: " << cookie_line.size();
- return;
- }
-
- ParseTokenValuePairs(cookie_line);
- if (!pairs_.empty()) {
- is_valid_ = true;
- SetupAttributes();
- }
-}
-
-CookieMonster::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;
-}
-// 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;
-}
-// 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;
-}
-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;
-}
-
-const char CookieMonster::ParsedCookie::kTerminator[] = "\n\r\0";
-const int CookieMonster::ParsedCookie::kTerminatorLen =
- sizeof(kTerminator) - 1;
-const char CookieMonster::ParsedCookie::kWhitespace[] = " \t";
-const char CookieMonster::ParsedCookie::kValueSeparator[] = ";";
-const char CookieMonster::ParsedCookie::kTokenSeparator[] = ";=";
-
-// 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 CookieMonster::ParsedCookie::DebugString() const {
- std::string out;
- for (PairList::const_iterator it = pairs_.begin();
- it != pairs_.end(); ++it) {
- out.append(it->first);
- out.append("=");
- out.append(it->second);
- out.append("; ");
- }
- return out;
-}
-
-std::string::const_iterator CookieMonster::ParsedCookie::FindFirstTerminator(
- const std::string& s) {
- std::string::const_iterator end = s.end();
- size_t term_pos =
- s.find_first_of(std::string(kTerminator, kTerminatorLen));
- if (term_pos != std::string::npos) {
- // We found a character we should treat as an end of string.
- end = s.begin() + term_pos;
- }
- return end;
-}
-
-bool CookieMonster::ParsedCookie::ParseToken(
- std::string::const_iterator* it,
- const std::string::const_iterator& end,
- std::string::const_iterator* token_start,
- std::string::const_iterator* token_end) {
- DCHECK(it && token_start && token_end);
- std::string::const_iterator token_real_end;
-
- // Seek past any whitespace before the "token" (the name).
- // token_start should point at the first character in the token
- if (SeekPast(it, end, kWhitespace))
- return false; // No token, whitespace or empty.
- *token_start = *it;
-
- // Seek over the token, to the token separator.
- // token_real_end should point at the token separator, i.e. '='.
- // If it == end after the seek, we probably have a token-value.
- SeekTo(it, end, kTokenSeparator);
- token_real_end = *it;
-
- // Ignore any whitespace between the token and the token separator.
- // token_end should point after the last interesting token character,
- // pointing at either whitespace, or at '=' (and equal to token_real_end).
- if (*it != *token_start) { // We could have an empty token name.
- --(*it); // Go back before the token separator.
- // Skip over any whitespace to the first non-whitespace character.
- SeekBackPast(it, *token_start, kWhitespace);
- // Point after it.
- ++(*it);
- }
- *token_end = *it;
-
- // Seek us back to the end of the token.
- *it = token_real_end;
- return true;
-}
-
-void CookieMonster::ParsedCookie::ParseValue(
- std::string::const_iterator* it,
- const std::string::const_iterator& end,
- std::string::const_iterator* value_start,
- std::string::const_iterator* value_end) {
- DCHECK(it && value_start && value_end);
-
- // Seek past any whitespace that might in-between the token and value.
- SeekPast(it, end, kWhitespace);
- // value_start should point at the first character of the value.
- *value_start = *it;
-
- // Just look for ';' to terminate ('=' allowed).
- // We can hit the end, maybe they didn't terminate.
- SeekTo(it, end, kValueSeparator);
-
- // Will be pointed at the ; seperator or the end.
- *value_end = *it;
-
- // Ignore any unwanted whitespace after the value.
- if (*value_end != *value_start) { // Could have an empty value
- --(*value_end);
- SeekBackPast(value_end, *value_start, kWhitespace);
- ++(*value_end);
- }
-}
-
-std::string CookieMonster::ParsedCookie::ParseTokenString(
- const std::string& token) {
- std::string::const_iterator it = token.begin();
- std::string::const_iterator end = FindFirstTerminator(token);
-
- std::string::const_iterator token_start, token_end;
- if (ParseToken(&it, end, &token_start, &token_end))
- return std::string(token_start, token_end);
- return std::string();
-}
-
-std::string CookieMonster::ParsedCookie::ParseValueString(
- const std::string& value) {
- std::string::const_iterator it = value.begin();
- std::string::const_iterator end = FindFirstTerminator(value);
-
- std::string::const_iterator value_start, value_end;
- ParseValue(&it, end, &value_start, &value_end);
- return std::string(value_start, value_end);
-}
-
-// Parse all token/value pairs and populate pairs_.
-void CookieMonster::ParsedCookie::ParseTokenValuePairs(
- const std::string& cookie_line) {
- pairs_.clear();
-
- // Ok, here we go. We should be expecting to be starting somewhere
- // before the cookie line, not including any header name...
- std::string::const_iterator start = cookie_line.begin();
- std::string::const_iterator it = start;
-
- // TODO(erikwright): Make sure we're stripping \r\n in the network code.
- // Then we can log any unexpected terminators.
- std::string::const_iterator end = FindFirstTerminator(cookie_line);
-
- for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
- TokenValuePair pair;
-
- std::string::const_iterator token_start, token_end;
- if (!ParseToken(&it, end, &token_start, &token_end))
- break;
-
- if (it == end || *it != '=') {
- // We have a token-value, we didn't have any token name.
- if (pair_num == 0) {
- // For the first time around, we want to treat single values
- // as a value with an empty name. (Mozilla bug 169091).
- // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
- // set 2 different cookies, and setting "BBB" will then replace "AAA".
- pair.first = "";
- // Rewind to the beginning of what we thought was the token name,
- // and let it get parsed as a value.
- it = token_start;
- } else {
- // Any not-first attribute we want to treat a value as a
- // name with an empty value... This is so something like
- // "secure;" will get parsed as a Token name, and not a value.
- pair.first = std::string(token_start, token_end);
- }
- } else {
- // We have a TOKEN=VALUE.
- pair.first = std::string(token_start, token_end);
- ++it; // Skip past the '='.
- }
-
- // OK, now try to parse a value.
- std::string::const_iterator value_start, value_end;
- ParseValue(&it, end, &value_start, &value_end);
- // OK, we're finished with a Token/Value.
- pair.second = std::string(value_start, value_end);
-
- // From RFC2109: "Attributes (names) (attr) are case-insensitive."
- if (pair_num != 0)
- StringToLowerASCII(&pair.first);
- pairs_.push_back(pair);
-
- // We've processed a token/value pair, we're either at the end of
- // the string or a ValueSeparator like ';', which we want to skip.
- if (it != end)
- ++it;
- }
-}
-
-void CookieMonster::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) {
- path_index_ = i;
- } else if (pairs_[i].first == kDomainTokenName) {
- domain_index_ = i;
- } else if (pairs_[i].first == kMACKeyTokenName) {
- mac_key_index_ = i;
- } else if (pairs_[i].first == kMACAlgorithmTokenName) {
- mac_algorithm_index_ = i;
- } else if (pairs_[i].first == kExpiresTokenName) {
- expires_index_ = i;
- } else if (pairs_[i].first == kMaxAgeTokenName) {
- maxage_index_ = i;
- } else if (pairs_[i].first == kSecureTokenName) {
- secure_index_ = i;
- } else if (pairs_[i].first == kHttpOnlyTokenName) {
- httponly_index_ = i;
- } else {
- /* some attribute we don't know or don't care about. */
- }
- }
-}
-
CookieMonster::CanonicalCookie::CanonicalCookie()
: secure_(false),
httponly_(false) {
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index 9cad01fa..4561a1c 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -35,6 +35,7 @@ class TimeTicks;
namespace net {
class CookieList;
+class ParsedCookie;
// The cookie monster is the system for storing and retrieving cookies. It has
// an in-memory list of all cookies, and synchronizes non-session cookies to an
@@ -64,7 +65,6 @@ class NET_EXPORT CookieMonster : public CookieStore {
public:
class CanonicalCookie;
class Delegate;
- class ParsedCookie;
class PersistentCookieStore;
// Terminology:
@@ -823,107 +823,6 @@ class CookieMonster::Delegate
virtual ~Delegate() {}
};
-class NET_EXPORT CookieMonster::ParsedCookie {
- public:
- typedef std::pair<std::string, std::string> TokenValuePair;
- typedef std::vector<TokenValuePair> PairList;
-
- // The maximum length of a cookie string we will try to parse
- static const size_t kMaxCookieSize = 4096;
- // The maximum number of Token/Value pairs. Shouldn't have more than 8.
- static const int kMaxPairs = 16;
-
- // Construct from a cookie string like "BLAH=1; path=/; domain=.google.com"
- 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_; }
-
- const std::string& Name() const { return pairs_[0].first; }
- const std::string& Token() const { return Name(); }
- const std::string& Value() const { return pairs_[0].second; }
-
- bool HasPath() const { return path_index_ != 0; }
- const std::string& Path() const { return pairs_[path_index_].second; }
- bool HasDomain() const { return domain_index_ != 0; }
- const std::string& Domain() const { return pairs_[domain_index_].second; }
- bool HasMACKey() const { return mac_key_index_ != 0; }
- const std::string& MACKey() const { return pairs_[mac_key_index_].second; }
- bool HasMACAlgorithm() const { return mac_algorithm_index_ != 0; }
- const std::string& MACAlgorithm() const {
- return pairs_[mac_algorithm_index_].second;
- }
- bool HasExpires() const { return expires_index_ != 0; }
- const std::string& Expires() const { return pairs_[expires_index_].second; }
- bool HasMaxAge() const { return maxage_index_ != 0; }
- const std::string& MaxAge() const { return pairs_[maxage_index_].second; }
- bool IsSecure() const { return secure_index_ != 0; }
- bool IsHttpOnly() const { return httponly_index_ != 0; }
-
- // Returns the number of attributes, for example, returning 2 for:
- // "BLAH=hah; path=/; domain=.google.com"
- size_t NumberOfAttributes() const { return pairs_.size() - 1; }
-
- // For debugging only!
- std::string DebugString() const;
-
- // Returns an iterator pointing to the first terminator character found in
- // the given string.
- static std::string::const_iterator FindFirstTerminator(const std::string& s);
-
- // Given iterators pointing to the beginning and end of a string segment,
- // returns as output arguments token_start and token_end to the start and end
- // positions of a cookie attribute token name parsed from the segment, and
- // updates the segment iterator to point to the next segment to be parsed.
- // If no token is found, the function returns false.
- static bool ParseToken(std::string::const_iterator* it,
- const std::string::const_iterator& end,
- std::string::const_iterator* token_start,
- std::string::const_iterator* token_end);
-
- // Given iterators pointing to the beginning and end of a string segment,
- // returns as output arguments value_start and value_end to the start and end
- // positions of a cookie attribute value parsed from the segment, and updates
- // the segment iterator to point to the next segment to be parsed.
- static void ParseValue(std::string::const_iterator* it,
- const std::string::const_iterator& end,
- std::string::const_iterator* value_start,
- std::string::const_iterator* value_end);
-
- // Same as the above functions, except the input is assumed to contain the
- // desired token/value and nothing else.
- static std::string ParseTokenString(const std::string& token);
- 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();
-
- PairList pairs_;
- bool is_valid_;
- // These will default to 0, but that should never be valid since the
- // 0th index is the user supplied token/value, not an attribute.
- // We're really never going to have more than like 8 attributes, so we
- // could fit these into 3 bits each if we're worried about size...
- size_t path_index_;
- size_t domain_index_;
- size_t mac_key_index_;
- size_t mac_algorithm_index_;
- size_t expires_index_;
- size_t maxage_index_;
- size_t secure_index_;
- size_t httponly_index_;
-
- DISALLOW_COPY_AND_ASSIGN(ParsedCookie);
-};
-
typedef base::RefCountedThreadSafe<CookieMonster::PersistentCookieStore>
RefcountedPersistentCookieStore;
diff --git a/net/cookies/cookie_monster_perftest.cc b/net/cookies/cookie_monster_perftest.cc
index eeab92f..2e8aa27 100644
--- a/net/cookies/cookie_monster_perftest.cc
+++ b/net/cookies/cookie_monster_perftest.cc
@@ -12,6 +12,7 @@
#include "googleurl/src/gurl.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_monster_store_test.h"
+#include "net/cookies/parsed_cookie.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -33,7 +34,7 @@ TEST(ParsedCookieTest, TestParseCookies) {
std::string cookie(kCookieLine);
PerfTimeLogger timer("Parsed_cookie_parse_cookies");
for (int i = 0; i < kNumCookies; ++i) {
- CookieMonster::ParsedCookie pc(cookie);
+ ParsedCookie pc(cookie);
EXPECT_TRUE(pc.IsValid());
}
timer.Done();
@@ -44,7 +45,7 @@ TEST(ParsedCookieTest, TestParseBigCookies) {
cookie += kCookieLine;
PerfTimeLogger timer("Parsed_cookie_parse_big_cookies");
for (int i = 0; i < kNumCookies; ++i) {
- CookieMonster::ParsedCookie pc(cookie);
+ ParsedCookie pc(cookie);
EXPECT_TRUE(pc.IsValid());
}
timer.Done();
diff --git a/net/cookies/cookie_monster_store_test.cc b/net/cookies/cookie_monster_store_test.cc
index ff5bb8a..6724d9d 100644
--- a/net/cookies/cookie_monster_store_test.cc
+++ b/net/cookies/cookie_monster_store_test.cc
@@ -9,6 +9,7 @@
#include "base/stringprintf.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
+#include "net/cookies/parsed_cookie.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -101,7 +102,7 @@ CookieMonster::CanonicalCookie BuildCanonicalCookie(
const base::Time& creation_time) {
// Parse the cookie line.
- CookieMonster::ParsedCookie pc(cookie_line);
+ ParsedCookie pc(cookie_line);
EXPECT_TRUE(pc.IsValid());
// This helper is simplistic in interpreting a parsed cookie, in order to
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 4f76439..c69c805 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -21,6 +21,7 @@
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_monster_store_test.h" // For CookieStore mock
#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -75,281 +76,6 @@ class GetCookieListCallback : public CookieCallback {
CookieList cookies_;
};
-class ParsedCookieTest : public testing::Test { };
-
-} // namespace
-
-TEST(ParsedCookieTest, TestBasic) {
- CookieMonster::ParsedCookie pc("a=b");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_FALSE(pc.IsSecure());
- EXPECT_EQ("a", pc.Name());
- EXPECT_EQ("b", pc.Value());
-}
-
-TEST(ParsedCookieTest, TestQuoted) {
- // These are some quoting cases which the major browsers all
- // handle differently. I've tested Internet Explorer 6, Opera 9.6,
- // Firefox 3, and Safari Windows 3.2.1. We originally tried to match
- // Firefox closely, however we now match Internet Explorer and Safari.
- const char* values[] = {
- // Trailing whitespace after a quoted value. The whitespace after
- // the quote is stripped in all browsers.
- "\"zzz \" ", "\"zzz \"",
- // Handling a quoted value with a ';', like FOO="zz;pp" ;
- // IE and Safari: "zz;
- // Firefox and Opera: "zz;pp"
- "\"zz;pp\" ;", "\"zz",
- // Handling a value with multiple quoted parts, like FOO="zzz " "ppp" ;
- // IE and Safari: "zzz " "ppp";
- // Firefox: "zzz ";
- // Opera: <rejects cookie>
- "\"zzz \" \"ppp\" ", "\"zzz \" \"ppp\"",
- // A quote in a value that didn't start quoted. like FOO=A"B ;
- // IE, Safari, and Firefox: A"B;
- // Opera: <rejects cookie>
- "A\"B", "A\"B",
- };
-
- for (size_t i = 0; i < arraysize(values); i += 2) {
- std::string input(values[i]);
- std::string expected(values[i + 1]);
-
- CookieMonster::ParsedCookie pc(
- "aBc=" + input + " ; path=\"/\" ; httponly ");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_FALSE(pc.IsSecure());
- EXPECT_TRUE(pc.IsHttpOnly());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("aBc", pc.Name());
- EXPECT_EQ(expected, pc.Value());
-
- // If a path was quoted, the path attribute keeps the quotes. This will
- // make the cookie effectively useless, but path parameters aren't supposed
- // to be quoted. Bug 1261605.
- EXPECT_EQ("\"/\"", pc.Path());
- }
-}
-
-TEST(ParsedCookieTest, TestNameless) {
- CookieMonster::ParsedCookie pc("BLAHHH; path=/; secure;");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_TRUE(pc.IsSecure());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/", pc.Path());
- EXPECT_EQ("", pc.Name());
- EXPECT_EQ("BLAHHH", pc.Value());
-}
-
-TEST(ParsedCookieTest, TestAttributeCase) {
- CookieMonster::ParsedCookie pc("BLAHHH; Path=/; sECuRe; httpONLY");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_TRUE(pc.IsSecure());
- EXPECT_TRUE(pc.IsHttpOnly());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/", pc.Path());
- EXPECT_EQ("", pc.Name());
- EXPECT_EQ("BLAHHH", pc.Value());
- EXPECT_EQ(3U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, TestDoubleQuotedNameless) {
- CookieMonster::ParsedCookie pc("\"BLA\\\"HHH\"; path=/; secure;");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_TRUE(pc.IsSecure());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/", pc.Path());
- EXPECT_EQ("", pc.Name());
- EXPECT_EQ("\"BLA\\\"HHH\"", pc.Value());
- EXPECT_EQ(2U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, QuoteOffTheEnd) {
- CookieMonster::ParsedCookie pc("a=\"B");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("a", pc.Name());
- EXPECT_EQ("\"B", pc.Value());
- EXPECT_EQ(0U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, MissingName) {
- CookieMonster::ParsedCookie pc("=ABC");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("", pc.Name());
- EXPECT_EQ("ABC", pc.Value());
- EXPECT_EQ(0U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, MissingValue) {
- CookieMonster::ParsedCookie pc("ABC=; path = /wee");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("ABC", pc.Name());
- EXPECT_EQ("", pc.Value());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/wee", pc.Path());
- EXPECT_EQ(1U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, Whitespace) {
- CookieMonster::ParsedCookie pc(" A = BC ;secure;;; httponly");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("A", pc.Name());
- EXPECT_EQ("BC", pc.Value());
- EXPECT_FALSE(pc.HasPath());
- EXPECT_FALSE(pc.HasDomain());
- EXPECT_TRUE(pc.IsSecure());
- EXPECT_TRUE(pc.IsHttpOnly());
- // We parse anything between ; as attributes, so we end up with two
- // attributes with an empty string name and value.
- EXPECT_EQ(4U, pc.NumberOfAttributes());
-}
-TEST(ParsedCookieTest, MultipleEquals) {
- CookieMonster::ParsedCookie pc(" A=== BC ;secure;;; httponly");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("A", pc.Name());
- EXPECT_EQ("== BC", pc.Value());
- EXPECT_FALSE(pc.HasPath());
- EXPECT_FALSE(pc.HasDomain());
- EXPECT_TRUE(pc.IsSecure());
- EXPECT_TRUE(pc.IsHttpOnly());
- EXPECT_EQ(4U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, MACKey) {
- CookieMonster::ParsedCookie pc("foo=bar; MAC-Key=3900ac9anw9incvw9f");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("foo", pc.Name());
- EXPECT_EQ("bar", pc.Value());
- EXPECT_EQ("3900ac9anw9incvw9f", pc.MACKey());
- EXPECT_EQ(1U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, MACAlgorithm) {
- CookieMonster::ParsedCookie pc("foo=bar; MAC-Algorithm=hmac-sha-1");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("foo", pc.Name());
- EXPECT_EQ("bar", pc.Value());
- EXPECT_EQ("hmac-sha-1", pc.MACAlgorithm());
- EXPECT_EQ(1U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, MACKeyAndMACAlgorithm) {
- CookieMonster::ParsedCookie pc(
- "foo=bar; MAC-Key=voiae-09fj0302nfqf; MAC-Algorithm=hmac-sha-256");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("foo", pc.Name());
- EXPECT_EQ("bar", pc.Value());
- EXPECT_EQ("voiae-09fj0302nfqf", pc.MACKey());
- EXPECT_EQ("hmac-sha-256", pc.MACAlgorithm());
- EXPECT_EQ(2U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, QuotedTrailingWhitespace) {
- CookieMonster::ParsedCookie pc("ANCUUID=\"zohNumRKgI0oxyhSsV3Z7D\" ; "
- "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
- "path=/ ; ");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("ANCUUID", pc.Name());
- // Stripping whitespace after the quotes matches all other major browsers.
- EXPECT_EQ("\"zohNumRKgI0oxyhSsV3Z7D\"", pc.Value());
- EXPECT_TRUE(pc.HasExpires());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/", pc.Path());
- EXPECT_EQ(2U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, TrailingWhitespace) {
- CookieMonster::ParsedCookie pc("ANCUUID=zohNumRKgI0oxyhSsV3Z7D ; "
- "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
- "path=/ ; ");
- EXPECT_TRUE(pc.IsValid());
- EXPECT_EQ("ANCUUID", pc.Name());
- EXPECT_EQ("zohNumRKgI0oxyhSsV3Z7D", pc.Value());
- EXPECT_TRUE(pc.HasExpires());
- EXPECT_TRUE(pc.HasPath());
- EXPECT_EQ("/", pc.Path());
- EXPECT_EQ(2U, pc.NumberOfAttributes());
-}
-
-TEST(ParsedCookieTest, TooManyPairs) {
- std::string blankpairs;
- blankpairs.resize(CookieMonster::ParsedCookie::kMaxPairs - 1, ';');
-
- CookieMonster::ParsedCookie pc1(blankpairs + "secure");
- EXPECT_TRUE(pc1.IsValid());
- EXPECT_TRUE(pc1.IsSecure());
-
- CookieMonster::ParsedCookie pc2(blankpairs + ";secure");
- EXPECT_TRUE(pc2.IsValid());
- EXPECT_FALSE(pc2.IsSecure());
-}
-
-// TODO(erikwright): some better test cases for invalid cookies.
-TEST(ParsedCookieTest, InvalidWhitespace) {
- CookieMonster::ParsedCookie pc(" ");
- EXPECT_FALSE(pc.IsValid());
-}
-
-TEST(ParsedCookieTest, InvalidTooLong) {
- std::string maxstr;
- maxstr.resize(CookieMonster::ParsedCookie::kMaxCookieSize, 'a');
-
- CookieMonster::ParsedCookie pc1(maxstr);
- EXPECT_TRUE(pc1.IsValid());
-
- CookieMonster::ParsedCookie pc2(maxstr + "A");
- EXPECT_FALSE(pc2.IsValid());
-}
-
-TEST(ParsedCookieTest, InvalidEmpty) {
- CookieMonster::ParsedCookie pc("");
- EXPECT_FALSE(pc.IsValid());
-}
-
-TEST(ParsedCookieTest, EmbeddedTerminator) {
- CookieMonster::ParsedCookie pc1("AAA=BB\0ZYX");
- CookieMonster::ParsedCookie pc2("AAA=BB\rZYX");
- CookieMonster::ParsedCookie pc3("AAA=BB\nZYX");
- EXPECT_TRUE(pc1.IsValid());
- EXPECT_EQ("AAA", pc1.Name());
- EXPECT_EQ("BB", pc1.Value());
- EXPECT_TRUE(pc2.IsValid());
- EXPECT_EQ("AAA", pc2.Name());
- EXPECT_EQ("BB", pc2.Value());
- EXPECT_TRUE(pc3.IsValid());
- EXPECT_EQ("AAA", pc3.Name());
- EXPECT_EQ("BB", pc3.Value());
-}
-
-TEST(ParsedCookieTest, ParseTokensAndValues) {
- EXPECT_EQ("hello",
- CookieMonster::ParsedCookie::ParseTokenString(
- "hello\nworld"));
- EXPECT_EQ("fs!!@",
- CookieMonster::ParsedCookie::ParseTokenString(
- "fs!!@;helloworld"));
- EXPECT_EQ("hello world\tgood",
- CookieMonster::ParsedCookie::ParseTokenString(
- "hello world\tgood\rbye"));
- EXPECT_EQ("A",
- CookieMonster::ParsedCookie::ParseTokenString(
- "A=B=C;D=E"));
- EXPECT_EQ("hello",
- CookieMonster::ParsedCookie::ParseValueString(
- "hello\nworld"));
- EXPECT_EQ("fs!!@",
- CookieMonster::ParsedCookie::ParseValueString(
- "fs!!@;helloworld"));
- EXPECT_EQ("hello world\tgood",
- CookieMonster::ParsedCookie::ParseValueString(
- "hello world\tgood\rbye"));
- EXPECT_EQ("A=B=C",
- CookieMonster::ParsedCookie::ParseValueString(
- "A=B=C;D=E"));
-}
-
-namespace {
-
struct CookieMonsterTestTraits {
static scoped_refptr<CookieStore> Create() {
return new CookieMonster(NULL, NULL);
@@ -2574,8 +2300,7 @@ TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckDeleteCanonicalCookie) {
}
TEST_F(CookieMonsterTest, InvalidExpiryTime) {
- CookieMonster::ParsedCookie pc(
- std::string(kValidCookieLine) + "; expires=Blarg arg arg");
+ ParsedCookie pc(std::string(kValidCookieLine) + "; expires=Blarg arg arg");
scoped_ptr<CookieMonster::CanonicalCookie> cookie(
CookieMonster::CanonicalCookie::Create(url_google_, pc));
diff --git a/net/cookies/parsed_cookie.cc b/net/cookies/parsed_cookie.cc
new file mode 100644
index 0000000..f9af3c9
--- /dev/null
+++ b/net/cookies/parsed_cookie.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Portions of this code based on Mozilla:
+// (netwerk/cookie/src/nsCookieService.cpp)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2003
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Witte (dwitte@stanford.edu)
+ * Michiel van Leeuwen (mvl@exedo.nl)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/cookies/parsed_cookie.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+
+namespace net {
+
+ParsedCookie::ParsedCookie(const std::string& cookie_line)
+ : is_valid_(false),
+ path_index_(0),
+ domain_index_(0),
+ mac_key_index_(0),
+ mac_algorithm_index_(0),
+ expires_index_(0),
+ maxage_index_(0),
+ secure_index_(0),
+ httponly_index_(0) {
+
+ if (cookie_line.size() > kMaxCookieSize) {
+ VLOG(1) << "Not parsing cookie, too large: " << cookie_line.size();
+ return;
+ }
+
+ ParseTokenValuePairs(cookie_line);
+ if (!pairs_.empty()) {
+ is_valid_ = true;
+ 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;
+}
+// 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;
+}
+// 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;
+}
+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;
+}
+
+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[] = ";=";
+
+// 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 out;
+ for (PairList::const_iterator it = pairs_.begin();
+ it != pairs_.end(); ++it) {
+ out.append(it->first);
+ out.append("=");
+ out.append(it->second);
+ out.append("; ");
+ }
+ return out;
+}
+
+std::string::const_iterator ParsedCookie::FindFirstTerminator(
+ const std::string& s) {
+ std::string::const_iterator end = s.end();
+ size_t term_pos =
+ s.find_first_of(std::string(kTerminator, kTerminatorLen));
+ if (term_pos != std::string::npos) {
+ // We found a character we should treat as an end of string.
+ end = s.begin() + term_pos;
+ }
+ return end;
+}
+
+bool ParsedCookie::ParseToken(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end) {
+ DCHECK(it && token_start && token_end);
+ std::string::const_iterator token_real_end;
+
+ // Seek past any whitespace before the "token" (the name).
+ // token_start should point at the first character in the token
+ if (SeekPast(it, end, kWhitespace))
+ return false; // No token, whitespace or empty.
+ *token_start = *it;
+
+ // Seek over the token, to the token separator.
+ // token_real_end should point at the token separator, i.e. '='.
+ // If it == end after the seek, we probably have a token-value.
+ SeekTo(it, end, kTokenSeparator);
+ token_real_end = *it;
+
+ // Ignore any whitespace between the token and the token separator.
+ // token_end should point after the last interesting token character,
+ // pointing at either whitespace, or at '=' (and equal to token_real_end).
+ if (*it != *token_start) { // We could have an empty token name.
+ --(*it); // Go back before the token separator.
+ // Skip over any whitespace to the first non-whitespace character.
+ SeekBackPast(it, *token_start, kWhitespace);
+ // Point after it.
+ ++(*it);
+ }
+ *token_end = *it;
+
+ // Seek us back to the end of the token.
+ *it = token_real_end;
+ return true;
+}
+
+void ParsedCookie::ParseValue(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end) {
+ DCHECK(it && value_start && value_end);
+
+ // Seek past any whitespace that might in-between the token and value.
+ SeekPast(it, end, kWhitespace);
+ // value_start should point at the first character of the value.
+ *value_start = *it;
+
+ // Just look for ';' to terminate ('=' allowed).
+ // We can hit the end, maybe they didn't terminate.
+ SeekTo(it, end, kValueSeparator);
+
+ // Will be pointed at the ; seperator or the end.
+ *value_end = *it;
+
+ // Ignore any unwanted whitespace after the value.
+ if (*value_end != *value_start) { // Could have an empty value
+ --(*value_end);
+ SeekBackPast(value_end, *value_start, kWhitespace);
+ ++(*value_end);
+ }
+}
+
+std::string ParsedCookie::ParseTokenString(const std::string& token) {
+ std::string::const_iterator it = token.begin();
+ std::string::const_iterator end = FindFirstTerminator(token);
+
+ std::string::const_iterator token_start, token_end;
+ if (ParseToken(&it, end, &token_start, &token_end))
+ return std::string(token_start, token_end);
+ return std::string();
+}
+
+std::string ParsedCookie::ParseValueString(const std::string& value) {
+ std::string::const_iterator it = value.begin();
+ std::string::const_iterator end = FindFirstTerminator(value);
+
+ std::string::const_iterator value_start, value_end;
+ ParseValue(&it, end, &value_start, &value_end);
+ return std::string(value_start, value_end);
+}
+
+// Parse all token/value pairs and populate pairs_.
+void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line) {
+ pairs_.clear();
+
+ // Ok, here we go. We should be expecting to be starting somewhere
+ // before the cookie line, not including any header name...
+ std::string::const_iterator start = cookie_line.begin();
+ std::string::const_iterator it = start;
+
+ // TODO(erikwright): Make sure we're stripping \r\n in the network code.
+ // Then we can log any unexpected terminators.
+ std::string::const_iterator end = FindFirstTerminator(cookie_line);
+
+ for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
+ TokenValuePair pair;
+
+ std::string::const_iterator token_start, token_end;
+ if (!ParseToken(&it, end, &token_start, &token_end))
+ break;
+
+ if (it == end || *it != '=') {
+ // We have a token-value, we didn't have any token name.
+ if (pair_num == 0) {
+ // For the first time around, we want to treat single values
+ // as a value with an empty name. (Mozilla bug 169091).
+ // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
+ // set 2 different cookies, and setting "BBB" will then replace "AAA".
+ pair.first = "";
+ // Rewind to the beginning of what we thought was the token name,
+ // and let it get parsed as a value.
+ it = token_start;
+ } else {
+ // Any not-first attribute we want to treat a value as a
+ // name with an empty value... This is so something like
+ // "secure;" will get parsed as a Token name, and not a value.
+ pair.first = std::string(token_start, token_end);
+ }
+ } else {
+ // We have a TOKEN=VALUE.
+ pair.first = std::string(token_start, token_end);
+ ++it; // Skip past the '='.
+ }
+
+ // OK, now try to parse a value.
+ std::string::const_iterator value_start, value_end;
+ ParseValue(&it, end, &value_start, &value_end);
+ // OK, we're finished with a Token/Value.
+ pair.second = std::string(value_start, value_end);
+
+ // From RFC2109: "Attributes (names) (attr) are case-insensitive."
+ if (pair_num != 0)
+ StringToLowerASCII(&pair.first);
+ pairs_.push_back(pair);
+
+ // We've processed a token/value pair, we're either at the end of
+ // the string or a ValueSeparator like ';', which we want to skip.
+ if (it != end)
+ ++it;
+ }
+}
+
+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) {
+ path_index_ = i;
+ } else if (pairs_[i].first == kDomainTokenName) {
+ domain_index_ = i;
+ } else if (pairs_[i].first == kMACKeyTokenName) {
+ mac_key_index_ = i;
+ } else if (pairs_[i].first == kMACAlgorithmTokenName) {
+ mac_algorithm_index_ = i;
+ } else if (pairs_[i].first == kExpiresTokenName) {
+ expires_index_ = i;
+ } else if (pairs_[i].first == kMaxAgeTokenName) {
+ maxage_index_ = i;
+ } else if (pairs_[i].first == kSecureTokenName) {
+ secure_index_ = i;
+ } else if (pairs_[i].first == kHttpOnlyTokenName) {
+ httponly_index_ = i;
+ } else {
+ /* some attribute we don't know or don't care about. */
+ }
+ }
+}
+
+} // namespace
diff --git a/net/cookies/parsed_cookie.h b/net/cookies/parsed_cookie.h
new file mode 100644
index 0000000..6f8e4ef
--- /dev/null
+++ b/net/cookies/parsed_cookie.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_COOKIES_PARSED_COOKIE_H_
+#define NET_COOKIES_PARSED_COOKIE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT ParsedCookie {
+ public:
+ typedef std::pair<std::string, std::string> TokenValuePair;
+ typedef std::vector<TokenValuePair> PairList;
+
+ // The maximum length of a cookie string we will try to parse
+ static const size_t kMaxCookieSize = 4096;
+ // The maximum number of Token/Value pairs. Shouldn't have more than 8.
+ static const int kMaxPairs = 16;
+
+ // Construct from a cookie string like "BLAH=1; path=/; domain=.google.com"
+ 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_; }
+
+ const std::string& Name() const { return pairs_[0].first; }
+ const std::string& Token() const { return Name(); }
+ const std::string& Value() const { return pairs_[0].second; }
+
+ bool HasPath() const { return path_index_ != 0; }
+ const std::string& Path() const { return pairs_[path_index_].second; }
+ bool HasDomain() const { return domain_index_ != 0; }
+ const std::string& Domain() const { return pairs_[domain_index_].second; }
+ bool HasMACKey() const { return mac_key_index_ != 0; }
+ const std::string& MACKey() const { return pairs_[mac_key_index_].second; }
+ bool HasMACAlgorithm() const { return mac_algorithm_index_ != 0; }
+ const std::string& MACAlgorithm() const {
+ return pairs_[mac_algorithm_index_].second;
+ }
+ bool HasExpires() const { return expires_index_ != 0; }
+ const std::string& Expires() const { return pairs_[expires_index_].second; }
+ bool HasMaxAge() const { return maxage_index_ != 0; }
+ const std::string& MaxAge() const { return pairs_[maxage_index_].second; }
+ bool IsSecure() const { return secure_index_ != 0; }
+ bool IsHttpOnly() const { return httponly_index_ != 0; }
+
+ // Returns the number of attributes, for example, returning 2 for:
+ // "BLAH=hah; path=/; domain=.google.com"
+ size_t NumberOfAttributes() const { return pairs_.size() - 1; }
+
+ // For debugging only!
+ std::string DebugString() const;
+
+ // Returns an iterator pointing to the first terminator character found in
+ // the given string.
+ static std::string::const_iterator FindFirstTerminator(const std::string& s);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments token_start and token_end to the start and end
+ // positions of a cookie attribute token name parsed from the segment, and
+ // updates the segment iterator to point to the next segment to be parsed.
+ // If no token is found, the function returns false.
+ static bool ParseToken(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments value_start and value_end to the start and end
+ // positions of a cookie attribute value parsed from the segment, and updates
+ // the segment iterator to point to the next segment to be parsed.
+ static void ParseValue(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end);
+
+ // Same as the above functions, except the input is assumed to contain the
+ // desired token/value and nothing else.
+ static std::string ParseTokenString(const std::string& token);
+ 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();
+
+ PairList pairs_;
+ bool is_valid_;
+ // These will default to 0, but that should never be valid since the
+ // 0th index is the user supplied token/value, not an attribute.
+ // We're really never going to have more than like 8 attributes, so we
+ // could fit these into 3 bits each if we're worried about size...
+ size_t path_index_;
+ size_t domain_index_;
+ size_t mac_key_index_;
+ size_t mac_algorithm_index_;
+ size_t expires_index_;
+ size_t maxage_index_;
+ size_t secure_index_;
+ size_t httponly_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(ParsedCookie);
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_MONSTER_H_
diff --git a/net/cookies/parsed_cookie_unittest.cc b/net/cookies/parsed_cookie_unittest.cc
new file mode 100644
index 0000000..7b6d562
--- /dev/null
+++ b/net/cookies/parsed_cookie_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "net/cookies/parsed_cookie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class ParsedCookieTest : public testing::Test { };
+
+} // namespace
+
+TEST(ParsedCookieTest, TestBasic) {
+ ParsedCookie pc("a=b");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_EQ("a", pc.Name());
+ EXPECT_EQ("b", pc.Value());
+}
+
+TEST(ParsedCookieTest, TestQuoted) {
+ // These are some quoting cases which the major browsers all
+ // handle differently. I've tested Internet Explorer 6, Opera 9.6,
+ // Firefox 3, and Safari Windows 3.2.1. We originally tried to match
+ // Firefox closely, however we now match Internet Explorer and Safari.
+ const char* values[] = {
+ // Trailing whitespace after a quoted value. The whitespace after
+ // the quote is stripped in all browsers.
+ "\"zzz \" ", "\"zzz \"",
+ // Handling a quoted value with a ';', like FOO="zz;pp" ;
+ // IE and Safari: "zz;
+ // Firefox and Opera: "zz;pp"
+ "\"zz;pp\" ;", "\"zz",
+ // Handling a value with multiple quoted parts, like FOO="zzz " "ppp" ;
+ // IE and Safari: "zzz " "ppp";
+ // Firefox: "zzz ";
+ // Opera: <rejects cookie>
+ "\"zzz \" \"ppp\" ", "\"zzz \" \"ppp\"",
+ // A quote in a value that didn't start quoted. like FOO=A"B ;
+ // IE, Safari, and Firefox: A"B;
+ // Opera: <rejects cookie>
+ "A\"B", "A\"B",
+ };
+
+ for (size_t i = 0; i < arraysize(values); i += 2) {
+ std::string input(values[i]);
+ std::string expected(values[i + 1]);
+
+ ParsedCookie pc("aBc=" + input + " ; path=\"/\" ; httponly ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("aBc", pc.Name());
+ EXPECT_EQ(expected, pc.Value());
+
+ // If a path was quoted, the path attribute keeps the quotes. This will
+ // make the cookie effectively useless, but path parameters aren't supposed
+ // to be quoted. Bug 1261605.
+ EXPECT_EQ("\"/\"", pc.Path());
+ }
+}
+
+TEST(ParsedCookieTest, TestNameless) {
+ ParsedCookie pc("BLAHHH; path=/; secure;");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("BLAHHH", pc.Value());
+}
+
+TEST(ParsedCookieTest, TestAttributeCase) {
+ ParsedCookie pc("BLAHHH; Path=/; sECuRe; httpONLY");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("BLAHHH", pc.Value());
+ EXPECT_EQ(3U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TestDoubleQuotedNameless) {
+ ParsedCookie pc("\"BLA\\\"HHH\"; path=/; secure;");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("\"BLA\\\"HHH\"", pc.Value());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, QuoteOffTheEnd) {
+ ParsedCookie pc("a=\"B");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("a", pc.Name());
+ EXPECT_EQ("\"B", pc.Value());
+ EXPECT_EQ(0U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MissingName) {
+ ParsedCookie pc("=ABC");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("ABC", pc.Value());
+ EXPECT_EQ(0U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MissingValue) {
+ ParsedCookie pc("ABC=; path = /wee");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ABC", pc.Name());
+ EXPECT_EQ("", pc.Value());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/wee", pc.Path());
+ EXPECT_EQ(1U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, Whitespace) {
+ ParsedCookie pc(" A = BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("A", pc.Name());
+ EXPECT_EQ("BC", pc.Value());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ // We parse anything between ; as attributes, so we end up with two
+ // attributes with an empty string name and value.
+ EXPECT_EQ(4U, pc.NumberOfAttributes());
+}
+TEST(ParsedCookieTest, MultipleEquals) {
+ ParsedCookie pc(" A=== BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("A", pc.Name());
+ EXPECT_EQ("== BC", pc.Value());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ(4U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MACKey) {
+ ParsedCookie pc("foo=bar; MAC-Key=3900ac9anw9incvw9f");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("foo", pc.Name());
+ EXPECT_EQ("bar", pc.Value());
+ EXPECT_EQ("3900ac9anw9incvw9f", pc.MACKey());
+ EXPECT_EQ(1U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MACAlgorithm) {
+ ParsedCookie pc("foo=bar; MAC-Algorithm=hmac-sha-1");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("foo", pc.Name());
+ EXPECT_EQ("bar", pc.Value());
+ EXPECT_EQ("hmac-sha-1", pc.MACAlgorithm());
+ EXPECT_EQ(1U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MACKeyAndMACAlgorithm) {
+ ParsedCookie pc(
+ "foo=bar; MAC-Key=voiae-09fj0302nfqf; MAC-Algorithm=hmac-sha-256");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("foo", pc.Name());
+ EXPECT_EQ("bar", pc.Value());
+ EXPECT_EQ("voiae-09fj0302nfqf", pc.MACKey());
+ EXPECT_EQ("hmac-sha-256", pc.MACAlgorithm());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, QuotedTrailingWhitespace) {
+ ParsedCookie pc("ANCUUID=\"zohNumRKgI0oxyhSsV3Z7D\" ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ANCUUID", pc.Name());
+ // Stripping whitespace after the quotes matches all other major browsers.
+ EXPECT_EQ("\"zohNumRKgI0oxyhSsV3Z7D\"", pc.Value());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TrailingWhitespace) {
+ ParsedCookie pc("ANCUUID=zohNumRKgI0oxyhSsV3Z7D ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ANCUUID", pc.Name());
+ EXPECT_EQ("zohNumRKgI0oxyhSsV3Z7D", pc.Value());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TooManyPairs) {
+ std::string blankpairs;
+ blankpairs.resize(ParsedCookie::kMaxPairs - 1, ';');
+
+ ParsedCookie pc1(blankpairs + "secure");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_TRUE(pc1.IsSecure());
+
+ ParsedCookie pc2(blankpairs + ";secure");
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_FALSE(pc2.IsSecure());
+}
+
+// TODO(erikwright): some better test cases for invalid cookies.
+TEST(ParsedCookieTest, InvalidWhitespace) {
+ ParsedCookie pc(" ");
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidTooLong) {
+ std::string maxstr;
+ maxstr.resize(ParsedCookie::kMaxCookieSize, 'a');
+
+ ParsedCookie pc1(maxstr);
+ EXPECT_TRUE(pc1.IsValid());
+
+ ParsedCookie pc2(maxstr + "A");
+ EXPECT_FALSE(pc2.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidEmpty) {
+ ParsedCookie pc("");
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, EmbeddedTerminator) {
+ ParsedCookie pc1("AAA=BB\0ZYX");
+ ParsedCookie pc2("AAA=BB\rZYX");
+ ParsedCookie pc3("AAA=BB\nZYX");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_EQ("AAA", pc1.Name());
+ EXPECT_EQ("BB", pc1.Value());
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_EQ("AAA", pc2.Name());
+ EXPECT_EQ("BB", pc2.Value());
+ EXPECT_TRUE(pc3.IsValid());
+ EXPECT_EQ("AAA", pc3.Name());
+ EXPECT_EQ("BB", pc3.Value());
+}
+
+TEST(ParsedCookieTest, ParseTokensAndValues) {
+ EXPECT_EQ("hello",
+ ParsedCookie::ParseTokenString("hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ ParsedCookie::ParseTokenString("fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ ParsedCookie::ParseTokenString("hello world\tgood\rbye"));
+ EXPECT_EQ("A",
+ ParsedCookie::ParseTokenString("A=B=C;D=E"));
+ EXPECT_EQ("hello",
+ ParsedCookie::ParseValueString("hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ ParsedCookie::ParseValueString("fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ ParsedCookie::ParseValueString("hello world\tgood\rbye"));
+ EXPECT_EQ("A=B=C",
+ ParsedCookie::ParseValueString("A=B=C;D=E"));
+}
+
+} // namespace net