1/* 2 * Copyright (C) 2014 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 "CustomProtocolManagerImpl.h" 22 23#if ENABLE(CUSTOM_PROTOCOLS) 24 25#include "ChildProcess.h" 26#include "CustomProtocolManagerProxyMessages.h" 27#include "DataReference.h" 28#include "WebCoreArgumentCoders.h" 29#include "WebKitSoupRequestGeneric.h" 30#include "WebKitSoupRequestInputStream.h" 31#include <WebCore/ResourceError.h> 32#include <WebCore/ResourceRequest.h> 33#include <WebCore/ResourceResponse.h> 34#include <WebCore/SoupNetworkSession.h> 35 36namespace WebKit { 37 38static uint64_t generateCustomProtocolID() 39{ 40 static uint64_t uniqueCustomProtocolID = 0; 41 return ++uniqueCustomProtocolID; 42} 43 44struct WebSoupRequestAsyncData { 45 WebSoupRequestAsyncData(GTask* task, WebKitSoupRequestGeneric* requestGeneric) 46 : task(task) 47 , request(requestGeneric) 48 , cancellable(g_task_get_cancellable(task)) 49 { 50 // If the struct contains a null request, it is because the request failed. 51 g_object_add_weak_pointer(G_OBJECT(request), reinterpret_cast<void**>(&request)); 52 } 53 54 ~WebSoupRequestAsyncData() 55 { 56 if (request) 57 g_object_remove_weak_pointer(G_OBJECT(request), reinterpret_cast<void**>(&request)); 58 } 59 60 bool requestFailed() 61 { 62 return g_cancellable_is_cancelled(cancellable.get()) || !request; 63 } 64 65 GRefPtr<GTask> releaseTask() 66 { 67 GTask* returnValue = task; 68 task = nullptr; 69 return adoptGRef(returnValue); 70 } 71 72 GTask* task; 73 WebKitSoupRequestGeneric* request; 74 GRefPtr<GCancellable> cancellable; 75 GRefPtr<GInputStream> stream; 76}; 77 78CustomProtocolManagerImpl::CustomProtocolManagerImpl(ChildProcess* childProcess) 79 : m_childProcess(childProcess) 80 , m_schemes(adoptGRef(g_ptr_array_new_with_free_func(g_free))) 81{ 82} 83 84CustomProtocolManagerImpl::~CustomProtocolManagerImpl() 85{ 86} 87 88void CustomProtocolManagerImpl::registerScheme(const String& scheme) 89{ 90 if (m_schemes->len) 91 g_ptr_array_remove_index_fast(m_schemes.get(), m_schemes->len - 1); 92 g_ptr_array_add(m_schemes.get(), g_strdup(scheme.utf8().data())); 93 g_ptr_array_add(m_schemes.get(), nullptr); 94 95 SoupSession* session = WebCore::SoupNetworkSession::defaultSession().soupSession(); 96 SoupRequestClass* genericRequestClass = static_cast<SoupRequestClass*>(g_type_class_ref(WEBKIT_TYPE_SOUP_REQUEST_GENERIC)); 97 genericRequestClass->schemes = const_cast<const char**>(reinterpret_cast<char**>(m_schemes->pdata)); 98 static_cast<WebKitSoupRequestGenericClass*>(g_type_class_ref(WEBKIT_TYPE_SOUP_REQUEST_GENERIC))->customProtocolManager = this; 99 soup_session_add_feature_by_type(session, WEBKIT_TYPE_SOUP_REQUEST_GENERIC); 100} 101 102bool CustomProtocolManagerImpl::supportsScheme(const String& scheme) 103{ 104 if (scheme.isNull()) 105 return false; 106 107 CString cScheme = scheme.utf8(); 108 for (unsigned i = 0; i < m_schemes->len; ++i) { 109 if (cScheme == static_cast<char*>(g_ptr_array_index(m_schemes.get(), i))) 110 return true; 111 } 112 113 return false; 114} 115 116void CustomProtocolManagerImpl::didFailWithError(uint64_t customProtocolID, const WebCore::ResourceError& error) 117{ 118 WebSoupRequestAsyncData* data = m_customProtocolMap.get(customProtocolID); 119 ASSERT(data); 120 121 GRefPtr<GTask> task = data->releaseTask(); 122 ASSERT(task.get()); 123 g_task_return_new_error(task.get(), g_quark_from_string(error.domain().utf8().data()), 124 error.errorCode(), "%s", error.localizedDescription().utf8().data()); 125 126 m_customProtocolMap.remove(customProtocolID); 127} 128 129void CustomProtocolManagerImpl::didLoadData(uint64_t customProtocolID, const IPC::DataReference& dataReference) 130{ 131 WebSoupRequestAsyncData* data = m_customProtocolMap.get(customProtocolID); 132 // The data might have been removed from the request map if a previous chunk failed 133 // and a new message was sent by the UI process before being notified about the failure. 134 if (!data) 135 return; 136 137 if (!data->stream) { 138 GRefPtr<GTask> task = data->releaseTask(); 139 ASSERT(task.get()); 140 141 goffset soupContentLength = soup_request_get_content_length(SOUP_REQUEST(g_task_get_source_object(task.get()))); 142 uint64_t contentLength = soupContentLength == -1 ? 0 : static_cast<uint64_t>(soupContentLength); 143 if (!dataReference.size()) { 144 // Empty reply, just create and empty GMemoryInputStream. 145 data->stream = g_memory_input_stream_new(); 146 } else if (dataReference.size() == contentLength) { 147 // We don't expect more data, so we can just create a GMemoryInputStream with all the data. 148 data->stream = g_memory_input_stream_new_from_data(g_memdup(dataReference.data(), dataReference.size()), contentLength, g_free); 149 } else { 150 // We expect more data chunks from the UI process. 151 data->stream = webkitSoupRequestInputStreamNew(contentLength); 152 webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); 153 } 154 g_task_return_pointer(task.get(), data->stream.get(), g_object_unref); 155 return; 156 } 157 158 if (data->requestFailed()) { 159 // ResourceRequest failed or it was cancelled. It doesn't matter here the error or if it was cancelled, 160 // because that's already handled by the resource handle client, we just want to notify the UI process 161 // to stop reading data from the user input stream. If UI process already sent all the data we simply 162 // finish silently. 163 if (!webkitSoupRequestInputStreamFinished(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()))) 164 m_childProcess->send(Messages::CustomProtocolManagerProxy::StopLoading(customProtocolID), 0); 165 166 return; 167 } 168 169 webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), dataReference.data(), dataReference.size()); 170} 171 172void CustomProtocolManagerImpl::didReceiveResponse(uint64_t customProtocolID, const WebCore::ResourceResponse& response) 173{ 174 WebSoupRequestAsyncData* data = m_customProtocolMap.get(customProtocolID); 175 ASSERT(data); 176 ASSERT(data->task); 177 178 WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(data->task)); 179 webkitSoupRequestGenericSetContentLength(request, response.expectedContentLength() ? response.expectedContentLength() : -1); 180 webkitSoupRequestGenericSetContentType(request, !response.mimeType().isEmpty() ? response.mimeType().utf8().data() : 0); 181} 182 183void CustomProtocolManagerImpl::didFinishLoading(uint64_t customProtocolID) 184{ 185 ASSERT(m_customProtocolMap.contains(customProtocolID)); 186 m_customProtocolMap.remove(customProtocolID); 187} 188 189void CustomProtocolManagerImpl::send(GTask* task) 190{ 191 uint64_t customProtocolID = generateCustomProtocolID(); 192 WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(task)); 193 m_customProtocolMap.set(customProtocolID, std::make_unique<WebSoupRequestAsyncData>(task, request)); 194 195 WebCore::ResourceRequest resourceRequest(SOUP_REQUEST(request)); 196 m_childProcess->send(Messages::CustomProtocolManagerProxy::StartLoading(customProtocolID, resourceRequest), 0); 197} 198 199GInputStream* CustomProtocolManagerImpl::finish(GTask* task, GError** error) 200{ 201 gpointer inputStream = g_task_propagate_pointer(task, error); 202 return inputStream ? G_INPUT_STREAM(inputStream) : 0; 203} 204 205} // namespace WebKit 206 207#endif // ENABLE(CUSTOM_PROTOCOLS) 208