/* * Copyright (C) 2013 Igalia S.L. * * 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 "CustomProtocolManager.h" #include "CustomProtocolManagerMessages.h" #include "DataReference.h" #include "NetworkProcess.h" #include "WebKitSoupRequestInputStream.h" #include #include #include #include #include #include #include #include using namespace WebCore; namespace WebKit { CustomProtocolManager::WebSoupRequestAsyncData::WebSoupRequestAsyncData(GRefPtr&& requestTask, WebKitSoupRequestGeneric* requestGeneric) : task(WTFMove(requestTask)) , request(requestGeneric) , cancellable(g_task_get_cancellable(task.get())) { // If the struct contains a null request, it is because the request failed. g_object_add_weak_pointer(G_OBJECT(request), reinterpret_cast(&request)); } CustomProtocolManager::WebSoupRequestAsyncData::~WebSoupRequestAsyncData() { if (request) g_object_remove_weak_pointer(G_OBJECT(request), reinterpret_cast(&request)); } class CustomProtocolRequestClient final : public WebKitSoupRequestGenericClient { public: static CustomProtocolRequestClient& singleton() { static NeverDestroyed client; return client; } private: void startRequest(GRefPtr&& task) override { WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(task.get())); auto* customProtocolManager = NetworkProcess::singleton().supplement(); if (!customProtocolManager) return; auto customProtocolID = customProtocolManager->addCustomProtocol(std::make_unique(WTFMove(task), request)); customProtocolManager->startLoading(customProtocolID, webkitSoupRequestGenericGetRequest(request)); } }; void CustomProtocolManager::registerProtocolClass() { static_cast(g_type_class_ref(WEBKIT_TYPE_SOUP_REQUEST_GENERIC))->client = &CustomProtocolRequestClient::singleton(); SoupNetworkSession::setCustomProtocolRequestType(WEBKIT_TYPE_SOUP_REQUEST_GENERIC); } void CustomProtocolManager::registerScheme(const String& scheme) { if (!m_registeredSchemes) m_registeredSchemes = adoptGRef(g_ptr_array_new_with_free_func(g_free)); if (m_registeredSchemes->len) g_ptr_array_remove_index_fast(m_registeredSchemes.get(), m_registeredSchemes->len - 1); g_ptr_array_add(m_registeredSchemes.get(), g_strdup(scheme.utf8().data())); g_ptr_array_add(m_registeredSchemes.get(), nullptr); auto* genericRequestClass = static_cast(g_type_class_peek(WEBKIT_TYPE_SOUP_REQUEST_GENERIC)); ASSERT(genericRequestClass); genericRequestClass->schemes = const_cast(reinterpret_cast(m_registeredSchemes->pdata)); NetworkStorageSession::forEach([](const WebCore::NetworkStorageSession& session) { if (auto* soupSession = session.soupNetworkSession()) soupSession->setupCustomProtocols(); }); } void CustomProtocolManager::unregisterScheme(const String&) { notImplemented(); } bool CustomProtocolManager::supportsScheme(const String& scheme) { if (scheme.isNull()) return false; CString cScheme = scheme.utf8(); for (unsigned i = 0; i < m_registeredSchemes->len; ++i) { if (cScheme == static_cast(g_ptr_array_index(m_registeredSchemes.get(), i))) return true; } return false; } void CustomProtocolManager::didFailWithError(uint64_t customProtocolID, const ResourceError& error) { auto* data = m_customProtocolMap.get(customProtocolID); ASSERT(data); // Either we haven't started reading the stream yet, in which case we need to complete the // task first, or we failed reading it and the task was already completed by didLoadData(). ASSERT(!data->stream || !data->task); if (!data->stream) { GRefPtr task = std::exchange(data->task, nullptr); ASSERT(task.get()); g_task_return_new_error(task.get(), g_quark_from_string(error.domain().utf8().data()), error.errorCode(), "%s", error.localizedDescription().utf8().data()); } else webkitSoupRequestInputStreamDidFailWithError(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), error); removeCustomProtocol(customProtocolID); } void CustomProtocolManager::didLoadData(uint64_t customProtocolID, const IPC::DataReference& dataReference) { auto* data = m_customProtocolMap.get(customProtocolID); // The data might have been removed from the request map if a previous chunk failed // and a new message was sent by the UI process before being notified about the failure. if (!data) return; if (!data->stream) { GRefPtr task = std::exchange(data->task, nullptr); ASSERT(task.get()); goffset soupContentLength = soup_request_get_content_length(SOUP_REQUEST(g_task_get_source_object(task.get()))); uint64_t contentLength = soupContentLength == -1 ? 0 : static_cast(soupContentLength); if (!dataReference.size()) { // Empty reply, just create and empty GMemoryInputStream. data->stream = g_memory_input_stream_new(); } else if (dataReference.size() == contentLength) { // We don't expect more data, so we can just create a GMemoryInputStream with all the data. data->stream = g_memory_input_stream_new_from_data(g_memdup(dataReference.data(), dataReference.size()), contentLength, g_free); } else { // We expect more data chunks from the UI process. data->stream = webkitSoupRequestInputStreamNew(contentLength); webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); } g_task_return_pointer(task.get(), data->stream.get(), g_object_unref); return; } if (g_cancellable_is_cancelled(data->cancellable.get()) || !data->request) { // ResourceRequest failed or it was cancelled. It doesn't matter here the error or if it was cancelled, // because that's already handled by the resource handle client, we just want to notify the UI process // to stop reading data from the user input stream. If UI process already sent all the data we simply // finish silently. if (!webkitSoupRequestInputStreamFinished(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()))) stopLoading(customProtocolID); return; } webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); } void CustomProtocolManager::didReceiveResponse(uint64_t customProtocolID, const ResourceResponse& response, uint32_t) { auto* data = m_customProtocolMap.get(customProtocolID); // The data might have been removed from the request map if an error happened even before this point. if (!data) return; ASSERT(data->task); WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(data->task.get())); webkitSoupRequestGenericSetContentLength(request, response.expectedContentLength() ? response.expectedContentLength() : -1); webkitSoupRequestGenericSetContentType(request, !response.mimeType().isEmpty() ? response.mimeType().utf8().data() : 0); } void CustomProtocolManager::didFinishLoading(uint64_t customProtocolID) { ASSERT(m_customProtocolMap.contains(customProtocolID)); removeCustomProtocol(customProtocolID); } void CustomProtocolManager::wasRedirectedToRequest(uint64_t, const ResourceRequest&, const ResourceResponse&) { notImplemented(); } } // namespace WebKit