1/*
2 * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
3 * Copyright (C) 2010, 2011, 2012, 2013 Research In Motion Limited. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#define ENABLE_COOKIE_DEBUG 0
28#define ENABLE_COOKIE_SUPER_VERBOSE_DEBUG 0
29#define ENABLE_COOKIE_LIMIT_DEBUG 0
30
31#include "config.h"
32#include "CookieManager.h"
33
34#include "CookieDatabaseBackingStore.h"
35#include "CookieParser.h"
36#include "FileSystem.h"
37#include "Logging.h"
38#include "WebSettings.h"
39#include <BlackBerryPlatformExecutableMessage.h>
40#include <BlackBerryPlatformMessageClient.h>
41#include <BlackBerryPlatformNavigatorHandler.h>
42#include <BlackBerryPlatformSettings.h>
43#include <network/DomainTools.h>
44#include <stdlib.h>
45#include <wtf/CurrentTime.h>
46#include <wtf/text/CString.h>
47#include <wtf/text/StringBuilder.h>
48#include <wtf/text/WTFString.h>
49
50#if ENABLE_COOKIE_DEBUG
51#include <BlackBerryPlatformLog.h>
52#endif
53
54#if ENABLE_COOKIE_SUPER_VERBOSE_DEBUG
55#define CookieLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
56#else
57#define CookieLog(format, ...)
58#endif // ENABLE_COOKIE_SUPER_VERBOSE_DEBUG
59
60#if ENABLE_COOKIE_LIMIT_DEBUG
61#define CookieLimitLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
62#else
63#define CookieLimitLog(format, ...)
64#endif // ENABLE_COOKIE_LIMIT_DEBUG
65
66namespace WebCore {
67
68// Max count constants.
69static const unsigned s_globalMaxCookieCount = 6000;
70static const unsigned s_maxCookieCountPerHost = 60;
71static const unsigned s_cookiesToDeleteWhenLimitReached = 60;
72static const unsigned s_delayToStartCookieCleanup = 10;
73
74CookieManager& cookieManager()
75{
76    static CookieManager *cookieManager = 0;
77    if (!cookieManager) {
78        // Open the cookieJar now and get the backing store cookies to fill the manager.
79        cookieManager = new CookieManager;
80        cookieManager->m_cookieBackingStore->open(cookieManager->cookieJar());
81    }
82    return *cookieManager;
83}
84
85CookieManager::CookieManager()
86    : m_count(0)
87    , m_privateMode(false)
88    , m_shouldDumpAllCookies(false)
89    , m_syncedWithDatabase(false)
90    , m_cookieJarFileName(pathByAppendingComponent(BlackBerry::Platform::Settings::instance()->applicationDataDirectory().c_str(), "/cookieCollection.db"))
91    , m_policy(CookieStorageAcceptPolicyAlways)
92    , m_cookieBackingStore(CookieDatabaseBackingStore::create())
93    , m_limitTimer(this, &CookieManager::cookieLimitCleanUp)
94{
95}
96
97CookieManager::~CookieManager()
98{
99    removeAllCookies(DoNotRemoveFromBackingStore);
100    // FIXME: m_managerMap and the top layer protocolMaps are not properly deleted.
101    // Do not delete any protocol maps to avoid double-deletion of the maps that are
102    // being used for both secure and non-secure protocols; this leak is OK since
103    // there's nothing important in the hashtable destructors, and the memory will be reclaimed on exit
104
105    // FIXME: CookieDatabaseBackingStore is not deleted, only flushed
106    // (currently the destructor is never called since this class is a
107    // singleton; on exit, the db is flushed manually. This call is only here
108    // as a fallback in case this class is made a non-singleton.
109    m_cookieBackingStore->sendChangesToDatabaseSynchronously();
110}
111
112// Sorting logic is based on Cookie Spec RFC6265, section 5.4.2
113static bool cookieSorter(PassRefPtr<ParsedCookie> a, PassRefPtr<ParsedCookie> b)
114{
115    if (a->path().length() == b->path().length())
116        return a->creationTime() < b->creationTime();
117    return a->path().length() > b->path().length();
118}
119
120// Return whether we should ignore the scheme
121static bool shouldIgnoreScheme(const String& protocol)
122{
123    // We want to ignore file and local schemes
124    return protocol == "file" || protocol == "local";
125}
126
127void CookieManager::setCookies(const KURL& url, const String& value, CookieFilter filter)
128{
129    // If the database hasn't been sync-ed at this point, force a sync load
130    if (!m_syncedWithDatabase && !m_privateMode)
131        m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
132
133    CookieLog("CookieManager - Setting cookies");
134    CookieParser parser(url);
135    Vector<RefPtr<ParsedCookie> > cookies = parser.parse(value);
136
137    for (size_t i = 0; i < cookies.size(); ++i) {
138        BackingStoreRemovalPolicy treatment = m_privateMode ? DoNotRemoveFromBackingStore : RemoveFromBackingStore;
139        checkAndTreatCookie(cookies[i], treatment, filter);
140    }
141}
142
143void CookieManager::setCookies(const KURL& url, const Vector<String>& cookies, CookieFilter filter)
144{
145    // If the database hasn't been sync-ed at this point, force a sync load
146    if (!m_syncedWithDatabase && !m_privateMode)
147        m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
148
149    CookieLog("CookieManager - Setting cookies");
150    CookieParser parser(url);
151    for (size_t i = 0; i < cookies.size(); ++i) {
152        BackingStoreRemovalPolicy treatment = m_privateMode ? DoNotRemoveFromBackingStore : RemoveFromBackingStore;
153        if (RefPtr<ParsedCookie> parsedCookie = parser.parseOneCookie(cookies[i]))
154            checkAndTreatCookie(parsedCookie, treatment, filter);
155    }
156}
157
158String CookieManager::getCookie(const KURL& url, CookieFilter filter) const
159{
160    // If the database hasn't been sync-ed at this point, force a sync load
161    if (!m_syncedWithDatabase && !m_privateMode)
162        m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
163
164    Vector<RefPtr<ParsedCookie> > rawCookies;
165    rawCookies.reserveInitialCapacity(s_maxCookieCountPerHost);
166
167    // Retrieve cookies related to this url
168    getRawCookies(rawCookies, url, filter);
169
170    CookieLog("CookieManager - there are %d cookies in raw cookies\n", rawCookies.size());
171
172    // Generate the cookie header string using the retrieved cookies
173    StringBuilder cookieStringBuilder;
174    cookieStringBuilder.reserveCapacity(512);
175    size_t cookieSize = rawCookies.size();
176    for (size_t i = 0; i < cookieSize; i++) {
177        cookieStringBuilder.append(rawCookies[i]->toNameValuePair());
178        if (i != cookieSize-1)
179            cookieStringBuilder.append("; ");
180    }
181
182    CookieLog("CookieManager - cookieString is - %s\n", cookieStringBuilder.toString().utf8().data());
183
184    return cookieStringBuilder.toString();
185}
186
187String CookieManager::generateHtmlFragmentForCookies()
188{
189    // If the database hasn't been sync-ed at this point, force a sync load
190    if (!m_syncedWithDatabase && !m_privateMode)
191        m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
192
193    CookieLog("CookieManager - generateHtmlFragmentForCookies\n");
194
195    Vector<RefPtr<ParsedCookie> > cookieCandidates;
196    for (HashMap<String, CookieMap*>::iterator it = m_managerMap.begin(); it != m_managerMap.end(); ++it)
197        it->value->getAllChildCookies(&cookieCandidates);
198
199    String result;
200    RefPtr<ParsedCookie> cookie = 0;
201    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>"));
202    for (size_t i = 0; i < cookieCandidates.size(); ++i) {
203        cookie = cookieCandidates[i];
204        result.append(String("<tr><td align=\"center\">"));
205        result.append(cookie->domain());
206        result.append(String("<td align=\"center\">"));
207        result.append(cookie->path());
208        result.append(String("<td align=\"center\">"));
209        result.append(cookie->protocol());
210        result.append(String("<td align=\"center\">"));
211        result.append(cookie->name());
212        result.append(String("<td align=\"center\" style= \"word-break:break-all\">"));
213        result.append(cookie->value());
214        result.append(String("<td align=\"center\">"));
215        result.append(String(cookie->isSecure() ? "Yes" : "No"));
216        result.append(String("<td align=\"center\">"));
217        result.append(String(cookie->isHttpOnly() ? "Yes" : "No"));
218        result.append(String("<td align=\"center\">"));
219        result.append(String(cookie->isSession() ? "Yes" : "No"));
220        result.append(String("</td></tr>"));
221    }
222    result.append(String("</table>"));
223    return result;
224}
225
226void CookieManager::getRawCookies(Vector<RefPtr<ParsedCookie> > &stackOfCookies, const KURL& requestURL, CookieFilter filter) const
227{
228    // Force a sync load of the database
229    if (!m_syncedWithDatabase && !m_privateMode)
230        m_cookieBackingStore->openAndLoadDatabaseSynchronously(cookieJar());
231
232    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());
233
234    const bool invalidScheme = shouldIgnoreScheme(requestURL.protocol());
235    const bool specialCaseForWebWorks = invalidScheme && m_shouldDumpAllCookies;
236    const bool isConnectionSecure = requestURL.protocolIs("https") || requestURL.protocolIs("wss") || specialCaseForWebWorks;
237
238    Vector<RefPtr<ParsedCookie> > cookieCandidates;
239    Vector<CookieMap*> protocolsToSearch;
240
241    // Special Case: If a server sets a "secure" cookie over a non-secure channel and tries to access the cookie
242    // over a secure channel, it will not succeed because the secure protocol isn't mapped to the insecure protocol yet.
243    // Set the map to the non-secure version, so it'll search the mapping for a secure cookie.
244    CookieMap* targetMap = m_managerMap.get(requestURL.protocol());
245    if (!targetMap && isConnectionSecure) {
246        CookieLog("CookieManager - special case: secure protocol are not linked yet.");
247        if (requestURL.protocolIs("https"))
248            targetMap = m_managerMap.get("http");
249        else if (requestURL.protocolIs("wss"))
250            targetMap = m_managerMap.get("ws");
251    }
252
253    // Decide which scheme tree we should look at.
254    // Return on invalid schemes. cookies are currently disabled on file and local.
255    // We only want to enable them for WebWorks that enabled a special flag.
256    if (specialCaseForWebWorks)
257        copyValuesToVector(m_managerMap, protocolsToSearch);
258    else if (invalidScheme)
259        return;
260    else {
261        protocolsToSearch.append(targetMap);
262        // FIXME: this is a hack for webworks apps; RFC 6265 says "Cookies do not provide isolation by scheme"
263        // so we should not be checking protocols at all. See PR 135595
264        if (m_shouldDumpAllCookies) {
265            protocolsToSearch.append(m_managerMap.get("file"));
266            protocolsToSearch.append(m_managerMap.get("local"));
267        }
268    }
269
270    Vector<String> delimitedHost;
271
272    // IP addresses are stored in a particular format (due to ipv6). Reduce the ip address so we can match
273    // it with the one in memory.
274    BlackBerry::Platform::String canonicalIP = BlackBerry::Platform::getCanonicalIPFormat(requestURL.host());
275    if (!canonicalIP.empty())
276        delimitedHost.append(String(canonicalIP.c_str()));
277    else
278        requestURL.host().lower().split(".", true, delimitedHost);
279
280    // Go through all the protocol trees that we need to search for
281    // and get all cookies that are valid for this domain
282    for (size_t k = 0; k < protocolsToSearch.size(); k++) {
283        CookieMap* currentMap = protocolsToSearch[k];
284
285        // if no cookies exist for this protocol, break right away
286        if (!currentMap)
287            continue;
288
289        CookieLog("CookieManager - looking at protocol map %s \n", currentMap->getName().utf8().data());
290
291        // Special case for local and files - because WebApps expect to get ALL cookies from the backing-store on local protocol
292        if (specialCaseForWebWorks) {
293            CookieLog("CookieManager - special case find in protocol map - %s\n", currentMap->getName().utf8().data());
294            currentMap->getAllChildCookies(&cookieCandidates);
295        } else {
296            // Get cookies from the null domain map
297            currentMap->getAllCookies(&cookieCandidates);
298
299            // Get cookies from Host-only cookies
300            if (canonicalIP.empty()) {
301                CookieLog("CookieManager - looking for host-only cookies for host - %s", requestURL.host().utf8().data());
302                CookieMap* hostMap = currentMap->getSubdomainMap(requestURL.host());
303                if (hostMap)
304                    hostMap->getAllCookies(&cookieCandidates);
305            }
306
307            // Get cookies from the valid domain maps
308            int i = delimitedHost.size() - 1;
309            while (i >= 0) {
310                CookieLog("CookieManager - finding %s in currentmap\n", delimitedHost[i].utf8().data());
311                currentMap = currentMap->getSubdomainMap(delimitedHost[i]);
312                // if this subdomain/domain does not exist in our mapping then we simply exit
313                if (!currentMap) {
314                    CookieLog("CookieManager - cannot find next map exiting the while loop.\n");
315                    break;
316                }
317                CookieLog("CookieManager - found the map, grabbing cookies from this map\n");
318                currentMap->getAllCookies(&cookieCandidates);
319                i--;
320            }
321        }
322    }
323
324    CookieLog("CookieManager - there are %d cookies in candidate\n", cookieCandidates.size());
325
326    for (size_t i = 0; i < cookieCandidates.size(); ++i) {
327        RefPtr<ParsedCookie> cookie = cookieCandidates[i];
328
329        // According to the path-matches rules in RFC6265, section 5.1.4,
330        // we should add a '/' at the end of cookie-path for comparison if the cookie-path is not end with '/'.
331        String path = cookie->path();
332        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());
333        if (!equalIgnoringCase(path, requestURL.path()) && !path.endsWith("/", false))
334            path = path + "/";
335
336        // Only secure connections have access to secure cookies. Unless specialCaseForWebWorks is true.
337        // Get the cookies filtering out HttpOnly cookies if requested.
338        if (requestURL.path().startsWith(path, false) && (isConnectionSecure || !cookie->isSecure()) && (filter == WithHttpOnlyCookies || !cookie->isHttpOnly())) {
339            CookieLog("CookieManager - cookie chosen - %s\n", cookie->toString().utf8().data());
340            cookie->setLastAccessed(currentTime());
341            stackOfCookies.append(cookie);
342        }
343    }
344
345    std::stable_sort(stackOfCookies.begin(), stackOfCookies.end(), cookieSorter);
346}
347
348void CookieManager::removeAllCookies(BackingStoreRemovalPolicy backingStoreRemoval)
349{
350    HashMap<String, CookieMap*>::iterator first = m_managerMap.begin();
351    HashMap<String, CookieMap*>::iterator end = m_managerMap.end();
352    for (HashMap<String, CookieMap*>::iterator it = first; it != end; ++it)
353        it->value->deleteAllCookiesAndDomains();
354
355    if (backingStoreRemoval == RemoveFromBackingStore)
356        m_cookieBackingStore->removeAll();
357    m_count = 0;
358}
359
360void CookieManager::setCookieJar(const char* fileName)
361{
362    m_cookieJarFileName = String(fileName);
363    m_cookieBackingStore->open(m_cookieJarFileName);
364}
365
366void CookieManager::checkAndTreatCookie(PassRefPtr<ParsedCookie> prpCandidateCookie, BackingStoreRemovalPolicy postToBackingStore, CookieFilter filter)
367{
368    RefPtr<ParsedCookie> candidateCookie = prpCandidateCookie;
369    CookieLog("CookieManager - checkAndTreatCookie - processing url with domain - %s & protocol %s\n", candidateCookie->domain().utf8().data(), candidateCookie->protocol().utf8().data());
370
371    // Delete invalid cookies:
372    // 1) A cookie which is not from http shouldn't have a httpOnly property.
373    // 2) Cookies coming from schemes that we do not support and the special flag isn't on
374    if ((filter == NoHttpOnlyCookie && candidateCookie->isHttpOnly()) || (shouldIgnoreScheme(candidateCookie->protocol()) && !m_shouldDumpAllCookies))
375        return;
376
377    const bool ignoreDomain = (candidateCookie->protocol() == "file" || candidateCookie->protocol() == "local");
378
379    // Determine which protocol tree to add the cookie to. Create one if necessary.
380    CookieMap* curMap = 0;
381    if (m_managerMap.contains(candidateCookie->protocol()))
382        curMap = m_managerMap.get(candidateCookie->protocol());
383    else {
384        // Check if it is a secure version, if it is, link it to the non-secure version
385        // Link curMap to the new protocol as well as the old one if it doesn't exist
386        if (candidateCookie->protocol() == "https") {
387            curMap = m_managerMap.get("http");
388            if (!curMap) {
389                curMap = new CookieMap("http");
390                m_managerMap.add("http", curMap);
391            }
392        } else if (candidateCookie->protocol() == "wss") {
393            curMap = m_managerMap.get("ws");
394            if (!curMap) {
395                curMap = new CookieMap("ws");
396                m_managerMap.add("ws", curMap);
397            }
398        } else
399            curMap = new CookieMap(candidateCookie->protocol());
400
401        CookieLog("CookieManager - adding protocol cookiemap - %s\n", curMap->getName().utf8().data());
402
403        m_managerMap.add(candidateCookie->protocol(), curMap);
404    }
405
406    // If protocol support domain, we have to traverse the domain tree to find the right
407    // cookieMap to handle with
408    if (!ignoreDomain)
409        curMap = findOrCreateCookieMap(curMap, candidateCookie);
410
411    // Now that we have the proper map for this cookie, we can modify it
412    // If cookie does not exist and has expired, delete it
413    // If cookie exists and it has expired, so we must remove it from the map, if not update it
414    // If cookie expired and came from the BackingStore (therefore does not exist), we have to remove from database
415    // If cookie does not exist & it's valid, add it to the current map
416
417    if (candidateCookie->hasExpired() || candidateCookie->isForceExpired()) {
418        // Special case for getBackingStoreCookies() to catch expired cookies
419        if (postToBackingStore == BackingStoreCookieEntry)
420            m_cookieBackingStore->remove(candidateCookie);
421        else if (curMap) {
422            // RemoveCookie will return 0 if the cookie doesn't exist.
423            RefPtr<ParsedCookie> expired = curMap->removeCookie(candidateCookie, filter);
424            // Cookie is useless, Remove the cookie from the backingstore if it exists.
425            // Backup check for BackingStoreCookieEntry incase someone incorrectly uses this enum.
426            if (expired && postToBackingStore != BackingStoreCookieEntry && !expired->isSession()) {
427                CookieLog("CookieManager - expired cookie is nonsession, deleting from db");
428                m_cookieBackingStore->remove(expired);
429            }
430        }
431    } else {
432        ASSERT(curMap);
433        CookieLog("CookieManager - adding cookiemap - %s\n", curMap->getName().utf8().data());
434        addCookieToMap(curMap, candidateCookie, postToBackingStore, filter);
435    }
436}
437
438void CookieManager::addCookieToMap(CookieMap* targetMap, PassRefPtr<ParsedCookie> prpCandidateCookie, BackingStoreRemovalPolicy postToBackingStore, CookieFilter filter)
439{
440    RefPtr<ParsedCookie> replacedCookie = 0;
441    RefPtr<ParsedCookie> candidateCookie = prpCandidateCookie;
442
443    if (!targetMap->addOrReplaceCookie(candidateCookie, replacedCookie, filter)) {
444        CookieLog("CookieManager - rejecting new cookie - %s.\n", candidateCookie->toString().utf8().data());
445        return;
446    }
447
448    if (replacedCookie) {
449        CookieLog("CookieManager - updating new cookie - %s.\n", candidateCookie->toString().utf8().data());
450
451        // A cookie was replaced in targetMap.
452        // If old cookie is non-session and new one is, we have to delete it from backingstore
453        // If new cookie is non-session and old one is, we have to add it to backingstore
454        // If both sessions are non-session, then we update it in the backingstore
455        bool newIsSession = candidateCookie->isSession();
456        bool oldIsSession = replacedCookie->isSession();
457
458        if (postToBackingStore == RemoveFromBackingStore) {
459            if (!newIsSession && !oldIsSession)
460                m_cookieBackingStore->update(candidateCookie);
461            else if (newIsSession && !oldIsSession) {
462                // Must manually decrease the counter because it was not counted when
463                // the cookie was removed in cookieVector.
464                removedCookie();
465                m_cookieBackingStore->remove(replacedCookie);
466            } else if (!newIsSession && oldIsSession) {
467                // Must manually increase the counter because it was not counted when
468                // the cookie was added in cookieVector.
469                addedCookie();
470                m_cookieBackingStore->insert(candidateCookie);
471            }
472        }
473        return;
474    }
475
476    CookieLog("CookieManager - adding new cookie - %s.\n", candidateCookie->toString().utf8().data());
477
478    RefPtr<ParsedCookie> oldestCookie = 0;
479    // Check if we have not reached the per cookie domain limit.
480    // If that is not true, we check if the global limit has been reached if backingstore mode is on
481    // Two points:
482    // 1) We only do a global check if backingstore mode is on because the global cookie limit only
483    //    counts session cookies that are saved in the database. If the user goes over the limit
484    //    when they are in private mode, we know that the total cookie limit will be under the limit
485    //    once the user goes back to normal mode (memory deleted and reloaded from the database)
486    // 2) We use else if for this statement because if we remove a cookie in the 1st statement
487    //    then it means the global count will never exceed the limit
488
489    CookieLimitLog("CookieManager - local count: %d  global count: %d", targetMap->count(), m_count);
490    if (targetMap->count() > s_maxCookieCountPerHost) {
491        CookieLog("CookieManager - deleting oldest cookie from this map due to domain count.\n");
492        oldestCookie = targetMap->removeOldestCookie();
493    } else if (m_count > s_globalMaxCookieCount && (postToBackingStore != DoNotRemoveFromBackingStore)) {
494        CookieLimitLog("CookieManager - Global limit reached, initiate cookie limit clean up.");
495        initiateCookieLimitCleanUp();
496    }
497
498    // Only add non session cookie to the backing store.
499    if (postToBackingStore == RemoveFromBackingStore) {
500        if (oldestCookie && !oldestCookie->isSession()) {
501            CookieLog("CookieManager - oldestCookie exists, deleting it from backingstore and destructing.\n");
502            m_cookieBackingStore->remove(oldestCookie);
503        }
504        if (!candidateCookie->isSession())
505            m_cookieBackingStore->insert(candidateCookie);
506    }
507}
508
509void CookieManager::getBackingStoreCookies()
510{
511    // Make sure private mode is off when the database thread calls this method
512    if (m_privateMode)
513        return;
514
515    // If there exists cookies in memory, flush them out and we'll load everything from the database again
516    if (m_count)
517        removeAllCookies(DoNotRemoveFromBackingStore);
518
519    Vector<RefPtr<ParsedCookie> > cookies;
520    m_cookieBackingStore->getCookiesFromDatabase(cookies);
521    CookieLog("CookieManager - Backingstore has %d cookies, loading them in memory now", cookies.size());
522    for (size_t i = 0; i < cookies.size(); ++i) {
523        RefPtr<ParsedCookie> newCookie = cookies[i];
524
525        // The IP flag is not persisted in the database.
526        if (BlackBerry::Platform::isIPAddress(newCookie->domain()))
527            newCookie->setDomain(newCookie->domain(), true);
528
529        checkAndTreatCookie(newCookie, BackingStoreCookieEntry);
530    }
531    CookieLog("CookieManager - Backingstore loading complete.");
532
533    m_syncedWithDatabase = true;
534}
535
536void CookieManager::setPrivateMode(bool privateMode)
537{
538    if (m_privateMode == privateMode)
539        return;
540
541    m_privateMode = privateMode;
542
543    // If we switched to private mode when the database cookies haven't loaded into memory yet
544    // we can return because there's nothing in memory anyway.
545    if (m_privateMode && !m_syncedWithDatabase)
546        return;
547
548    removeAllCookies(DoNotRemoveFromBackingStore);
549
550    // If we are switching back to public mode, reload the database to memory.
551    if (!m_privateMode)
552        getBackingStoreCookies();
553}
554
555CookieMap* CookieManager::findOrCreateCookieMap(CookieMap* protocolMap, const PassRefPtr<ParsedCookie> candidateCookie)
556{
557    // Explode the domain with the '.' delimiter
558    Vector<String> delimitedHost;
559
560    // If the domain is an IP address or is a host-only domain, don't split it.
561    if (candidateCookie->domainIsIPAddress() || candidateCookie->isHostOnly())
562        delimitedHost.append(candidateCookie->domain());
563    else
564        candidateCookie->domain().split(".", delimitedHost);
565
566    CookieMap* curMap = protocolMap;
567    size_t hostSize = delimitedHost.size();
568
569    CookieLog("CookieManager - looking at protocol map %s \n", protocolMap->getName().utf8().data());
570
571    // Find & create necessary CookieMaps by traversing down the domain tree
572    // Each CookieMap represent a subsection of the domain, delimited by "."
573    int i = hostSize - 1;
574    while (i >= 0) {
575        CookieLog("CookieManager - finding %s in currentmap\n", delimitedHost[i].utf8().data());
576        CookieMap* nextMap = curMap->getSubdomainMap(delimitedHost[i]);
577        if (!nextMap) {
578            CookieLog("CookieManager - cannot find map\n");
579            if (candidateCookie->hasExpired())
580                return 0;
581            CookieLog("CookieManager - creating %s in currentmap %s\n", delimitedHost[i].utf8().data(), curMap->getName().utf8().data());
582            nextMap = new CookieMap(delimitedHost[i]);
583            CookieLog("CookieManager - adding subdomain to map\n");
584            curMap->addSubdomainMap(delimitedHost[i], nextMap);
585        }
586        curMap = nextMap;
587        i--;
588    }
589    return curMap;
590}
591
592void CookieManager::removeCookieWithName(const KURL& url, const String& cookieName)
593{
594    // Dispatch the message because the database cookies are not loaded in memory yet.
595    if (!m_syncedWithDatabase && !m_privateMode) {
596        typedef void (WebCore::CookieManager::*FunctionType)(const KURL&, const String&);
597        BlackBerry::Platform::webKitThreadMessageClient()->dispatchMessage(
598            BlackBerry::Platform::createMethodCallMessage<FunctionType, CookieManager, const KURL, const String>(
599                &CookieManager::removeCookieWithName, this, url, cookieName));
600        return;
601    }
602
603    // We get all cookies from all domains that domain matches the request domain
604    // and delete any cookies with the specified name that path matches the request path
605    Vector<RefPtr<ParsedCookie> > results;
606    getRawCookies(results, url, WithHttpOnlyCookies);
607    // Delete the cookies that path matches the request path
608    for (size_t i = 0; i < results.size(); i++) {
609        RefPtr<ParsedCookie> cookie = results[i];
610        if (!equalIgnoringCase(cookie->name(), cookieName))
611            continue;
612        if (url.path().startsWith(cookie->path(), false)) {
613            cookie->forceExpire();
614            checkAndTreatCookie(cookie, RemoveFromBackingStore);
615        }
616    }
617}
618
619void CookieManager::initiateCookieLimitCleanUp()
620{
621    if (!m_limitTimer.isActive()) {
622        CookieLog("CookieManager - Starting a timer for cookie cleanup");
623        m_limitTimer.startOneShot(s_delayToStartCookieCleanup);
624    } else {
625#ifndef NDEBUG
626        CookieLog("CookieManager - Cookie cleanup timer already running");
627#endif
628    }
629}
630
631void CookieManager::cookieLimitCleanUp(Timer<CookieManager>* timer)
632{
633    ASSERT_UNUSED(timer, timer == &m_limitTimer);
634
635    CookieLimitLog("CookieManager - Starting cookie clean up");
636
637    size_t numberOfCookiesOverLimit = (m_count > s_globalMaxCookieCount) ? m_count - s_globalMaxCookieCount : 0;
638    size_t amountToDelete = s_cookiesToDeleteWhenLimitReached + numberOfCookiesOverLimit;
639
640    CookieLimitLog("CookieManager - Excess: %d  Amount to Delete: %d", numberOfCookiesOverLimit, amountToDelete);
641
642    // Call the database to delete 'amountToDelete' of cookies
643    Vector<RefPtr<ParsedCookie> > cookiesToDelete;
644    cookiesToDelete.reserveInitialCapacity(amountToDelete);
645
646    CookieLimitLog("CookieManager - Calling database to clean up");
647    m_cookieBackingStore->getCookiesFromDatabase(cookiesToDelete, amountToDelete);
648
649    // Cookies are ordered in ASC order by lastAccessed
650    for (size_t i = 0; i < amountToDelete; ++i) {
651        // Expire them and call checkandtreat to delete them from memory and database
652        RefPtr<ParsedCookie> newCookie = cookiesToDelete[i];
653        CookieLimitLog("CookieManager - Expire cookie: %s and delete", newCookie->toString().utf8().data());
654        newCookie->forceExpire();
655        checkAndTreatCookie(newCookie, RemoveFromBackingStore);
656    }
657
658    CookieLimitLog("CookieManager - Cookie clean up complete.");
659}
660
661} // namespace WebCore
662