+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
--- /dev/null
+<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>
+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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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