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 | |