1/*
2 * Copyright (C) 2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "NetworkHTTPSUpgradeChecker.h"
28
29#include "Logging.h"
30#include <WebCore/SQLiteDatabase.h>
31#include <WebCore/SQLiteStatement.h>
32#include <pal/SessionID.h>
33#include <wtf/CompletionHandler.h>
34#include <wtf/NeverDestroyed.h>
35#include <wtf/RunLoop.h>
36
37#define RELEASE_LOG_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkHTTPSUpgradeChecker::" fmt, this, ##__VA_ARGS__)
38#define RELEASE_LOG_ERROR_IF_ALLOWED(sessionID, fmt, ...) RELEASE_LOG_ERROR_IF(sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkHTTPSUpgradeChecker::" fmt, this, ##__VA_ARGS__)
39
40namespace WebKit {
41
42constexpr auto httpsUpgradeCheckerQuery = "SELECT host FROM hosts WHERE host = ?"_s;
43
44static const String& networkHTTPSUpgradeCheckerDatabasePath()
45{
46 static NeverDestroyed<String> networkHTTPSUpgradeCheckerDatabasePath;
47#if PLATFORM(COCOA)
48 if (networkHTTPSUpgradeCheckerDatabasePath.get().isNull()) {
49 CFBundleRef webKitBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit"));
50 auto resourceURL = adoptCF(CFBundleCopyResourceURL(webKitBundle, CFSTR("HTTPSUpgradeList"), CFSTR("db"), nullptr));
51 if (resourceURL)
52 networkHTTPSUpgradeCheckerDatabasePath.get() = CFURLGetString(resourceURL.get());
53 }
54#endif // PLATFORM(COCOA)
55 return networkHTTPSUpgradeCheckerDatabasePath;
56}
57
58NetworkHTTPSUpgradeChecker::NetworkHTTPSUpgradeChecker()
59 : m_workQueue(WorkQueue::create("HTTPS Upgrade Checker Thread"))
60{
61 ASSERT(RunLoop::isMain());
62
63 m_workQueue->dispatch([this] {
64 auto path = networkHTTPSUpgradeCheckerDatabasePath();
65 if (path.isEmpty()) {
66 RELEASE_LOG_ERROR(Network, "%p - NetworkHTTPSUpgradeChecker failed to initialize because the database path is empty", this);
67 return;
68 }
69
70 m_database = std::make_unique<WebCore::SQLiteDatabase>();
71 bool isDatabaseOpen = m_database->open(path, WebCore::SQLiteDatabase::OpenMode::ReadOnly);
72 if (!isDatabaseOpen) {
73#if PLATFORM(COCOA)
74 RELEASE_LOG_ERROR(Network, "%p - NetworkHTTPSUpgradeChecker::open failed, error message: %{public}s, database path: %{public}s", this, m_database->lastErrorMsg(), path.utf8().data());
75#endif
76 ASSERT_NOT_REACHED();
77 return;
78 }
79
80 // Since we are using a workerQueue, the sequential dispatch blocks may be called by different threads.
81 m_database->disableThreadingChecks();
82
83 m_statement = std::make_unique<WebCore::SQLiteStatement>(*m_database, httpsUpgradeCheckerQuery);
84 int isStatementPrepared = (m_statement->prepare() == SQLITE_OK);
85 ASSERT(isStatementPrepared);
86 if (!isStatementPrepared)
87 return;
88
89 m_didSetupCompleteSuccessfully = true;
90 });
91}
92
93NetworkHTTPSUpgradeChecker::~NetworkHTTPSUpgradeChecker()
94{
95 if (m_database)
96 m_workQueue->dispatch([database = WTFMove(m_database), statement = WTFMove(m_statement)] { });
97}
98
99void NetworkHTTPSUpgradeChecker::query(String&& host, PAL::SessionID sessionID, CompletionHandler<void(bool)>&& callback)
100{
101 ASSERT(RunLoop::isMain());
102
103 m_workQueue->dispatch([this, host = host.isolatedCopy(), sessionID, callback = WTFMove(callback)] () mutable {
104 ASSERT(m_didSetupCompleteSuccessfully);
105
106 int bindTextResult = m_statement->bindText(1, WTFMove(host));
107 ASSERT_UNUSED(bindTextResult, bindTextResult == SQLITE_OK);
108
109 int stepResult = m_statement->step();
110 if (stepResult != SQLITE_ROW && stepResult != SQLITE_DONE) {
111#if PLATFORM(COCOA)
112 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());
113#endif
114 ASSERT_NOT_REACHED();
115 RunLoop::main().dispatch([callback = WTFMove(callback)] () mutable {
116 callback(false);
117 });
118 return;
119 }
120
121 int resetResult = m_statement->reset();
122 ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK);
123
124 bool foundHost = (stepResult == SQLITE_ROW);
125 RELEASE_LOG_IF_ALLOWED(sessionID, "query - Ran successfully. Result = %s", (foundHost ? "true" : "false"));
126 RunLoop::main().dispatch([foundHost, callback = WTFMove(callback)] () mutable {
127 callback(foundHost);
128 });
129 });
130}
131
132} // namespace WebKit
133
134