/* */ #include "CookieStorage.h" #include #include #include #include "util.h" #include "LogFactory.h" #include "Logger.h" #include "DlAbortEx.h" #include "fmt.h" #include "NsCookieParser.h" #include "File.h" #include "a2functional.h" #include "A2STR.h" #include "message.h" #include "cookie_helper.h" #include "BufferedFile.h" #ifdef HAVE_SQLITE3 #include "Sqlite3CookieParserImpl.h" #endif // HAVE_SQLITE3 namespace aria2 { DomainNode::DomainNode(std::string label, DomainNode* parent) : label_{std::move(label)}, parent_{parent}, lastAccessTime_{0}, lruAccessTime_{0}, inLru_{false} { } void DomainNode::findCookie(std::vector& out, const std::string& requestHost, const std::string& requestPath, time_t now, bool secure) { if (cookies_) { for (auto& c : *cookies_) { if (c->match(requestHost, requestPath, now, secure)) { c->setLastAccessTime(now); out.push_back(c.get()); } } } } bool DomainNode::addCookie(std::unique_ptr cookie, time_t now) { using namespace std::placeholders; setLastAccessTime(now); if (!cookies_) { if (cookie->isExpired(now)) { return false; } else { cookies_ = make_unique>>(); cookies_->push_back(std::move(cookie)); return true; } } auto i = std::find_if( std::begin(*cookies_), std::end(*cookies_), [&](const std::unique_ptr& c) { return *c == *cookie; }); if (i == std::end(*cookies_)) { if (cookie->isExpired(now)) { return false; } else { if (cookies_->size() >= CookieStorage::MAX_COOKIE_PER_DOMAIN) { cookies_->erase(std::remove_if(std::begin(*cookies_), std::end(*cookies_), std::bind(&Cookie::isExpired, _1, now)), std::end(*cookies_)); if (cookies_->size() >= CookieStorage::MAX_COOKIE_PER_DOMAIN) { auto m = std::min_element(std::begin(*cookies_), std::end(*cookies_), [](const std::unique_ptr& lhs, const std::unique_ptr& rhs) { return lhs->getLastAccessTime() < rhs->getLastAccessTime(); }); *m = std::move(cookie); } else { cookies_->push_back(std::move(cookie)); } } else { cookies_->push_back(std::move(cookie)); } return true; } } else if (cookie->isExpired(now)) { cookies_->erase(i); return false; } else { cookie->setCreationTime((*i)->getCreationTime()); *i = std::move(cookie); return true; } } bool DomainNode::contains(const Cookie& cookie) const { if (cookies_) { for (auto& i : *cookies_) { if (*i == cookie) { return true; } } } return false; } bool DomainNode::writeCookie(BufferedFile& fp) const { if (cookies_) { for (const auto& c : *cookies_) { std::string data = c->toNsCookieFormat(); data += "\n"; if (fp.write(data.data(), data.size()) != data.size()) { return false; } } } return true; } size_t DomainNode::countCookie() const { if (cookies_) { return cookies_->size(); } else { return 0; } } void DomainNode::clearCookie() { cookies_->clear(); } void DomainNode::setLastAccessTime(time_t lastAccessTime) { lastAccessTime_ = lastAccessTime; } time_t DomainNode::getLastAccessTime() const { return lastAccessTime_; } void DomainNode::setLruAccessTime(time_t t) { lruAccessTime_ = t; } time_t DomainNode::getLruAccessTime() const { return lruAccessTime_; } bool DomainNode::empty() const { return !cookies_ || cookies_->empty(); } bool DomainNode::hasNext() const { return !next_.empty(); } DomainNode* DomainNode::getParent() const { return parent_; } void DomainNode::removeNode(DomainNode* node) { next_.erase(node->getLabel()); } DomainNode* DomainNode::findNext(const std::string& label) const { auto i = next_.find(label); if (i == std::end(next_)) { return nullptr; } else { return (*i).second.get(); } } DomainNode* DomainNode::addNext(std::string label, std::unique_ptr node) { auto& res = next_[std::move(label)] = std::move(node); return res.get(); } const std::string& DomainNode::getLabel() const { return label_; } bool DomainNode::getInLru() const { return inLru_; } void DomainNode::setInLru(bool f) { inLru_ = f; } CookieStorage::CookieStorage() : rootNode_{make_unique("", nullptr)} { } namespace { // See CookieStorageTest::testDomainIsFull() in CookieStorageTest.cc const size_t DOMAIN_EVICTION_TRIGGER = 2000; const double DOMAIN_EVICTION_RATE = 0.1; } // namespace namespace { std::vector splitDomainLabel(const std::string& domain) { auto labels = std::vector{}; if (util::isNumericHost(domain)) { labels.push_back(domain); } else { util::split(std::begin(domain), std::end(domain), std::back_inserter(labels), '.'); } return labels; } } // namespace size_t CookieStorage::getLruTrackerSize() const { return lruTracker_.size(); } void CookieStorage::evictNode(size_t delnum) { for (; delnum > 0 && !lruTracker_.empty(); --delnum) { auto node = (*lruTracker_.begin()).second; lruTracker_.erase(lruTracker_.begin()); node->setInLru(false); node->clearCookie(); while (node->empty() && !node->hasNext()) { auto parent = node->getParent(); parent->removeNode(node); if (!parent->empty() || parent->hasNext() || parent == rootNode_.get()) { break; } node = parent; if (node->getInLru()) { lruTracker_.erase({node->getLruAccessTime(), node}); node->setInLru(false); } } } } const DomainNode* CookieStorage::getRootNode() const { return rootNode_.get(); } bool CookieStorage::store(std::unique_ptr cookie, time_t now) { if (lruTracker_.size() >= DOMAIN_EVICTION_TRIGGER) { auto delnum = size_t(lruTracker_.size() * DOMAIN_EVICTION_RATE); evictNode(delnum); } auto labels = splitDomainLabel(cookie->getDomain()); auto node = rootNode_.get(); for (auto i = labels.rbegin(), eoi = labels.rend(); i != eoi; ++i) { auto nextNode = node->findNext(*i); if (nextNode) { node = nextNode; } else { node = node->addNext(*i, make_unique(*i, node)); } } bool ok = node->addCookie(std::move(cookie), now); if (ok) { updateLru(node, now); } return ok; } void CookieStorage::updateLru(DomainNode* node, time_t now) { if (node->getInLru()) { lruTracker_.erase({node->getLruAccessTime(), node}); } else { node->setInLru(true); } node->setLruAccessTime(now); lruTracker_.insert({node->getLruAccessTime(), node}); } bool CookieStorage::parseAndStore(const std::string& setCookieString, const std::string& requestHost, const std::string& defaultPath, time_t now) { auto cookie = cookie::parse(setCookieString, requestHost, defaultPath, now); return cookie && store(std::move(cookie), now); } namespace { struct CookiePathDivider { const Cookie* cookie_; int pathDepth_; CookiePathDivider(const Cookie* cookie) : cookie_(cookie), pathDepth_(0) { const std::string& path = cookie_->getPath(); if (!path.empty()) { for (size_t i = 1, len = path.size(); i < len; ++i) { if (path[i] == '/' && path[i - 1] != '/') { ++pathDepth_; } } if (path[path.size() - 1] != '/') { ++pathDepth_; } } } }; } // namespace namespace { class CookiePathDividerConverter { public: CookiePathDivider operator()(const Cookie* cookie) const { return CookiePathDivider(cookie); } const 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 { // From http://tools.ietf.org/html/rfc6265#section-5.4: // 2. The user agent SHOULD sort the cookie-list in the following // order: // // * Cookies with longer paths are listed before cookies with // shorter paths. // // * Among cookies that have equal-length path fields, cookies with // earlier creation-times are listed before cookies with later // creation-times. return lhs.pathDepth_ > rhs.pathDepth_ || (!(rhs.pathDepth_ > lhs.pathDepth_) && lhs.cookie_->getCreationTime() < rhs.cookie_->getCreationTime()); } }; } // namespace namespace { DomainNode* findNode(const std::string& domain, DomainNode* node) { auto labels = splitDomainLabel(domain); for (auto i = labels.rbegin(), eoi = labels.rend(); i != eoi && node; ++i) { node = node->findNext(*i); } return node; } } // namespace bool CookieStorage::contains(const Cookie& cookie) const { auto node = findNode(cookie.getDomain(), rootNode_.get()); return node && node->contains(cookie); } std::vector CookieStorage::criteriaFind(const std::string& requestHost, const std::string& requestPath, time_t now, bool secure) { auto res = std::vector{}; if (requestPath.empty()) { return res; } auto labels = splitDomainLabel(requestHost); auto node = rootNode_.get(); for (auto i = labels.rbegin(), eoi = labels.rend(); i != eoi; ++i) { auto nextNode = node->findNext(*i); if (!nextNode) { break; } nextNode->setLastAccessTime(now); if (nextNode->getInLru()) { updateLru(nextNode, now); } nextNode->findCookie(res, requestHost, requestPath, now, secure); node = nextNode; } auto divs = std::vector{}; std::transform(std::begin(res), std::end(res), std::back_inserter(divs), CookiePathDividerConverter{}); std::sort(std::begin(divs), std::end(divs), OrderByPathDepthDesc{}); std::transform(std::begin(divs), std::end(divs), std::begin(res), CookiePathDividerConverter{}); return res; } size_t CookieStorage::size() const { size_t n = 0; for (auto& p : lruTracker_) { n += p.second->countCookie(); } return n; } bool CookieStorage::load(const std::string& filename, time_t now) { char header[16]; // "SQLite format 3" plus \0 size_t headlen; { BufferedFile fp{filename.c_str(), BufferedFile::READ}; if (!fp) { A2_LOG_ERROR(fmt("Failed to open cookie file %s", filename.c_str())); return false; } headlen = fp.read(header, sizeof(header)); } try { if (headlen == 16 && memcmp(header, "SQLite format 3\0", 16) == 0) { #ifdef HAVE_SQLITE3 try { auto cookies = Sqlite3MozCookieParser(filename).parse(); storeCookies(std::make_move_iterator(std::begin(cookies)), std::make_move_iterator(std::end(cookies)), now); } catch (RecoverableException& e) { A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, e); A2_LOG_INFO("This does not look like Firefox3 cookie file." " Retrying, assuming it is Chromium cookie file."); // Try chrome cookie format auto cookies = Sqlite3ChromiumCookieParser(filename).parse(); storeCookies(std::make_move_iterator(std::begin(cookies)), std::make_move_iterator(std::end(cookies)), now); } #else // !HAVE_SQLITE3 throw DL_ABORT_EX( "Cannot read SQLite3 database because SQLite3 support is disabled by" " configuration."); #endif // !HAVE_SQLITE3 } else { auto cookies = NsCookieParser().parse(filename, now); storeCookies(std::make_move_iterator(std::begin(cookies)), std::make_move_iterator(std::end(cookies)), now); } return true; } catch (RecoverableException& e) { A2_LOG_ERROR(fmt("Failed to load cookies from %s", filename.c_str())); return false; } } bool CookieStorage::saveNsFormat(const std::string& filename) { auto tempfilename = filename; tempfilename += "__temp"; { BufferedFile fp{tempfilename.c_str(), BufferedFile::WRITE}; if (!fp) { A2_LOG_ERROR(fmt("Cannot create cookie file %s", filename.c_str())); return false; } for (auto& p : lruTracker_) { if (!p.second->writeCookie(fp)) { A2_LOG_ERROR(fmt("Failed to save cookies to %s", filename.c_str())); return false; } } if (fp.close() == EOF) { A2_LOG_ERROR(fmt("Failed to save cookies to %s", filename.c_str())); return false; } } if (File(tempfilename).renameTo(filename)) { return true; } else { A2_LOG_ERROR(fmt("Could not rename file %s as %s", tempfilename.c_str(), filename.c_str())); return false; } } } // namespace aria2