b27003ad063b5870e188357a8ae2e47fafce94e5
[WebKit-https.git] / Source / WebCore / platform / blackberry / CookieDatabaseBackingStore / CookieDatabaseBackingStore.cpp
1 /*
2  * Copyright (C) 2009 Julien Chaffraix <jchaffraix@pleyo.com>
3  * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26 #define ENABLE_COOKIE_DEBUG 0
27
28 #include "config.h"
29 #include "CookieDatabaseBackingStore.h"
30
31 #include "CookieManager.h"
32 #include "Logging.h"
33 #include "ParsedCookie.h"
34 #include "SQLiteStatement.h"
35 #include "SQLiteTransaction.h"
36 #include <wtf/text/StringBuilder.h>
37 #include <wtf/text/WTFString.h>
38
39 #if ENABLE_COOKIE_DEBUG
40 #include <BlackBerryPlatformLog.h>
41 #define CookieLog(format, ...) BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelInfo, format, ## __VA_ARGS__)
42 #else
43 #define CookieLog(format, ...)
44 #endif
45
46 #include <BlackBerryPlatformExecutableMessage.h>
47
48 using BlackBerry::Platform::MessageClient;
49 using BlackBerry::Platform::TypedReplyBuffer;
50 using BlackBerry::Platform::createMethodCallMessage;
51
52 static const double s_databaseTimerInterval = 2;
53
54 namespace WebCore {
55
56 CookieDatabaseBackingStore::CookieDatabaseBackingStore()
57     : MessageClient(MessageClient::ReplyFeature | MessageClient::SyncFeature)
58     , m_tableName("cookies") // This is chosen to match Mozilla's table name.
59     , m_dbTimer(this, &CookieDatabaseBackingStore::sendChangesToDatabaseTimerFired)
60     , m_insertStatement(0)
61     , m_updateStatement(0)
62     , m_deleteStatement(0)
63 {
64     m_dbTimerClient = new BlackBerry::Platform::GenericTimerClient(this);
65     m_dbTimer.setClient(m_dbTimerClient);
66
67     pthread_attr_t threadAttrs;
68     pthread_attr_init(&threadAttrs);
69     createThread("cookie_database", threadAttrs);
70 }
71
72 CookieDatabaseBackingStore::~CookieDatabaseBackingStore()
73 {
74     deleteGuardedObject(m_dbTimerClient);
75     m_dbTimerClient = 0;
76     // FIXME: This object will never be deleted due to the set up of CookieManager (it's a singleton)
77     CookieLog("CookieBackingStore - Destructing");
78 #ifndef NDEBUG
79     {
80         MutexLocker lock(m_mutex);
81         ASSERT(m_changedCookies.isEmpty());
82     }
83 #endif
84 }
85
86 void CookieDatabaseBackingStore::upgradeTableIfNeeded(const String& databaseFields, const String& primaryKeyFields)
87 {
88     ASSERT(isCurrentThread());
89
90     bool creationTimeExists = false;
91     bool protocolExists = false;
92
93     if (!m_db.tableExists(m_tableName))
94         return;
95
96     // Check if the existing table has the required database fields
97     {
98         String query = "PRAGMA table_info(" + m_tableName + ");";
99
100         SQLiteStatement statement(m_db, query);
101         if (statement.prepare()) {
102             LOG_ERROR("Cannot prepare statement to query cookie table info. sql:%s", query.utf8().data());
103             LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
104             return;
105         }
106
107         while (statement.step() == SQLResultRow) {
108             DEFINE_STATIC_LOCAL(String, creationTime, ("creationTime"));
109             DEFINE_STATIC_LOCAL(String, protocol, ("protocol"));
110             String name = statement.getColumnText(1);
111             if (name == creationTime)
112                 creationTimeExists = true;
113             if (name == protocol)
114                 protocolExists = true;
115             if (creationTimeExists && protocolExists)
116                 return;
117         }
118         LOG(Network, "Need to update cookie table schema.");
119     }
120
121     // Drop and recreate the cookie table to update to the latest database fields.
122     // We do not use alter table - add column because that method cannot add primary keys.
123     Vector<String> commands;
124
125     // Backup existing table
126     String renameQuery = "ALTER TABLE " + m_tableName + " RENAME TO Backup_" + m_tableName + ";";
127     commands.append(renameQuery);
128
129     // Recreate the cookie table using the new database and primary key fields
130     String createTableQuery("CREATE TABLE ");
131     createTableQuery += m_tableName;
132     createTableQuery += " (" + databaseFields + ", " + primaryKeyFields + ");";
133     commands.append(createTableQuery);
134
135     // Copy the old data into the new table. If a column does not exists,
136     // we have to put a '' in the select statement to make the number of columns
137     // equal in the insert statement.
138     String migrationQuery("INSERT OR REPLACE INTO ");
139     migrationQuery += m_tableName;
140     migrationQuery += " SELECT *";
141     if (!creationTimeExists)
142         migrationQuery += ",''";
143     if (!protocolExists)
144         migrationQuery += ",''";
145     migrationQuery += " FROM Backup_" + m_tableName;
146     commands.append(migrationQuery);
147
148     // The new columns will be blank, set the new values.
149     if (!creationTimeExists) {
150         String setCreationTimeQuery = "UPDATE " + m_tableName + " SET creationTime = lastAccessed;";
151         commands.append(setCreationTimeQuery);
152     }
153
154     if (!protocolExists) {
155         String setProtocolQuery = "UPDATE " + m_tableName + " SET protocol = 'http' WHERE isSecure = '0';";
156         String setProtocolQuery2 = "UPDATE " + m_tableName + " SET protocol = 'https' WHERE isSecure = '1';";
157         commands.append(setProtocolQuery);
158         commands.append(setProtocolQuery2);
159     }
160
161     // Drop the backup table
162     String dropBackupQuery = "DROP TABLE IF EXISTS Backup_" + m_tableName + ";";
163     commands.append(dropBackupQuery);
164
165     SQLiteTransaction transaction(m_db, false);
166     transaction.begin();
167     size_t commandSize = commands.size();
168     for (size_t i = 0; i < commandSize; ++i) {
169         if (!m_db.executeCommand(commands[i])) {
170             LOG_ERROR("Failed to alter cookie table when executing sql:%s", commands[i].utf8().data());
171             LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
172             transaction.rollback();
173
174             // We should never get here, but if we do, rename the current cookie table for future restoration. This has the side effect of
175             // clearing the current cookie table, but that's better than continually hitting this case and hence never being able to use the
176             // cookie table.
177             ASSERT_NOT_REACHED();
178             String renameQuery = "ALTER TABLE " + m_tableName + " RENAME TO Backup2_" + m_tableName + ";";
179             if (!m_db.executeCommand(renameQuery)) {
180                 LOG_ERROR("Failed to backup existing cookie table.");
181                 LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
182             }
183             return;
184         }
185     }
186     transaction.commit();
187     LOG(Network, "Successfully updated cookie table schema.");
188 }
189
190 void CookieDatabaseBackingStore::open(const String& cookieJar)
191 {
192     dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeOpen, this, cookieJar));
193 }
194
195 void CookieDatabaseBackingStore::invokeOpen(const String& cookieJar)
196 {
197     ASSERT(isCurrentThread());
198     if (m_db.isOpen())
199         close();
200
201     if (!m_db.open(cookieJar)) {
202         LOG_ERROR("Could not open the cookie database. No cookie will be stored!");
203         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
204         return;
205     }
206
207     m_db.executeCommand("PRAGMA locking_mode=EXCLUSIVE;");
208     m_db.executeCommand("PRAGMA journal_mode=TRUNCATE;");
209
210     const String primaryKeyFields("PRIMARY KEY (protocol, host, path, name)");
211     const String databaseFields("name TEXT, value TEXT, host TEXT, path TEXT, expiry DOUBLE, lastAccessed DOUBLE, isSecure INTEGER, isHttpOnly INTEGER, creationTime DOUBLE, protocol TEXT");
212     // Update table to add the new column creationTime and protocol for backwards compatability.
213     upgradeTableIfNeeded(databaseFields, primaryKeyFields);
214
215     // Create table if not exsist in case that the upgradeTableIfNeeded() failed accidentally.
216     String createTableQuery("CREATE TABLE IF NOT EXISTS ");
217     createTableQuery += m_tableName;
218     // This table schema is compliant with Mozilla's.
219     createTableQuery += " (" + databaseFields + ", " + primaryKeyFields+");";
220
221     if (!m_db.executeCommand(createTableQuery)) {
222         LOG_ERROR("Could not create the table to store the cookies into. No cookie will be stored!");
223         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
224         close();
225         return;
226     }
227
228     String insertQuery("INSERT OR REPLACE INTO ");
229     insertQuery += m_tableName;
230     insertQuery += " (name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, creationTime, protocol) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10);";
231
232     m_insertStatement = new SQLiteStatement(m_db, insertQuery);
233     if (m_insertStatement->prepare()) {
234         LOG_ERROR("Cannot save cookies");
235         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
236     }
237
238     String updateQuery("UPDATE ");
239     updateQuery += m_tableName;
240     // The where statement is chosen to match CookieMap key.
241     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;";
242     m_updateStatement = new SQLiteStatement(m_db, updateQuery);
243
244     if (m_updateStatement->prepare()) {
245         LOG_ERROR("Cannot update cookies");
246         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
247     }
248
249     String deleteQuery("DELETE FROM ");
250     deleteQuery += m_tableName;
251     // The where statement is chosen to match CookieMap key.
252     deleteQuery += " WHERE name=?1 and host=?2 and path=?3 and protocol=?4;";
253     m_deleteStatement = new SQLiteStatement(m_db, deleteQuery);
254
255     if (m_deleteStatement->prepare()) {
256         LOG_ERROR("Cannot delete cookies");
257         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
258     }
259
260 }
261
262 void CookieDatabaseBackingStore::close()
263 {
264     ASSERT(isCurrentThread());
265     CookieLog("CookieBackingStore - Closing");
266
267     size_t changedCookiesSize;
268     {
269         MutexLocker lock(m_mutex);
270         if (m_dbTimer.started())
271             m_dbTimer.stop();
272         changedCookiesSize = m_changedCookies.size();
273     }
274
275     if (changedCookiesSize > 0)
276         invokeSendChangesToDatabase();
277
278     delete m_insertStatement;
279     m_insertStatement = 0;
280     delete m_updateStatement;
281     m_updateStatement = 0;
282     delete m_deleteStatement;
283     m_deleteStatement = 0;
284
285     if (m_db.isOpen())
286         m_db.close();
287 }
288
289 void CookieDatabaseBackingStore::insert(const ParsedCookie* cookie)
290 {
291     CookieLog("CookieBackingStore - adding inserting cookie %s to queue.", cookie->toString().utf8().data());
292     addToChangeQueue(cookie, Insert);
293 }
294
295 void CookieDatabaseBackingStore::update(const ParsedCookie* cookie)
296 {
297     CookieLog("CookieBackingStore - adding updating cookie %s to queue.", cookie->toString().utf8().data());
298     addToChangeQueue(cookie, Update);
299 }
300
301 void CookieDatabaseBackingStore::remove(const ParsedCookie* cookie)
302 {
303     CookieLog("CookieBackingStore - adding deleting cookie %s to queue.", cookie->toString().utf8().data());
304     addToChangeQueue(cookie, Delete);
305 }
306
307 void CookieDatabaseBackingStore::removeAll()
308 {
309     dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeRemoveAll, this));
310 }
311
312 void CookieDatabaseBackingStore::invokeRemoveAll()
313 {
314     ASSERT(isCurrentThread());
315     if (!m_db.isOpen())
316         return;
317
318     CookieLog("CookieBackingStore - remove All cookies from backingstore");
319
320     {
321         MutexLocker lock(m_mutex);
322         m_changedCookies.clear();
323     }
324
325     String deleteQuery("DELETE FROM ");
326     deleteQuery += m_tableName;
327     deleteQuery += ";";
328
329     SQLiteStatement deleteStatement(m_db, deleteQuery);
330     if (deleteStatement.prepare()) {
331         LOG_ERROR("Could not prepare DELETE * statement");
332         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
333         return;
334     }
335
336     if (!deleteStatement.executeCommand()) {
337         LOG_ERROR("Cannot delete cookie from database");
338         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
339         return;
340     }
341 }
342
343 void CookieDatabaseBackingStore::getCookiesFromDatabase(Vector<ParsedCookie*>& stackOfCookies, unsigned int limit)
344 {
345     // 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.
346     TypedReplyBuffer< Vector<ParsedCookie*>* > replyBuffer(0);
347     dispatchMessage(createMethodCallMessageWithReturn(&CookieDatabaseBackingStore::invokeGetCookiesWithLimit, &replyBuffer, this, limit));
348     Vector<ParsedCookie*>* cookies = replyBuffer.pointer();
349     stackOfCookies.swap(*cookies);
350     delete cookies;
351 }
352
353 Vector<ParsedCookie*>* CookieDatabaseBackingStore::invokeGetCookiesWithLimit(unsigned int limit)
354 {
355     ASSERT(isCurrentThread());
356
357     // Check that the table exists to avoid doing an unnecessary request.
358     if (!m_db.isOpen())
359         return 0;
360
361     StringBuilder selectQuery;
362     selectQuery.append("SELECT name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly, creationTime, protocol FROM ");
363     selectQuery.append(m_tableName);
364     if (limit > 0) {
365         selectQuery.append(" ORDER BY lastAccessed ASC");
366         selectQuery.append(" LIMIT " + String::number(limit));
367     }
368     selectQuery.append(";");
369
370     CookieLog("CookieBackingStore - invokeGetAllCookies with select query %s", selectQuery.toString().utf8().data());
371
372     SQLiteStatement selectStatement(m_db, selectQuery.toString());
373
374     if (selectStatement.prepare()) {
375         LOG_ERROR("Cannot retrieved cookies from the database");
376         LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
377         return 0;
378     }
379
380     Vector<ParsedCookie*>* cookies = new Vector<ParsedCookie*>;
381     while (selectStatement.step() == SQLResultRow) {
382         // There is a row to fetch
383
384         String name = selectStatement.getColumnText(0);
385         String value = selectStatement.getColumnText(1);
386         String domain = selectStatement.getColumnText(2);
387         String path = selectStatement.getColumnText(3);
388         double expiry = selectStatement.getColumnDouble(4);
389         double lastAccessed = selectStatement.getColumnDouble(5);
390         bool isSecure = selectStatement.getColumnInt(6);
391         bool isHttpOnly = selectStatement.getColumnInt(7);
392         double creationTime = selectStatement.getColumnDouble(8);
393         String protocol = selectStatement.getColumnText(9);
394
395         cookies->append(new ParsedCookie(name, value, domain, protocol, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly));
396     }
397
398     return cookies;
399 }
400
401 void CookieDatabaseBackingStore::sendChangesToDatabaseSynchronously()
402 {
403     CookieLog("CookieBackingStore - sending to database immediately");
404     {
405         MutexLocker lock(m_mutex);
406         if (m_dbTimer.started())
407             m_dbTimer.stop();
408     }
409     dispatchSyncMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeSendChangesToDatabase, this));
410 }
411
412 void CookieDatabaseBackingStore::sendChangesToDatabase(int nextInterval)
413 {
414     MutexLocker lock(m_mutex);
415     if (!m_dbTimer.started()) {
416         CookieLog("CookieBackingStore - Starting one shot send to database");
417         m_dbTimer.start(nextInterval);
418     } else {
419 #ifndef NDEBUG
420         CookieLog("CookieBackingStore - Timer already running, skipping this request");
421 #endif
422     }
423 }
424
425 void CookieDatabaseBackingStore::sendChangesToDatabaseTimerFired()
426 {
427     dispatchMessage(createMethodCallMessage(&CookieDatabaseBackingStore::invokeSendChangesToDatabase, this));
428 }
429
430 void CookieDatabaseBackingStore::invokeSendChangesToDatabase()
431 {
432     ASSERT(isCurrentThread());
433
434     if (!m_db.isOpen()) {
435         LOG_ERROR("Timer Fired, but database is closed.");
436         return;
437     }
438
439     Vector<CookieAction> changedCookies;
440     {
441         MutexLocker lock(m_mutex);
442         changedCookies.swap(m_changedCookies);
443         ASSERT(m_changedCookies.isEmpty());
444     }
445
446     if (changedCookies.isEmpty()) {
447         CookieLog("CookieBackingStore - Timer fired, but no cookies in changelist");
448         return;
449     }
450     CookieLog("CookieBackingStore - Timer fired, sending changes to database. We have %d changes", changedCookies.size());
451     SQLiteTransaction transaction(m_db, false);
452     transaction.begin();
453
454     // Iterate through every element in the change list to make calls
455     // If error occurs, ignore it and continue to the next statement
456     size_t sizeOfChange = changedCookies.size();
457     for (size_t i = 0; i < sizeOfChange; i++) {
458         SQLiteStatement* m_statement;
459         const ParsedCookie cookie = changedCookies[i].first;
460         UpdateParameter action = changedCookies[i].second;
461
462         if (action == Delete) {
463             m_statement = m_deleteStatement;
464             CookieLog("CookieBackingStore - deleting cookie %s.", cookie.toString().utf8().data());
465
466             // Binds all the values
467             if (m_statement->bindText(1, cookie.name()) || m_statement->bindText(2, cookie.domain())
468                 || m_statement->bindText(3, cookie.path()) || m_statement->bindText(4, cookie.protocol())) {
469                 LOG_ERROR("Cannot bind cookie data to delete");
470                 LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
471                 ASSERT_NOT_REACHED();
472                 continue;
473             }
474         } else {
475             if (action == Update) {
476                 CookieLog("CookieBackingStore - updating cookie %s.", cookie.toString().utf8().data());
477                 m_statement = m_updateStatement;
478             } else {
479                 CookieLog("CookieBackingStore - inserting cookie %s.", cookie.toString().utf8().data());
480                 m_statement = m_insertStatement;
481             }
482
483             // Binds all the values
484             if (m_statement->bindText(1, cookie.name()) || m_statement->bindText(2, cookie.value())
485                 || m_statement->bindText(3, cookie.domain()) || m_statement->bindText(4, cookie.path())
486                 || m_statement->bindDouble(5, cookie.expiry()) || m_statement->bindDouble(6, cookie.lastAccessed())
487                 || m_statement->bindInt64(7, cookie.isSecure()) || m_statement->bindInt64(8, cookie.isHttpOnly())
488                 || m_statement->bindDouble(9, cookie.creationTime()) || m_statement->bindText(10, cookie.protocol())) {
489                 LOG_ERROR("Cannot bind cookie data to save");
490                 LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
491                 ASSERT_NOT_REACHED();
492                 continue;
493             }
494         }
495
496         int rc = m_statement->step();
497         m_statement->reset();
498         if (rc != SQLResultOk && rc != SQLResultDone) {
499             LOG_ERROR("Cannot make call to the database");
500             LOG_ERROR("SQLite Error Message: %s", m_db.lastErrorMsg());
501             ASSERT_NOT_REACHED();
502             continue;
503         }
504     }
505     transaction.commit();
506     CookieLog("CookieBackingStore - transaction complete");
507 }
508
509 void CookieDatabaseBackingStore::addToChangeQueue(const ParsedCookie* changedCookie, UpdateParameter actionParam)
510 {
511     ASSERT(!changedCookie->isSession());
512     ParsedCookie cookieCopy(changedCookie);
513     CookieAction action(cookieCopy, actionParam);
514     {
515         MutexLocker lock(m_mutex);
516         m_changedCookies.append(action);
517         CookieLog("CookieBackingStore - m_changedcookies has %d.", m_changedCookies.size());
518     }
519     sendChangesToDatabase(s_databaseTimerInterval);
520 }
521
522 } // namespace WebCore