CookieManager.cpp   [plain text]


/*
 * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
 * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#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 <BlackBerryPlatformClient.h>
#include <BlackBerryPlatformExecutableMessage.h>
#include <BlackBerryPlatformMessageClient.h>
#include <BlackBerryPlatformNavigatorHandler.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 {

// Max count constants.
static const unsigned s_globalMaxCookieCount = 6000;
static const unsigned s_maxCookieCountPerHost = 60;
static const unsigned s_cookiesToDeleteWhenLimitReached = 60;
static const unsigned s_delayToStartCookieCleanup = 10;

static void flushCookiesOnExit(void)
{
    cookieManager().flushCookiesToBackingStore();
}

CookieManager& cookieManager()
{
    static CookieManager *cookieManager = 0;
    if (!cookieManager) {
        // Open the cookieJar now and get the backing store cookies to fill the manager.
        cookieManager = new CookieManager;
        cookieManager->m_cookieBackingStore->open(cookieManager->cookieJar());
        cookieManager->getBackingStoreCookies();
        CookieLog("CookieManager - Backingstore load complete.\n");

        atexit(&flushCookiesOnExit);
    }
    return *cookieManager;
}

CookieManager::CookieManager()
    : m_count(0)
    , m_privateMode(false)
    , m_shouldDumpAllCookies(false)
    , m_cookieJarFileName(pathByAppendingComponent(BlackBerry::Platform::Client::get()->getApplicationDataDirectory().c_str(), "/cookieCollection.db"))
    , m_policy(CookieStorageAcceptPolicyAlways)
    , m_cookieBackingStore(CookieDatabaseBackingStore::create())
    , m_limitTimer(this, &CookieManager::cookieLimitCleanUp)
{
}

CookieManager::~CookieManager()
{
    removeAllCookies(DoNotRemoveFromBackingStore);
    // FIXME: m_managerMap and the top layer protocolMaps are not properly deleted.
    // Do not delete any protocol maps to avoid double-deletion of the maps that are
    // being used for both secure and non-secure protocols; this leak is OK since
    // there's nothing important in the hashtable destructors, and the memory will be reclaimed on exit

    // FIXME: CookieDatabaseBackingStore is not deleted, only flushed
    // (currently the destructor is never called since this class is a
    // singleton; on exit, the db is flushed manually. This call is only here
    // as a fallback in case this class is made a non-singleton.
    m_cookieBackingStore->sendChangesToDatabaseSynchronously();
}

// Sorting logic is based on Cookie Spec RFC6265, section 5.4.2
static bool cookieSorter(ParsedCookie* a, ParsedCookie* b)
{
    if (a->path().length() == b->path().length())
        return a->creationTime() < b->creationTime();
    return a->path().length() > b->path().length();
}

// Returns whether the protocol supports domains
static bool shouldIgnoreDomain(const String protocol)
{
    // ignore domain security for file and local
    return protocol == "file" || protocol == "local";
}

void CookieManager::setCookies(const KURL& url, const String& value)
{
    CookieLog("CookieManager - Setting cookies");
    CookieParser parser(url);
    Vector<ParsedCookie*> cookies = parser.parse(value);

    for (size_t i = 0; i < cookies.size(); ++i) {
        ParsedCookie* cookie = cookies[i];
        if (!shouldRejectForSecurityReason(cookie, url)) {
            BackingStoreRemovalPolicy treatment = m_privateMode ? DoNotRemoveFromBackingStore : RemoveFromBackingStore;
            checkAndTreatCookie(cookie, treatment);
        } else
            delete cookie;
    }
}

bool CookieManager::shouldRejectForSecurityReason(const ParsedCookie* cookie, const KURL& url)
{
    // We have to disable the following check because sites like Facebook and
    // Gmail currently do not follow the spec.
#if 0
    // Check if path attribute is a prefix of the request URI.
    if (!url.path().startsWith(cookie->path())) {
        LOG_ERROR("Cookie %s is rejected because its path does not math the URL %s\n", cookie->toString().utf8().data(), url.string().utf8().data());
        return true;
    }
#endif

    // ignore domain security if protocol doesn't have domains
    if (shouldIgnoreDomain(cookie->protocol()))
        return false;

    // Reject Cookie if domain is empty
    if (!cookie->domain().length())
        return true;

    // If an explicitly specified value does not start with a dot, the user agent supplies. (RFC 2965 3.2.2)
    // Domain: Defaults to the effective request-host. There is no dot at the beginning of request-host. (RFC 2965 3.3.1)
    if (cookie->domain()[0] == '.') {
        // Check if the domain contains an embedded dot.
        size_t dotPosition = cookie->domain().find(".", 1);
        if (dotPosition == notFound || dotPosition == cookie->domain().length()) {
            LOG_ERROR("Cookie %s is rejected because its domain does not contain an embedded dot.\n", cookie->toString().utf8().data());
            return true;
        }
    }

    // The request host should domain match the Domain attribute.
    // Domain string starts with a dot, so a.b.com should domain match .a.b.com.
    // add a "." at beginning of host name, because it can handle many cases such as
    // a.b.com matches b.com, a.b.com matches .B.com and a.b.com matches .A.b.Com
    // and so on.
    String hostDomainName = url.host();
    hostDomainName = hostDomainName.startsWith('.') ? hostDomainName : "." + hostDomainName;
    if (!hostDomainName.endsWith(cookie->domain(), false)) {
        LOG_ERROR("Cookie %s is rejected because its domain does not domain match the URL %s\n", cookie->toString().utf8().data(), url.string().utf8().data());
        return true;
    }
    // We should check for an embedded dot in the portion of string in the host not in the domain
    // but to match firefox behaviour we do not.

    return false;
}

String CookieManager::getCookie(const KURL& url, CookieFilter filter) const
{
    Vector<ParsedCookie*> rawCookies;
    rawCookies.reserveInitialCapacity(s_maxCookieCountPerHost);

    // Retrieve cookies related to this url
    getRawCookies(rawCookies, url, filter);

    CookieLog("CookieManager - there are %d cookies in raw cookies\n", rawCookies.size());

    // Generate the cookie header string using the retrieved cookies
    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()
{
    CookieLog("CookieManager - generateHtmlFragmentForCookies\n");

    Vector<ParsedCookie*> cookieCandidates;
    for (HashMap<String, CookieMap*>::iterator it = m_managerMap.begin(); it != m_managerMap.end(); ++it)
        it->second->getAllChildCookies(&cookieCandidates);

    String result;
    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<ParsedCookie*> &stackOfCookies, const KURL& requestURL, CookieFilter filter) const
{
    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());

    bool specialCaseForLocal = (requestURL.protocolIs("local") || requestURL.protocolIs("file")) && m_shouldDumpAllCookies;
    bool isConnectionSecure = requestURL.protocolIs("https") || requestURL.protocolIs("wss") || specialCaseForLocal;

    Vector<ParsedCookie*> cookieCandidates;
    Vector<CookieMap*> protocolsToSearch;

    if (specialCaseForLocal)
        copyValuesToVector(m_managerMap, protocolsToSearch);
    else {
        protocolsToSearch.append(m_managerMap.get(requestURL.protocol()));
        // FIXME: this is a hack for webworks apps; RFC 6265 says "Cookies do not provide isolation by scheme"
        // so we should not be checking protocols at all. See PR 135595
        if (m_shouldDumpAllCookies) {
            protocolsToSearch.append(m_managerMap.get("file"));
            protocolsToSearch.append(m_managerMap.get("local"));
       }
    }

    Vector<String> delimitedHost;
    requestURL.host().lower().split(".", true, delimitedHost);

    // Go through all the protocol trees that we need to search for
    // and get all cookies that are valid for this domain
    for (size_t k = 0; k < protocolsToSearch.size(); k++) {
        CookieMap* currentMap = protocolsToSearch[k];

        // if no cookies exist for this protocol, break right away
        if (!currentMap)
            continue;

        CookieLog("CookieManager - looking at protocol map %s \n", currentMap->getName().utf8().data());

        // Special case for local and files - because WebApps expect to get ALL cookies from the backing-store on local protocol
        if (specialCaseForLocal) {
            CookieLog("CookieManager - special case find in protocol map - %s\n", currentMap->getName().utf8().data());
            currentMap->getAllChildCookies(&cookieCandidates);
        } else {
            // Get cookies from the null domain map
            currentMap->getAllCookies(&cookieCandidates);

            // Get cookies from the valid domain maps
            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 this subdomain/domain does not exist in our mapping then we simply exit
                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) {
        ParsedCookie* cookie = cookieCandidates[i];

        // According to the path-matches rules in RFC6265, section 5.1.4,
        // we should add a '/' at the end of cookie-path for comparison if the cookie-path is not end with '/'.
        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 += "/";

        // Only secure connections have access to secure cookies. Unless specialCaseForLocal is true
        // Get the cookies filtering out HttpOnly cookies if requested.
        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->second->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(ParsedCookie* candidateCookie, BackingStoreRemovalPolicy postToBackingStore)
{
    CookieLog("CookieManager - checkAndTreatCookie - processing url with domain - %s & protocol %s\n", candidateCookie->domain().utf8().data(), candidateCookie->protocol().utf8().data());

    const bool ignoreDomain = shouldIgnoreDomain(candidateCookie->protocol());

    // Determine which protocol tree to add the cookie to. Create one if necessary.
    CookieMap* curMap = 0;
    if (m_managerMap.contains(candidateCookie->protocol()))
        curMap = m_managerMap.get(candidateCookie->protocol());
    else {
        // Check if it is a secure version, if it is, link it to the non-secure version
        // Link curMap to the new protocol as well as the old one if it doesn't exist
        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 protocol support domain, we have to traverse the domain tree to find the right
    // cookieMap to handle with
    if (!ignoreDomain)
        curMap = findOrCreateCookieMap(curMap, candidateCookie->domain(), candidateCookie->hasExpired());

    // Now that we have the proper map for this cookie, we can modify it
    // If cookie does not exist and has expired, delete it
    // If cookie exists and it has expired, so we must remove it from the map, if not update it
    // If cookie expired and came from the BackingStore (therefore does not exist), we have to remove from database
    // If cookie does not exist & it's valid, add it to the current map

    if (candidateCookie->hasExpired() || candidateCookie->isForceExpired()) {
        // Special case for getBackingStoreCookies() to catch expired cookies
        if (postToBackingStore == BackingStoreCookieEntry)
            m_cookieBackingStore->remove(candidateCookie);
        else if (curMap) {
            // RemoveCookie will return 0 if the cookie doesn't exist.
            ParsedCookie* expired = curMap->removeCookie(candidateCookie);
            // Cookie is useless, Remove the cookie from the backingstore if it exists.
            // Backup check for BackingStoreCookieEntry incase someone incorrectly uses this enum.
            if (expired && postToBackingStore != BackingStoreCookieEntry && !expired->isSession()) {
                CookieLog("CookieManager - expired cookie is nonsession, deleting from db");
                m_cookieBackingStore->remove(expired);
            }
            delete expired;

        } else
            delete candidateCookie;
    } else {
        ASSERT(curMap);
        addCookieToMap(curMap, candidateCookie, postToBackingStore);
    }
}

void CookieManager::addCookieToMap(CookieMap* targetMap, ParsedCookie* candidateCookie, BackingStoreRemovalPolicy postToBackingStore)
{
    ParsedCookie* prevCookie = targetMap->addOrReplaceCookie(candidateCookie);
    if (prevCookie) {

        CookieLog("CookieManager - updating new cookie - %s.\n", candidateCookie->toString().utf8().data());

        // A cookie was replaced in targetMap.
        // If old cookie is non-session and new one is, we have to delete it from backingstore
        // If new cookie is non-session and old one is, we have to add it to backingstore
        // If both sessions are non-session, then we update it in the backingstore
        bool newIsSession = candidateCookie->isSession();
        bool oldIsSession = prevCookie->isSession();

        if (postToBackingStore == RemoveFromBackingStore) {
            if (!newIsSession && !oldIsSession)
                m_cookieBackingStore->update(candidateCookie);
            else if (newIsSession && !oldIsSession) {
                // Must manually decrease the counter because it was not counted when
                // the cookie was removed in cookieVector.
                removedCookie();
                m_cookieBackingStore->remove(prevCookie);
            } else if (!newIsSession && oldIsSession) {
                // Must manually increase the counter because it was not counted when
                // the cookie was added in cookieVector.
                addedCookie();
                m_cookieBackingStore->insert(candidateCookie);
            }
        }
        delete prevCookie;
        return;
    }

    CookieLog("CookieManager - adding new cookie - %s.\n", candidateCookie->toString().utf8().data());

    ParsedCookie* oldestCookie = 0;
    // Check if we have not reached the per cookie domain limit.
    // If that is not true, we check if the global limit has been reached if backingstore mode is on
    // Two points:
    // 1) We only do a global check if backingstore mode is on because the global cookie limit only
    //    counts session cookies that are saved in the database. If the user goes over the limit
    //    when they are in private mode, we know that the total cookie limit will be under the limit
    //    once the user goes back to normal mode (memory deleted and reloaded from the database)
    // 2) We use else if for this statement because if we remove a cookie in the 1st statement
    //    then it means the global count will never exceed the limit

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

    // Only add non session cookie to the backing store.
    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);
    }
    if (oldestCookie)
        delete oldestCookie;
}

void CookieManager::getBackingStoreCookies()
{
    // This method should be called just after having created the cookieManager
    // NEVER afterwards!
    ASSERT(!m_count);

    Vector<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) {
        ParsedCookie* newCookie = cookies[i];
        checkAndTreatCookie(newCookie, BackingStoreCookieEntry);
    }
}

void CookieManager::setPrivateMode(const bool mode)
{
    if (m_privateMode == mode)
        return;

    m_privateMode = mode;
    if (!mode) {
        removeAllCookies(DoNotRemoveFromBackingStore);
        getBackingStoreCookies();
    }
}

CookieMap* CookieManager::findOrCreateCookieMap(CookieMap* protocolMap, const String& domain, bool findOnly)
{
    // Explode the domain with the '.' delimiter
    Vector<String> delimitedHost;
    domain.split(".", delimitedHost);

    CookieMap* curMap = protocolMap;
    size_t hostSize = delimitedHost.size();

    CookieLog("CookieManager - looking at protocol map %s \n", protocolMap->getName().utf8().data());

    // Find & create necessary CookieMaps by traversing down the domain tree
    // Each CookieMap represent a subsection of the domain, delimited by "."
    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 (findOnly)
                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)
{
    // We get all cookies from all domains that domain matches the request domain
    // and delete any cookies with the specified name that path matches the request path
    Vector<ParsedCookie*> results;
    getRawCookies(results, url, WithHttpOnlyCookies);
    // Delete the cookies that path matches the request path
    for (size_t i = 0; i < results.size(); i++) {
        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);

    // Call the database to delete 'amountToDelete' of cookies
    Vector<ParsedCookie*> cookiesToDelete;
    cookiesToDelete.reserveInitialCapacity(amountToDelete);

    CookieLimitLog("CookieManager - Calling database to clean up");
    m_cookieBackingStore->getCookiesFromDatabase(cookiesToDelete, amountToDelete);

    // Cookies are ordered in ASC order by lastAccessed
    for (size_t i = 0; i < amountToDelete; ++i) {
        // Expire them and call checkandtreat to delete them from memory and database
        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.");
}

void CookieManager::flushCookiesToBackingStore()
{
    CookieLog("CookieManager - flushCookiesToBackingStore starting.\n");
    // This is called from shutdown, so we need to be sure the OS doesn't kill us before the db write finishes.
    // Once should be enough since this extends terimination by 2 seconds.
    BlackBerry::Platform::NavigatorHandler::sendExtendTerminate();
    m_cookieBackingStore->sendChangesToDatabaseSynchronously();
    CookieLog("CookieManager - flushCookiesToBackingStore finished.\n");
}

} // namespace WebCore