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