| 1 | /* |
| 2 | * Copyright (C) 2013 Igalia S.L. |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Library General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 2 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Library General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Library General Public License |
| 15 | * along with this library; see the file COPYING.LIB. If not, write to |
| 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 17 | * Boston, MA 02110-1301, USA. |
| 18 | */ |
| 19 | |
| 20 | #include "config.h" |
| 21 | #include "LegacyCustomProtocolManager.h" |
| 22 | |
| 23 | #include "DataReference.h" |
| 24 | #include "LegacyCustomProtocolManagerMessages.h" |
| 25 | #include "NetworkProcess.h" |
| 26 | #include "WebKitSoupRequestInputStream.h" |
| 27 | #include <WebCore/NetworkStorageSession.h> |
| 28 | #include <WebCore/NotImplemented.h> |
| 29 | #include <WebCore/ResourceError.h> |
| 30 | #include <WebCore/ResourceRequest.h> |
| 31 | #include <WebCore/ResourceResponse.h> |
| 32 | #include <WebCore/SoupNetworkSession.h> |
| 33 | #include <WebCore/WebKitSoupRequestGeneric.h> |
| 34 | #include <wtf/NeverDestroyed.h> |
| 35 | |
| 36 | namespace WebKit { |
| 37 | using namespace WebCore; |
| 38 | |
| 39 | RefPtr<NetworkProcess>& lastCreatedNetworkProcess() |
| 40 | { |
| 41 | static NeverDestroyed<RefPtr<NetworkProcess>> networkProcess; |
| 42 | return networkProcess.get(); |
| 43 | } |
| 44 | |
| 45 | void LegacyCustomProtocolManager::networkProcessCreated(NetworkProcess& networkProcess) |
| 46 | { |
| 47 | lastCreatedNetworkProcess() = &networkProcess; |
| 48 | } |
| 49 | |
| 50 | LegacyCustomProtocolManager::WebSoupRequestAsyncData::WebSoupRequestAsyncData(GRefPtr<GTask>&& requestTask, WebKitSoupRequestGeneric* requestGeneric) |
| 51 | : task(WTFMove(requestTask)) |
| 52 | , request(requestGeneric) |
| 53 | , cancellable(g_task_get_cancellable(task.get())) |
| 54 | { |
| 55 | // If the struct contains a null request, it is because the request failed. |
| 56 | g_object_add_weak_pointer(G_OBJECT(request), reinterpret_cast<void**>(&request)); |
| 57 | } |
| 58 | |
| 59 | LegacyCustomProtocolManager::WebSoupRequestAsyncData::~WebSoupRequestAsyncData() |
| 60 | { |
| 61 | if (request) |
| 62 | g_object_remove_weak_pointer(G_OBJECT(request), reinterpret_cast<void**>(&request)); |
| 63 | } |
| 64 | |
| 65 | class CustomProtocolRequestClient final : public WebKitSoupRequestGenericClient { |
| 66 | public: |
| 67 | static CustomProtocolRequestClient& singleton() |
| 68 | { |
| 69 | static NeverDestroyed<CustomProtocolRequestClient> client; |
| 70 | return client; |
| 71 | } |
| 72 | |
| 73 | private: |
| 74 | void startRequest(GRefPtr<GTask>&& task) override |
| 75 | { |
| 76 | WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(task.get())); |
| 77 | auto* customProtocolManager = lastCreatedNetworkProcess()->supplement<LegacyCustomProtocolManager>(); |
| 78 | if (!customProtocolManager) |
| 79 | return; |
| 80 | |
| 81 | auto customProtocolID = customProtocolManager->addCustomProtocol(std::make_unique<LegacyCustomProtocolManager::WebSoupRequestAsyncData>(WTFMove(task), request)); |
| 82 | customProtocolManager->startLoading(customProtocolID, webkitSoupRequestGenericGetRequest(request)); |
| 83 | } |
| 84 | }; |
| 85 | |
| 86 | void LegacyCustomProtocolManager::registerProtocolClass() |
| 87 | { |
| 88 | static_cast<WebKitSoupRequestGenericClass*>(g_type_class_ref(WEBKIT_TYPE_SOUP_REQUEST_GENERIC))->client = &CustomProtocolRequestClient::singleton(); |
| 89 | SoupNetworkSession::setCustomProtocolRequestType(WEBKIT_TYPE_SOUP_REQUEST_GENERIC); |
| 90 | } |
| 91 | |
| 92 | void LegacyCustomProtocolManager::registerScheme(const String& scheme) |
| 93 | { |
| 94 | if (!m_registeredSchemes) |
| 95 | m_registeredSchemes = adoptGRef(g_ptr_array_new_with_free_func(g_free)); |
| 96 | |
| 97 | if (m_registeredSchemes->len) |
| 98 | g_ptr_array_remove_index_fast(m_registeredSchemes.get(), m_registeredSchemes->len - 1); |
| 99 | g_ptr_array_add(m_registeredSchemes.get(), g_strdup(scheme.utf8().data())); |
| 100 | g_ptr_array_add(m_registeredSchemes.get(), nullptr); |
| 101 | |
| 102 | auto* genericRequestClass = static_cast<SoupRequestClass*>(g_type_class_peek(WEBKIT_TYPE_SOUP_REQUEST_GENERIC)); |
| 103 | ASSERT(genericRequestClass); |
| 104 | genericRequestClass->schemes = const_cast<const char**>(reinterpret_cast<char**>(m_registeredSchemes->pdata)); |
| 105 | lastCreatedNetworkProcess()->forEachNetworkStorageSession([](const auto& session) { |
| 106 | session.soupNetworkSession().setupCustomProtocols(); |
| 107 | }); |
| 108 | } |
| 109 | |
| 110 | void LegacyCustomProtocolManager::unregisterScheme(const String&) |
| 111 | { |
| 112 | notImplemented(); |
| 113 | } |
| 114 | |
| 115 | bool LegacyCustomProtocolManager::supportsScheme(const String& scheme) |
| 116 | { |
| 117 | if (scheme.isNull()) |
| 118 | return false; |
| 119 | |
| 120 | CString cScheme = scheme.utf8(); |
| 121 | for (unsigned i = 0; i < m_registeredSchemes->len; ++i) { |
| 122 | if (cScheme == static_cast<char*>(g_ptr_array_index(m_registeredSchemes.get(), i))) |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | return false; |
| 127 | } |
| 128 | |
| 129 | void LegacyCustomProtocolManager::didFailWithError(uint64_t customProtocolID, const ResourceError& error) |
| 130 | { |
| 131 | auto* data = m_customProtocolMap.get(customProtocolID); |
| 132 | ASSERT(data); |
| 133 | |
| 134 | // Either we haven't started reading the stream yet, in which case we need to complete the |
| 135 | // task first, or we failed reading it and the task was already completed by didLoadData(). |
| 136 | ASSERT(!data->stream || !data->task); |
| 137 | |
| 138 | if (!data->stream) { |
| 139 | GRefPtr<GTask> task = std::exchange(data->task, nullptr); |
| 140 | ASSERT(task.get()); |
| 141 | g_task_return_new_error(task.get(), g_quark_from_string(error.domain().utf8().data()), |
| 142 | error.errorCode(), "%s" , error.localizedDescription().utf8().data()); |
| 143 | } else |
| 144 | webkitSoupRequestInputStreamDidFailWithError(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), error); |
| 145 | |
| 146 | removeCustomProtocol(customProtocolID); |
| 147 | } |
| 148 | |
| 149 | void LegacyCustomProtocolManager::didLoadData(uint64_t customProtocolID, const IPC::DataReference& dataReference) |
| 150 | { |
| 151 | auto* data = m_customProtocolMap.get(customProtocolID); |
| 152 | // The data might have been removed from the request map if a previous chunk failed |
| 153 | // and a new message was sent by the UI process before being notified about the failure. |
| 154 | if (!data) |
| 155 | return; |
| 156 | |
| 157 | if (!data->stream) { |
| 158 | GRefPtr<GTask> task = std::exchange(data->task, nullptr); |
| 159 | ASSERT(task.get()); |
| 160 | |
| 161 | goffset soupContentLength = soup_request_get_content_length(SOUP_REQUEST(g_task_get_source_object(task.get()))); |
| 162 | uint64_t contentLength = soupContentLength == -1 ? 0 : static_cast<uint64_t>(soupContentLength); |
| 163 | if (!dataReference.size()) { |
| 164 | // Empty reply, just create and empty GMemoryInputStream. |
| 165 | data->stream = g_memory_input_stream_new(); |
| 166 | } else if (dataReference.size() == contentLength) { |
| 167 | // We don't expect more data, so we can just create a GMemoryInputStream with all the data. |
| 168 | data->stream = g_memory_input_stream_new_from_data(g_memdup(dataReference.data(), dataReference.size()), contentLength, g_free); |
| 169 | } else { |
| 170 | // We expect more data chunks from the UI process. |
| 171 | data->stream = webkitSoupRequestInputStreamNew(contentLength); |
| 172 | webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); |
| 173 | } |
| 174 | g_task_return_pointer(task.get(), data->stream.get(), g_object_unref); |
| 175 | return; |
| 176 | } |
| 177 | |
| 178 | if (g_cancellable_is_cancelled(data->cancellable.get()) || !data->request) { |
| 179 | // ResourceRequest failed or it was cancelled. It doesn't matter here the error or if it was cancelled, |
| 180 | // because that's already handled by the resource handle client, we just want to notify the UI process |
| 181 | // to stop reading data from the user input stream. If UI process already sent all the data we simply |
| 182 | // finish silently. |
| 183 | if (!webkitSoupRequestInputStreamFinished(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()))) |
| 184 | stopLoading(customProtocolID); |
| 185 | |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); |
| 190 | } |
| 191 | |
| 192 | void LegacyCustomProtocolManager::didReceiveResponse(uint64_t customProtocolID, const ResourceResponse& response, uint32_t) |
| 193 | { |
| 194 | auto* data = m_customProtocolMap.get(customProtocolID); |
| 195 | // The data might have been removed from the request map if an error happened even before this point. |
| 196 | if (!data) |
| 197 | return; |
| 198 | |
| 199 | ASSERT(data->task); |
| 200 | |
| 201 | WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(data->task.get())); |
| 202 | webkitSoupRequestGenericSetContentLength(request, response.expectedContentLength() ? response.expectedContentLength() : -1); |
| 203 | webkitSoupRequestGenericSetContentType(request, !response.mimeType().isEmpty() ? response.mimeType().utf8().data() : 0); |
| 204 | } |
| 205 | |
| 206 | void LegacyCustomProtocolManager::didFinishLoading(uint64_t customProtocolID) |
| 207 | { |
| 208 | ASSERT(m_customProtocolMap.contains(customProtocolID)); |
| 209 | removeCustomProtocol(customProtocolID); |
| 210 | } |
| 211 | |
| 212 | void LegacyCustomProtocolManager::wasRedirectedToRequest(uint64_t, const ResourceRequest&, const ResourceResponse&) |
| 213 | { |
| 214 | notImplemented(); |
| 215 | } |
| 216 | |
| 217 | } // namespace WebKit |
| 218 | |