From 4043b6ccae25087c4a707b6ff687026e3447b8d3 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> Date: Thu, 28 Jan 2010 14:01:50 +0000 Subject: [PATCH] 2010-01-28 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net> Rewritten Cookie storage. * src/Cookie.cc * src/Cookie.h * src/CookieParser.cc * src/CookieStorage.cc * src/CookieStorage.h * src/a2functional.h * test/CookieParserTest.cc * test/CookieStorageTest.cc * test/CookieTest.cc * test/HttpResponseTest.cc * test/TestUtil.h * test/a2functionalTest.cc --- ChangeLog | 16 +++ src/Cookie.cc | 86 ++++++++------ src/Cookie.h | 20 ++++ src/CookieParser.cc | 22 +++- src/CookieStorage.cc | 228 +++++++++++++++++++++++++++++++------- src/CookieStorage.h | 113 +++++++++++++++++-- src/a2functional.h | 9 ++ test/CookieParserTest.cc | 2 +- test/CookieStorageTest.cc | 157 ++++++++++++++++++++------ test/CookieTest.cc | 12 ++ test/HttpResponseTest.cc | 7 +- test/TestUtil.h | 15 +++ test/a2functionalTest.cc | 30 ++++- 13 files changed, 587 insertions(+), 130 deletions(-) diff --git a/ChangeLog b/ChangeLog index f125642f..fea2cb25 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2010-01-28 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net> + + Rewritten Cookie storage. + * src/Cookie.cc + * src/Cookie.h + * src/CookieParser.cc + * src/CookieStorage.cc + * src/CookieStorage.h + * src/a2functional.h + * test/CookieParserTest.cc + * test/CookieStorageTest.cc + * test/CookieTest.cc + * test/HttpResponseTest.cc + * test/TestUtil.h + * test/a2functionalTest.cc + 2010-01-26 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net> Handle redirected URI which is not properly percent encoded. diff --git a/src/Cookie.cc b/src/Cookie.cc index ffd15edd..b8ff5eeb 100644 --- a/src/Cookie.cc +++ b/src/Cookie.cc @@ -54,7 +54,7 @@ static std::string prependDotIfNotExists(const std::string& domain) return (!domain.empty() && domain[0] != '.') ? "."+domain : domain; } -static std::string normalizeDomain(const std::string& domain) +std::string Cookie::normalizeDomain(const std::string& domain) { if(domain.empty() || util::isNumbersAndDotsNotation(domain)) { return domain; @@ -79,7 +79,9 @@ Cookie::Cookie(const std::string& name, _expiry(expiry), _path(path), _domain(normalizeDomain(domain)), - _secure(secure) {} + _secure(secure), + _creationTime(time(0)), + _lastAccess(_creationTime) {} Cookie::Cookie(const std::string& name, const std::string& value, @@ -91,9 +93,11 @@ Cookie::Cookie(const std::string& name, _expiry(0), _path(path), _domain(normalizeDomain(domain)), - _secure(secure) {} + _secure(secure), + _creationTime(time(0)), + _lastAccess(_creationTime) {} -Cookie::Cookie():_expiry(0), _secure(false) {} +Cookie::Cookie():_expiry(0), _secure(false), _lastAccess(time(0)) {} Cookie::~Cookie() {} @@ -142,9 +146,9 @@ bool Cookie::match(const std::string& requestHost, const std::string& requestPath, time_t date, bool secure) const { - std::string normReqHost = normalizeDomain(requestHost); if((secure || (!_secure && !secure)) && - domainMatch(normReqHost, _domain) && + (requestHost == _domain || // For default domain or IP address + domainMatch(normalizeDomain(requestHost), _domain)) && pathInclude(requestPath, _path) && (isSessionCookie() || (date < _expiry))) { return true; @@ -156,35 +160,37 @@ bool Cookie::match(const std::string& requestHost, bool Cookie::validate(const std::string& requestHost, const std::string& requestPath) const { - std::string normReqHost = normalizeDomain(requestHost); - // If _domain is IP address, then it should be matched to requestHost. - // In other words, _domain == normReqHost - if(normReqHost != _domain) { - // domain must start with '.' - if(*_domain.begin() != '.') { - return false; + // If _domain doesn't start with "." or it is IP address, then it + // must equal to requestHost. Otherwise, do domain tail match. + if(requestHost != _domain) { + std::string normReqHost = normalizeDomain(requestHost); + if(normReqHost != _domain) { + // domain must start with '.' + if(*_domain.begin() != '.') { + return false; + } + // domain must not end with '.' + if(*_domain.rbegin() == '.') { + return false; + } + // domain must include at least one embeded '.' + if(_domain.size() < 4 || _domain.find(".", 1) == std::string::npos) { + return false; + } + if(!util::endsWith(normReqHost, _domain)) { + return false; + } + // From RFC2965 3.3.2 Rejecting Cookies + // * The request-host is a HDN (not IP address) and has the form HD, + // where D is the value of the Domain attribute, and H is a string + // that contains one or more dots. + size_t dotCount = std::count(normReqHost.begin(), + normReqHost.begin()+ + (normReqHost.size()-_domain.size()), '.'); + if(dotCount > 1 || (dotCount == 1 && normReqHost[0] != '.')) { + return false; + } } - // domain must not end with '.' - if(*_domain.rbegin() == '.') { - return false; - } - // domain must include at least one embeded '.' - if(_domain.size() < 4 || _domain.find(".", 1) == std::string::npos) { - return false; - } - if(!util::endsWith(normReqHost, _domain)) { - return false; - } - // From RFC2965 3.3.2 Rejecting Cookies - // * The request-host is a HDN (not IP address) and has the form HD, - // where D is the value of the Domain attribute, and H is a string - // that contains one or more dots. - size_t dotCount = std::count(normReqHost.begin(), - normReqHost.begin()+ - (normReqHost.size()-_domain.size()), '.'); - if(dotCount > 1 || (dotCount == 1 && normReqHost[0] != '.')) { - return false; - } } if(requestPath != _path) { // From RFC2965 3.3.2 Rejecting Cookies @@ -230,4 +236,16 @@ std::string Cookie::toNsCookieFormat() const return ss.str(); } +void Cookie::markOriginServerOnly() +{ + if(util::startsWith(_domain, A2STR::DOT_C)) { + _domain.erase(_domain.begin(), _domain.begin()+1); + } +} + +void Cookie::updateLastAccess() +{ + _lastAccess = time(0); +} + } // namespace aria2 diff --git a/src/Cookie.h b/src/Cookie.h index e88ebd25..9b223aaa 100644 --- a/src/Cookie.h +++ b/src/Cookie.h @@ -52,6 +52,8 @@ private: std::string _path; std::string _domain; bool _secure; + time_t _creationTime; + time_t _lastAccess; public: /* * If expires = 0 is given, then the cookie becomes session cookie. @@ -127,6 +129,24 @@ public: } std::string toNsCookieFormat() const; + + // Makes this Cookie only sent to the origin server. This function + // removes first "." from _domain if _domain starts with ".". + void markOriginServerOnly(); + + time_t getCreationTime() const + { + return _creationTime; + } + + void updateLastAccess(); + + time_t getLastAccess() const + { + return _lastAccess; + } + + static std::string normalizeDomain(const std::string& domain); }; typedef std::deque<Cookie> Cookies; diff --git a/src/CookieParser.cc b/src/CookieParser.cc index bf14af7b..a0cbe2dd 100644 --- a/src/CookieParser.cc +++ b/src/CookieParser.cc @@ -71,26 +71,38 @@ Cookie CookieParser::parse(const std::string& cookieStr, const std::string& defa util::split(nameValue, terms.front(), '='); std::map<std::string, std::string> values; - values[C_DOMAIN] = defaultDomain; - values[C_PATH] = defaultPath; - for(std::vector<std::string>::iterator itr = terms.begin()+1; itr != terms.end(); ++itr) { std::pair<std::string, std::string> nv; util::split(nv, *itr, '='); values[nv.first] = nv.second; } + bool useDefaultDomain = false; + std::map<std::string, std::string>::iterator mitr; + mitr = values.find(C_DOMAIN); + if(mitr == values.end() || (*mitr).second.empty()) { + useDefaultDomain = true; + values[C_DOMAIN] = defaultDomain; + } + mitr = values.find(C_PATH); + if(mitr == values.end() || (*mitr).second.empty()) { + values[C_PATH] = defaultPath; + } time_t expiry = 0; - if(values.find(C_EXPIRES) != values.end()) { + if(values.count(C_EXPIRES)) { Time expiryTime = Time::parseHTTPDate(values[C_EXPIRES]); if(expiryTime.good()) { expiry = expiryTime.getTime(); } } - return Cookie(nameValue.first, nameValue.second, + Cookie cookie(nameValue.first, nameValue.second, expiry, values[C_PATH], values[C_DOMAIN], values.find(C_SECURE) != values.end()); + if(useDefaultDomain) { + cookie.markOriginServerOnly(); + } + return cookie; } diff --git a/src/CookieStorage.cc b/src/CookieStorage.cc index 1dee23de..4347e98f 100644 --- a/src/CookieStorage.cc +++ b/src/CookieStorage.cc @@ -45,28 +45,40 @@ #include "StringFormat.h" #include "NsCookieParser.h" #include "File.h" +#include "a2functional.h" #ifdef HAVE_SQLITE3 # include "Sqlite3MozCookieParser.h" #endif // HAVE_SQLITE3 namespace aria2 { -CookieStorage::CookieStorage():_logger(LogFactory::getInstance()) {} - -CookieStorage::~CookieStorage() {} - -bool CookieStorage::store(const Cookie& cookie) +CookieStorage::DomainEntry::DomainEntry +(const std::string& domain):_key(domain) { - if(!cookie.good()) { - return false; - } + std::reverse(_key.begin(), _key.end()); +} + +void CookieStorage::DomainEntry::updateLastAccess() +{ + _lastAccess = time(0); +} + +bool CookieStorage::DomainEntry::addCookie(const Cookie& cookie) +{ + updateLastAccess(); std::deque<Cookie>::iterator i = std::find(_cookies.begin(), _cookies.end(), cookie); if(i == _cookies.end()) { if(cookie.isExpired()) { return false; } else { - _cookies.push_back(cookie); + if(_cookies.size() >= CookieStorage::MAX_COOKIE_PER_DOMAIN) { + std::deque<Cookie>::iterator m = std::min_element + (_cookies.begin(), _cookies.end(), LeastRecentAccess<Cookie>()); + *m = cookie; + } else { + _cookies.push_back(cookie); + } return true; } } else if(cookie.isExpired()) { @@ -78,6 +90,55 @@ bool CookieStorage::store(const Cookie& cookie) } } +bool CookieStorage::DomainEntry::contains(const Cookie& cookie) const +{ + return std::find(_cookies.begin(), _cookies.end(), cookie) != _cookies.end(); +} + +void CookieStorage::DomainEntry::writeCookie(std::ostream& o) const +{ + for(std::deque<Cookie>::const_iterator i = _cookies.begin(); + i != _cookies.end(); ++i) { + o << (*i).toNsCookieFormat() << "\n"; + } +} + +CookieStorage::CookieStorage():_logger(LogFactory::getInstance()) {} + +CookieStorage::~CookieStorage() {} + +// See CookieStorageTest::testDomainIsFull() in CookieStorageTest.cc +static const size_t DOMAIN_EVICTION_TRIGGER = 600; + +static const double DOMAIN_EVICTION_RATE = 0.1; + +bool CookieStorage::store(const Cookie& cookie) +{ + if(!cookie.good()) { + return false; + } + if(_domains.size() >= DOMAIN_EVICTION_TRIGGER) { + std::sort(_domains.begin(), _domains.end(), + LeastRecentAccess<DomainEntry>()); + size_t delnum = _domains.size()*DOMAIN_EVICTION_RATE; + _domains.erase(_domains.begin(), _domains.begin()+delnum); + std::sort(_domains.begin(), _domains.end()); + } + DomainEntry v(cookie.getDomain()); + std::deque<DomainEntry>::iterator i = + std::lower_bound(_domains.begin(), _domains.end(), v); + bool added = false; + if(i != _domains.end() && (*i).getKey() == v.getKey()) { + added = (*i).addCookie(cookie); + } else { + added = v.addCookie(cookie); + if(added) { + _domains.insert(i, v); + } + } + return added; +} + void CookieStorage::storeCookies(const std::deque<Cookie>& cookies) { for(std::deque<Cookie>::const_iterator i = cookies.begin(); @@ -98,48 +159,138 @@ bool CookieStorage::parseAndStore(const std::string& setCookieString, } } -class CriteriaMatch:public std::unary_function<Cookie, bool> { -private: - std::string _requestHost; - std::string _requestPath; - time_t _date; - bool _secure; -public: - CriteriaMatch(const std::string& requestHost, const std::string& requestPath, - time_t date, bool secure): - _requestHost(requestHost), - _requestPath(requestPath), - _date(date), - _secure(secure) {} - - bool operator()(const Cookie& cookie) const +struct CookiePathDivider { + Cookie _cookie; + int _pathDepth; + CookiePathDivider(const Cookie& cookie):_cookie(cookie) { - return cookie.match(_requestHost, _requestPath, _date, _secure); + std::vector<std::string> paths; + util::split(_cookie.getPath(), std::back_inserter(paths), A2STR::SLASH_C); + _pathDepth = paths.size(); } }; -class OrderByPathDesc:public std::binary_function<Cookie, Cookie, bool> { +class CookiePathDividerConverter { public: - bool operator()(const Cookie& lhs, const Cookie& rhs) const + CookiePathDivider operator()(const Cookie& cookie) const { - return lhs.getPath() > rhs.getPath(); + return CookiePathDivider(cookie); + } + + Cookie operator()(const CookiePathDivider& cookiePathDivider) const + { + return cookiePathDivider._cookie; } }; +class OrderByPathDepthDesc:public std::binary_function<Cookie, Cookie, bool> { +public: + bool operator() + (const CookiePathDivider& lhs, const CookiePathDivider& rhs) const + { + // Sort by path-length. + // + // RFC2965 says: Note that the NAME=VALUE pair for the cookie with + // the more specific Path attribute, /acme/ammo, comes before the + // one with the less specific Path attribute, /acme. Further note + // that the same cookie name appears more than once. + // + // Netscape spec says: When sending cookies to a server, all + // cookies with a more specific path mapping should be sent before + // cookies with less specific path mappings. For example, a cookie + // "name1=foo" with a path mapping of "/" should be sent after a + // cookie "name1=foo2" with a path mapping of "/bar" if they are + // both to be sent. + int comp = lhs._pathDepth-rhs._pathDepth; + if(comp == 0) { + return lhs._cookie.getCreationTime() < rhs._cookie.getCreationTime(); + } else { + return comp > 0; + } + } +}; + +template<typename DomainInputIterator, typename CookieOutputIterator> +static void searchCookieByDomainSuffix +(const std::string& domain, + DomainInputIterator first, DomainInputIterator last, CookieOutputIterator out, + const std::string& requestHost, + const std::string& requestPath, + time_t date, bool secure) +{ + CookieStorage::DomainEntry v(domain); + std::deque<CookieStorage::DomainEntry>::iterator i = + std::lower_bound(first, last, v); + if(i != last && (*i).getKey() == v.getKey()) { + (*i).updateLastAccess(); + (*i).findCookie(out, requestHost, requestPath, date, secure); + } +} + +bool CookieStorage::contains(const Cookie& cookie) const +{ + CookieStorage::DomainEntry v(cookie.getDomain()); + std::deque<CookieStorage::DomainEntry>::const_iterator i = + std::lower_bound(_domains.begin(), _domains.end(), v); + if(i != _domains.end() && (*i).getKey() == v.getKey()) { + return (*i).contains(cookie); + } else { + return false; + } +} + std::deque<Cookie> CookieStorage::criteriaFind(const std::string& requestHost, const std::string& requestPath, - time_t date, bool secure) const + time_t date, bool secure) { std::deque<Cookie> res; - std::remove_copy_if(_cookies.begin(), _cookies.end(), std::back_inserter(res), - std::not1(CriteriaMatch(requestHost, requestPath, date, secure))); - std::sort(res.begin(), res.end(), OrderByPathDesc()); + bool numericHost = util::isNumbersAndDotsNotation(requestHost); + searchCookieByDomainSuffix + ((!numericHost && requestHost.find(A2STR::DOT_C) == std::string::npos)? + requestHost+".local":requestHost, + _domains.begin(), _domains.end(), + std::back_inserter(res), + requestHost, requestPath, date, secure); + if(!numericHost) { + std::string normRequestHost = Cookie::normalizeDomain(requestHost); + std::vector<std::string> domainComponents; + util::split(normRequestHost, std::back_inserter(domainComponents), + A2STR::DOT_C); + if(domainComponents.size() <= 1) { + return res; + } + std::reverse(domainComponents.begin(), domainComponents.end()); + std::string domain = A2STR::DOT_C; + domain += domainComponents[0]; + for(std::vector<std::string>::const_iterator di = + domainComponents.begin()+1; di != domainComponents.end(); ++di) { + domain = strconcat(A2STR::DOT_C, *di, domain); + const size_t prenum = res.size(); + searchCookieByDomainSuffix(domain, _domains.begin(), _domains.end(), + std::back_inserter(res), + normRequestHost, requestPath, date, secure); + if(prenum == res.size()) { + break; + } + } + } + std::vector<CookiePathDivider> divs; + std::transform(res.begin(), res.end(), std::back_inserter(divs), + CookiePathDividerConverter()); + std::sort(divs.begin(), divs.end(), OrderByPathDepthDesc()); + std::transform(divs.begin(), divs.end(), res.begin(), + CookiePathDividerConverter()); return res; } size_t CookieStorage::size() const { - return _cookies.size(); + size_t numCookie = 0; + for(std::deque<DomainEntry>::const_iterator i = _domains.begin(); + i != _domains.end(); ++i) { + numCookie += (*i).countCookie(); + } + return numCookie; } bool CookieStorage::load(const std::string& filename) @@ -185,14 +336,9 @@ bool CookieStorage::saveNsFormat(const std::string& filename) filename.c_str(), strerror(errno)); return false; } - for(std::deque<Cookie>::const_iterator i = _cookies.begin(); - i != _cookies.end(); ++i) { - o << (*i).toNsCookieFormat() << "\n"; - if(!o) { - _logger->error("Failed to save cookies to %s, cause %s", - filename.c_str(), strerror(errno)); - return false; - } + for(std::deque<DomainEntry>::const_iterator i = _domains.begin(); + i != _domains.end(); ++i) { + (*i).writeCookie(o); } o.flush(); if(!o) { diff --git a/src/CookieStorage.h b/src/CookieStorage.h index 193d8d1e..84121a6e 100644 --- a/src/CookieStorage.h +++ b/src/CookieStorage.h @@ -39,6 +39,7 @@ #include <string> #include <deque> +#include <algorithm> #include "a2time.h" #include "Cookie.h" @@ -49,8 +50,96 @@ namespace aria2 { class Logger; class CookieStorage { +public: + + static const size_t MAX_COOKIE_PER_DOMAIN = 50; + + class FindCookie:public std::unary_function<Cookie&, bool> { + private: + std::string _requestHost; + std::string _requestPath; + time_t _date; + bool _secure; + public: + FindCookie(const std::string& requestHost, + const std::string& requestPath, + time_t date, bool secure): + _requestHost(requestHost), + _requestPath(requestPath), + _date(date), + _secure(secure) {} + + bool operator()(Cookie& cookie) const + { + if(cookie.match(_requestHost, _requestPath, _date, _secure)) { + cookie.updateLastAccess(); + return true; + } else { + return false; + } + } + }; + + class DomainEntry { + private: + std::string _key; + + time_t _lastAccess; + + std::deque<Cookie> _cookies; + public: + DomainEntry(const std::string& domain); + + const std::string& getKey() const + { + return _key; + } + + template<typename OutputIterator> + OutputIterator findCookie + (OutputIterator out, + const std::string& requestHost, + const std::string& requestPath, + time_t date, bool secure) + { + OutputIterator last = + std::remove_copy_if + (_cookies.begin(), _cookies.end(), out, + std::not1(FindCookie(requestHost, requestPath, date, secure))); + return last; + } + + size_t countCookie() const + { + return _cookies.size(); + } + + bool addCookie(const Cookie& cookie); + + void updateLastAccess(); + + time_t getLastAccess() const + { + return _lastAccess; + } + + void writeCookie(std::ostream& o) const; + + bool contains(const Cookie& cookie) const; + + template<typename OutputIterator> + OutputIterator dumpCookie(OutputIterator out) const + { + return std::copy(_cookies.begin(), _cookies.end(), out); + } + + bool operator<(const DomainEntry& de) const + { + return _key < de._key; + } + }; private: - std::deque<Cookie> _cookies; + std::deque<DomainEntry> _domains; CookieParser _parser; @@ -72,9 +161,11 @@ public: const std::string& requestHost, const std::string& requestPath); + // Finds cookies matched with given criteria and returns them. + // Matched cookies' _lastAccess property is updated. std::deque<Cookie> criteriaFind(const std::string& requestHost, const std::string& requestPath, - time_t date, bool secure) const; + time_t date, bool secure); // Loads Cookies from file denoted by filename. If compiled with // libsqlite3, this method automatically detects the specified file @@ -88,18 +179,22 @@ public: // this method returns true, otherwise returns false. bool saveNsFormat(const std::string& filename); + // Returns the number of cookies this object stores. size_t size() const; - std::deque<Cookie>::const_iterator begin() const - { - return _cookies.begin(); - } + // Returns true if this object contains a cookie x where x == cookie + // satisfies. + bool contains(const Cookie& cookie) const; - std::deque<Cookie>::const_iterator end() const + template<typename OutputIterator> + OutputIterator dumpCookie(OutputIterator out) const { - return _cookies.end(); + for(std::deque<DomainEntry>::const_iterator i = _domains.begin(); + i != _domains.end(); ++i) { + out = (*i).dumpCookie(out); + } + return out; } - }; } // namespace aria2 diff --git a/src/a2functional.h b/src/a2functional.h index eee08b82..34e0ccf4 100644 --- a/src/a2functional.h +++ b/src/a2functional.h @@ -381,6 +381,15 @@ inline void strappend(std::string& base, base += a7; base += a8; } +template<typename T> +class LeastRecentAccess:public std::binary_function<T, T, bool> { +public: + bool operator()(const T& lhs, const T& rhs) const + { + return lhs.getLastAccess() < rhs.getLastAccess(); + } +}; + } // namespace aria2 #endif // _D_A2_FUNCTIONAL_H_ diff --git a/test/CookieParserTest.cc b/test/CookieParserTest.cc index 8cb8aaaa..245243a9 100644 --- a/test/CookieParserTest.cc +++ b/test/CookieParserTest.cc @@ -43,7 +43,7 @@ void CookieParserTest::testParse() CPPUNIT_ASSERT_EQUAL(std::string("JSESSIONID"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("123456789"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)0, c.getExpiry()); - CPPUNIT_ASSERT_EQUAL(std::string(".default.domain"), c.getDomain()); + CPPUNIT_ASSERT_EQUAL(std::string("default.domain"), c.getDomain()); CPPUNIT_ASSERT_EQUAL(std::string("/default/path"), c.getPath()); CPPUNIT_ASSERT_EQUAL(false, c.isSecureCookie()); CPPUNIT_ASSERT_EQUAL(true, c.isSessionCookie()); diff --git a/test/CookieStorageTest.cc b/test/CookieStorageTest.cc index fa15a3d9..e2ff5278 100644 --- a/test/CookieStorageTest.cc +++ b/test/CookieStorageTest.cc @@ -11,6 +11,7 @@ #include "CookieParser.h" #include "RecoverableException.h" #include "File.h" +#include "TestUtil.h" namespace aria2 { @@ -25,12 +26,16 @@ class CookieStorageTest:public CppUnit::TestFixture { CPPUNIT_TEST(testLoad_fileNotfound); CPPUNIT_TEST(testSaveNsFormat); CPPUNIT_TEST(testSaveNsFormat_fail); + CPPUNIT_TEST(testCookieIsFull); + CPPUNIT_TEST(testDomainIsFull); CPPUNIT_TEST_SUITE_END(); public: void setUp() {} void tearDown() {} + void dumpCookie(std::vector<Cookie>& cookies, const CookieStorage& cs); + void testStore(); void testParseAndStore(); void testCriteriaFind(); @@ -39,57 +44,64 @@ public: void testLoad_fileNotfound(); void testSaveNsFormat(); void testSaveNsFormat_fail(); + void testCookieIsFull(); + void testDomainIsFull(); }; CPPUNIT_TEST_SUITE_REGISTRATION(CookieStorageTest); +void CookieStorageTest::dumpCookie +(std::vector<Cookie>& cookies, const CookieStorage& st) +{ + st.dumpCookie(std::back_inserter(cookies)); + std::sort(cookies.begin(), cookies.end(), CookieSorter()); +} + void CookieStorageTest::testStore() { CookieStorage st; Cookie goodCookie("k", "v", "/", "localhost", false); CPPUNIT_ASSERT(st.store(goodCookie)); CPPUNIT_ASSERT_EQUAL((size_t)1, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), goodCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(goodCookie)); Cookie anotherCookie("k", "v", "/", "mirror", true); CPPUNIT_ASSERT(st.store(anotherCookie)); CPPUNIT_ASSERT_EQUAL((size_t)2, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), anotherCookie) != st.end()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), goodCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(anotherCookie)); + CPPUNIT_ASSERT(st.contains(goodCookie)); Cookie updateGoodCookie("k", "v2", "/", "localhost", false); CPPUNIT_ASSERT(st.store(goodCookie)); CPPUNIT_ASSERT_EQUAL((size_t)2, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), updateGoodCookie) != st.end()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), anotherCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(updateGoodCookie)); + CPPUNIT_ASSERT(st.contains(anotherCookie)); Cookie expireGoodCookie("k", "v3", 1, "/", "localhost", false); CPPUNIT_ASSERT(!st.store(expireGoodCookie)); CPPUNIT_ASSERT_EQUAL((size_t)1, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), anotherCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(anotherCookie)); Cookie badCookie("", "", "/", "localhost", false); CPPUNIT_ASSERT(!st.store(badCookie)); CPPUNIT_ASSERT_EQUAL((size_t)1, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), anotherCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(anotherCookie)); Cookie fromNumericHost("k", "v", "/", "192.168.1.1", false); CPPUNIT_ASSERT(st.store(fromNumericHost)); CPPUNIT_ASSERT_EQUAL((size_t)2, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), fromNumericHost) != st.end()); + CPPUNIT_ASSERT(st.contains(fromNumericHost)); Cookie sessionScopedGoodCookie("k", "v3", 0, "/", "localhost", false); CPPUNIT_ASSERT(st.store(sessionScopedGoodCookie)); CPPUNIT_ASSERT_EQUAL((size_t)3, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), - sessionScopedGoodCookie) != st.end()); + CPPUNIT_ASSERT(st.contains(sessionScopedGoodCookie)); Cookie sessionScopedGoodCookie2("k2", "v3", "/", "localhost", false); CPPUNIT_ASSERT(st.store(sessionScopedGoodCookie2)); CPPUNIT_ASSERT_EQUAL((size_t)4, st.size()); - CPPUNIT_ASSERT(std::find(st.begin(), st.end(), - sessionScopedGoodCookie2) != st.end()); + CPPUNIT_ASSERT(st.contains(sessionScopedGoodCookie2)); } void CookieStorageTest::testParseAndStore() @@ -111,6 +123,10 @@ void CookieStorageTest::testParseAndStore() " expires=Fri, 2038-01-01 00:00:00 GMT; path=/; domain=192.168.1.1;"; CPPUNIT_ASSERT(st.parseAndStore(numericHostCookieStr, "192.168.1.1", "/")); + // No domain and no path are specified. + std::string noDomainPathCookieStr = "k=v"; + CPPUNIT_ASSERT + (st.parseAndStore(noDomainPathCookieStr, "aria2.sf.net", "/downloads")); } void CookieStorageTest::testCriteriaFind() @@ -125,6 +141,16 @@ void CookieStorageTest::testCriteriaFind() Cookie echo("echo", "ECHO", "/", "www.aria2.org", false); Cookie foxtrot("foxtrot", "FOXTROT", "/", ".sf.net", false); Cookie golf("golf", "GOLF", "/", "192.168.1.1", false); + Cookie hotel1("hotel", "HOTEL1", "/", "samename.x", false); + Cookie hotel2("hotel", "HOTEL2", "/hotel", "samename.x", false); + Cookie hotel3("hotel", "HOTEL3", "/bar/wine", "samename.x", false); + Cookie hotel4("hotel", "HOTEL4", "/bar/", "samename.x", false); + Cookie india1("india", "INDIA1", "/foo", "default.domain", false); + india1.markOriginServerOnly(); + Cookie india2("india", "INDIA2", "/", "default.domain", false); + Cookie juliet1("juliet", "JULIET1", "/foo", "localhost", false); + juliet1.markOriginServerOnly(); + Cookie juliet2("juliet", "JULIET2", "/", "localhost", false); CPPUNIT_ASSERT(st.store(alpha)); CPPUNIT_ASSERT(st.store(bravo)); @@ -133,7 +159,15 @@ void CookieStorageTest::testCriteriaFind() CPPUNIT_ASSERT(st.store(echo)); CPPUNIT_ASSERT(st.store(foxtrot)); CPPUNIT_ASSERT(st.store(golf)); - + CPPUNIT_ASSERT(st.store(hotel1)); + CPPUNIT_ASSERT(st.store(hotel2)); + CPPUNIT_ASSERT(st.store(hotel3)); + CPPUNIT_ASSERT(st.store(hotel4)); + CPPUNIT_ASSERT(st.store(india1)); + CPPUNIT_ASSERT(st.store(india2)); + CPPUNIT_ASSERT(st.store(juliet1)); + CPPUNIT_ASSERT(st.store(juliet2)); + std::deque<Cookie> aria2Slash = st.criteriaFind("www.aria2.org", "/", 0, false); CPPUNIT_ASSERT_EQUAL((size_t)2, aria2Slash.size()); @@ -168,6 +202,30 @@ void CookieStorageTest::testCriteriaFind() false); CPPUNIT_ASSERT_EQUAL((size_t)1, numericHostCookies.size()); CPPUNIT_ASSERT_EQUAL(std::string("golf"), numericHostCookies[0].getName()); + + std::deque<Cookie> sameNameCookies = + st.criteriaFind("samename.x", "/bar/wine", 0, false); + CPPUNIT_ASSERT_EQUAL((size_t)3, sameNameCookies.size()); + CPPUNIT_ASSERT_EQUAL(std::string("HOTEL3"), sameNameCookies[0].getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("HOTEL4"), sameNameCookies[1].getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("HOTEL1"), sameNameCookies[2].getValue()); + + std::deque<Cookie> defaultDomainCookies = + st.criteriaFind("default.domain", "/foo", 0, false); + CPPUNIT_ASSERT_EQUAL((size_t)2, defaultDomainCookies.size()); + CPPUNIT_ASSERT_EQUAL(std::string("INDIA1"), + defaultDomainCookies[0].getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("INDIA2"), + defaultDomainCookies[1].getValue()); + + // localhost.local case + std::deque<Cookie> localDomainCookies = + st.criteriaFind("localhost", "/foo", 0, false); + CPPUNIT_ASSERT_EQUAL((size_t)2, localDomainCookies.size()); + CPPUNIT_ASSERT_EQUAL(std::string("JULIET1"), + localDomainCookies[0].getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("JULIET2"), + localDomainCookies[1].getValue()); } void CookieStorageTest::testLoad() @@ -178,7 +236,10 @@ void CookieStorageTest::testLoad() CPPUNIT_ASSERT_EQUAL((size_t)4, st.size()); - Cookie c = *st.begin(); + std::vector<Cookie> cookies; + dumpCookie(cookies, st); + + Cookie c = cookies[0]; CPPUNIT_ASSERT_EQUAL(std::string("JSESSIONID"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("123456789"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); @@ -186,7 +247,15 @@ void CookieStorageTest::testLoad() CPPUNIT_ASSERT_EQUAL(std::string(".localhost.local"), c.getDomain()); CPPUNIT_ASSERT(c.isSecureCookie()); - c = *(st.begin()+1); + c = cookies[1]; + CPPUNIT_ASSERT_EQUAL(std::string("novalue"), c.getName()); + CPPUNIT_ASSERT_EQUAL(std::string(""), c.getValue()); + CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); + CPPUNIT_ASSERT_EQUAL(std::string("/"), c.getPath()); + CPPUNIT_ASSERT_EQUAL(std::string(".localhost.local"), c.getDomain()); + CPPUNIT_ASSERT(!c.isSecureCookie()); + + c = cookies[2]; CPPUNIT_ASSERT_EQUAL(std::string("passwd"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("secret"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); @@ -194,21 +263,13 @@ void CookieStorageTest::testLoad() CPPUNIT_ASSERT_EQUAL(std::string(".localhost.local"), c.getDomain()); CPPUNIT_ASSERT(!c.isSecureCookie()); - c = *(st.begin()+2); + c = cookies[3]; CPPUNIT_ASSERT_EQUAL(std::string("TAX"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("1000"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); CPPUNIT_ASSERT_EQUAL(std::string("/"), c.getPath()); CPPUNIT_ASSERT_EQUAL(std::string(".overflow.local"), c.getDomain()); CPPUNIT_ASSERT(!c.isSecureCookie()); - - c = *(st.begin()+3); - CPPUNIT_ASSERT_EQUAL(std::string("novalue"), c.getName()); - CPPUNIT_ASSERT_EQUAL(std::string(""), c.getValue()); - CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); - CPPUNIT_ASSERT_EQUAL(std::string("/"), c.getPath()); - CPPUNIT_ASSERT_EQUAL(std::string(".localhost.local"), c.getDomain()); - CPPUNIT_ASSERT(!c.isSecureCookie()); } void CookieStorageTest::testLoad_sqlite3() @@ -217,8 +278,9 @@ void CookieStorageTest::testLoad_sqlite3() #ifdef HAVE_SQLITE3 st.load("cookies.sqlite"); CPPUNIT_ASSERT_EQUAL((size_t)3, st.size()); - std::deque<Cookie>::const_iterator i = st.begin(); - Cookie c = *i++; + std::vector<Cookie> cookies; + dumpCookie(cookies, st); + Cookie c = cookies[0]; CPPUNIT_ASSERT_EQUAL(std::string("JSESSIONID"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("123456789"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); @@ -227,7 +289,7 @@ void CookieStorageTest::testLoad_sqlite3() CPPUNIT_ASSERT(c.isSecureCookie()); CPPUNIT_ASSERT(!c.isSessionCookie()); - c = *i++; + c = cookies[1]; CPPUNIT_ASSERT_EQUAL(std::string("uid"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string(""), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)0, c.getExpiry()); @@ -236,7 +298,7 @@ void CookieStorageTest::testLoad_sqlite3() CPPUNIT_ASSERT(!c.isSecureCookie()); CPPUNIT_ASSERT(c.isSessionCookie()); - c = *i++; + c = cookies[2]; CPPUNIT_ASSERT_EQUAL(std::string("foo"), c.getName()); CPPUNIT_ASSERT_EQUAL(std::string("bar"), c.getValue()); CPPUNIT_ASSERT_EQUAL((time_t)2147483647, c.getExpiry()); @@ -258,19 +320,24 @@ void CookieStorageTest::testLoad_fileNotfound() void CookieStorageTest::testSaveNsFormat() { + // TODO add cookie with default domain std::string filename = "/tmp/aria2_CookieStorageTest_testSaveNsFormat"; File(filename).remove(); CookieStorage st; - st.store(Cookie("uid","tujikawa","/",".domain.org",true)); - st.store(Cookie("favorite","classic","/config",".domain.org",false)); + st.store(Cookie("favorite","classic","/config",".domain.org",true)); + st.store(Cookie("uid","tujikawa","/",".domain.org",false)); st.saveNsFormat(filename); CookieStorage loadst; loadst.load(filename); CPPUNIT_ASSERT_EQUAL((size_t)2, loadst.size()); - CPPUNIT_ASSERT_EQUAL(std::string("uid"), (*loadst.begin()).getName()); - CPPUNIT_ASSERT_EQUAL((time_t)0, (*loadst.begin()).getExpiry()); - CPPUNIT_ASSERT((*loadst.begin()).isSessionCookie()); - CPPUNIT_ASSERT_EQUAL(std::string("favorite"), (*(loadst.begin()+1)).getName()); + + std::vector<Cookie> cookies; + dumpCookie(cookies, loadst); + + CPPUNIT_ASSERT_EQUAL(std::string("favorite"), cookies[0].getName()); + CPPUNIT_ASSERT_EQUAL((time_t)0, cookies[0].getExpiry()); + CPPUNIT_ASSERT(cookies[0].isSessionCookie()); + CPPUNIT_ASSERT_EQUAL(std::string("uid"), cookies[1].getName()); } void CookieStorageTest::testSaveNsFormat_fail() @@ -284,4 +351,26 @@ void CookieStorageTest::testSaveNsFormat_fail() CPPUNIT_ASSERT(!st.saveNsFormat(filename)); } +void CookieStorageTest::testCookieIsFull() +{ + CookieStorage st; + for(size_t i = 0; i < CookieStorage::MAX_COOKIE_PER_DOMAIN+1; ++i) { + Cookie c("k"+util::itos(i), "v", "/", ".aria2.org", false); + st.store(c); + } + CPPUNIT_ASSERT_EQUAL((size_t)CookieStorage::MAX_COOKIE_PER_DOMAIN, st.size()); +} + +void CookieStorageTest::testDomainIsFull() +{ + // See DOMAIN_EVICTION_TRIGGER and DOMAIN_EVICTION_RATE in + // CookieStorage.cc + CookieStorage st; + for(size_t i = 0; i < 601; ++i) { + Cookie c("k", "v", "/", "domain"+util::itos(i), false); + st.store(c); + } + CPPUNIT_ASSERT_EQUAL((size_t)541, st.size()); +} + } // namespace aria2 diff --git a/test/CookieTest.cc b/test/CookieTest.cc index e18bf96d..4290c7fe 100644 --- a/test/CookieTest.cc +++ b/test/CookieTest.cc @@ -19,6 +19,7 @@ class CookieTest:public CppUnit::TestFixture { CPPUNIT_TEST(testIsExpired); CPPUNIT_TEST(testNormalizeDomain); CPPUNIT_TEST(testToNsCookieFormat); + CPPUNIT_TEST(testMarkOriginServerOnly); CPPUNIT_TEST_SUITE_END(); public: void setUp() {} @@ -31,6 +32,7 @@ public: void testIsExpired(); void testNormalizeDomain(); void testToNsCookieFormat(); + void testMarkOriginServerOnly(); }; @@ -189,4 +191,14 @@ void CookieTest::testToNsCookieFormat() Cookie("hello","world","/","domain.org",true).toNsCookieFormat()); } +void CookieTest::testMarkOriginServerOnly() +{ + Cookie c("k", "v", "/", "aria2.sf.net", false); + c.markOriginServerOnly(); + CPPUNIT_ASSERT_EQUAL(std::string("aria2.sf.net"), c.getDomain()); + Cookie ip("k", "v", "/", "192.168.0.1", false); + ip.markOriginServerOnly(); + CPPUNIT_ASSERT_EQUAL(std::string("192.168.0.1"), ip.getDomain()); +} + } // namespace aria2 diff --git a/test/HttpResponseTest.cc b/test/HttpResponseTest.cc index 4b3d3b4b..42c342cb 100644 --- a/test/HttpResponseTest.cc +++ b/test/HttpResponseTest.cc @@ -495,9 +495,10 @@ void HttpResponseTest::testRetrieveCookie() CPPUNIT_ASSERT_EQUAL((size_t)2, st->size()); - std::deque<Cookie>::const_iterator citer = st->begin(); - CPPUNIT_ASSERT_EQUAL(std::string("k2=v2"), (*(st->begin())).toString()); - CPPUNIT_ASSERT_EQUAL(std::string("k3=v3"), (*(st->begin()+1)).toString()); + std::vector<Cookie> cookies; + st->dumpCookie(std::back_inserter(cookies)); + CPPUNIT_ASSERT_EQUAL(std::string("k2=v2"), cookies[0].toString()); + CPPUNIT_ASSERT_EQUAL(std::string("k3=v3"), cookies[1].toString()); } void HttpResponseTest::testSupportsPersistentConnection() diff --git a/test/TestUtil.h b/test/TestUtil.h index e63c52b1..51d67569 100644 --- a/test/TestUtil.h +++ b/test/TestUtil.h @@ -1,10 +1,25 @@ #include "common.h" + #include <string> +#include "Cookie.h" + namespace aria2 { void createFile(const std::string& filename, size_t length); std::string readFile(const std::string& path); +class CookieSorter { +public: + bool operator()(const Cookie& lhs, const Cookie& rhs) const + { + if(lhs.getDomain() == rhs.getDomain()) { + return lhs.getName() < rhs.getName(); + } else { + return lhs.getDomain() < rhs.getDomain(); + } + } +}; + } // namespace aria2 diff --git a/test/a2functionalTest.cc b/test/a2functionalTest.cc index fd811d0b..776930c7 100644 --- a/test/a2functionalTest.cc +++ b/test/a2functionalTest.cc @@ -1,9 +1,10 @@ #include "a2functional.h" -#include <cppunit/extensions/HelperMacros.h> - #include <string> #include <numeric> +#include <algorithm> + +#include <cppunit/extensions/HelperMacros.h> namespace aria2 { @@ -15,6 +16,7 @@ class a2functionalTest:public CppUnit::TestFixture { CPPUNIT_TEST(testStrjoin); CPPUNIT_TEST(testStrconcat); CPPUNIT_TEST(testStrappend); + CPPUNIT_TEST(testLeastRecentAccess); CPPUNIT_TEST_SUITE_END(); public: void testMemFunSh(); @@ -22,7 +24,8 @@ public: void testStrjoin(); void testStrconcat(); void testStrappend(); - + void testLeastRecentAccess(); + class Greeting { public: virtual ~Greeting() {} @@ -48,6 +51,15 @@ public: }; + struct LastAccess { + time_t _lastAccess; + LastAccess(time_t lastAccess):_lastAccess(lastAccess) {} + + time_t getLastAccess() const + { + return _lastAccess; + } + }; }; @@ -100,4 +112,16 @@ void a2functionalTest::testStrappend() CPPUNIT_ASSERT_EQUAL(std::string("X=3,Y=5"), str); } +void a2functionalTest::testLeastRecentAccess() +{ + std::vector<LastAccess> v; + for(int i = 99; i >= 0; --i) { + v.push_back(LastAccess(i)); + } + std::sort(v.begin(), v.end(), LeastRecentAccess<LastAccess>()); + for(int i = 0; i < 100; ++i) { + CPPUNIT_ASSERT_EQUAL((time_t)i, v[i]._lastAccess); + } +} + } // namespace aria2