Upstream BlackBerry Cookie Management Classes
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Feb 2012 04:34:56 +0000 (04:34 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Feb 2012 04:34:56 +0000 (04:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=73654

Patch by Konrad Piascik <kpiascik@rim.com> on 2012-02-23
Reviewed by Rob Buis.

.:

Manual test to see in milliseconds how quickly 100 writes followed by 100 reads take.
This test is ran twice and the average read and write for each of the 2 runs is shown.

* ManualTests/cookieSpeedTest.html: Added.

Source/WebCore:

Added ManualTests/cookieSpeedTest.html as well as tested functionality
on the BlackBerry port with http://testsuites.opera.com/cookies/
Passes all non Cookie 2 tests since Cookie 2 is not implemented/supported at this time.
Error handling and extended tests do not all pass and will be updated with future bugs/patches.

* platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.cpp: Added.
* platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.h: Added.
* platform/blackberry/CookieJarBlackBerry.cpp: Added.
* platform/blackberry/CookieManager.cpp: Added.
* platform/blackberry/CookieManager.h: Added.
* platform/blackberry/CookieMap.cpp: Added.
* platform/blackberry/CookieMap.h: Added.
* platform/blackberry/CookieParser.cpp: Added.
* platform/blackberry/CookieParser.h: Added.
* platform/blackberry/ParsedCookie.cpp: Added.
* platform/blackberry/ParsedCookie.h: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@108722 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
ChangeLog
ManualTests/cookieSpeedTest.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.h [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieJarBlackBerry.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieManager.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieManager.h [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieMap.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieMap.h [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieParser.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/CookieParser.h [new file with mode: 0644]
Source/WebCore/platform/blackberry/ParsedCookie.cpp [new file with mode: 0644]
Source/WebCore/platform/blackberry/ParsedCookie.h [new file with mode: 0644]

index 6d78656c3a0ec68fdb882cd2ccde9173baaca7f1..43bceb3baa7d0170a9b17f0eaadd0a5fb797452c 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2012-02-23  Konrad Piascik  <kpiascik@rim.com>
+
+        Upstream BlackBerry Cookie Management Classes
+        https://bugs.webkit.org/show_bug.cgi?id=73654
+
+        Reviewed by Rob Buis.
+
+        Manual test to see in milliseconds how quickly 100 writes followed by 100 reads take.
+        This test is ran twice and the average read and write for each of the 2 runs is shown.
+
+        * ManualTests/cookieSpeedTest.html: Added.
+
 2012-02-23  Dave Tu  <dtu@chromium.org>
 
         Add Chromium gpu_tests to the flakiness dashboard
diff --git a/ManualTests/cookieSpeedTest.html b/ManualTests/cookieSpeedTest.html
new file mode 100644 (file)
index 0000000..8633557
--- /dev/null
@@ -0,0 +1,28 @@
+<html><head><title>Cookie Test</title>
+<script>
+function cookieTest(){
+    var totalW = 0.0;
+    var totalR = 0.0;
+    var numLoops = 100;
+    for(var i=0; i < numLoops; i++){
+        var randomNumber=Math.floor(Math.random()*11);
+        var time = new Date();
+        time.setTime(time.getTime() + 1000);
+        var cookieString = "cookie" + randomNumber + "=true; expires=" + time.toGMTString();
+        var preW = new Date().getTime();
+        document.cookie = cookieString;
+        var postW = new Date().getTime();
+        if(document.cookie.indexOf(("cookie" + randomNumber)> 0));
+        var postR = new Date().getTime();
+        totalW += (postW - preW);
+        totalR += (postR - postW);
+    }
+    document.write("<br><br>avg R(millis):" + totalR/numLoops);
+    document.write("<br>avg W(millis):" + totalW/numLoops);
+}
+
+</script>
+</head>
+<body onload="javascript:cookieTest();setTimeout(cookieTest(), 1000);">
+</body>
+</html>
index 5000e8b371604fe2e943054866e47761dc90a513..76063077a0ed2d267e05011a4a6ed821ddaae72e 100644 (file)
@@ -1,3 +1,27 @@
+2012-02-23  Konrad Piascik  <kpiascik@rim.com>
+
+        Upstream BlackBerry Cookie Management Classes
+        https://bugs.webkit.org/show_bug.cgi?id=73654
+
+        Reviewed by Rob Buis.
+
+        Added ManualTests/cookieSpeedTest.html as well as tested functionality
+        on the BlackBerry port with http://testsuites.opera.com/cookies/
+        Passes all non Cookie 2 tests since Cookie 2 is not implemented/supported at this time.
+        Error handling and extended tests do not all pass and will be updated with future bugs/patches.
+
+        * platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.cpp: Added.
+        * platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.h: Added.
+        * platform/blackberry/CookieJarBlackBerry.cpp: Added.
+        * platform/blackberry/CookieManager.cpp: Added.
+        * platform/blackberry/CookieManager.h: Added.
+        * platform/blackberry/CookieMap.cpp: Added.
+        * platform/blackberry/CookieMap.h: Added.
+        * platform/blackberry/CookieParser.cpp: Added.
+        * platform/blackberry/CookieParser.h: Added.
+        * platform/blackberry/ParsedCookie.cpp: Added.
+        * platform/blackberry/ParsedCookie.h: Added.
+
 2012-02-23  Levi Weintraub  <leviw@chromium.org>
 
         Switch drawLineForBoxSide to use integers
diff --git a/Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.cpp b/Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.cpp
new file mode 100644 (file)
index 0000000..7319cb0
--- /dev/null
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2009 Julien Chaffraix <jchaffraix@pleyo.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
+
+#include "config.h"
+#include "CookieDatabaseBackingStore.h"
+
+#include "CookieManager.h"
+#include "Logging.h"
+#include "ParsedCookie.h"
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+#include <wtf/text/StringBuilder.h>
+#include <wtf/text/WTFString.h>
+
+#if ENABLE_COOKIE_DEBUG
+#include <BlackBerryPlatformLog.h>
+#define CookieLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
+#else
+#define CookieLog(format, ...)
+#endif
+
+#include <BlackBerryPlatformExecutableMessage.h>
+
+using BlackBerry::Platform::MessageClient;
+using BlackBerry::Platform::TypedReplyBuffer;
+using BlackBerry::Platform::createMethodCallMessage;
+
+static const double s_databaseTimerInterval = 2;
+
+namespace WebCore {
+
+CookieDatabaseBackingStore::CookieDatabaseBackingStore()
+    : MessageClient(MessageClient::ReplyFeature | MessageClient::SyncFeature)
+    , m_tableName("cookies") // This is chosen to match Mozilla's table name.
+    , m_dbTimer(this, &CookieDatabaseBackingStore::sendChangesToDatabaseTimerFired)
+    , m_insertStatement(0)
+    , m_updateStatement(0)
+    , m_deleteStatement(0)
+{
+    m_dbTimerClient = new BlackBerry::Platform::GenericTimerClient(this);
+    m_dbTimer.setClient(m_dbTimerClient);
+
+    pthread_attr_t threadAttrs;
+    pthread_attr_init(&threadAttrs);
+    createThread("cookie_database", threadAttrs);
+}
+
+CookieDatabaseBackingStore::~CookieDatabaseBackingStore()
+{
+    delete m_dbTimerClient;
+    m_dbTimerClient = 0;
+    // FIXME: This object will never be deleted due to the set up of CookieManager (it's a singleton)
+    CookieLog("CookieBackingStore - Destructing");
+#ifndef NDEBUG
+    {
+        MutexLocker lock(m_mutex);
+        ASSERT(m_changedCookies.isEmpty());
+    }
+#endif
+}
+
+void CookieDatabaseBackingStore::upgradeTableIfNeeded(const String& databaseFields, const String& primaryKeyFields)
+{
+    ASSERT(isCurrentThread());
+
+    bool creationTimeExists = false;
+    bool protocolExists = false;
+
+    if (!m_db.tableExists(m_tableName))
+        return;
+
+    // Check if the existing table has the required database fields
+    {
+        String query = "PRAGMA table_info(" + m_tableName + ");";
+
+        SQLiteStatement statement(m_db, query);
+        if (statement.prepare()) {
+            LOG_ERROR("Cannot prepare statement to query cookie table info. sql:%s", query.utf8().data());
+            LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+            return;
+        }
+
+        while (statement.step() == SQLResultRow) {
+            DEFINE_STATIC_LOCAL(String, creationTime, ("creationTime"));
+            DEFINE_STATIC_LOCAL(String, protocol, ("protocol"));
+            String name = statement.getColumnText(1);
+            if (name == creationTime)
+                creationTimeExists = true;
+            if (name == protocol)
+                protocolExists = true;
+            if (creationTimeExists && protocolExists)
+                return;
+        }
+        LOG(Network, "Need to update cookie table schema.");
+    }
+
+    // Drop and recreate the cookie table to update to the latest database fields.
+    // We do not use alter table - add column because that method cannot add primary keys.
+    Vector<String> commands;
+
+    // Backup existing table
+    String renameQuery = "ALTER TABLE " + m_tableName + " RENAME TO Backup_" + m_tableName + ";";
+    commands.append(renameQuery);
+
+    // Recreate the cookie table using the new database and primary key fields
+    String createTableQuery("CREATE TABLE ");
+    createTableQuery += m_tableName;
+    createTableQuery += " (" + databaseFields + ", " + primaryKeyFields + ");";
+    commands.append(createTableQuery);
+
+    // Copy the old data into the new table. If a column does not exists,
+    // we have to put a '' in the select statement to make the number of columns
+    // equal in the insert statement.
+    String migrationQuery("INSERT OR REPLACE INTO ");
+    migrationQuery += m_tableName;
+    migrationQuery += " SELECT *";
+    if (!creationTimeExists)
+        migrationQuery += ",''";
+    if (!protocolExists)
+        migrationQuery += ",''";
+    migrationQuery += " FROM Backup_" + m_tableName;
+    commands.append(migrationQuery);
+
+    // The new columns will be blank, set the new values.
+    if (!creationTimeExists) {
+        String setCreationTimeQuery = "UPDATE " + m_tableName + " SET creationTime = lastAccessed;";
+        commands.append(setCreationTimeQuery);
+    }
+
+    if (!protocolExists) {
+        String setProtocolQuery = "UPDATE " + m_tableName + " SET protocol = 'http' WHERE isSecure = '0';";
+        String setProtocolQuery2 = "UPDATE " + m_tableName + " SET protocol = 'https' WHERE isSecure = '1';";
+        commands.append(setProtocolQuery);
+        commands.append(setProtocolQuery2);
+    }
+
+    // Drop the backup table
+    String dropBackupQuery = "DROP TABLE IF EXISTS Backup_" + m_tableName + ";";
+    commands.append(dropBackupQuery);
+
+    SQLiteTransaction transaction(m_db, false);
+    transaction.begin();
+    size_t commandSize = commands.size();
+    for (size_t i = 0; i < commandSize; ++i) {
+        if (!m_db.executeCommand(commands[i])) {
+            LOG_ERROR("Failed to alter cookie table when executing sql:%s", commands[i].utf8().data());
+            LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+            transaction.rollback();
+
+            // We should never get here, but if we do, rename the current cookie table for future restoration. This has the side effect of
+            // clearing the current cookie table, but that's better than continually hitting this case and hence never being able to use the
+            // cookie table.
+            ASSERT_NOT_REACHED();
+            String renameQuery = "ALTER TABLE " + m_tableName + " RENAME TO Backup2_" + m_tableName + ";";
+            if (!m_db.executeCommand(renameQuery)) {
+                LOG_ERROR("Failed to backup existing cookie table.");
+                LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+            }
+            return;
+        }
+    }
+    transaction.commit();
+    LOG(Network, "Successfully updated cookie table schema.");
+}
+
+void CookieDatabaseBackingStore::open(const String& cookieJar)
+{
+    dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeOpen, this, cookieJar));
+}
+
+void CookieDatabaseBackingStore::invokeOpen(const String& cookieJar)
+{
+    ASSERT(isCurrentThread());
+    if (m_db.isOpen())
+        close();
+
+    if (!m_db.open(cookieJar)) {
+        LOG_ERROR("Could not open the cookie database. No cookie will be stored!");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+        return;
+    }
+
+    m_db.executeCommand("PRAGMA locking_mode=EXCLUSIVE;");
+    m_db.executeCommand("PRAGMA journal_mode=TRUNCATE;");
+
+    const String primaryKeyFields("PRIMARY KEY (protocol, host, path, name)");
+    const String databaseFields("name TEXT, value TEXT, host TEXT, path TEXT, expiry DOUBLE, lastAccessed DOUBLE, isSecure INTEGER, isHttpOnly INTEGER, creationTime DOUBLE, protocol TEXT");
+    // Update table to add the new column creationTime and protocol for backwards compatability.
+    upgradeTableIfNeeded(databaseFields, primaryKeyFields);
+
+    // Create table if not exsist in case that the upgradeTableIfNeeded() failed accidentally.
+    String createTableQuery("CREATE TABLE IF NOT EXISTS ");
+    createTableQuery += m_tableName;
+    // This table schema is compliant with Mozilla's.
+    createTableQuery += " (" + databaseFields + ", " + primaryKeyFields+");";
+
+    if (!m_db.executeCommand(createTableQuery)) {
+        LOG_ERROR("Could not create the table to store the cookies into. No cookie will be stored!");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+        close();
+        return;
+    }
+
+    String insertQuery("INSERT OR REPLACE INTO ");
+    insertQuery += m_tableName;
+    insertQuery += " (name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, creationTime, protocol) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10);";
+
+    m_insertStatement = new SQLiteStatement(m_db, insertQuery);
+    if (m_insertStatement->prepare()) {
+        LOG_ERROR("Cannot save cookies");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+    }
+
+    String updateQuery("UPDATE ");
+    updateQuery += m_tableName;
+    // The where statement is chosen to match CookieMap key.
+    updateQuery += " SET name = ?1, value = ?2, host = ?3, path = ?4, expiry = ?5, lastAccessed = ?6, isSecure = ?7, isHttpOnly = ?8, creationTime = ?9, protocol = ?10 where protocol = ?10 and name = ?1 and host = ?3 and path = ?4;";
+    m_updateStatement = new SQLiteStatement(m_db, updateQuery);
+
+    if (m_updateStatement->prepare()) {
+        LOG_ERROR("Cannot update cookies");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+    }
+
+    String deleteQuery("DELETE FROM ");
+    deleteQuery += m_tableName;
+    // The where statement is chosen to match CookieMap key.
+    deleteQuery += " WHERE name=?1 and host=?2 and path=?3 and protocol=?4;";
+    m_deleteStatement = new SQLiteStatement(m_db, deleteQuery);
+
+    if (m_deleteStatement->prepare()) {
+        LOG_ERROR("Cannot delete cookies");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+    }
+
+}
+
+void CookieDatabaseBackingStore::close()
+{
+    ASSERT(isCurrentThread());
+    CookieLog("CookieBackingStore - Closing");
+
+    size_t changedCookiesSize;
+    {
+        MutexLocker lock(m_mutex);
+        if (m_dbTimer.started())
+            m_dbTimer.stop();
+        changedCookiesSize = m_changedCookies.size();
+    }
+
+    if (changedCookiesSize > 0)
+        invokeSendChangesToDatabase();
+
+    delete m_insertStatement;
+    m_insertStatement = 0;
+    delete m_updateStatement;
+    m_updateStatement = 0;
+    delete m_deleteStatement;
+    m_deleteStatement = 0;
+
+    if (m_db.isOpen())
+        m_db.close();
+}
+
+void CookieDatabaseBackingStore::insert(const ParsedCookie* cookie)
+{
+    CookieLog("CookieBackingStore - adding inserting cookie %s to queue.", cookie->toString().utf8().data());
+    addToChangeQueue(cookie, Insert);
+}
+
+void CookieDatabaseBackingStore::update(const ParsedCookie* cookie)
+{
+    CookieLog("CookieBackingStore - adding updating cookie %s to queue.", cookie->toString().utf8().data());
+    addToChangeQueue(cookie, Update);
+}
+
+void CookieDatabaseBackingStore::remove(const ParsedCookie* cookie)
+{
+    CookieLog("CookieBackingStore - adding deleting cookie %s to queue.", cookie->toString().utf8().data());
+    addToChangeQueue(cookie, Delete);
+}
+
+void CookieDatabaseBackingStore::removeAll()
+{
+    dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeRemoveAll, this));
+}
+
+void CookieDatabaseBackingStore::invokeRemoveAll()
+{
+    ASSERT(isCurrentThread());
+    if (!m_db.isOpen())
+        return;
+
+    CookieLog("CookieBackingStore - remove All cookies from backingstore");
+
+    {
+        MutexLocker lock(m_mutex);
+        m_changedCookies.clear();
+    }
+
+    String deleteQuery("DELETE FROM ");
+    deleteQuery += m_tableName;
+    deleteQuery += ";";
+
+    SQLiteStatement deleteStatement(m_db, deleteQuery);
+    if (deleteStatement.prepare()) {
+        LOG_ERROR("Could not prepare DELETE * statement");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+        return;
+    }
+
+    if (!deleteStatement.executeCommand()) {
+        LOG_ERROR("Cannot delete cookie from database");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+        return;
+    }
+}
+
+void CookieDatabaseBackingStore::getCookiesFromDatabase(Vector<ParsedCookie*>& stackOfCookies, unsigned int limit)
+{
+    // It is not a huge performance hit to wait on the reply here because this is only done once during setup and when turning off private mode.
+    TypedReplyBuffer< Vector<ParsedCookie*>* > replyBuffer(0);
+    dispatchMessage(createMethodCallMessageWithReturn(&CookieDatabaseBackingStore::invokeGetCookiesWithLimit, &replyBuffer, this, limit));
+    Vector<ParsedCookie*>* cookies = replyBuffer.pointer();
+    stackOfCookies.swap(*cookies);
+    delete cookies;
+}
+
+Vector<ParsedCookie*>* CookieDatabaseBackingStore::invokeGetCookiesWithLimit(unsigned int limit)
+{
+    ASSERT(isCurrentThread());
+
+    // Check that the table exists to avoid doing an unnecessary request.
+    if (!m_db.isOpen())
+        return 0;
+
+    StringBuilder selectQuery;
+    selectQuery.append("SELECT name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, creationTime, protocol FROM ");
+    selectQuery.append(m_tableName);
+    if (limit > 0) {
+        selectQuery.append(" ORDER BY lastAccessed ASC");
+        selectQuery.append(" LIMIT " + String::number(limit));
+    }
+    selectQuery.append(";");
+
+    CookieLog("CookieBackingStore - invokeGetAllCookies with select query %s", selectQuery.toString().utf8().data());
+
+    SQLiteStatement selectStatement(m_db, selectQuery.toString());
+
+    if (selectStatement.prepare()) {
+        LOG_ERROR("Cannot retrieved cookies from the database");
+        LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+        return 0;
+    }
+
+    Vector<ParsedCookie*>* cookies = new Vector<ParsedCookie*>;
+    while (selectStatement.step() == SQLResultRow) {
+        // There is a row to fetch
+
+        String name = selectStatement.getColumnText(0);
+        String value = selectStatement.getColumnText(1);
+        String domain = selectStatement.getColumnText(2);
+        String path = selectStatement.getColumnText(3);
+        double expiry = selectStatement.getColumnDouble(4);
+        double lastAccessed = selectStatement.getColumnDouble(5);
+        bool isSecure = selectStatement.getColumnInt(6);
+        bool isHttpOnly = selectStatement.getColumnInt(7);
+        double creationTime = selectStatement.getColumnDouble(8);
+        String protocol = selectStatement.getColumnText(9);
+
+        cookies->append(new ParsedCookie(name, value, domain, protocol, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly));
+    }
+
+    return cookies;
+}
+
+void CookieDatabaseBackingStore::sendChangesToDatabaseSynchronously()
+{
+    CookieLog("CookieBackingStore - sending to database immediately");
+    {
+        MutexLocker lock(m_mutex);
+        if (m_dbTimer.started())
+            m_dbTimer.stop();
+    }
+    dispatchSyncMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeSendChangesToDatabase, this));
+}
+
+void CookieDatabaseBackingStore::sendChangesToDatabase(int nextInterval)
+{
+    MutexLocker lock(m_mutex);
+    if (!m_dbTimer.started()) {
+        CookieLog("CookieBackingStore - Starting one shot send to database");
+        m_dbTimer.start(nextInterval);
+    } else {
+#if !NDEBUG
+        CookieLog("CookieBackingStore - Timer already running, skipping this request");
+#endif
+    }
+}
+
+void CookieDatabaseBackingStore::sendChangesToDatabaseTimerFired()
+{
+    dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeSendChangesToDatabase, this));
+}
+
+void CookieDatabaseBackingStore::invokeSendChangesToDatabase()
+{
+    ASSERT(isCurrentThread());
+
+    if (!m_db.isOpen()) {
+        LOG_ERROR("Timer Fired, but database is closed.");
+        return;
+    }
+
+    Vector<CookieAction> changedCookies;
+    {
+        MutexLocker lock(m_mutex);
+        changedCookies.swap(m_changedCookies);
+        ASSERT(m_changedCookies.isEmpty());
+    }
+
+    if (changedCookies.isEmpty()) {
+        CookieLog("CookieBackingStore - Timer fired, but no cookies in changelist");
+        return;
+    }
+    CookieLog("CookieBackingStore - Timer fired, sending changes to database. We have %d changes", changedCookies.size());
+    SQLiteTransaction transaction(m_db, false);
+    transaction.begin();
+
+    // Iterate through every element in the change list to make calls
+    // If error occurs, ignore it and continue to the next statement
+    size_t sizeOfChange = changedCookies.size();
+    for (size_t i = 0; i < sizeOfChange; i++) {
+        SQLiteStatement* m_statement;
+        const ParsedCookie cookie = changedCookies[i].first;
+        UpdateParameter action = changedCookies[i].second;
+
+        if (action == Delete) {
+            m_statement = m_deleteStatement;
+            CookieLog("CookieBackingStore - deleting cookie %s.", cookie.toString().utf8().data());
+
+            // Binds all the values
+            if (m_statement->bindText(1, cookie.name()) || m_statement->bindText(2, cookie.domain())
+                || m_statement->bindText(3, cookie.path()) || m_statement->bindText(4, cookie.protocol())) {
+                LOG_ERROR("Cannot bind cookie data to delete");
+                LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+                ASSERT_NOT_REACHED();
+                continue;
+            }
+        } else {
+            if (action == Update) {
+                CookieLog("CookieBackingStore - updating cookie %s.", cookie.toString().utf8().data());
+                m_statement = m_updateStatement;
+            } else {
+                CookieLog("CookieBackingStore - inserting cookie %s.", cookie.toString().utf8().data());
+                m_statement = m_insertStatement;
+            }
+
+            // Binds all the values
+            if (m_statement->bindText(1, cookie.name()) || m_statement->bindText(2, cookie.value())
+                || m_statement->bindText(3, cookie.domain()) || m_statement->bindText(4, cookie.path())
+                || m_statement->bindDouble(5, cookie.expiry()) || m_statement->bindDouble(6, cookie.lastAccessed())
+                || m_statement->bindInt64(7, cookie.isSecure()) || m_statement->bindInt64(8, cookie.isHttpOnly())
+                || m_statement->bindDouble(9, cookie.creationTime()) || m_statement->bindText(10, cookie.protocol())) {
+                LOG_ERROR("Cannot bind cookie data to save");
+                LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+                ASSERT_NOT_REACHED();
+                continue;
+            }
+        }
+
+        int rc = m_statement->step();
+        m_statement->reset();
+        if (rc != SQLResultOk && rc != SQLResultDone) {
+            LOG_ERROR("Cannot make call to the database");
+            LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
+            ASSERT_NOT_REACHED();
+            continue;
+        }
+    }
+    transaction.commit();
+    CookieLog("CookieBackingStore - transaction complete");
+}
+
+void CookieDatabaseBackingStore::addToChangeQueue(const ParsedCookie* changedCookie, UpdateParameter actionParam)
+{
+    ASSERT(!changedCookie->isSession());
+    ParsedCookie cookieCopy(changedCookie);
+    CookieAction action(cookieCopy, actionParam);
+    {
+        MutexLocker lock(m_mutex);
+        m_changedCookies.append(action);
+        CookieLog("CookieBackingStore - m_changedcookies has %d.", m_changedCookies.size());
+    }
+    sendChangesToDatabase(s_databaseTimerInterval);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.h b/Source/WebCore/platform/blackberry/CookieDatabaseBackingStore/CookieDatabaseBackingStore.h
new file mode 100644 (file)
index 0000000..b9b7dec
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 Julien Chaffraix <jchaffraix@pleyo.com>
+ * Copyright (C) 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.
+ */
+
+#ifndef CookieDatabaseBackingStore_h
+#define CookieDatabaseBackingStore_h
+
+#include "PlatformString.h"
+#include "SQLiteDatabase.h"
+#include "Timer.h"
+
+#include <BlackBerryPlatformMessageClient.h>
+#include <BlackBerryPlatformTimer.h>
+#include <GenericTimerClient.h>
+#include <ThreadTimerClient.h>
+#include <wtf/ThreadingPrimitives.h>
+#include <wtf/Vector.h>
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class ParsedCookie;
+
+class CookieDatabaseBackingStore : public BlackBerry::Platform::MessageClient
+                                 , public BlackBerry::Platform::ThreadTimerClient {
+public:
+    static CookieDatabaseBackingStore* create() { return new CookieDatabaseBackingStore; }
+
+    void open(const String& cookieJar);
+
+    void insert(const ParsedCookie*);
+    void update(const ParsedCookie*);
+    void remove(const ParsedCookie*);
+
+    void removeAll();
+
+    // If a limit is not set, the method will return all cookies in the database
+    void getCookiesFromDatabase(Vector<ParsedCookie*>& stackOfCookies, unsigned int limit = 0);
+
+    void sendChangesToDatabaseSynchronously();
+
+    // ThreadTimerClient methods
+    virtual bool willFireTimer() { return true; }
+    virtual int getPulseCode() const { return pulseCode(); }
+    virtual int getConnectionId() const { return connectionId(); }
+    virtual int defaultTimerPriority() const { return threadPriority(); }
+
+private:
+    enum UpdateParameter {
+        Insert,
+        Update,
+        Delete,
+    };
+
+    CookieDatabaseBackingStore();
+    ~CookieDatabaseBackingStore();
+
+    void addToChangeQueue(const ParsedCookie* changedCookie, UpdateParameter actionParam);
+    void sendChangesToDatabase(int interval);
+    void sendChangesToDatabaseTimerFired();
+    void upgradeTableIfNeeded(const String& databaseSchema, const String& primarykeyFields);
+
+    void invokeOpen(const String& cookieJar);
+    void invokeRemoveAll();
+    Vector<ParsedCookie*>* invokeGetCookiesWithLimit(unsigned int limit);
+    void invokeSendChangesToDatabase();
+
+    void close();
+
+    typedef pair<const ParsedCookie, UpdateParameter> CookieAction;
+    Vector<CookieAction> m_changedCookies;
+    Mutex m_mutex;
+
+    String m_tableName;
+    BlackBerry::Platform::Timer<CookieDatabaseBackingStore> m_dbTimer;
+    BlackBerry::Platform::GenericTimerClient* m_dbTimerClient;
+    SQLiteDatabase m_db;
+    SQLiteStatement *m_insertStatement;
+    SQLiteStatement *m_updateStatement;
+    SQLiteStatement *m_deleteStatement;
+};
+
+CookieDatabaseBackingStore& cookieBackingStore();
+
+} // namespace WebCore
+
+#endif // CookieDatabaseBackingStore_h
diff --git a/Source/WebCore/platform/blackberry/CookieJarBlackBerry.cpp b/Source/WebCore/platform/blackberry/CookieJarBlackBerry.cpp
new file mode 100644 (file)
index 0000000..395c6af
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2011, 2012 Research In Motion Limited. All rights reserved.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
+ * Copyright (C) Research In Motion Limited 2010-2011. All rights reserved.
+ */
+
+#include "config.h"
+#include "CookieJar.h"
+
+#include "Cookie.h"
+#include "CookieManager.h"
+#include "Document.h"
+#include "Frame.h"
+#include "FrameLoaderClientBlackBerry.h"
+#include "KURL.h"
+#include "NotImplemented.h"
+#include "Page.h"
+#include "PageGroupLoadDeferrer.h"
+#include "Settings.h"
+#include <wtf/text/CString.h>
+
+namespace WebCore {
+
+String cookies(Document const* document, KURL const& url)
+{
+    Frame* frame = document->frame();
+    Page* page = frame ? frame->page() : 0;
+
+    if (!page)
+        return String();
+
+    if (!(frame && frame->loader() && frame->loader()->client()))
+        return String();
+
+    if (!static_cast<FrameLoaderClientBlackBerry*>(frame->loader()->client())->cookiesEnabled())
+        return String();
+
+    ASSERT(document && url == document->cookieURL());
+    // 'HttpOnly' cookies should no be accessible from scripts, so we filter them out here
+    return cookieManager().getCookie(url, NoHttpOnlyCookie);
+}
+
+void setCookies(Document* document, KURL const& url, String const& value)
+{
+    Frame* frame = document->frame();
+    Page* page = frame ? frame->page() : 0;
+
+    if (!page)
+        return;
+
+    if (!(frame && frame->loader() && frame->loader()->client()))
+        return;
+
+    if (!static_cast<FrameLoaderClientBlackBerry*>(frame->loader()->client())->cookiesEnabled())
+        return;
+
+    ASSERT(document && url == document->cookieURL());
+    cookieManager().setCookies(url, value);
+}
+
+bool cookiesEnabled(Document const*)
+{
+    // FIXME. Currently cookie is enabled by default, no setting on property page.
+    return true;
+}
+
+bool getRawCookies(const Document* document, const KURL& url, Vector<Cookie>& rawCookies)
+{
+    Vector<ParsedCookie*> result;
+    cookieManager().getRawCookies(result, url, WithHttpOnlyCookies);
+    for (size_t i = 0; i < result.size(); i++)
+        result[i]->appendWebCoreCookie(rawCookies);
+    return true;
+}
+
+void deleteCookie(const Document* document, const KURL& url, const String& cookieName)
+{
+    // Cookies are not bound to the document. Therefore, we don't need to pass
+    // in the document object to find the targeted cookies in cookie manager.
+    cookieManager().removeCookieWithName(url, cookieName);
+}
+
+String cookieRequestHeaderFieldValue(const Document* document, const KURL &url)
+{
+    ASSERT(document);
+
+    if (!(document->frame() && document->frame()->loader() && document->frame()->loader()->client()))
+        return String();
+
+    if (!static_cast<FrameLoaderClientBlackBerry*>(document->frame()->loader()->client())->cookiesEnabled())
+        return String();
+
+    return cookieManager().getCookie(url, WithHttpOnlyCookies);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/blackberry/CookieManager.cpp b/Source/WebCore/platform/blackberry/CookieManager.cpp
new file mode 100644 (file)
index 0000000..d79380f
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * 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 "CurrentTime.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/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 (!cookie->hasDefaultDomain()) {
+        // Check if the domain contains an embedded dot.
+        int dotPosition = cookie->domain().find(".", 1);
+        if (dotPosition == -1 || static_cast<unsigned int>(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();
+}
+
+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::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) {
+            bool cookieAlreadyExists = curMap->existsCookie(candidateCookie);
+            if (cookieAlreadyExists) {
+                CookieLog("CookieManager - expired cookie exists in memory");
+                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 (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);
+        bool cookieAlreadyExists = curMap->existsCookie(candidateCookie);
+        if (cookieAlreadyExists)
+            update(curMap, candidateCookie, postToBackingStore);
+        else
+            addCookieToMap(curMap, candidateCookie, postToBackingStore);
+    }
+}
+
+void CookieManager::addCookieToMap(CookieMap* targetMap, ParsedCookie* candidateCookie, BackingStoreRemovalPolicy postToBackingStore)
+{
+    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();
+    }
+
+    CookieLog("CookieManager - adding new cookie - %s.\n", candidateCookie->toString().utf8().data());
+
+    targetMap->addCookie(candidateCookie);
+
+    // 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::update(CookieMap* targetMap, ParsedCookie* newCookie, BackingStoreRemovalPolicy postToBackingStore)
+{
+    // 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
+
+    CookieLog("CookieManager - updating new cookie - %s.\n", newCookie->toString().utf8().data());
+
+    ParsedCookie* oldCookie = targetMap->updateCookie(newCookie);
+
+    ASSERT(oldCookie);
+
+    if (postToBackingStore == RemoveFromBackingStore) {
+        bool newIsSession = newCookie->isSession();
+        bool oldIsSession = oldCookie->isSession();
+        if (!newIsSession && !oldIsSession)
+            m_cookieBackingStore->update(newCookie);
+        else if (newIsSession && !oldIsSession) {
+            // Must manually decrease the counter because it was not counted when
+            // the cookie was removed in cookieMap.
+            removedCookie();
+            m_cookieBackingStore->remove(oldCookie);
+        } else if (!newIsSession && oldIsSession) {
+            // Must manually increase the counter because it was not counted when
+            // the cookie was added in cookieMap.
+            addedCookie();
+            m_cookieBackingStore->insert(newCookie);
+        }
+    }
+    delete oldCookie;
+}
+
+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 {
+#if !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
diff --git a/Source/WebCore/platform/blackberry/CookieManager.h b/Source/WebCore/platform/blackberry/CookieManager.h
new file mode 100644 (file)
index 0000000..b97b9f9
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
+ * Copyright (C) 2010, 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.
+ */
+
+#ifndef CookieManager_h
+#define CookieManager_h
+
+#include "CookieMap.h"
+#include "ParsedCookie.h"
+#include "PlatformString.h"
+#include "Timer.h"
+#include <BlackBerryPlatformGuardedPointer.h>
+#include <wtf/HashMap.h>
+
+namespace WTF {
+class String;
+}
+
+namespace WebCore {
+
+class CookieDatabaseBackingStore;
+class KURL;
+
+enum BackingStoreRemovalPolicy {
+    RemoveFromBackingStore,
+    BackingStoreCookieEntry,
+    DoNotRemoveFromBackingStore
+};
+
+enum CookieFilter {
+    NoHttpOnlyCookie,
+    WithHttpOnlyCookies,
+};
+
+enum CookieStorageAcceptPolicy {
+    CookieStorageAcceptPolicyAlways,
+    CookieStorageAcceptPolicyNever,
+    CookieStorageAcceptPolicyOnlyFromMainDocumentDomain
+};
+
+/*
+  * The CookieManager class is a singleton class that handles and selectively persists
+  * incoming cookies. This class contains a tree of domains for quicker
+  * cookie domain lookup. The top of the tree represents a null value for a null domain.
+  * The null domain contains references to top level domains and each node below
+  * represents a sub-section of a domain, delimited by "."
+  *
+  * If a cookie has a domain "a.b.com", it will be stored in the node named "a" in this tree.
+  * in the branch ""->"com"->"b"->"a"
+  *
+  * Cookie specs follow the RFC 6265 spec sheet.
+  * http://tools.ietf.org/html/rfc6265
+  */
+
+class CookieManager: public BlackBerry::Platform::GuardedPointerBase {
+public:
+    bool canLocalAccessAllCookies() const { return m_shouldDumpAllCookies; }
+    void setCanLocalAccessAllCookies(bool enabled) { m_shouldDumpAllCookies = enabled; }
+
+    void setCookies(const KURL&, const String& value);
+
+    void removeAllCookies(BackingStoreRemovalPolicy);
+    void removeCookieWithName(const KURL&, const String& cookieName);
+
+    unsigned short cookiesCount() const { return m_count; }
+
+    void setCookieJar(const char*);
+    const String& cookieJar() const { return m_cookieJarFileName; }
+
+    // Count update method
+    void removedCookie()
+    {
+        ASSERT(m_count > 0);
+        --m_count;
+    }
+    void addedCookie() { ++m_count; }
+
+    static unsigned maxCookieLength() { return s_maxCookieLength; }
+
+    void setCookiePolicy(CookieStorageAcceptPolicy policy) { m_policy = policy; }
+    CookieStorageAcceptPolicy cookiePolicy() const { return m_policy; }
+    void setPrivateMode(const bool);
+
+    String getCookie(const KURL& requestURL, CookieFilter) const;
+
+    // Returns all cookies that are associated with the specified URL as raw cookies.
+    void getRawCookies(Vector<ParsedCookie*>& stackOfCookies, const KURL& requestURL, CookieFilter = WithHttpOnlyCookies) const;
+
+    void flushCookiesToBackingStore();
+
+private:
+    friend CookieManager& cookieManager();
+
+    CookieManager();
+    ~CookieManager();
+
+    void checkAndTreatCookie(ParsedCookie*, BackingStoreRemovalPolicy);
+
+    bool shouldRejectForSecurityReason(const ParsedCookie*, const KURL&);
+
+    void addCookieToMap(CookieMap*, ParsedCookie*, BackingStoreRemovalPolicy);
+    void update(CookieMap*, ParsedCookie*, BackingStoreRemovalPolicy);
+
+    CookieMap* findOrCreateCookieMap(CookieMap* protocolMap, const String& domain, bool findOnly);
+
+    void initiateCookieLimitCleanUp();
+    void cookieLimitCleanUp(Timer<CookieManager>*);
+
+    HashMap<String, CookieMap*> m_managerMap;
+
+    unsigned short m_count;
+
+    bool m_privateMode;
+    bool m_shouldDumpAllCookies;
+
+    String m_cookieJarFileName;
+
+    // FIXME: This method should be removed.
+    void getBackingStoreCookies();
+
+    // Cookie size limit of 4kB as advised per RFC2109
+    static const unsigned s_maxCookieLength = 4096;
+
+    CookieStorageAcceptPolicy m_policy;
+
+    CookieDatabaseBackingStore* m_cookieBackingStore;
+    Timer<CookieManager> m_limitTimer;
+
+};
+
+// Get the global instance.
+CookieManager& cookieManager();
+
+} // namespace WebCore
+
+#endif // CookieManager_h
diff --git a/Source/WebCore/platform/blackberry/CookieMap.cpp b/Source/WebCore/platform/blackberry/CookieMap.cpp
new file mode 100644 (file)
index 0000000..4c6d541
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
+ * Copyright (C) 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
+
+#include "config.h"
+#include "CookieMap.h"
+
+#include "CookieManager.h"
+#include "Logging.h"
+#include "ParsedCookie.h"
+#include <wtf/text/CString.h>
+
+#if ENABLE_COOKIE_DEBUG
+#include <BlackBerryPlatformLog.h>
+#define CookieLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
+#else
+#define CookieLog(format, ...)
+#endif // ENABLE_COOKIE_DEBUG
+
+namespace WebCore {
+
+CookieMap::CookieMap(const String& name)
+    : m_oldestCookie(0)
+    , m_name(name)
+{
+}
+
+CookieMap::~CookieMap()
+{
+    deleteAllCookiesAndDomains();
+}
+
+bool CookieMap::existsCookie(const ParsedCookie* cookie) const
+{
+    String key = cookie->name() + cookie->path();
+    return m_cookieMap.contains(key);
+}
+
+void CookieMap::addCookie(ParsedCookie* cookie)
+{
+    String key = cookie->name() + cookie->path();
+
+    CookieLog("CookieMap - Attempting to add cookie - %s", cookie->name().utf8().data());
+
+    ASSERT(!m_cookieMap.contains(key));
+    m_cookieMap.add(key, cookie);
+    if (!cookie->isSession())
+        cookieManager().addedCookie();
+    if (!m_oldestCookie || m_oldestCookie->lastAccessed() > cookie->lastAccessed())
+        m_oldestCookie = cookie;
+}
+
+ParsedCookie* CookieMap::updateCookie(ParsedCookie* newCookie)
+{
+    String key = newCookie->name() + newCookie->path();
+    ParsedCookie* oldCookie = m_cookieMap.take(key);
+    ASSERT(oldCookie);
+    m_cookieMap.add(key, newCookie);
+    if (oldCookie == m_oldestCookie)
+        updateOldestCookie();
+    return oldCookie;
+}
+
+ParsedCookie* CookieMap::removeCookie(const ParsedCookie* cookie)
+{
+    // Find a previous entry for deletion
+    String key = cookie->name() + cookie->path();
+    ParsedCookie* prevCookie = m_cookieMap.take(key);
+
+    if (!prevCookie)
+        return 0;
+
+    if (prevCookie == m_oldestCookie)
+        updateOldestCookie();
+    else if (prevCookie != cookie) {
+        // The cookie we used to search is force expired, we must do the same
+        // to the cookie in memory too.
+        if (cookie->isForceExpired())
+            prevCookie->forceExpire();
+        delete cookie;
+    }
+
+    if (!prevCookie->isSession())
+        cookieManager().removedCookie();
+    return prevCookie;
+}
+
+CookieMap* CookieMap::getSubdomainMap(const String& subdomain)
+{
+#if ENABLE_COOKIE_DEBUG
+    if (!m_subdomains.contains(subdomain))
+        CookieLog("CookieMap - %s does not exist in this map", subdomain.utf8().data());
+#endif
+    return m_subdomains.get(subdomain);
+}
+
+void CookieMap::addSubdomainMap(const String& subdomain, CookieMap* newDomain)
+{
+    CookieLog("CookieMap - Attempting to add subdomain - %s", subdomain.utf8().data());
+    m_subdomains.add(subdomain, newDomain);
+}
+
+void CookieMap::getAllCookies(Vector<ParsedCookie*>* stackOfCookies)
+{
+    CookieLog("CookieMap - Attempting to copy Map:%s cookies with %d cookies into vectors", m_name.utf8().data(), m_cookieMap.size());
+
+    Vector<ParsedCookie*> newCookies;
+    copyValuesToVector(m_cookieMap, newCookies);
+    for (size_t i = 0; i < newCookies.size(); i++) {
+        ParsedCookie* newCookie = newCookies[i];
+        if (newCookie->hasExpired()) {
+            // Notice that we don't delete from backingstore. These expired cookies will be
+            // deleted when manager loads the backingstore again.
+            ParsedCookie* expired = removeCookie(newCookie);
+            delete expired;
+        } else
+            stackOfCookies->append(newCookie);
+    }
+
+    CookieLog("CookieMap - stack of cookies now have %d cookies in it", (*stackOfCookies).size());
+}
+
+ParsedCookie* CookieMap::removeOldestCookie()
+{
+    // FIXME: Make sure it finds the GLOBAL oldest cookie, not the first oldestcookie it finds.
+    ParsedCookie* oldestCookie = m_oldestCookie;
+
+    // If this map has an oldestCookie, remove it. If not, do a DFS to search for a child that does
+    if (!oldestCookie) {
+
+        CookieLog("CookieMap - no oldestCookie exist");
+
+        // Base case is if the map has no child and no cookies, we return a null.
+        if (!m_subdomains.size()) {
+            CookieLog("CookieMap - no subdomains, base case reached, return 0");
+            return 0;
+        }
+
+        CookieLog("CookieMap - looking into subdomains");
+
+        for (HashMap<String, CookieMap*>::iterator it = m_subdomains.begin(); it != m_subdomains.end(); ++it) {
+            oldestCookie = it->second->removeOldestCookie();
+            if (oldestCookie)
+                break;
+        }
+    } else {
+        CookieLog("CookieMap - oldestCookie exist.");
+        oldestCookie = removeCookie(m_oldestCookie);
+    }
+
+    return oldestCookie;
+}
+
+void CookieMap::updateOldestCookie()
+{
+    if (!m_cookieMap.size())
+        m_oldestCookie = 0;
+    else {
+        HashMap<String, ParsedCookie*>::iterator it = m_cookieMap.begin();
+        m_oldestCookie = it->second;
+        ++it;
+        for (; it != m_cookieMap.end(); ++it)
+            if (m_oldestCookie->lastAccessed() > it->second->lastAccessed())
+                m_oldestCookie = it->second;
+    }
+}
+
+void CookieMap::deleteAllCookiesAndDomains()
+{
+    deleteAllValues(m_subdomains);
+    m_subdomains.clear();
+    deleteAllValues(m_cookieMap);
+    m_cookieMap.clear();
+
+    m_oldestCookie = 0;
+}
+
+void CookieMap::getAllChildCookies(Vector<ParsedCookie*>* stackOfCookies)
+{
+    CookieLog("CookieMap - getAllChildCookies in Map - %s", getName().utf8().data());
+    getAllCookies(stackOfCookies);
+    for (HashMap<String, CookieMap*>::iterator it = m_subdomains.begin(); it != m_subdomains.end(); ++it)
+        it->second->getAllChildCookies(stackOfCookies);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/blackberry/CookieMap.h b/Source/WebCore/platform/blackberry/CookieMap.h
new file mode 100644 (file)
index 0000000..73ba13f
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) Julien Chaffraix <julien.chaffraix@gmail.com>
+ * Copyright (C) 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.
+ */
+
+#ifndef CookieMap_h
+#define CookieMap_h
+
+#include "HashMap.h"
+#include "PlatformString.h"
+#include "Vector.h"
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class ParsedCookie;
+
+/* A cookie map is a node in the tree held by CookieManager that represents
+ * cookies that matches a common domain.
+ *
+ * A CookieMap holds reference to the cookies that it contains and the child
+ * domains that exist within the tree.
+ *
+ * The number of cookie per host is limited by CookieManager::s_maxCookieCountPerHost
+ */
+
+class CookieMap {
+
+public:
+    CookieMap(const String& name = "");
+    ~CookieMap();
+
+    unsigned int count() const { return m_cookieMap.size(); }
+    const String& getName() const { return m_name; }
+
+    void addCookie(ParsedCookie*);
+
+    // Returning the original cookie object so manager can keep a reference to the updates in the database queue.
+    ParsedCookie* updateCookie(ParsedCookie*);
+
+    // Need to return the reference to the removed cookie so manager can deal with it (garbage collect).
+    ParsedCookie* removeCookie(const ParsedCookie*);
+    bool existsCookie(const ParsedCookie*) const;
+
+    // Returns a map with that given subdomain.
+    CookieMap* getSubdomainMap(const String&);
+    void addSubdomainMap(const String&, CookieMap*);
+    void deleteAllCookiesAndDomains();
+
+    void getAllCookies(Vector<ParsedCookie*>*);
+    void getAllChildCookies(Vector<ParsedCookie*>* stackOfCookies);
+    ParsedCookie* removeOldestCookie();
+
+private:
+    void updateOldestCookie();
+
+    // The key is the tuple (name, path).
+    // The spec asks to have also domain, which is implied by choosing the CookieMap relevant to the domain.
+    HashMap<String, ParsedCookie*> m_cookieMap;
+
+    // The key is a subsection of the domain.
+    // ex: if inserting accounts.google.com & this cookiemap is "com", this subdomain map will contain "google"
+    // the "google" cookiemap will contain "accounts" in its subdomain map.
+    HashMap<String, CookieMap*> m_subdomains;
+
+    // Store the oldest cookie to speed up LRU checks.
+    ParsedCookie* m_oldestCookie;
+    const String m_name;
+
+    // FIXME : should have a m_shouldUpdate flag to update the network layer only when the map has changed.
+};
+
+} // namespace WebCore
+
+#endif // CookieMap_h
diff --git a/Source/WebCore/platform/blackberry/CookieParser.cpp b/Source/WebCore/platform/blackberry/CookieParser.cpp
new file mode 100644 (file)
index 0000000..e1eb44a
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2009 Julien Chaffraix <jchaffraix@pleyo.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.
+ */ 
+
+#include "config.h"
+#include "CookieParser.h"
+
+#include "CurrentTime.h"
+#include "Logging.h"
+#include "ParsedCookie.h"
+#include <wtf/text/CString.h>
+
+namespace WebCore {
+
+#define LOG_AND_DELETE(format, ...) \
+    { \
+        LOG_ERROR(format, ## __VA_ARGS__); \
+        delete res; \
+        return 0; \
+    }
+
+static inline bool isCookieHeaderSeparator(UChar c)
+{
+    return (c == '\r' || c =='\n');
+}
+
+static inline bool isLightweightSpace(UChar c)
+{
+    return (c == ' ' || c == '\t');
+}
+
+CookieParser::CookieParser(const KURL& defaultCookieURL)
+    : m_defaultCookieURL(defaultCookieURL)
+{
+}
+
+CookieParser::~CookieParser()
+{
+}
+
+Vector<ParsedCookie*> CookieParser::parse(const String& cookies)
+{
+    unsigned cookieStart, cookieEnd = 0;
+    double curTime = currentTime();
+    Vector<ParsedCookie*, 4> parsedCookies;
+
+    unsigned cookiesLength = cookies.length();
+    if (!cookiesLength) // Code below doesn't handle this case
+        return parsedCookies;
+
+    // Iterate over the header to parse all the cookies.
+    while (cookieEnd <= cookiesLength) {
+        cookieStart = cookieEnd;
+        
+        // Find a cookie separator.
+        while (cookieEnd <= cookiesLength && !isCookieHeaderSeparator(cookies[cookieEnd]))
+            cookieEnd++;
+
+        // Detect an empty cookie and go to the next one.
+        if (cookieStart == cookieEnd) {
+            ++cookieEnd;
+            continue;
+        }
+
+        if (cookieEnd < cookiesLength && isCookieHeaderSeparator(cookies[cookieEnd]))
+            ++cookieEnd;
+
+        ParsedCookie* cookie = parseOneCookie(cookies, cookieStart, cookieEnd - 1, curTime);
+        if (cookie)
+            parsedCookies.append(cookie);
+    }
+    return parsedCookies;
+}
+
+// The cookie String passed into this method will only contian the name value pairs as well as other related cookie
+// attributes such as max-age and domain. Set-Cookie should never be part of this string.
+ParsedCookie* CookieParser::parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime)
+{
+    ParsedCookie* res = new ParsedCookie(curTime);
+
+    if (!res)
+        LOG_AND_DELETE("Out of memory");
+
+    res->setProtocol(m_defaultCookieURL.protocol());
+
+    // Parse [NAME "="] VALUE
+    unsigned tokenEnd = start; // Token end contains the position of the '=' or the end of a token
+    unsigned pairEnd = start; // Pair end contains always the position of the ';'
+
+    // find the *first* ';' and the '=' (if they exist)
+    bool quoteFound = false;
+    bool foundEqual = false;
+    while (pairEnd < end && (cookie[pairEnd] != ';' || quoteFound)) {
+        if (tokenEnd == start && cookie[pairEnd] == '=') {
+            tokenEnd = pairEnd;
+            foundEqual = true;
+        }
+        if (cookie[pairEnd] == '"')
+            quoteFound = !quoteFound;
+        pairEnd++;
+    }
+
+    unsigned tokenStart = start;
+
+    bool hasName = false; // This is a hack to avoid changing too much in this
+                          // brutally brittle code.
+    if (tokenEnd != start) {
+        // There is a '=' so parse the NAME
+        unsigned nameEnd = tokenEnd;
+
+        // The tokenEnd is the position of the '=' so the nameEnd is one less
+        nameEnd--;
+
+        // Remove lightweight spaces.
+        while (nameEnd && isLightweightSpace(cookie[nameEnd]))
+            nameEnd--;
+
+        while (tokenStart < nameEnd && isLightweightSpace(cookie[tokenStart]))
+            tokenStart++;
+
+        if (nameEnd + 1 <= tokenStart)
+            LOG_AND_DELETE("Empty name. Rejecting the cookie");
+
+        String name = cookie.substring(tokenStart, nameEnd + 1 - start);
+        res->setName(name);
+        hasName = true;
+    }
+
+    // Now parse the VALUE
+    tokenStart = tokenEnd + 1;
+    if (!hasName)
+        --tokenStart;
+
+    // Skip lightweight spaces in our token
+    while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart]))
+        tokenStart++;
+
+    tokenEnd = pairEnd;
+    while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1]))
+        tokenEnd--;
+
+    String value;
+    if (tokenEnd == tokenStart) {
+        // Firefox accepts empty value so we will do the same
+        value = String();
+    } else
+        value = cookie.substring(tokenStart, tokenEnd - tokenStart);
+
+    if (hasName)
+        res->setValue(value);
+    else if (foundEqual) {
+        delete res;
+        return 0;
+    } else
+        res->setName(value); // No NAME=VALUE, only NAME
+
+    while (pairEnd < end) {
+        // Switch to the next pair as pairEnd is on the ';' and fast-forward any lightweight spaces.
+        pairEnd++;
+        while (pairEnd < end && isLightweightSpace(cookie[pairEnd]))
+            pairEnd++;
+
+        tokenStart = pairEnd;
+        tokenEnd = tokenStart; // initialize token end to catch first '='
+
+        while (pairEnd < end && cookie[pairEnd] != ';') {
+            if (tokenEnd == tokenStart && cookie[pairEnd] == '=')
+                tokenEnd = pairEnd;
+            pairEnd++;
+        }
+
+        // FIXME : should we skip lightweight spaces here ?
+
+        unsigned length = tokenEnd - tokenStart;
+        unsigned tokenStartSvg = tokenStart;
+
+        String parsedValue;
+        if (tokenStart != tokenEnd) {
+            // There is an equal sign so remove lightweight spaces in VALUE
+            tokenStart = tokenEnd + 1;
+            while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart]))
+                tokenStart++;
+
+            tokenEnd = pairEnd;
+            while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1]))
+                tokenEnd--;
+
+            parsedValue = cookie.substring(tokenStart, tokenEnd - tokenStart);
+        } else {
+            // If the parsedValue is empty, initialise it in case we need it
+            parsedValue = String();
+            // Handle a token without value.
+            length = pairEnd - tokenStart;
+        }
+
+       // Detect which "cookie-av" is parsed
+       // Look at the first char then parse the whole for performance issue
+        switch (cookie[tokenStartSvg]) {
+        case 'P':
+        case 'p' : {
+            if (length >= 4 && cookie.find("ath", tokenStartSvg + 1, false)) {
+                // We need the path to be decoded to match those returned from KURL::path().
+                // The path attribute may or may not include percent-encoded characters. Fortunately
+                // if there are no percent-encoded characters, decoding the url is a no-op.
+                res->setPath(decodeURLEscapeSequences(parsedValue));
+            } else
+                LOG_AND_DELETE("Invalid cookie %s (path)", cookie.ascii().data());
+            break;
+        }
+
+        case 'D':
+        case 'd' : {
+            if (length >= 6 && cookie.find("omain", tokenStartSvg + 1, false)) {
+                if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"')
+                    parsedValue = parsedValue.substring(1, parsedValue.length() - 2);
+                // If the domain does not start with a dot, add one for security checks,
+                // For example: ab.c.com dose not domain match b.c.com;
+                String realDomain = parsedValue[0] == '.' ? parsedValue : "." + parsedValue;
+                res->setDomain(realDomain);
+            } else
+                LOG_AND_DELETE("Invalid cookie %s (domain)", cookie.ascii().data());
+            break;
+        }
+
+        case 'E' :
+        case 'e' : {
+            if (length >= 7 && cookie.find("xpires", tokenStartSvg + 1, false))
+                res->setExpiry(parsedValue);
+            else
+                LOG_AND_DELETE("Invalid cookie %s (expires)", cookie.ascii().data());
+            break;
+        }
+
+        case 'M' :
+        case 'm' : {
+            if (length >= 7 && cookie.find("ax-age", tokenStartSvg + 1, false))
+                res->setMaxAge(parsedValue);
+            else
+                LOG_AND_DELETE("Invalid cookie %s (max-age)", cookie.ascii().data());
+            break;
+        }
+
+        case 'C' :
+        case 'c' : {
+            if (length >= 7 && cookie.find("omment", tokenStartSvg + 1, false))
+                // We do not have room for the comment part (and so do Mozilla) so just log the comment.
+                LOG(Network, "Comment %s for ParsedCookie : %s\n", parsedValue.ascii().data(), cookie.ascii().data());
+            else
+                LOG_AND_DELETE("Invalid cookie %s (comment)", cookie.ascii().data());
+            break;
+        }
+
+        case 'V' :
+        case 'v' : {
+            if (length >= 7 && cookie.find("ersion", tokenStartSvg + 1, false)) {
+                // Although the out-of-dated Cookie Spec(RFC2965, http://tools.ietf.org/html/rfc2965) defined
+                // the value of version can only contain DIGIT, some random sites, e.g. https://devforums.apple.com
+                // would use double quotation marks to quote the digit. So we need to get rid of them for compliance.
+                if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"')
+                    parsedValue = parsedValue.substring(1, parsedValue.length() - 2);
+
+                if (parsedValue.toInt() != 1)
+                    LOG_AND_DELETE("ParsedCookie version %d not supported (only support version=1)", parsedValue.toInt());
+            } else
+                LOG_AND_DELETE("Invalid cookie %s (version)", cookie.ascii().data());
+            break;
+        }
+
+        case 'S' :
+        case 's' : {
+            // Secure is a standalone token ("Secure;")
+            if (length >= 6 && cookie.find("ecure", tokenStartSvg + 1, false))
+                res->setSecureFlag(true);
+            else
+                LOG_AND_DELETE("Invalid cookie %s (secure)", cookie.ascii().data());
+            break;
+        }
+        case 'H':
+        case 'h': {
+            // HttpOnly is a standalone token ("HttpOnly;")
+            if (length >= 8 && cookie.find("ttpOnly", tokenStartSvg + 1, false))
+                res->setIsHttpOnly(true);
+            else
+                LOG_AND_DELETE("Invalid cookie %s (HttpOnly)", cookie.ascii().data());
+            break;
+        }
+
+        default : {
+            // If length == 0, we should be at the end of the cookie (case : ";\r") so ignore it
+            if (length)
+                LOG_ERROR("Invalid token for cookie %s", cookie.ascii().data());
+        }
+        }
+    }
+
+    // Check if the cookie is valid with respect to the size limit.
+    if (!res->isUnderSizeLimit())
+        LOG_AND_DELETE("ParsedCookie %s is above the 4kb in length : REJECTED", cookie.ascii().data());
+
+    // If some pair was not provided, during parsing then apply some default value
+    // the rest has been done in the constructor.
+
+    // If no domain was provided, set it to the host
+    if (!res->domain())
+        res->setDefaultDomain(m_defaultCookieURL);
+
+    // According to the Cookie Specificaiton (RFC6265, section 4.1.2.4 and 5.2.4, http://tools.ietf.org/html/rfc6265),
+    // If no path was provided or the first character of the path value is not '/', set it to the host's path
+    //
+    // REFERENCE
+    // 4.1.2.4. The Path Attribute
+    //
+    // The scope of each cookie is limited to a set of paths, controlled by
+    // the Path attribute. If the server omits the Path attribute, the user
+    // agent will use the "directory" of the request-uri's path component as
+    // the default value. (See Section 5.1.4 for more details.)
+    // ...........
+    // 5.2.4. The Path Attribute
+    //
+    // If the attribute-name case-insensitively matches the string "Path",
+    // the user agent MUST process the cookie-av as follows.
+    //
+    // If the attribute-value is empty or if the first character of the
+    // attribute-value is not %x2F ("/"):
+    //
+    // Let cookie-path be the default-path.
+    //
+    // Otherwise:
+    //
+    // Let cookie-path be the attribute-value.
+    //
+    // Append an attribute to the cookie-attribute-list with an attribute-
+    // name of Path and an attribute-value of cookie-path.
+    if (!res->path() || !res->path().length() || !res->path().startsWith("/", false)) {
+        String path = m_defaultCookieURL.string().substring(m_defaultCookieURL.pathStart(), m_defaultCookieURL.pathAfterLastSlash() - m_defaultCookieURL.pathStart() - 1);
+        if (path.isEmpty())
+            path = "/";
+        // Since this is reading the raw url string, it could contain percent-encoded sequences. We
+        // want it to be comparable to the return value of url.path(), which is not percent-encoded,
+        // so we must remove the escape sequences.
+        res->setPath(decodeURLEscapeSequences(path));
+    }
+    return res;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/blackberry/CookieParser.h b/Source/WebCore/platform/blackberry/CookieParser.h
new file mode 100644 (file)
index 0000000..92e48d3
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 Julien Chaffraix <jchaffraix@pleyo.com>
+ * Copyright (C) 2010, 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.
+ */
+
+#ifndef CookieParser_h
+#define CookieParser_h
+
+#include "KURL.h"
+#include "Vector.h"
+
+namespace WTF {
+class String;
+}
+namespace WebCore {
+
+class ParsedCookie;
+
+class CookieParser {
+public:
+    CookieParser(const KURL& defaultCookieURL);
+    ~CookieParser();
+
+    // Parses a sequence of "Cookie:" header and return the parsed cookies.
+    Vector<ParsedCookie*> parse(const String& cookies);
+
+private:
+    // FIXME: curTime, start, end parameters should be removed. And this method can be public.
+    ParsedCookie* parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime);
+
+    KURL m_defaultCookieURL;
+};
+
+} // namespace WebCore
+
+#endif // CookieParser_h
diff --git a/Source/WebCore/platform/blackberry/ParsedCookie.cpp b/Source/WebCore/platform/blackberry/ParsedCookie.cpp
new file mode 100644 (file)
index 0000000..1ff7fe6
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
+ * Copyright (C) 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.
+ */
+
+#include "config.h"
+#include "ParsedCookie.h"
+
+#include "CookieManager.h"
+#include "CurrentTime.h"
+#include "KURL.h"
+#include "Logging.h"
+#include <curl/curl.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+ParsedCookie::ParsedCookie(double currentTime)
+    : m_expiry(0)
+    , m_creationTime(currentTime)
+    , m_lastAccessed(currentTime)
+    , m_isMaxAgeSet(false)
+    , m_hasDefaultDomain(false)
+    , m_isSecure(false)
+    , m_isHttpOnly(false)
+    , m_isSession(true)
+    , m_isForceExpired(false)
+{
+}
+
+ParsedCookie::ParsedCookie(const String& name, const String& value, const String& domain, const String& protocol, const String& path, double expiry, double lastAccessed, double creationTime, bool isSecure, bool isHttpOnly)
+    : m_name(name)
+    , m_value(value)
+    , m_domain(domain)
+    , m_protocol(protocol)
+    , m_path(path)
+    , m_expiry(expiry)
+    , m_creationTime(creationTime)
+    , m_lastAccessed(lastAccessed)
+    , m_isMaxAgeSet(false)
+    , m_hasDefaultDomain(false)
+    , m_isSecure(isSecure)
+    , m_isHttpOnly(isHttpOnly)
+    , m_isSession(false)
+    , m_isForceExpired(false)
+{
+}
+
+ParsedCookie::ParsedCookie(const ParsedCookie* cookie)
+    : m_name(String(cookie->m_name))
+    , m_value(String(cookie->m_value))
+    , m_domain(String(cookie->m_domain))
+    , m_protocol(String(cookie->m_protocol))
+    , m_path(String(cookie->m_path))
+    , m_expiry(cookie->m_expiry)
+    , m_creationTime(cookie->m_creationTime)
+    , m_lastAccessed(cookie->m_lastAccessed)
+    , m_isMaxAgeSet(cookie->m_isMaxAgeSet)
+    , m_hasDefaultDomain(cookie->m_hasDefaultDomain)
+    , m_isSecure(cookie->m_isSecure)
+    , m_isHttpOnly(cookie->m_isHttpOnly)
+    , m_isSession(cookie->m_isSession)
+    , m_isForceExpired(cookie->m_isForceExpired)
+{
+}
+
+ParsedCookie::~ParsedCookie()
+{
+}
+
+void ParsedCookie::setExpiry(const String& expiry)
+{
+    // If a cookie has both the Max-Age and the Expires attribute,
+    // the Max-Age attribute has precedence and controls the expiration date of the cookie.
+    if (m_isMaxAgeSet || expiry.isEmpty())
+        return;
+
+    m_isSession = false;
+
+    m_expiry = static_cast<double>(curl_getdate(expiry.utf8().data(), 0));
+
+    if (m_expiry == -1) {
+        LOG_ERROR("Could not parse date");
+        // In this case, consider that the cookie is session only
+        m_isSession = true;
+    }
+}
+
+void ParsedCookie::setMaxAge(const String& maxAge)
+{
+    // According to the HTTP Cookie specification (RFC6265, http://tools.ietf.org/html/rfc6265),
+    // the first character can be a DIGIT or a "-", and the reminder
+    // of the value can only contain DIGIT characters.
+    if (maxAge.isEmpty() || (maxAge[0] != '-' && !isASCIIDigit(maxAge[0]))) {
+        LOG_ERROR("Could not parse Max-Age : %s, first character can only be '-' or ascii digit.", maxAge.ascii().data());
+        return;
+    }
+
+    bool ok;
+    int value = maxAge.toIntStrict(&ok);
+
+    if (!ok) {
+        LOG_ERROR("Could not parse Max-Age : %s", maxAge.ascii().data());
+        return;
+    }
+    m_expiry = value;
+    m_isMaxAgeSet = true;
+    m_isSession = false;
+
+    // If maxAge value is not positive, let expiry-time be the earliest representable time.
+    if (m_expiry > 0)
+        m_expiry += currentTime();
+    else
+        m_expiry = 0;
+}
+
+void ParsedCookie::setDefaultDomain(const KURL& requestURL)
+{
+    setDomain(requestURL.host());
+    m_hasDefaultDomain = true;
+}
+
+bool ParsedCookie::hasExpired() const
+{
+    // Session cookies do not expire, they will just not be saved to the backing store.
+    return !m_isSession && (m_isForceExpired || m_expiry < currentTime());
+}
+
+bool ParsedCookie::isUnderSizeLimit() const
+{
+    return m_value.length() <= CookieManager::maxCookieLength() && m_name.length() <= CookieManager::maxCookieLength();
+}
+
+String ParsedCookie::toString() const
+{
+    StringBuilder builder;
+    builder.append(name());
+    builder.append(" = ");
+    builder.append(value());
+    builder.append("; Domain = ");
+    builder.append(domain());
+    builder.append("; Path = ");
+    builder.append(path());
+    builder.append("; Protocol = ");
+    builder.append(protocol());
+    return builder.toString();
+}
+
+String ParsedCookie::toNameValuePair() const
+{
+    static const String equal("=");
+
+    size_t cookieLength = m_name.length() + m_value.length() + 2;
+    Vector<UChar> result;
+    result.reserveInitialCapacity(cookieLength);
+    append(result, m_name);
+    append(result, equal);
+    append(result, m_value);
+
+    return String::adopt(result);
+}
+
+void ParsedCookie::appendWebCoreCookie(Vector<Cookie>& cookieVector) const
+{
+    cookieVector.append(Cookie(String(m_name), String(m_value), String(m_domain),
+            String(m_path), m_expiry, m_isHttpOnly, m_isSecure, m_isSession));
+}
+} // namespace WebCore
diff --git a/Source/WebCore/platform/blackberry/ParsedCookie.h b/Source/WebCore/platform/blackberry/ParsedCookie.h
new file mode 100644 (file)
index 0000000..ca343d1
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2008, 2009 Julien Chaffraix <julien.chaffraix@gmail.com>
+ * Copyright (C) 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.
+ */
+
+#ifndef ParsedCookie_h
+#define ParsedCookie_h
+
+#include "Cookie.h"
+#include <wtf/FastAllocBase.h>
+
+namespace WTF {
+class String;
+}
+namespace WebCore {
+
+class KURL;
+
+// This class represents a cookie internally
+// It can either be created by the CookieParser which will then fill it
+// or it can be created by the backing store filling it in the constructor.
+class ParsedCookie {
+WTF_MAKE_FAST_ALLOCATED;
+public:
+    // Default cookie : empty domain, non secure and session
+    ParsedCookie(double currentTime);
+
+    // For backing store cookies (those cookies are never session cookies).
+    ParsedCookie(const String& name, const String& value, const String& domain, const String& protocol, const String& path, double expiry, double lastAccessed, double creationTime, bool isSecure, bool isHttpOnly);
+
+    ParsedCookie(const ParsedCookie*);
+
+    ~ParsedCookie();
+
+    const String& name() const { return m_name; }
+    void setName(const String& name) { m_name = name; }
+
+    const String& value() const { return m_value; }
+    void setValue(const String& value) { m_value = value; }
+
+    const String& path() const { return m_path; }
+    void setPath(const String& path) { m_path = path; }
+
+    const String& domain() const { return m_domain; }
+    void setDomain(const String& domain) { m_domain = domain.lower(); }
+
+    const String& protocol() const { return m_protocol; }
+    void setProtocol(const String& protocol) { m_protocol = protocol; }
+
+    // This is a special method used to set the domain to the request's url.
+    void setDefaultDomain(const KURL&);
+    bool hasDefaultDomain() const { return m_hasDefaultDomain; }
+
+    double expiry() const { return m_expiry; }
+    void setExpiry(const String&);
+    void forceExpire() { m_isForceExpired  = true; }
+    void setMaxAge(const String&);
+
+    double lastAccessed() const { return m_lastAccessed; }
+    void setLastAccessed(double lastAccessed) { m_lastAccessed = lastAccessed;}
+
+    double creationTime() const { return m_creationTime; }
+    void setCreationTime(double creationTime) { m_creationTime = creationTime; }
+
+    bool isSecure() const { return m_isSecure; }
+    void setSecureFlag(bool secure) { m_isSecure = secure; }
+
+    bool isHttpOnly() const { return m_isHttpOnly; }
+    void setIsHttpOnly(bool isHttpOnly) { m_isHttpOnly = isHttpOnly; }
+
+    bool isSession() const { return m_isSession; }
+
+    bool hasExpired() const;
+    bool isForceExpired() const { return m_isForceExpired; }
+    bool isUnderSizeLimit() const;
+
+    String toString() const;
+    String toNameValuePair() const;
+    void appendWebCoreCookie(Vector<Cookie>& cookieVector) const;
+
+private:
+    String m_name;
+    String m_value;
+    String m_domain;
+    String m_protocol;
+    String m_path;
+    double m_expiry;
+    double m_creationTime;
+    // This is used for the LRU replacement policy.
+    double m_lastAccessed;
+
+    bool m_isMaxAgeSet;
+    bool m_hasDefaultDomain;
+    bool m_isSecure;
+    bool m_isHttpOnly;
+    bool m_isSession;
+    bool m_isForceExpired;
+};
+
+} // namespace WebCore
+
+#endif // ParsedCookie_h