NetworkHTTPSUpgradeChecker.cpp   [plain text]


/*
 * Copyright (C) 2018 Apple Inc. 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. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "NetworkHTTPSUpgradeChecker.h"

#include "Logging.h"
#include <WebCore/SQLiteDatabase.h>
#include <WebCore/SQLiteStatement.h>
#include <pal/SessionID.h>
#include <wtf/CompletionHandler.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RunLoop.h>

#undef RELEASE_LOG_IF_ALLOWED
#undef RELEASE_LOG_ERROR_IF_ALLOWED
#define RELEASE_LOG_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkHTTPSUpgradeChecker::" fmt, this, ##__VA_ARGS__)
#define RELEASE_LOG_ERROR_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_ERROR_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkHTTPSUpgradeChecker::" fmt, this, ##__VA_ARGS__)

namespace WebKit {

constexpr auto httpsUpgradeCheckerQuery = "SELECT host FROM hosts WHERE host = ?"_s;

static const String& networkHTTPSUpgradeCheckerDatabasePath()
{
    static NeverDestroyed<String> networkHTTPSUpgradeCheckerDatabasePath;
#if PLATFORM(COCOA)
    if (networkHTTPSUpgradeCheckerDatabasePath.get().isNull()) {
        CFBundleRef webKitBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit"));
        auto resourceURL = adoptCF(CFBundleCopyResourceURL(webKitBundle, CFSTR("HTTPSUpgradeList"), CFSTR("db"), nullptr));
        if (resourceURL)
            networkHTTPSUpgradeCheckerDatabasePath.get() = CFURLGetString(resourceURL.get());
    }
#endif // PLATFORM(COCOA)
    return networkHTTPSUpgradeCheckerDatabasePath;
}

NetworkHTTPSUpgradeChecker::NetworkHTTPSUpgradeChecker()
    : m_workQueue(WorkQueue::create("HTTPS Upgrade Checker Thread"))
{
    ASSERT(RunLoop::isMain());

    m_workQueue->dispatch([this] {
        auto path = networkHTTPSUpgradeCheckerDatabasePath();
        if (path.isEmpty()) {
            RELEASE_LOG_ERROR(Network, "%p - NetworkHTTPSUpgradeChecker failed to initialize because the database path is empty", this);
            return;
        }

        m_database = makeUnique<WebCore::SQLiteDatabase>();
        bool isDatabaseOpen = m_database->open(path, WebCore::SQLiteDatabase::OpenMode::ReadOnly);
        if (!isDatabaseOpen) {
#if PLATFORM(COCOA)
            RELEASE_LOG_ERROR(Network, "%p - NetworkHTTPSUpgradeChecker::open failed, error message: %{public}s, database path: %{public}s", this, m_database->lastErrorMsg(), path.utf8().data());
#endif
            ASSERT_NOT_REACHED();
            return;
        }

        // Since we are using a workerQueue, the sequential dispatch blocks may be called by different threads.
        m_database->disableThreadingChecks();

        m_statement = makeUnique<WebCore::SQLiteStatement>(*m_database, httpsUpgradeCheckerQuery);
        int isStatementPrepared = (m_statement->prepare() == SQLITE_OK);
        ASSERT(isStatementPrepared);
        if (!isStatementPrepared)
            return;

        m_didSetupCompleteSuccessfully = true;
    });
}

NetworkHTTPSUpgradeChecker::~NetworkHTTPSUpgradeChecker()
{
    if (m_database)
        m_workQueue->dispatch([database = WTFMove(m_database), statement = WTFMove(m_statement)] { });
}

void NetworkHTTPSUpgradeChecker::query(String&& host, PAL::SessionID sessionID, CompletionHandler<void(bool)>&& callback)
{
    ASSERT(RunLoop::isMain());

    m_workQueue->dispatch([this, host = host.isolatedCopy(), sessionID, callback = WTFMove(callback)] () mutable {
        ASSERT(m_didSetupCompleteSuccessfully);

        int bindTextResult = m_statement->bindText(1, WTFMove(host));
        ASSERT_UNUSED(bindTextResult, bindTextResult == SQLITE_OK);

        int stepResult = m_statement->step();
        if (stepResult != SQLITE_ROW && stepResult != SQLITE_DONE) {
#if PLATFORM(COCOA)
            RELEASE_LOG_ERROR_IF_ALLOWED(sessionID, "step failed with error code %d, error message: %{public}s, database path: %{public}s", stepResult, m_database->lastErrorMsg(), networkHTTPSUpgradeCheckerDatabasePath().utf8().data());
#else
            UNUSED_VARIABLE(sessionID);
#endif
            ASSERT_NOT_REACHED();
            RunLoop::main().dispatch([callback = WTFMove(callback)] () mutable {
                callback(false);
            });
            return;
        }

        int resetResult = m_statement->reset();
        ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK);

        bool foundHost = (stepResult == SQLITE_ROW);
        RELEASE_LOG_IF_ALLOWED(sessionID, "query - Ran successfully. Result = %s", (foundHost ? "true" : "false"));
        RunLoop::main().dispatch([foundHost, callback = WTFMove(callback)] () mutable {
            callback(foundHost);
        });
    });
}

} // namespace WebKit