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
42static const String& networkHTTPSUpgradeCheckerDatabasePath()
43{
44 static NeverDestroyed<String> networkHTTPSUpgradeCheckerDatabasePath;
45#if PLATFORM(COCOA)
46 if (networkHTTPSUpgradeCheckerDatabasePath.get().isNull()) {
47 CFBundleRef webKitBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit"));
48 auto resourceURL = adoptCF(CFBundleCopyResourceURL(webKitBundle, CFSTR("HTTPSUpgradeList"), CFSTR("db"), nullptr));
49 if (resourceURL)
50 networkHTTPSUpgradeCheckerDatabasePath.get() = CFURLGetString(resourceURL.get());
51 }
52#endif // PLATFORM(COCOA)
53 return networkHTTPSUpgradeCheckerDatabasePath;
54}
55
56NetworkHTTPSUpgradeChecker::NetworkHTTPSUpgradeChecker()
57 : m_workQueue(WorkQueue::create("HTTPS Upgrade Checker Thread"))
58 , m_database(makeUniqueRef<WebCore::SQLiteDatabase>())
59 , m_statement(makeUniqueRef<WebCore::SQLiteStatement>(m_database.get(), "SELECT host FROM hosts WHERE host = ?;"_s))
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 bool isDatabaseOpen = m_database->open(path, WebCore::SQLiteDatabase::OpenMode::ReadOnly);
71 if (!isDatabaseOpen) {
72#if PLATFORM(COCOA)
73 RELEASE_LOG_ERROR(Network, "%p - NetworkHTTPSUpgradeChecker::open failed, error message: %{public}s, database path: %{public}s", this, m_database->lastErrorMsg(), path.utf8().data());
74#endif
75 ASSERT_NOT_REACHED();
76 return;
77 }
78
79 // Since we are using a workerQueue, the sequential dispatch blocks may be called by different threads.
80 m_database->disableThreadingChecks();
81
82 int isStatementPrepared = (m_statement->prepare() == SQLITE_OK);
83 ASSERT(isStatementPrepared);
84 if (!isStatementPrepared)
85 return;
86
87 m_didSetupCompleteSuccessfully = true;
88 });
89}
90
91NetworkHTTPSUpgradeChecker::~NetworkHTTPSUpgradeChecker()
92{
93 // This object should be owned by a singleton object.
94 ASSERT_NOT_REACHED();
95}
96
97void NetworkHTTPSUpgradeChecker::query(String&& host, PAL::SessionID sessionID, CompletionHandler<void(bool)>&& callback)
98{
99 ASSERT(RunLoop::isMain());
100
101 m_workQueue->dispatch([this, host = host.isolatedCopy(), sessionID, callback = WTFMove(callback)] () mutable {
102 ASSERT(m_didSetupCompleteSuccessfully);
103
104 int bindTextResult = m_statement->bindText(1, WTFMove(host));
105 ASSERT_UNUSED(bindTextResult, bindTextResult == SQLITE_OK);
106
107 int stepResult = m_statement->step();
108 if (stepResult != SQLITE_ROW && stepResult != SQLITE_DONE) {
109#if PLATFORM(COCOA)
110 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());
111#endif
112 ASSERT_NOT_REACHED();
113 RunLoop::main().dispatch([callback = WTFMove(callback)] () mutable {
114 callback(false);
115 });
116 return;
117 }
118
119 int resetResult = m_statement->reset();
120 ASSERT_UNUSED(resetResult, resetResult == SQLITE_OK);
121
122 bool foundHost = (stepResult == SQLITE_ROW);
123 RELEASE_LOG_IF_ALLOWED(sessionID, "query - Ran successfully. Result = %s", (foundHost ? "true" : "false"));
124 RunLoop::main().dispatch([foundHost, callback = WTFMove(callback)] () mutable {
125 callback(foundHost);
126 });
127 });
128}
129
130} // namespace WebKit
131
132