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
pull/1/head
Tatsuhiro Tsujikawa 2010-01-28 14:01:50 +00:00
parent 60c16887e6
commit 4043b6ccae
13 changed files with 587 additions and 130 deletions

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -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

View File

@ -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_

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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