]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blobdiff - src/databases/CookiesDatabase.cpp
Add durable cookie support.
[PrivacyBrowserPC.git] / src / databases / CookiesDatabase.cpp
diff --git a/src/databases/CookiesDatabase.cpp b/src/databases/CookiesDatabase.cpp
new file mode 100644 (file)
index 0000000..adc4f1b
--- /dev/null
@@ -0,0 +1,377 @@
+/*
+ * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
+ *
+ * Privacy Browser PC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser PC 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "CookiesDatabase.h"
+
+// Define the private static schema constants.
+const int CookiesDatabase::SCHEMA_VERSION = 0;
+
+// Define the public static database constants.
+const QString CookiesDatabase::CONNECTION_NAME = "cookies_database";
+const QString CookiesDatabase::COOKIES_TABLE = "cookies";
+
+// Define the public static database field names.
+const QString CookiesDatabase::_ID = "_id";
+const QString CookiesDatabase::DOMAIN = "domain";
+const QString CookiesDatabase::NAME = "name";
+const QString CookiesDatabase::PATH = "path";
+const QString CookiesDatabase::EXPIRATION_DATE = "expiration_date";
+const QString CookiesDatabase::HTTP_ONLY = "http_only";
+const QString CookiesDatabase::SECURE = "secure";
+const QString CookiesDatabase::VALUE = "value";
+
+// Construct the class.
+CookiesDatabase::CookiesDatabase() {}
+
+void CookiesDatabase::addDatabase()
+{
+    // Add the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), CONNECTION_NAME);
+
+    // Set the database name.
+    cookiesDatabase.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/cookies.db");
+
+    // Open the database.
+    if (cookiesDatabase.open())  // Opening the database succeeded.
+    {
+        // Check to see if the cookies table already exists.
+        if (cookiesDatabase.tables().contains(COOKIES_TABLE))  // The cookies table exists.
+        {
+            // Query the database schema version.
+            QSqlQuery schemaVersionQuery = cookiesDatabase.exec(QStringLiteral("PRAGMA user_version"));
+
+            // Move to the first record.
+            schemaVersionQuery.first();
+
+            // Get the current schema versin.
+            int currentSchemaVersion = schemaVersionQuery.value(0).toInt();
+
+            // Check to see if the schema has been updated.
+            if (currentSchemaVersion < SCHEMA_VERSION)
+            {
+                // Run the schema update code.
+                switch (currentSchemaVersion)
+                {
+                    // Upgrade code here.
+                }
+
+                // Update the schema version.
+                cookiesDatabase.exec("PRAGMA user_version = " + QString::number(SCHEMA_VERSION));
+            }
+        }
+        else  // The cookies table does not exist.
+        {
+            // Instantiate a create table query.
+            QSqlQuery createTableQuery(cookiesDatabase);
+
+            // Prepare the create table query.
+            createTableQuery.prepare("CREATE TABLE " + COOKIES_TABLE + "(" +
+                _ID + " INTEGER PRIMARY KEY, " +
+                DOMAIN + " TEXT NOT NULL, " +
+                NAME + " TEXT NOT NULL, " +
+                PATH + " TEXT NOT NULL, " +
+                EXPIRATION_DATE + " TEXT, " +
+                HTTP_ONLY + " INTEGER NOT NULL DEFAULT 0, " +
+                SECURE + " INTEGER NOT NULL DEFAULT 0, " +
+                VALUE + " TEXT NOT NULL)"
+            );
+
+            // Execute the query.
+            if (!createTableQuery.exec())
+            {
+                // Log any errors.
+                qDebug().noquote().nospace() << "Error creating table:  " << cookiesDatabase.lastError();
+            }
+
+            // Set the schema version.
+            cookiesDatabase.exec("PRAGMA user_version = " + QString::number(SCHEMA_VERSION));
+        }
+    }
+    else  // Opening the database failed.
+    {
+        // Write the last database error message to the debug output.
+        qDebug().noquote().nospace() << "Error opening database:  " << cookiesDatabase.lastError();
+    }
+}
+
+void CookiesDatabase::addCookie(const QNetworkCookie &cookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Check to see if the cookie already exists in the database.
+    if (isDurable(cookie))  // The cookie already exists.
+    {
+        // Update the existing cookie.
+        updateCookie(cookie);
+    }
+    else  // The cookie doesn't already exist.
+    {
+        // Instantiate an add cookie query.
+        QSqlQuery addCookieQuery(cookiesDatabase);
+
+        // Prepare the add cookie query.
+        addCookieQuery.prepare("INSERT INTO " + COOKIES_TABLE + " (" + DOMAIN + ", " + NAME + ", " + PATH + ", " + EXPIRATION_DATE + ", " + HTTP_ONLY + ", " + SECURE + ", " + VALUE + ") "
+                            "VALUES (:domain, :name, :path, :expiration_date, :http_only, :secure, :value)"
+        );
+
+        // Bind the values.
+        addCookieQuery.bindValue(":domain", cookie.domain());
+        addCookieQuery.bindValue(":name", QString(cookie.name()));
+        addCookieQuery.bindValue(":path", cookie.path());
+        addCookieQuery.bindValue(":expiration_date", cookie.expirationDate());
+        addCookieQuery.bindValue(":http_only", cookie.isHttpOnly());
+        addCookieQuery.bindValue(":secure", cookie.isSecure());
+        addCookieQuery.bindValue(":value", QString(cookie.value()));
+
+        // Execute the query.
+        addCookieQuery.exec();
+    }
+}
+
+void CookiesDatabase::deleteAllCookies()
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a delete all cookies query.
+    QSqlQuery deleteAllCookiesQuery(cookiesDatabase);
+
+    // Prepare the delete all cookies query.
+    deleteAllCookiesQuery.prepare("DELETE FROM " + COOKIES_TABLE);
+
+    // Execute the query.
+    deleteAllCookiesQuery.exec();
+}
+
+void CookiesDatabase::deleteCookie(const QNetworkCookie &cookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a delete cookie query.
+    QSqlQuery deleteCookieQuery(cookiesDatabase);
+
+    // Prepare the delete cookie query.
+    deleteCookieQuery.prepare("DELETE FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+
+    // Bind the values.
+    deleteCookieQuery.bindValue(":domain", cookie.domain());
+    deleteCookieQuery.bindValue(":name", QString(cookie.name()));
+    deleteCookieQuery.bindValue(":path", cookie.path());
+
+    // Execute the query.
+    deleteCookieQuery.exec();
+}
+
+QList<QNetworkCookie*>* CookiesDatabase::getCookies()
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate a cookies query.
+    QSqlQuery cookiesQuery(cookiesDatabase);
+
+    // Set the query to be forward only.
+    cookiesQuery.setForwardOnly(true);
+
+    // Prepare the cookies query.
+    cookiesQuery.prepare("SELECT * FROM " + COOKIES_TABLE);
+
+    // Execute the query.
+    cookiesQuery.exec();
+
+    // Create a cookie list.
+    QList<QNetworkCookie*> *cookieListPointer = new QList<QNetworkCookie*>;
+
+    // Populate the cookie list.
+    while (cookiesQuery.next())
+    {
+        // Create a cookie.
+        QNetworkCookie *cookiePointer = new QNetworkCookie();
+
+        // Populate the cookie.
+        cookiePointer->setDomain(cookiesQuery.value(DOMAIN).toString());
+        cookiePointer->setName(cookiesQuery.value(NAME).toString().toUtf8());
+        cookiePointer->setPath(cookiesQuery.value(PATH).toString());
+        cookiePointer->setExpirationDate(QDateTime::fromString(cookiesQuery.value(EXPIRATION_DATE).toString(), Qt::ISODate));
+        cookiePointer->setHttpOnly(cookiesQuery.value(HTTP_ONLY).toBool());
+        cookiePointer->setSecure(cookiesQuery.value(SECURE).toBool());
+        cookiePointer->setValue(cookiesQuery.value(VALUE).toString().toUtf8());
+
+        // Add the cookie to the list.
+        cookieListPointer->append(cookiePointer);
+    }
+
+    // Return the cookie list.
+    return cookieListPointer;
+}
+
+bool CookiesDatabase::isDurable(const QNetworkCookie &cookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an is durable query.
+    QSqlQuery isDurableQuery(cookiesDatabase);
+
+    // Set the query to be forward only.
+    isDurableQuery.setForwardOnly(true);
+
+    // Prepare the is durable query.
+    isDurableQuery.prepare("SELECT " + _ID + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+
+    // Bind the values.
+    isDurableQuery.bindValue(":domain", cookie.domain());
+    isDurableQuery.bindValue(":name", QString(cookie.name()));
+    isDurableQuery.bindValue(":path", cookie.path());
+
+    // Execute the query.
+    isDurableQuery.exec();
+
+    // Move to the first entry.
+    isDurableQuery.first();
+
+    // Return the status of the cookie in the database.
+    return (isDurableQuery.isValid());
+}
+
+bool CookiesDatabase::isUpdate(const QNetworkCookie &cookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Instantiate an is update query.
+    QSqlQuery isUpdateQuery(cookiesDatabase);
+
+    // Prepare the is update query.
+    isUpdateQuery.prepare("SELECT " + EXPIRATION_DATE + " , " + HTTP_ONLY + " , " + SECURE + " , " + VALUE + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " +
+                          NAME + " = :name AND " + PATH + " = :path");
+
+    // Bind the values.
+    isUpdateQuery.bindValue(":domain", cookie.domain());
+    isUpdateQuery.bindValue(":name", QString(cookie.name()));
+    isUpdateQuery.bindValue(":path", cookie.path());
+
+    // Execute the query.
+    isUpdateQuery.exec();
+
+    // Move to the first entry.
+    isUpdateQuery.first();
+
+    // Check to see if the cookie exists.
+    if (isUpdateQuery.isValid())  // The cookie exists in the database.
+    {
+        // Check to see if the cookie data has changed.
+        if ((QDateTime::fromString(isUpdateQuery.value(0).toString(), Qt::ISODate) != cookie.expirationDate()) ||
+            (isUpdateQuery.value(1).toBool() != cookie.isHttpOnly()) ||
+            (isUpdateQuery.value(2).toBool() != cookie.isSecure()) ||
+            (isUpdateQuery.value(3).toString().toUtf8() != cookie.value()))  // The cookies data has changed.
+        {
+            qDebug() << "The durable cookie data has changed.";
+
+            // Return true.
+            return true;
+        }
+        else  // The cookie data has not changed.
+        {
+            qDebug() << "The durable cookie data is unchanged.";
+
+            // Return false.
+            return false;
+        }
+    }
+    else  // The cookie does not exist in the database.
+    {
+        // Return false.
+        return false;
+    }
+}
+
+void CookiesDatabase::updateCookie(const QNetworkCookie &cookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Create the update cookie query.
+    QSqlQuery updateCookieQuery(cookiesDatabase);
+
+    // Prepare the edit cookie query.
+    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE + " SET " + EXPIRATION_DATE + " = :expiration_date , " + HTTP_ONLY + " = :http_only , " + SECURE + " = :secure , " +
+                              VALUE + " = :value WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+
+    // Bind the values.
+    updateCookieQuery.bindValue(":domain", cookie.domain());
+    updateCookieQuery.bindValue(":name", QString(cookie.name()));
+    updateCookieQuery.bindValue(":path", cookie.path());
+    updateCookieQuery.bindValue(":expiration_date", cookie.expirationDate());
+    updateCookieQuery.bindValue(":http_only", cookie.isHttpOnly());
+    updateCookieQuery.bindValue(":secure", cookie.isSecure());
+    updateCookieQuery.bindValue(":value", QString(cookie.value()));
+
+    // Execute the query.
+    updateCookieQuery.exec();
+}
+
+void CookiesDatabase::updateCookie(const QNetworkCookie &oldCookie, const QNetworkCookie &newCookie)
+{
+    // Get a handle for the cookies database.
+    QSqlDatabase cookiesDatabase = QSqlDatabase::database(CONNECTION_NAME);
+
+    // Create the old cookie query.
+    QSqlQuery oldCookieQuery(cookiesDatabase);
+
+    // Set the query to be forward only.
+    oldCookieQuery.setForwardOnly(true);
+
+    // Prepare the old cookie query.
+    oldCookieQuery.prepare("SELECT " + _ID + " FROM " + COOKIES_TABLE + " WHERE " + DOMAIN + " = :domain AND " + NAME + " = :name AND " + PATH + " = :path");
+
+    // Bind the values.
+    oldCookieQuery.bindValue(":domain", oldCookie.domain());
+    oldCookieQuery.bindValue(":name", QString(oldCookie.name()));
+    oldCookieQuery.bindValue(":path", oldCookie.path());
+
+    // Execute the query.
+    oldCookieQuery.exec();
+
+    // Move to the first entry.
+    oldCookieQuery.first();
+
+    // Create the update cookie query.
+    QSqlQuery updateCookieQuery(cookiesDatabase);
+
+    // Prepare the update cookie query.
+    updateCookieQuery.prepare("UPDATE " + COOKIES_TABLE + " SET " + DOMAIN + " = :domain , " + NAME + " = :name , " + PATH + " = :path , " + EXPIRATION_DATE + " = :expiration_date , " +
+                              HTTP_ONLY + " = :http_only , " + SECURE + " = :secure , " + VALUE + " = :value WHERE " + _ID + " = :id");
+
+    // Bind the values.
+    updateCookieQuery.bindValue(":id", oldCookieQuery.value(0).toLongLong());
+    updateCookieQuery.bindValue(":domain", newCookie.domain());
+    updateCookieQuery.bindValue(":name", QString(newCookie.name()));
+    updateCookieQuery.bindValue(":path", newCookie.path());
+    updateCookieQuery.bindValue(":expiration_date", newCookie.expirationDate());
+    updateCookieQuery.bindValue(":http_only", newCookie.isHttpOnly());
+    updateCookieQuery.bindValue(":secure", newCookie.isSecure());
+    updateCookieQuery.bindValue(":value", QString(newCookie.value()));
+
+    // Execute the query.
+    updateCookieQuery.exec();
+}