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