/* */ #include "CookieStorage.h" #include #include #include #include "util.h" #include "LogFactory.h" #include "Logger.h" #include "DlAbortEx.h" #include "StringFormat.h" #include "NsCookieParser.h" #include "File.h" #include "a2functional.h" #include "A2STR.h" #include "message.h" #include "cookie_helper.h" #ifdef HAVE_SQLITE3 # include "Sqlite3CookieParserImpl.h" #endif // HAVE_SQLITE3 namespace aria2 { CookieStorage::DomainEntry::DomainEntry (const std::string& domain): key_(util::isNumericHost(domain)?domain:cookie::reverseDomainLevel(domain)) {} bool CookieStorage::DomainEntry::addCookie(const Cookie& cookie, time_t now) { setLastAccessTime(now); std::deque::iterator i = std::find(cookies_.begin(), cookies_.end(), cookie); if(i == cookies_.end()) { if(cookie.isExpired(now)) { return false; } else { if(cookies_.size() >= CookieStorage::MAX_COOKIE_PER_DOMAIN) { cookies_.erase (std::remove_if(cookies_.begin(), cookies_.end(), std::bind2nd (std::mem_fun_ref(&Cookie::isExpired), now)), cookies_.end()); if(cookies_.size() >= CookieStorage::MAX_COOKIE_PER_DOMAIN) { std::deque::iterator m = std::min_element (cookies_.begin(), cookies_.end(), LeastRecentAccess()); *m = cookie; } else { cookies_.push_back(cookie); } } else { cookies_.push_back(cookie); } return true; } } else if(cookie.isExpired(now)) { cookies_.erase(i); return false; } else { *i = cookie; return true; } } 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::const_iterator i = cookies_.begin(), eoi = cookies_.end(); i != eoi; ++i) { o << (*i).toNsCookieFormat() << "\n"; } } CookieStorage::CookieStorage():logger_(LogFactory::getInstance()) {} CookieStorage::~CookieStorage() {} namespace { // See CookieStorageTest::testDomainIsFull() in CookieStorageTest.cc const size_t DOMAIN_EVICTION_TRIGGER = 2000; const double DOMAIN_EVICTION_RATE = 0.1; } // namespace bool CookieStorage::store(const Cookie& cookie, time_t now) { if(domains_.size() >= DOMAIN_EVICTION_TRIGGER) { std::sort(domains_.begin(), domains_.end(), LeastRecentAccess()); size_t delnum = (size_t)(domains_.size()*DOMAIN_EVICTION_RATE); domains_.erase(domains_.begin(), domains_.begin()+delnum); std::sort(domains_.begin(), domains_.end()); } DomainEntry v(cookie.getDomain()); std::deque::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, now); } else { added = v.addCookie(cookie, now); if(added) { domains_.insert(i, v); } } return added; } bool CookieStorage::parseAndStore (const std::string& setCookieString, const std::string& requestHost, const std::string& defaultPath, time_t now) { Cookie cookie; if(cookie::parse(cookie, setCookieString, requestHost, defaultPath, now)) { return store(cookie, now); } else { return false; } } struct CookiePathDivider { Cookie cookie_; int pathDepth_; CookiePathDivider(const Cookie& cookie):cookie_(cookie) { std::vector paths; util::split(cookie_.getPath(), std::back_inserter(paths), A2STR::SLASH_C); pathDepth_ = paths.size(); } }; namespace { class CookiePathDividerConverter { public: CookiePathDivider operator()(const Cookie& cookie) const { return CookiePathDivider(cookie); } Cookie operator()(const CookiePathDivider& cookiePathDivider) const { return cookiePathDivider.cookie_; } }; } // namespace namespace { class OrderByPathDepthDesc:public std::binary_function { 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. // // See also http://tools.ietf.org/html/draft-ietf-httpstate-cookie-14 // section5.4 return lhs.pathDepth_ > rhs.pathDepth_ || (!(rhs.pathDepth_ > lhs.pathDepth_) && lhs.cookie_.getCreationTime() < rhs.cookie_.getCreationTime()); } }; } // namespace namespace { template void searchCookieByDomainSuffix (const std::string& domain, DomainInputIterator first, DomainInputIterator last, CookieOutputIterator out, const std::string& requestHost, const std::string& requestPath, time_t now, bool secure) { CookieStorage::DomainEntry v(domain); std::deque::iterator i = std::lower_bound(first, last, v); if(i != last && (*i).getKey() == v.getKey()) { (*i).setLastAccessTime(now); (*i).findCookie(out, requestHost, requestPath, now, secure); } } } // namespace bool CookieStorage::contains(const Cookie& cookie) const { CookieStorage::DomainEntry v(cookie.getDomain()); std::deque::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::vector CookieStorage::criteriaFind (const std::string& requestHost, const std::string& requestPath, time_t now, bool secure) { std::vector res; if(requestPath.empty()) { return res; } if(util::isNumericHost(requestHost)) { searchCookieByDomainSuffix (requestHost, domains_.begin(), domains_.end(), std::back_inserter(res), requestHost, requestPath, now, secure); } else { std::vector levels; util::split(requestHost, std::back_inserter(levels),A2STR::DOT_C); std::reverse(levels.begin(), levels.end()); std::string domain; for(std::vector::const_iterator i = levels.begin(), eoi = levels.end(); i != eoi; ++i, domain.insert(domain.begin(), '.')) { domain.insert(domain.begin(), (*i).begin(), (*i).end()); searchCookieByDomainSuffix (domain, domains_.begin(), domains_.end(), std::back_inserter(res), requestHost, requestPath, now, secure); } } std::vector 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 { size_t numCookie = 0; for(std::deque::const_iterator i = domains_.begin(), eoi = domains_.end(); i != eoi; ++i) { numCookie += (*i).countCookie(); } return numCookie; } bool CookieStorage::load(const std::string& filename, time_t now) { char header[16]; // "SQLite format 3" plus \0 std::ifstream s(filename.c_str(), std::ios::binary); if(!s) { logger_->error("Failed to open cookie file %s", filename.c_str()); return false; } s.get(header, sizeof(header)); if(!s) { logger_->error("Failed to read header of cookie file %s", filename.c_str()); return false; } try { if(std::string(header) == "SQLite format 3") { #ifdef HAVE_SQLITE3 std::vector cookies; try { Sqlite3MozCookieParser(filename).parse(cookies); } catch(RecoverableException& e) { if(logger_->info()) { logger_->info(EX_EXCEPTION_CAUGHT, e); logger_->info("This does not look like Firefox3 cookie file." " Retrying, assuming it is Chromium cookie file."); } // Try chrome cookie format Sqlite3ChromiumCookieParser(filename).parse(cookies); } storeCookies(cookies.begin(), cookies.end(), now); #else // !HAVE_SQLITE3 throw DL_ABORT_EX ("Cannot read SQLite3 database because SQLite3 support is disabled by" " configuration."); #endif // !HAVE_SQLITE3 } else { std::vector cookies = NsCookieParser().parse(filename, now); storeCookies(cookies.begin(), cookies.end(), now); } return true; } catch(RecoverableException& e) { logger_->error("Failed to load cookies from %s", filename.c_str()); return false; } } bool CookieStorage::saveNsFormat(const std::string& filename) { std::string tempfilename = filename+"__temp"; { std::ofstream o(tempfilename.c_str(), std::ios::binary); if(!o) { logger_->error("Cannot create cookie file %s", filename.c_str()); return false; } for(std::deque::const_iterator i = domains_.begin(), eoi = domains_.end(); i != eoi; ++i) { (*i).writeCookie(o); } o.flush(); if(!o) { logger_->error("Failed to save cookies to %s", filename.c_str()); return false; } } if(File(tempfilename).renameTo(filename)) { return true; } else { logger_->error("Could not rename file %s as %s", tempfilename.c_str(), filename.c_str()); return false; } } } // namespace aria2