#define ENABLE_COOKIE_DEBUG 0
#define ENABLE_COOKIE_SUPER_VERBOSE_DEBUG 0
#define ENABLE_COOKIE_LIMIT_DEBUG 0
#include "config.h"
#include "CookieManager.h"
#include "CookieDatabaseBackingStore.h"
#include "CookieParser.h"
#include "FileSystem.h"
#include "Logging.h"
#include "WebSettings.h"
#include <BlackBerryPlatformExecutableMessage.h>
#include <BlackBerryPlatformMessageClient.h>
#include <BlackBerryPlatformNavigatorHandler.h>
#include <BlackBerryPlatformSettings.h>
#include <network/DomainTools.h>
#include <stdlib.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
#if ENABLE_COOKIE_DEBUG
#include <BlackBerryPlatformLog.h>
#endif
#if ENABLE_COOKIE_SUPER_VERBOSE_DEBUG
#define CookieLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
#else
#define CookieLog(format, ...)
#endif // ENABLE_COOKIE_SUPER_VERBOSE_DEBUG
#if ENABLE_COOKIE_LIMIT_DEBUG
#define CookieLimitLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
#else
#define CookieLimitLog(format, ...)
#endif // ENABLE_COOKIE_LIMIT_DEBUG
namespace WebCore {
static const unsigned s_globalMaxCookieCount = 6000;
static const unsigned s_maxCookieCountPerHost = 60;
static const unsigned s_cookiesToDeleteWhenLimitReached = 60;
static const unsigned s_delayToStartCookieCleanup = 10;
CookieManager& cookieManager()
{
static CookieManager *cookieManager = 0;
if (!cookieManager) {
cookieManager = new CookieManager;
cookieManager->m_cookieBackingStore->open(cookieManager->cookieJar());
}
return *cookieManager;
}
CookieManager::CookieManager()
: m_count(0)
, m_privateMode(false)
, m_shouldDumpAllCookies(false)
, m_syncedWithDatabase(false)
, m_cookieJarFileName(pathByAppendingComponent(BlackBerry::Platform::Settings::instance()->applicationDataDirectory().c_str(), "/cookieCollection.db"))
, m_policy(CookieStorageAcceptPolicyAlways)
, m_cookieBackingStore(CookieDatabaseBackingStore::create())
, m_limitTimer(this, &CookieManager::cookieLimitCleanUp)
{
}
CookieManager::~CookieManager()
{
removeAllCookies(DoNotRemoveFromBackingStore);
m_cookieBackingStore->sendChangesToDatabaseSynchronously();
}
static bool cookieSorter(PassRefPtr<ParsedCookie> a, PassRefPtr<ParsedCookie> b)
{
if (a->path().length() == b->path().length())
return a->creationTime() < b->creationTime();
return a->path().length() > b->path().length();
}
static bool shouldIgnoreScheme(const String& protocol)
{
return protocol == "file" || protocol == "local";
}
void CookieManager::setCookies(const KURL& url, const String& value, CookieFilter filter)
{
if (!m_syncedWithDatabase && !m_privateMode)
m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
CookieLog("CookieManager - Setting cookies");
CookieParser parser(url);
Vector<RefPtr<ParsedCookie> > cookies = parser.parse(value);
for (size_t i = 0; i < cookies.size(); ++i) {
BackingStoreRemovalPolicy treatment = m_privateMode ? DoNotRemoveFromBackingStore : RemoveFromBackingStore;
checkAndTreatCookie(cookies[i], treatment, filter);
}
}
void CookieManager::setCookies(const KURL& url, const Vector<String>& cookies, CookieFilter filter)
{
if (!m_syncedWithDatabase && !m_privateMode)
m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
CookieLog("CookieManager - Setting cookies");
CookieParser parser(url);
for (size_t i = 0; i < cookies.size(); ++i) {
BackingStoreRemovalPolicy treatment = m_privateMode ? DoNotRemoveFromBackingStore : RemoveFromBackingStore;
if (RefPtr<ParsedCookie> parsedCookie = parser.parseOneCookie(cookies[i]))
checkAndTreatCookie(parsedCookie, treatment, filter);
}
}
String CookieManager::getCookie(const KURL& url, CookieFilter filter) const
{
if (!m_syncedWithDatabase && !m_privateMode)
m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
Vector<RefPtr<ParsedCookie> > rawCookies;
rawCookies.reserveInitialCapacity(s_maxCookieCountPerHost);
getRawCookies(rawCookies, url, filter);
CookieLog("CookieManager - there are %d cookies in raw cookies\n", rawCookies.size());
StringBuilder cookieStringBuilder;
cookieStringBuilder.reserveCapacity(512);
size_t cookieSize = rawCookies.size();
for (size_t i = 0; i < cookieSize; i++) {
cookieStringBuilder.append(rawCookies[i]->toNameValuePair());
if (i != cookieSize-1)
cookieStringBuilder.append("; ");
}
CookieLog("CookieManager - cookieString is - %s\n", cookieStringBuilder.toString().utf8().data());
return cookieStringBuilder.toString();
}
String CookieManager::generateHtmlFragmentForCookies()
{
if (!m_syncedWithDatabase && !m_privateMode)
m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
CookieLog("CookieManager - generateHtmlFragmentForCookies\n");
Vector<RefPtr<ParsedCookie> > cookieCandidates;
for (HashMap<String, CookieMap*>::iterator it = m_managerMap.begin(); it != m_managerMap.end(); ++it)
it->value->getAllChildCookies(&cookieCandidates);
String result;
RefPtr<ParsedCookie> cookie = 0;
result.append(String("<table style=\"word-wrap:break-word\" cellSpacing=\"0\" cellPadding=\"0\" border=\"1\"><tr><th>Domain</th><th>Path</th><th>Protocol</th><th>Name</th><th>Value</th><th>Secure</th><th>HttpOnly</th><th>Session</th></tr>"));
for (size_t i = 0; i < cookieCandidates.size(); ++i) {
cookie = cookieCandidates[i];
result.append(String("<tr><td align=\"center\">"));
result.append(cookie->domain());
result.append(String("<td align=\"center\">"));
result.append(cookie->path());
result.append(String("<td align=\"center\">"));
result.append(cookie->protocol());
result.append(String("<td align=\"center\">"));
result.append(cookie->name());
result.append(String("<td align=\"center\" style= \"word-break:break-all\">"));
result.append(cookie->value());
result.append(String("<td align=\"center\">"));
result.append(String(cookie->isSecure() ? "Yes" : "No"));
result.append(String("<td align=\"center\">"));
result.append(String(cookie->isHttpOnly() ? "Yes" : "No"));
result.append(String("<td align=\"center\">"));
result.append(String(cookie->isSession() ? "Yes" : "No"));
result.append(String("</td></tr>"));
}
result.append(String("</table>"));
return result;
}
void CookieManager::getRawCookies(Vector<RefPtr<ParsedCookie> > &stackOfCookies, const KURL& requestURL, CookieFilter filter) const
{
if (!m_syncedWithDatabase && !m_privateMode)
m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
CookieLog("CookieManager - getRawCookies - processing url with domain - %s & protocol: %s & path: %s\n", requestURL.host().utf8().data(), requestURL.protocol().utf8().data(), requestURL.path().utf8().data());
const bool invalidScheme = shouldIgnoreScheme(requestURL.protocol());
const bool specialCaseForWebWorks = invalidScheme && m_shouldDumpAllCookies;
const bool isConnectionSecure = requestURL.protocolIs("https") || requestURL.protocolIs("wss") || specialCaseForWebWorks;
Vector<RefPtr<ParsedCookie> > cookieCandidates;
Vector<CookieMap*> protocolsToSearch;
CookieMap* targetMap = m_managerMap.get(requestURL.protocol());
if (!targetMap && isConnectionSecure) {
CookieLog("CookieManager - special case: secure protocol are not linked yet.");
if (requestURL.protocolIs("https"))
targetMap = m_managerMap.get("http");
else if (requestURL.protocolIs("wss"))
targetMap = m_managerMap.get("ws");
}
if (specialCaseForWebWorks)
copyValuesToVector(m_managerMap, protocolsToSearch);
else if (invalidScheme)
return;
else {
protocolsToSearch.append(targetMap);
if (m_shouldDumpAllCookies) {
protocolsToSearch.append(m_managerMap.get("file"));
protocolsToSearch.append(m_managerMap.get("local"));
}
}
Vector<String> delimitedHost;
BlackBerry::Platform::String canonicalIP = BlackBerry::Platform::getCanonicalIPFormat(requestURL.host());
if (!canonicalIP.empty())
delimitedHost.append(String(canonicalIP.c_str()));
else
requestURL.host().lower().split(".", true, delimitedHost);
for (size_t k = 0; k < protocolsToSearch.size(); k++) {
CookieMap* currentMap = protocolsToSearch[k];
if (!currentMap)
continue;
CookieLog("CookieManager - looking at protocol map %s \n", currentMap->getName().utf8().data());
if (specialCaseForWebWorks) {
CookieLog("CookieManager - special case find in protocol map - %s\n", currentMap->getName().utf8().data());
currentMap->getAllChildCookies(&cookieCandidates);
} else {
currentMap->getAllCookies(&cookieCandidates);
if (canonicalIP.empty()) {
CookieLog("CookieManager - looking for host-only cookies for host - %s", requestURL.host().utf8().data());
CookieMap* hostMap = currentMap->getSubdomainMap(requestURL.host());
if (hostMap)
hostMap->getAllCookies(&cookieCandidates);
}
int i = delimitedHost.size() - 1;
while (i >= 0) {
CookieLog("CookieManager - finding %s in currentmap\n", delimitedHost[i].utf8().data());
currentMap = currentMap->getSubdomainMap(delimitedHost[i]);
if (!currentMap) {
CookieLog("CookieManager - cannot find next map exiting the while loop.\n");
break;
}
CookieLog("CookieManager - found the map, grabbing cookies from this map\n");
currentMap->getAllCookies(&cookieCandidates);
i--;
}
}
}
CookieLog("CookieManager - there are %d cookies in candidate\n", cookieCandidates.size());
for (size_t i = 0; i < cookieCandidates.size(); ++i) {
RefPtr<ParsedCookie> cookie = cookieCandidates[i];
String path = cookie->path();
CookieLog("CookieManager - comparing cookie path %s (len %d) to request path %s (len %d)", path.utf8().data(), path.length(), requestURL.path().utf8().data(), path.length());
if (!equalIgnoringCase(path, requestURL.path()) && !path.endsWith("/", false))
path = path + "/";
if (requestURL.path().startsWith(path, false) && (isConnectionSecure || !cookie->isSecure()) && (filter == WithHttpOnlyCookies || !cookie->isHttpOnly())) {
CookieLog("CookieManager - cookie chosen - %s\n", cookie->toString().utf8().data());
cookie->setLastAccessed(currentTime());
stackOfCookies.append(cookie);
}
}
std::stable_sort(stackOfCookies.begin(), stackOfCookies.end(), cookieSorter);
}
void CookieManager::removeAllCookies(BackingStoreRemovalPolicy backingStoreRemoval)
{
HashMap<String, CookieMap*>::iterator first = m_managerMap.begin();
HashMap<String, CookieMap*>::iterator end = m_managerMap.end();
for (HashMap<String, CookieMap*>::iterator it = first; it != end; ++it)
it->value->deleteAllCookiesAndDomains();
if (backingStoreRemoval == RemoveFromBackingStore)
m_cookieBackingStore->removeAll();
m_count = 0;
}
void CookieManager::setCookieJar(const char* fileName)
{
m_cookieJarFileName = String(fileName);
m_cookieBackingStore->open(m_cookieJarFileName);
}
void CookieManager::checkAndTreatCookie(PassRefPtr<ParsedCookie> prpCandidateCookie, BackingStoreRemovalPolicy postToBackingStore, CookieFilter filter)
{
RefPtr<ParsedCookie> candidateCookie = prpCandidateCookie;
CookieLog("CookieManager - checkAndTreatCookie - processing url with domain - %s & protocol %s\n", candidateCookie->domain().utf8().data(), candidateCookie->protocol().utf8().data());
if ((filter == NoHttpOnlyCookie && candidateCookie->isHttpOnly()) || (shouldIgnoreScheme(candidateCookie->protocol()) && !m_shouldDumpAllCookies))
return;
const bool ignoreDomain = (candidateCookie->protocol() == "file" || candidateCookie->protocol() == "local");
CookieMap* curMap = 0;
if (m_managerMap.contains(candidateCookie->protocol()))
curMap = m_managerMap.get(candidateCookie->protocol());
else {
if (candidateCookie->protocol() == "https") {
curMap = m_managerMap.get("http");
if (!curMap) {
curMap = new CookieMap("http");
m_managerMap.add("http", curMap);
}
} else if (candidateCookie->protocol() == "wss") {
curMap = m_managerMap.get("ws");
if (!curMap) {
curMap = new CookieMap("ws");
m_managerMap.add("ws", curMap);
}
} else
curMap = new CookieMap(candidateCookie->protocol());
CookieLog("CookieManager - adding protocol cookiemap - %s\n", curMap->getName().utf8().data());
m_managerMap.add(candidateCookie->protocol(), curMap);
}
if (!ignoreDomain)
curMap = findOrCreateCookieMap(curMap, candidateCookie);
if (candidateCookie->hasExpired() || candidateCookie->isForceExpired()) {
if (postToBackingStore == BackingStoreCookieEntry)
m_cookieBackingStore->remove(candidateCookie);
else if (curMap) {
RefPtr<ParsedCookie> expired = curMap->removeCookie(candidateCookie, filter);
if (expired && postToBackingStore != BackingStoreCookieEntry && !expired->isSession()) {
CookieLog("CookieManager - expired cookie is nonsession, deleting from db");
m_cookieBackingStore->remove(expired);
}
}
} else {
ASSERT(curMap);
CookieLog("CookieManager - adding cookiemap - %s\n", curMap->getName().utf8().data());
addCookieToMap(curMap, candidateCookie, postToBackingStore, filter);
}
}
void CookieManager::addCookieToMap(CookieMap* targetMap, PassRefPtr<ParsedCookie> prpCandidateCookie, BackingStoreRemovalPolicy postToBackingStore, CookieFilter filter)
{
RefPtr<ParsedCookie> replacedCookie = 0;
RefPtr<ParsedCookie> candidateCookie = prpCandidateCookie;
if (!targetMap->addOrReplaceCookie(candidateCookie, replacedCookie, filter)) {
CookieLog("CookieManager - rejecting new cookie - %s.\n", candidateCookie->toString().utf8().data());
return;
}
if (replacedCookie) {
CookieLog("CookieManager - updating new cookie - %s.\n", candidateCookie->toString().utf8().data());
bool newIsSession = candidateCookie->isSession();
bool oldIsSession = replacedCookie->isSession();
if (postToBackingStore == RemoveFromBackingStore) {
if (!newIsSession && !oldIsSession)
m_cookieBackingStore->update(candidateCookie);
else if (newIsSession && !oldIsSession) {
removedCookie();
m_cookieBackingStore->remove(replacedCookie);
} else if (!newIsSession && oldIsSession) {
addedCookie();
m_cookieBackingStore->insert(candidateCookie);
}
}
return;
}
CookieLog("CookieManager - adding new cookie - %s.\n", candidateCookie->toString().utf8().data());
RefPtr<ParsedCookie> oldestCookie = 0;
CookieLimitLog("CookieManager - local count: %d global count: %d", targetMap->count(), m_count);
if (targetMap->count() > s_maxCookieCountPerHost) {
CookieLog("CookieManager - deleting oldest cookie from this map due to domain count.\n");
oldestCookie = targetMap->removeOldestCookie();
} else if (m_count > s_globalMaxCookieCount && (postToBackingStore != DoNotRemoveFromBackingStore)) {
CookieLimitLog("CookieManager - Global limit reached, initiate cookie limit clean up.");
initiateCookieLimitCleanUp();
}
if (postToBackingStore == RemoveFromBackingStore) {
if (oldestCookie && !oldestCookie->isSession()) {
CookieLog("CookieManager - oldestCookie exists, deleting it from backingstore and destructing.\n");
m_cookieBackingStore->remove(oldestCookie);
}
if (!candidateCookie->isSession())
m_cookieBackingStore->insert(candidateCookie);
}
}
void CookieManager::getBackingStoreCookies()
{
if (m_privateMode)
return;
if (m_count)
removeAllCookies(DoNotRemoveFromBackingStore);
Vector<RefPtr<ParsedCookie> > cookies;
m_cookieBackingStore->getCookiesFromDatabase(cookies);
CookieLog("CookieManager - Backingstore has %d cookies, loading them in memory now", cookies.size());
for (size_t i = 0; i < cookies.size(); ++i) {
RefPtr<ParsedCookie> newCookie = cookies[i];
if (BlackBerry::Platform::isIPAddress(newCookie->domain()))
newCookie->setDomain(newCookie->domain(), true);
checkAndTreatCookie(newCookie, BackingStoreCookieEntry);
}
CookieLog("CookieManager - Backingstore loading complete.");
m_syncedWithDatabase = true;
}
void CookieManager::setPrivateMode(bool privateMode)
{
if (m_privateMode == privateMode)
return;
m_privateMode = privateMode;
if (m_privateMode && !m_syncedWithDatabase)
return;
removeAllCookies(DoNotRemoveFromBackingStore);
if (!m_privateMode)
getBackingStoreCookies();
}
CookieMap* CookieManager::findOrCreateCookieMap(CookieMap* protocolMap, const PassRefPtr<ParsedCookie> candidateCookie)
{
Vector<String> delimitedHost;
if (candidateCookie->domainIsIPAddress() || candidateCookie->isHostOnly())
delimitedHost.append(candidateCookie->domain());
else
candidateCookie->domain().split(".", delimitedHost);
CookieMap* curMap = protocolMap;
size_t hostSize = delimitedHost.size();
CookieLog("CookieManager - looking at protocol map %s \n", protocolMap->getName().utf8().data());
int i = hostSize - 1;
while (i >= 0) {
CookieLog("CookieManager - finding %s in currentmap\n", delimitedHost[i].utf8().data());
CookieMap* nextMap = curMap->getSubdomainMap(delimitedHost[i]);
if (!nextMap) {
CookieLog("CookieManager - cannot find map\n");
if (candidateCookie->hasExpired())
return 0;
CookieLog("CookieManager - creating %s in currentmap %s\n", delimitedHost[i].utf8().data(), curMap->getName().utf8().data());
nextMap = new CookieMap(delimitedHost[i]);
CookieLog("CookieManager - adding subdomain to map\n");
curMap->addSubdomainMap(delimitedHost[i], nextMap);
}
curMap = nextMap;
i--;
}
return curMap;
}
void CookieManager::removeCookieWithName(const KURL& url, const String& cookieName)
{
if (!m_syncedWithDatabase && !m_privateMode) {
typedef void (WebCore::CookieManager::*FunctionType)(const KURL&, const String&);
BlackBerry::Platform::webKitThreadMessageClient()->dispatchMessage(
BlackBerry::Platform::createMethodCallMessage<FunctionType, CookieManager, const KURL, const String>(
&CookieManager::removeCookieWithName, this, url, cookieName));
return;
}
Vector<RefPtr<ParsedCookie> > results;
getRawCookies(results, url, WithHttpOnlyCookies);
for (size_t i = 0; i < results.size(); i++) {
RefPtr<ParsedCookie> cookie = results[i];
if (!equalIgnoringCase(cookie->name(), cookieName))
continue;
if (url.path().startsWith(cookie->path(), false)) {
cookie->forceExpire();
checkAndTreatCookie(cookie, RemoveFromBackingStore);
}
}
}
void CookieManager::initiateCookieLimitCleanUp()
{
if (!m_limitTimer.isActive()) {
CookieLog("CookieManager - Starting a timer for cookie cleanup");
m_limitTimer.startOneShot(s_delayToStartCookieCleanup);
} else {
#ifndef NDEBUG
CookieLog("CookieManager - Cookie cleanup timer already running");
#endif
}
}
void CookieManager::cookieLimitCleanUp(Timer<CookieManager>* timer)
{
ASSERT_UNUSED(timer, timer == &m_limitTimer);
CookieLimitLog("CookieManager - Starting cookie clean up");
size_t numberOfCookiesOverLimit = (m_count > s_globalMaxCookieCount) ? m_count - s_globalMaxCookieCount : 0;
size_t amountToDelete = s_cookiesToDeleteWhenLimitReached + numberOfCookiesOverLimit;
CookieLimitLog("CookieManager - Excess: %d Amount to Delete: %d", numberOfCookiesOverLimit, amountToDelete);
Vector<RefPtr<ParsedCookie> > cookiesToDelete;
cookiesToDelete.reserveInitialCapacity(amountToDelete);
CookieLimitLog("CookieManager - Calling database to clean up");
m_cookieBackingStore->getCookiesFromDatabase(cookiesToDelete, amountToDelete);
for (size_t i = 0; i < amountToDelete; ++i) {
RefPtr<ParsedCookie> newCookie = cookiesToDelete[i];
CookieLimitLog("CookieManager - Expire cookie: %s and delete", newCookie->toString().utf8().data());
newCookie->forceExpire();
checkAndTreatCookie(newCookie, RemoveFromBackingStore);
}
CookieLimitLog("CookieManager - Cookie clean up complete.");
}
}