1/*
2 * Copyright (C) 2012 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 <wtf/gobject/GOwnPtr.h>
34#include <wtf/text/CString.h>
35
36namespace WebKit {
37
38static uint64_t generateSoupRequestID()
39{
40    static uint64_t uniqueSoupRequestID = 1;
41    return uniqueSoupRequestID++;
42}
43
44struct WebSoupRequestAsyncData {
45    WebSoupRequestAsyncData(GSimpleAsyncResult* result, WebKitSoupRequestGeneric* requestGeneric, GCancellable* cancellable)
46        : result(result)
47        , request(requestGeneric)
48        , cancellable(cancellable)
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<GSimpleAsyncResult> releaseResult()
66    {
67        GSimpleAsyncResult* returnValue = result;
68        result = 0;
69        return adoptGRef(returnValue);
70    }
71
72    GSimpleAsyncResult* result;
73    WebKitSoupRequestGeneric* request;
74    GRefPtr<GCancellable> cancellable;
75    GRefPtr<GInputStream> stream;
76};
77
78const char* WebSoupRequestManager::supplementName()
79{
80    return "WebSoupRequestManager";
81}
82
83WebSoupRequestManager::WebSoupRequestManager(WebProcess* process)
84    : m_process(process)
85    , m_schemes(adoptGRef(g_ptr_array_new_with_free_func(g_free)))
86{
87    m_process->addMessageReceiver(Messages::WebSoupRequestManager::messageReceiverName(), this);
88}
89
90WebSoupRequestManager::~WebSoupRequestManager()
91{
92}
93
94void WebSoupRequestManager::registerURIScheme(const String& scheme)
95{
96    if (m_schemes->len)
97        g_ptr_array_remove_index_fast(m_schemes.get(), m_schemes->len - 1);
98    g_ptr_array_add(m_schemes.get(), g_strdup(scheme.utf8().data()));
99    g_ptr_array_add(m_schemes.get(), 0);
100
101    SoupSession* session = WebCore::ResourceHandle::defaultSession();
102    SoupRequestClass* genericRequestClass = static_cast<SoupRequestClass*>(g_type_class_ref(WEBKIT_TYPE_SOUP_REQUEST_GENERIC));
103    genericRequestClass->schemes = const_cast<const char**>(reinterpret_cast<char**>(m_schemes->pdata));
104    soup_session_add_feature_by_type(session, WEBKIT_TYPE_SOUP_REQUEST_GENERIC);
105}
106
107void WebSoupRequestManager::didHandleURIRequest(const CoreIPC::DataReference& requestData, uint64_t contentLength, const String& mimeType, uint64_t requestID)
108{
109    WebSoupRequestAsyncData* data = m_requestMap.get(requestID);
110    ASSERT(data);
111    GRefPtr<GSimpleAsyncResult> result = data->releaseResult();
112    ASSERT(result.get());
113
114    GRefPtr<WebKitSoupRequestGeneric> request = adoptGRef(WEBKIT_SOUP_REQUEST_GENERIC(g_async_result_get_source_object(G_ASYNC_RESULT(result.get()))));
115    webkitSoupRequestGenericSetContentLength(request.get(), contentLength ? contentLength : -1);
116    webkitSoupRequestGenericSetContentType(request.get(), !mimeType.isEmpty() ? mimeType.utf8().data() : 0);
117
118    GInputStream* dataStream;
119    if (!requestData.size()) {
120        // Empty reply, just create and empty GMemoryInputStream.
121        dataStream = g_memory_input_stream_new();
122        m_requestMap.remove(requestID);
123    } else if (requestData.size() == contentLength) {
124        // We don't expect more data, so we can just create a GMemoryInputStream with all the data.
125        dataStream = g_memory_input_stream_new_from_data(g_memdup(requestData.data(), requestData.size()), contentLength, g_free);
126        m_requestMap.remove(requestID);
127    } else {
128        // We expect more data chunks from the UI process.
129        dataStream = webkitSoupRequestInputStreamNew(contentLength);
130        data->stream = dataStream;
131        webkitSoupRequestInputStreamAddData(WEBKIT_SOUP_REQUEST_INPUT_STREAM(dataStream), requestData.data(), requestData.size());
132    }
133    g_simple_async_result_set_op_res_gpointer(result.get(), dataStream, g_object_unref);
134    g_simple_async_result_complete(result.get());
135}
136
137void WebSoupRequestManager::didReceiveURIRequestData(const CoreIPC::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<GSimpleAsyncResult> result = data->releaseResult();
168    ASSERT(result.get());
169
170    g_simple_async_result_take_error(result.get(),
171        g_error_new_literal(g_quark_from_string(error.domain().utf8().data()),
172            error.errorCode(),
173            error.localizedDescription().utf8().data()));
174    g_simple_async_result_complete(result.get());
175
176    m_requestMap.remove(requestID);
177}
178
179void WebSoupRequestManager::send(GSimpleAsyncResult* result, GCancellable* cancellable)
180{
181    GRefPtr<WebKitSoupRequestGeneric> request = adoptGRef(WEBKIT_SOUP_REQUEST_GENERIC(g_async_result_get_source_object(G_ASYNC_RESULT(result))));
182    SoupRequest* soupRequest = SOUP_REQUEST(request.get());
183    GOwnPtr<char> uriString(soup_uri_to_string(soup_request_get_uri(soupRequest), FALSE));
184
185    uint64_t requestID = generateSoupRequestID();
186    m_requestMap.set(requestID, adoptPtr(new WebSoupRequestAsyncData(result, request.get(), cancellable)));
187
188    uint64_t initiatingPageID = WebCore::ResourceHandle::getSoupRequestInitiatingPageID(soupRequest);
189    m_process->parentProcessConnection()->send(Messages::WebPageProxy::DidReceiveURIRequest(String::fromUTF8(uriString.get()), requestID), initiatingPageID);
190}
191
192GInputStream* WebSoupRequestManager::finish(GSimpleAsyncResult* result)
193{
194    return G_INPUT_STREAM(g_object_ref(g_simple_async_result_get_op_res_gpointer(result)));
195}
196
197} // namespace WebKit
198