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
36namespace WebKit {
37using namespace WebCore;
38
39RefPtr<NetworkProcess>& lastCreatedNetworkProcess()
40{
41 static NeverDestroyed<RefPtr<NetworkProcess>> networkProcess;
42 return networkProcess.get();
43}
44
45void LegacyCustomProtocolManager::networkProcessCreated(NetworkProcess& networkProcess)
46{
47 lastCreatedNetworkProcess() = &networkProcess;
48}
49
50LegacyCustomProtocolManager::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
59LegacyCustomProtocolManager::WebSoupRequestAsyncData::~WebSoupRequestAsyncData()
60{
61 if (request)
62 g_object_remove_weak_pointer(G_OBJECT(request), reinterpret_cast<void**>(&request));
63}
64
65class CustomProtocolRequestClient final : public WebKitSoupRequestGenericClient {
66public:
67 static CustomProtocolRequestClient& singleton()
68 {
69 static NeverDestroyed<CustomProtocolRequestClient> client;
70 return client;
71 }
72
73private:
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
86void 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
92void 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
110void LegacyCustomProtocolManager::unregisterScheme(const String&)
111{
112 notImplemented();
113}
114
115bool 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
129void 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
149void 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
192void 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
206void LegacyCustomProtocolManager::didFinishLoading(uint64_t customProtocolID)
207{
208 ASSERT(m_customProtocolMap.contains(customProtocolID));
209 removeCustomProtocol(customProtocolID);
210}
211
212void LegacyCustomProtocolManager::wasRedirectedToRequest(uint64_t, const ResourceRequest&, const ResourceResponse&)
213{
214 notImplemented();
215}
216
217} // namespace WebKit
218