/*
 * Copyright (C) 2012 Igalia S.L.
 * Copyright (C) 2017 Endless Mobile, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include "WebKitTestServer.h"
#include "WebViewTest.h"
#include <WebCore/GUniquePtrSoup.h>
#include <WebCore/SoupVersioning.h>
#include <glib/gstdio.h>

static WebKitTestServer* kServer;

static const char* kFirstPartyDomain = "127.0.0.1";
static const char* kThirdPartyDomain = "localhost";

static const char* kCookieName = "foo";
static const char* kCookieValue = "bar";
static const char* kCookiePath = "/";

static const char* kCookiePathNew = "/new";
static const char* kCookieValueNew = "new-value";

static const char* kIndexHtmlFormat =
    "<html><body>"
    " <p>WebKitGTK Cookie Manager test</p>"
    " <img src='http://localhost:%u/image.png' width=5 height=5></img>"
    "</body></html>";

class CookieManagerTest: public WebViewTest {
public:
    MAKE_GLIB_TEST_FIXTURE(CookieManagerTest);

    static void cookiesChangedCallback(WebKitCookieManager*, CookieManagerTest* test)
    {
        test->m_cookiesChanged = true;
        if (test->m_finishLoopWhenCookiesChange && !(--test->m_cookiesExpectedToChangeCount))
            g_main_loop_quit(test->m_mainLoop);
    }

    CookieManagerTest()
        : WebViewTest()
        , m_cookieManager(webkit_web_context_get_cookie_manager(m_webContext.get()))
    {
        g_assert_true(webkit_website_data_manager_get_cookie_manager(webkit_web_context_get_website_data_manager(m_webContext.get())) == m_cookieManager);
        g_signal_connect(m_cookieManager, "changed", G_CALLBACK(cookiesChangedCallback), this);
    }

    ~CookieManagerTest()
    {
        g_strfreev(m_domains);
        g_list_free_full(m_cookies, reinterpret_cast<GDestroyNotify>(soup_cookie_free));

        g_signal_handlers_disconnect_matched(m_cookieManager, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
        if (m_cookiesTextFile)
            g_unlink(m_cookiesTextFile.get());
        if (m_cookiesSQLiteFile)
            g_unlink(m_cookiesSQLiteFile.get());
    }

    void setPersistentStorage(WebKitCookiePersistentStorage storage)
    {
        const char* filename = 0;
        switch (storage) {
        case WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT:
            if (!m_cookiesTextFile)
                m_cookiesTextFile.reset(g_build_filename(Test::dataDirectory(), "cookies.txt", nullptr));
            filename = m_cookiesTextFile.get();
            break;
        case WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE:
            if (!m_cookiesSQLiteFile)
                m_cookiesSQLiteFile.reset(g_build_filename(Test::dataDirectory(), "cookies.db", nullptr));
            filename = m_cookiesSQLiteFile.get();
            break;
        default:
            g_assert_not_reached();
        }
        webkit_cookie_manager_set_persistent_storage(m_cookieManager, filename, storage);
    }

    static void getAcceptPolicyReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
    {
        GUniqueOutPtr<GError> error;
        WebKitCookieAcceptPolicy policy = webkit_cookie_manager_get_accept_policy_finish(WEBKIT_COOKIE_MANAGER(object), result, &error.outPtr());
        g_assert_no_error(error.get());

        CookieManagerTest* test = static_cast<CookieManagerTest*>(userData);
        test->m_acceptPolicy = policy;
        g_main_loop_quit(test->m_mainLoop);
    }

    WebKitCookieAcceptPolicy getAcceptPolicy()
    {
        m_acceptPolicy = WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
        webkit_cookie_manager_get_accept_policy(m_cookieManager, 0, getAcceptPolicyReadyCallback, this);
        g_main_loop_run(m_mainLoop);

        return m_acceptPolicy;
    }

    static void addCookieReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
    {
        GUniqueOutPtr<GError> error;
        bool added = webkit_cookie_manager_add_cookie_finish(WEBKIT_COOKIE_MANAGER(object), result, &error.outPtr());
        g_assert_no_error(error.get());
        g_assert_true(added);

        CookieManagerTest* test = static_cast<CookieManagerTest*>(userData);
        g_main_loop_quit(test->m_mainLoop);
    }

    void addCookie(SoupCookie* cookie)
    {
        webkit_cookie_manager_add_cookie(m_cookieManager, cookie, 0, addCookieReadyCallback, this);
        g_main_loop_run(m_mainLoop);
    }

    static void getCookiesReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
    {
        GUniqueOutPtr<GError> error;
        GList* cookies = webkit_cookie_manager_get_cookies_finish(WEBKIT_COOKIE_MANAGER(object), result, &error.outPtr());
        g_assert_no_error(error.get());

        CookieManagerTest* test = static_cast<CookieManagerTest*>(userData);
        test->m_cookies = cookies;
        g_main_loop_quit(test->m_mainLoop);
    }

    GList* getCookies(const char* uri)
    {
        g_list_free_full(m_cookies, reinterpret_cast<GDestroyNotify>(soup_cookie_free));
        m_cookies = nullptr;
        webkit_cookie_manager_get_cookies(m_cookieManager, uri, 0, getCookiesReadyCallback, this);
        g_main_loop_run(m_mainLoop);

        return m_cookies;
    }

    static void deleteCookieReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
    {
        GUniqueOutPtr<GError> error;
        bool deleted = webkit_cookie_manager_delete_cookie_finish(WEBKIT_COOKIE_MANAGER(object), result, &error.outPtr());
        g_assert_no_error(error.get());
        g_assert_true(deleted);

        CookieManagerTest* test = static_cast<CookieManagerTest*>(userData);
        g_main_loop_quit(test->m_mainLoop);
    }

    void deleteCookie(SoupCookie* cookie)
    {
        webkit_cookie_manager_delete_cookie(m_cookieManager, cookie, 0, deleteCookieReadyCallback, this);
        g_main_loop_run(m_mainLoop);
    }

    void setAcceptPolicy(WebKitCookieAcceptPolicy policy)
    {
        webkit_cookie_manager_set_accept_policy(m_cookieManager, policy);
    }

    static void getDomainsReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
    {
        GUniqueOutPtr<GError> error;
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
        char** domains = webkit_cookie_manager_get_domains_with_cookies_finish(WEBKIT_COOKIE_MANAGER(object), result, &error.outPtr());
        G_GNUC_END_IGNORE_DEPRECATIONS;
        g_assert_no_error(error.get());

        CookieManagerTest* test = static_cast<CookieManagerTest*>(userData);
        test->m_domains = domains;
        g_main_loop_quit(test->m_mainLoop);
    }

    char** getDomains()
    {
        g_strfreev(m_domains);
        m_domains = 0;
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
        webkit_cookie_manager_get_domains_with_cookies(m_cookieManager, 0, getDomainsReadyCallback, this);
        G_GNUC_END_IGNORE_DEPRECATIONS;
        g_main_loop_run(m_mainLoop);

        return m_domains;
    }

    bool hasDomain(const char* domain)
    {
        if (!m_domains)
            return false;

        for (size_t i = 0; m_domains[i]; ++i) {
            if (g_str_equal(m_domains[i], domain))
                return true;
        }
        return false;
    }

    void deleteCookiesForDomain(const char* domain)
    {
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
        webkit_cookie_manager_delete_cookies_for_domain(m_cookieManager, domain);
        G_GNUC_END_IGNORE_DEPRECATIONS;
    }

    void deleteAllCookies()
    {
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
        webkit_cookie_manager_delete_all_cookies(m_cookieManager);
        G_GNUC_END_IGNORE_DEPRECATIONS;
    }

    void waitUntilCookiesChanged(int cookiesExpectedToChangeCount = 1)
    {
        m_cookiesChanged = false;
        m_cookiesExpectedToChangeCount = cookiesExpectedToChangeCount;
        m_finishLoopWhenCookiesChange = true;
        g_main_loop_run(m_mainLoop);
        m_finishLoopWhenCookiesChange = false;
    }

    WebKitCookieManager* m_cookieManager { nullptr };
    WebKitCookieAcceptPolicy m_acceptPolicy { WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY };
    char** m_domains { nullptr };
    GList* m_cookies { nullptr };
    bool m_cookiesChanged { false };
    int m_cookiesExpectedToChangeCount { 0 };
    bool m_finishLoopWhenCookiesChange { false };
    GUniquePtr<char> m_cookiesTextFile;
    GUniquePtr<char> m_cookiesSQLiteFile;
};

static void testCookieManagerAcceptPolicy(CookieManagerTest* test, gconstpointer)
{
    // Default policy is NO_THIRD_PARTY.
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    char** domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 1);
    g_assert_cmpstr(domains[0], ==, kFirstPartyDomain);
    test->deleteAllCookies();

    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 2);
    g_assert_true(test->hasDomain(kFirstPartyDomain));
    g_assert_true(test->hasDomain(kThirdPartyDomain));
    test->deleteAllCookies();

    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);

    // ITP never uses NO_THIRD_PARTY.
    auto* manager = webkit_web_context_get_website_data_manager(test->m_webContext.get());
    webkit_website_data_manager_set_itp_enabled(manager, TRUE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    webkit_website_data_manager_set_itp_enabled(manager, FALSE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
    webkit_website_data_manager_set_itp_enabled(manager, TRUE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    webkit_website_data_manager_set_itp_enabled(manager, FALSE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    webkit_website_data_manager_set_itp_enabled(manager, TRUE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
    webkit_website_data_manager_set_itp_enabled(manager, FALSE);
    g_assert_cmpint(test->getAcceptPolicy(), ==, WEBKIT_COOKIE_POLICY_ACCEPT_NEVER);
}

static void testCookieManagerAddCookie(CookieManagerTest* test, gconstpointer)
{
    // Load the html content, with the default NO_THIRD_PARTY accept policy,
    // which will automatically add one cookie.
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Check the cookies that have been added for the domain.
    GUniquePtr<char> uri(g_strdup_printf("http://%s", kFirstPartyDomain));
    GList* foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    SoupCookie* foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kFirstPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePath);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValue);

    // Try to add now a cookie with same (name, domain, path) than the ones already added.
    GUniquePtr<SoupCookie> firstCookie(soup_cookie_new(kCookieName, kCookieValueNew, kFirstPartyDomain, kCookiePath, SOUP_COOKIE_MAX_AGE_ONE_HOUR));
    test->addCookie(firstCookie.get());

    // Still one cookie, since (name, domain, path) are the same than the already existing
    // one, but the new value is now stored as replaced by the recently added cookie.
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kFirstPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePath);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValueNew);

    // Now create another cookie with a different path and add it.
    GUniquePtr<SoupCookie> secondCookie(soup_cookie_new(kCookieName, kCookieValueNew, kFirstPartyDomain, kCookiePathNew, SOUP_COOKIE_MAX_AGE_ONE_HOUR));
    test->addCookie(secondCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Retrieve the list of cookies for the same domain and path again now and check.
    uri.reset(g_strdup_printf("http://%s%s", kFirstPartyDomain, kCookiePathNew));
    foundCookies = test->getCookies(uri.get());

    // We have now two cookies that would apply to the passed URL, one is the cookie initially
    // loaded with the web content and the other cookie the one we manually added.
    g_assert_cmpint(g_list_length(foundCookies), ==, 2);

    // Add a third new cookie for a different domain than the previous ones.
    GUniquePtr<SoupCookie> thirdCookie(soup_cookie_new(kCookieName, kCookieValueNew, kThirdPartyDomain, kCookiePathNew, SOUP_COOKIE_MAX_AGE_ONE_HOUR));
    test->addCookie(thirdCookie.get());

    // Only one cookie now, since the domain is different.
    uri.reset(g_strdup_printf("http://%s%s", kThirdPartyDomain, kCookiePathNew));
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 2);

    foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kThirdPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePathNew);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValueNew);

    // Finally, delete all cookies and check they are all gone.
    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);
}

static void testCookieManagerGetCookies(CookieManagerTest* test, gconstpointer)
{
    // Load the html content and retrieve the two cookies automatically added with ALWAYS policy.
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 2);

    // Retrieve the first cookie using a HTTP scheme.
    GUniquePtr<char> uri(g_strdup_printf("http://%s", kFirstPartyDomain));
    GList* foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    SoupCookie* foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kFirstPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePath);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValue);

    // Retrieve the second cookie using a HTTPS scheme.
    uri.reset(g_strdup_printf("https://%s", kThirdPartyDomain));
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kThirdPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePath);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValue);

    // Create a new cookie and add it to the first domain.
    GUniquePtr<SoupCookie> newCookie(soup_cookie_new(kCookieName, kCookieValueNew, kFirstPartyDomain, kCookiePathNew, SOUP_COOKIE_MAX_AGE_ONE_HOUR));
    test->addCookie(newCookie.get());

    // We should get two cookies that would apply to the same URL passed, since
    // http://127.0.0.1/new is a subset of the http://127.0.0.1/ URL.
    uri.reset(g_strdup_printf("http://%s%s", kFirstPartyDomain, kCookiePathNew));
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 2);

    // We have now two cookies that would apply to the passed URL, one is the cookie initially
    // loaded with the web content and the other cookie the one we manually added.
    g_assert_cmpint(g_list_length(foundCookies), ==, 2);

    bool newPathChecked = false;
    const char* pathFound = nullptr;
    const char* valueFound = nullptr;
    for (uint i = 0; i < 2; i++) {
        foundCookie = static_cast<SoupCookie*>(g_list_nth_data(foundCookies, i));
        g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
        g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kFirstPartyDomain);

        // Cookies will have different values for 'value' and 'path', so make sure that
        // we check for both possibilities, but different ones for each cookie found.
        pathFound = soup_cookie_get_path(foundCookie);
        valueFound = soup_cookie_get_value(foundCookie);
        if (i > 0) {
            if (newPathChecked) {
                g_assert_cmpstr(pathFound, ==, kCookiePath);
                g_assert_cmpstr(valueFound, ==, kCookieValue);
            } else {
                g_assert_cmpstr(pathFound, ==, kCookiePathNew);
                g_assert_cmpstr(valueFound, ==, kCookieValueNew);
            }
        } else {
            if (g_strcmp0(pathFound, kCookiePath)) {
                g_assert_cmpstr(pathFound, ==, kCookiePathNew);
                g_assert_cmpstr(valueFound, ==, kCookieValueNew);
                newPathChecked = true;
            }

            if (g_strcmp0(pathFound, kCookiePathNew)) {
                g_assert_cmpstr(pathFound, ==, kCookiePath);
                g_assert_cmpstr(valueFound, ==, kCookieValue);
                newPathChecked = false;
            }
        }
    }

    // We should get 1 cookie only if we specify http://127.0.0.1/, though.
    uri.reset(g_strdup_printf("http://%s", kFirstPartyDomain));
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    foundCookie = static_cast<SoupCookie*>(foundCookies->data);
    g_assert_cmpstr(soup_cookie_get_name(foundCookie), ==, kCookieName);
    g_assert_cmpstr(soup_cookie_get_domain(foundCookie), ==, kFirstPartyDomain);
    g_assert_cmpstr(soup_cookie_get_path(foundCookie), ==, kCookiePath);
    g_assert_cmpstr(soup_cookie_get_value(foundCookie), ==, kCookieValue);

    // Finally, delete all cookies and try to retrieve them again, one by one.
    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);

    uri.reset(g_strdup_printf("http://%s", kFirstPartyDomain));
    foundCookies = test->getCookies(uri.get());
    g_assert_null(foundCookies);
}

static void testCookieManagerDeleteCookie(CookieManagerTest* test, gconstpointer)
{
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();

    // Initially, there should be two cookies available.
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 2);

    // Delete the cookie for the first party domain.
    GUniquePtr<char> uri(g_strdup_printf("http://%s", kFirstPartyDomain));
    GList* foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    GUniquePtr<SoupCookie> firstPartyCookie(soup_cookie_copy(static_cast<SoupCookie*>(foundCookies->data)));
    test->deleteCookie(firstPartyCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Try deleting a non-existent cookie (wrong name).
    GUniquePtr<SoupCookie> wrongCookie(soup_cookie_new("wrong-name", kCookieValue, kThirdPartyDomain, kCookiePath, -1));
    test->deleteCookie(wrongCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Try deleting a non-existent cookie (wrong domain).
    wrongCookie.reset(soup_cookie_new(kCookieName, kCookieValue, "wrong-domain", kCookiePath, -1));
    test->deleteCookie(wrongCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Try deleting a non-existent cookie (wrong path).
    wrongCookie.reset(soup_cookie_new(kCookieName, kCookieValue, kThirdPartyDomain, "wrong-path", -1));
    test->deleteCookie(wrongCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Delete the cookie for the third party domain.
    uri.reset(g_strdup_printf("http://%s", kThirdPartyDomain));
    foundCookies = test->getCookies(uri.get());
    g_assert_cmpint(g_list_length(foundCookies), ==, 1);

    GUniquePtr<SoupCookie> thirdPartyCookie(soup_cookie_copy(static_cast<SoupCookie*>(foundCookies->data)));
    test->deleteCookie(thirdPartyCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);

    // Finally, add a new cookie now we don't have any and delete it afterwards.
    GUniquePtr<SoupCookie> newCookie(soup_cookie_new(kCookieName, kCookieValueNew, kFirstPartyDomain, kCookiePathNew, SOUP_COOKIE_MAX_AGE_ONE_HOUR));
    test->addCookie(newCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);
    test->deleteCookie(newCookie.get());
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);
}

static void testCookieManagerDeleteCookies(CookieManagerTest* test, gconstpointer)
{
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 2);

    // Delete first party cookies.
    test->deleteCookiesForDomain(kFirstPartyDomain);
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 1);

    // Delete third party cookies.
    test->deleteCookiesForDomain(kThirdPartyDomain);
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);

    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 2);

    // Delete all cookies.
    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);
}

static void testCookieManagerCookiesChanged(CookieManagerTest* test, gconstpointer)
{
    g_assert_false(test->m_cookiesChanged);
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_true(test->m_cookiesChanged);

    test->deleteCookiesForDomain(kFirstPartyDomain);
    test->waitUntilCookiesChanged();
    g_assert_true(test->m_cookiesChanged);

    test->deleteAllCookies();
    test->waitUntilCookiesChanged();
    g_assert_true(test->m_cookiesChanged);
}

class CookiePersistentStorageTest : public CookieManagerTest {
public:
    MAKE_GLIB_TEST_FIXTURE_WITH_SETUP_TEARDOWN(CookiePersistentStorageTest, setup, teardown);

    static void setup()
    {
        WebViewTest::shouldInitializeWebViewInConstructor = false;
    }

    static void teardown()
    {
        WebViewTest::shouldInitializeWebViewInConstructor = true;
    }
};

static void testCookieManagerPersistentStorage(CookiePersistentStorageTest* test, gconstpointer)
{
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);

    g_unlink(test->m_cookiesTextFile.get());
    g_unlink(test->m_cookiesSQLiteFile.get());

    // Text storage using a new file.
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
    char** domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);

    // Initialization of web view is deferred to ensure it's not required for
    // setting persistent storage to work.
    test->initializeWebView();
    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_true(test->m_cookiesChanged);
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 2);
    g_assert_true(g_file_test(test->m_cookiesTextFile.get(), G_FILE_TEST_EXISTS));

    // SQLite storage using a new file.
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);

    test->loadURI(kServer->getURIForPath("/index.html").data());
    test->waitUntilLoadFinished();
    g_assert_true(test->m_cookiesChanged);
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 2);
    g_assert_true(g_file_test(test->m_cookiesSQLiteFile.get(), G_FILE_TEST_EXISTS));

    // Text storage using an existing file.
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 2);
    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);

    // SQLite storage with an existing file.
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 2);
    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);
}

static void testCookieManagerPersistentStorageDeleteAll(CookieManagerTest* test, gconstpointer)
{
    // This checks that we can remove all the cookies of an existing file before a web process is created.
    // See bug https://bugs.webkit.org/show_bug.cgi?id=175265.
    static const char cookiesFileFormat[] = "127.0.0.1\tFALSE\t/\tFALSE\t%ld\tfoo\tbar\nlocalhost\tFALSE\t/\tFALSE\t%ld\tbaz\tqux\n";
    time_t expires = time(nullptr) + 60;
    GUniquePtr<char> cookiesFileContents(g_strdup_printf(cookiesFileFormat, expires, expires));
    GUniquePtr<char> cookiesFile(g_build_filename(Test::dataDirectory(), "cookies.txt", nullptr));
    g_assert_true(g_file_set_contents(cookiesFile.get(), cookiesFileContents.get(), -1, nullptr));

    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
    test->deleteAllCookies();
    // Changed signal is emitted for every deleted cookie, twice in this case.
    test->waitUntilCookiesChanged(2);

    // Ensure the web process is created and load something without cookies.
    test->m_cookiesChanged = false;
    test->loadURI(kServer->getURIForPath("/no-cookies.html").data());
    test->waitUntilLoadFinished();
    g_assert_false(test->m_cookiesChanged);
    char** domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);
}

static void ephemeralViewloadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent, WebViewTest* test)
{
    if (loadEvent != WEBKIT_LOAD_FINISHED)
        return;
    g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(ephemeralViewloadChanged), test);
    test->quitMainLoop();
}

static void testCookieManagerEphemeral(CookieManagerTest* test, gconstpointer)
{
    test->setAcceptPolicy(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
    char** domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);

    auto webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
        "backend", Test::createWebViewBackend(),
#endif
        "web-context", webkit_web_view_get_context(test->m_webView),
        "is-ephemeral", TRUE,
        nullptr));
    g_assert_true(webkit_web_view_is_ephemeral(webView.get()));
    g_assert_false(webkit_web_context_is_ephemeral(webkit_web_view_get_context(webView.get())));

    g_signal_connect(webView.get(), "load-changed", G_CALLBACK(ephemeralViewloadChanged), test);
    webkit_web_view_load_uri(webView.get(), kServer->getURIForPath("/index.html").data());
    g_main_loop_run(test->m_mainLoop);

    domains = test->getDomains();
    g_assert_nonnull(domains);
    g_assert_cmpint(g_strv_length(domains), ==, 0);

    auto* viewDataManager = webkit_web_view_get_website_data_manager(webView.get());
    g_assert_true(WEBKIT_IS_WEBSITE_DATA_MANAGER(viewDataManager));
    test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(viewDataManager));
    g_assert_true(viewDataManager != webkit_web_context_get_website_data_manager(webkit_web_view_get_context(test->m_webView)));
    auto* cookieManager = webkit_website_data_manager_get_cookie_manager(viewDataManager);
    g_assert_true(WEBKIT_IS_COOKIE_MANAGER(cookieManager));
    test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(cookieManager));
    g_assert_true(cookieManager != test->m_cookieManager);
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
    webkit_cookie_manager_get_domains_with_cookies(cookieManager, nullptr, [](GObject* object, GAsyncResult* result, gpointer userData) {
        auto* test = static_cast<CookieManagerTest*>(userData);
        GUniquePtr<char*> domains(webkit_cookie_manager_get_domains_with_cookies_finish(WEBKIT_COOKIE_MANAGER(object), result, nullptr));
        g_assert_nonnull(domains);
        g_assert_cmpint(g_strv_length(domains.get()), ==, 1);
        g_assert_cmpstr(domains.get()[0], ==, kFirstPartyDomain);
        test->quitMainLoop();
    }, test);
    G_GNUC_END_IGNORE_DEPRECATIONS;
    g_main_loop_run(test->m_mainLoop);
}

static void testCookieManagerLongExpires(CookieManagerTest* test, gconstpointer)
{
    g_unlink(test->m_cookiesTextFile.get());
    g_unlink(test->m_cookiesSQLiteFile.get());

    GRefPtr<GDateTime> now = adoptGRef(g_date_time_new_now_utc());
    GRefPtr<GDateTime> expires = adoptGRef(g_date_time_add_years(now.get(), 35));
    GUniquePtr<char> line(g_strdup_printf("#HttpOnly_localhost\tFALSE\t/\tFALSE\t%ld\tprov\t123\tNone", g_date_time_to_unix(expires.get())));
    test->m_cookiesTextFile.reset(g_build_filename(Test::dataDirectory(), "cookies.txt", nullptr));
    g_file_set_contents(test->m_cookiesTextFile.get(), line.get(), -1, nullptr);
    test->setPersistentStorage(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);

    GList* cookies = test->getCookies("http://localhost/");
    g_assert_cmpint(g_list_length(cookies), ==, 1);
    SoupCookie* cookie = static_cast<SoupCookie*>(cookies->data);
    auto* cookiesExpires = soup_cookie_get_expires(cookie);
    g_assert_nonnull(cookiesExpires);
#if USE(SOUP2)
    g_assert_cmpint(g_date_time_to_unix(expires.get()), ==, soup_date_to_time_t(cookiesExpires));
#else
    g_assert_cmpint(g_date_time_to_unix(expires.get()), ==, g_date_time_to_unix(cookiesExpires));
#endif

    test->deleteAllCookies();
    g_assert_cmpint(g_strv_length(test->getDomains()), ==, 0);
}

#if USE(SOUP2)
static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
#else
static void serverCallback(SoupServer* server, SoupServerMessage* message, const char* path, GHashTable*, gpointer)
#endif
{
    if (soup_server_message_get_method(message) != SOUP_METHOD_GET) {
        soup_server_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED, nullptr);
        return;
    }

    soup_server_message_set_status(message, SOUP_STATUS_OK, nullptr);
    gchar* header_str = g_strdup_printf("%s=%s; Max-Age=60", kCookieName, kCookieValue);

    if (g_str_equal(path, "/index.html")) {
        char* indexHtml = g_strdup_printf(kIndexHtmlFormat, kServer->port());
        soup_message_headers_replace(soup_server_message_get_response_headers(message), "Set-Cookie", header_str);
        soup_message_body_append(soup_server_message_get_response_body(message), SOUP_MEMORY_TAKE, indexHtml, strlen(indexHtml));
    } else if (g_str_equal(path, "/image.png"))
        soup_message_headers_replace(soup_server_message_get_response_headers(message), "Set-Cookie", header_str);
    else if (g_str_equal(path, "/no-cookies.html")) {
        static const char* indexHtml = "<html><body><p>No cookies</p></body></html>";
        soup_message_body_append(soup_server_message_get_response_body(message), SOUP_MEMORY_STATIC, indexHtml, strlen(indexHtml));
    } else
        soup_server_message_set_status(message, SOUP_STATUS_NOT_FOUND, nullptr);
    soup_message_body_complete(soup_server_message_get_response_body(message));
}

void beforeAll()
{
    kServer = new WebKitTestServer();
    kServer->run(serverCallback);

    CookieManagerTest::add("WebKitCookieManager", "accept-policy", testCookieManagerAcceptPolicy);
    CookieManagerTest::add("WebKitCookieManager", "add-cookie", testCookieManagerAddCookie);
    CookieManagerTest::add("WebKitCookieManager", "get-cookies", testCookieManagerGetCookies);
    CookieManagerTest::add("WebKitCookieManager", "delete-cookie", testCookieManagerDeleteCookie);
    CookieManagerTest::add("WebKitCookieManager", "delete-cookies", testCookieManagerDeleteCookies);
    CookieManagerTest::add("WebKitCookieManager", "cookies-changed", testCookieManagerCookiesChanged);
    CookiePersistentStorageTest::add("WebKitCookieManager", "persistent-storage", testCookieManagerPersistentStorage);
    CookieManagerTest::add("WebKitCookieManager", "persistent-storage-delete-all", testCookieManagerPersistentStorageDeleteAll);
    CookieManagerTest::add("WebKitCookieManager", "ephemeral", testCookieManagerEphemeral);
    CookieManagerTest::add("WebKitCookieManager", "long-expires", testCookieManagerLongExpires);
}

void afterAll()
{
    delete kServer;
}
