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 "WebKitURISchemeRequest.h"
22
23#include "APIData.h"
24#include "WebKitPrivate.h"
25#include "WebKitURISchemeRequestPrivate.h"
26#include "WebKitWebContextPrivate.h"
27#include "WebKitWebView.h"
28#include "WebPageProxy.h"
29#include <WebCore/GUniquePtrSoup.h>
30#include <WebCore/ResourceError.h>
31#include <libsoup/soup.h>
32#include <wtf/gobject/GRefPtr.h>
33#include <wtf/text/CString.h>
34
35using namespace WebKit;
36
37/**
38 * SECTION: WebKitURISchemeRequest
39 * @Short_description: Represents a URI scheme request
40 * @Title: WebKitURISchemeRequest
41 *
42 * If you register a particular URI scheme in a #WebKitWebContext,
43 * using webkit_web_context_register_uri_scheme(), you have to provide
44 * a #WebKitURISchemeRequestCallback. After that, when a URI request
45 * is made with that particular scheme, your callback will be
46 * called. There you will be able to access properties such as the
47 * scheme, the URI and path, and the #WebKitWebView that initiated the
48 * request, and also finish the request with
49 * webkit_uri_scheme_request_finish().
50 *
51 */
52
53static const unsigned int gReadBufferSize = 8192;
54
55struct _WebKitURISchemeRequestPrivate {
56    WebKitWebContext* webContext;
57    RefPtr<WebSoupCustomProtocolRequestManager> webRequestManager;
58    RefPtr<WebPageProxy> initiatingPage;
59    uint64_t requestID;
60    CString uri;
61    GUniquePtr<SoupURI> soupURI;
62
63    GRefPtr<GInputStream> stream;
64    uint64_t streamLength;
65    GRefPtr<GCancellable> cancellable;
66    char readBuffer[gReadBufferSize];
67    uint64_t bytesRead;
68    CString mimeType;
69};
70
71WEBKIT_DEFINE_TYPE(WebKitURISchemeRequest, webkit_uri_scheme_request, G_TYPE_OBJECT)
72
73static void webkit_uri_scheme_request_class_init(WebKitURISchemeRequestClass*)
74{
75}
76
77WebKitURISchemeRequest* webkitURISchemeRequestCreate(uint64_t requestID, WebKitWebContext* webContext, API::URLRequest* urlRequest)
78{
79    WebKitURISchemeRequest* request = WEBKIT_URI_SCHEME_REQUEST(g_object_new(WEBKIT_TYPE_URI_SCHEME_REQUEST, NULL));
80    request->priv->webContext = webContext;
81    request->priv->webRequestManager = webkitWebContextGetRequestManager(webContext);
82    request->priv->uri = urlRequest->resourceRequest().url().string().utf8();
83    request->priv->initiatingPage = WebProcessProxy::webPage(urlRequest->resourceRequest().initiatingPageID());
84    request->priv->requestID = requestID;
85    return request;
86}
87
88void webkitURISchemeRequestCancel(WebKitURISchemeRequest* request)
89{
90    if (request->priv->cancellable.get())
91        g_cancellable_cancel(request->priv->cancellable.get());
92}
93
94/**
95 * webkit_uri_scheme_request_get_scheme:
96 * @request: a #WebKitURISchemeRequest
97 *
98 * Get the URI scheme of @request
99 *
100 * Returns: the URI scheme of @request
101 */
102const char* webkit_uri_scheme_request_get_scheme(WebKitURISchemeRequest* request)
103{
104    g_return_val_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request), 0);
105
106    if (!request->priv->soupURI)
107        request->priv->soupURI.reset(soup_uri_new(request->priv->uri.data()));
108    return request->priv->soupURI->scheme;
109}
110
111/**
112 * webkit_uri_scheme_request_get_uri:
113 * @request: a #WebKitURISchemeRequest
114 *
115 * Get the URI of @request
116 *
117 * Returns: the full URI of @request
118 */
119const char* webkit_uri_scheme_request_get_uri(WebKitURISchemeRequest* request)
120{
121    g_return_val_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request), 0);
122
123    return request->priv->uri.data();
124}
125
126/**
127 * webkit_uri_scheme_request_get_path:
128 * @request: a #WebKitURISchemeRequest
129 *
130 * Get the URI path of @request
131 *
132 * Returns: the URI path of @request
133 */
134const char* webkit_uri_scheme_request_get_path(WebKitURISchemeRequest* request)
135{
136    g_return_val_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request), 0);
137
138    if (!request->priv->soupURI)
139        request->priv->soupURI.reset(soup_uri_new(request->priv->uri.data()));
140    return request->priv->soupURI->path;
141}
142
143/**
144 * webkit_uri_scheme_request_get_web_view:
145 * @request: a #WebKitURISchemeRequest
146 *
147 * Get the #WebKitWebView that initiated the request.
148 *
149 * Returns: (transfer none): the #WebKitWebView that initiated @request.
150 */
151WebKitWebView* webkit_uri_scheme_request_get_web_view(WebKitURISchemeRequest* request)
152{
153    g_return_val_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request), 0);
154
155    // FIXME: initiatingPage is now always null, we need to re-implement this somehow.
156    return request->priv->initiatingPage ? WEBKIT_WEB_VIEW(request->priv->initiatingPage->viewWidget()) : nullptr;
157}
158
159static void webkitURISchemeRequestReadCallback(GInputStream* inputStream, GAsyncResult* result, WebKitURISchemeRequest* schemeRequest)
160{
161    GRefPtr<WebKitURISchemeRequest> request = adoptGRef(schemeRequest);
162    GUniqueOutPtr<GError> error;
163    gssize bytesRead = g_input_stream_read_finish(inputStream, result, &error.outPtr());
164    if (bytesRead == -1) {
165        webkit_uri_scheme_request_finish_error(request.get(), error.get());
166        return;
167    }
168
169    WebKitURISchemeRequestPrivate* priv = request->priv;
170    RefPtr<API::Data> webData = API::Data::create(reinterpret_cast<const unsigned char*>(priv->readBuffer), bytesRead);
171    if (!priv->bytesRead) {
172        // First chunk read. In case of empty reply an empty API::Data is sent to the networking process.
173        WebCore::ResourceResponse response(WebCore::URL(WebCore::URL(), String::fromUTF8(priv->uri)), String::fromUTF8(priv->mimeType.data()),
174            priv->streamLength, emptyString(), emptyString());
175        priv->webRequestManager->didReceiveResponse(priv->requestID, response);
176        priv->webRequestManager->didLoadData(priv->requestID, webData.get());
177    } else if (bytesRead || (!bytesRead && !priv->streamLength)) {
178        // Subsequent chunk read. We only send an empty API::Data to the networking process when stream length is unknown.
179        priv->webRequestManager->didLoadData(priv->requestID, webData.get());
180    }
181
182    if (!bytesRead) {
183        priv->webRequestManager->didFinishLoading(request->priv->requestID);
184        webkitWebContextDidFinishLoadingCustomProtocol(request->priv->webContext, request->priv->requestID);
185        return;
186    }
187
188    priv->bytesRead += bytesRead;
189    g_input_stream_read_async(inputStream, priv->readBuffer, gReadBufferSize, G_PRIORITY_DEFAULT, priv->cancellable.get(),
190        reinterpret_cast<GAsyncReadyCallback>(webkitURISchemeRequestReadCallback), g_object_ref(request.get()));
191}
192
193/**
194 * webkit_uri_scheme_request_finish:
195 * @request: a #WebKitURISchemeRequest
196 * @stream: a #GInputStream to read the contents of the request
197 * @stream_length: the length of the stream or -1 if not known
198 * @mime_type: (allow-none): the content type of the stream or %NULL if not known
199 *
200 * Finish a #WebKitURISchemeRequest by setting the contents of the request and its mime type.
201 */
202void webkit_uri_scheme_request_finish(WebKitURISchemeRequest* request, GInputStream* inputStream, gint64 streamLength, const gchar* mimeType)
203{
204    g_return_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request));
205    g_return_if_fail(G_IS_INPUT_STREAM(inputStream));
206    g_return_if_fail(streamLength == -1 || streamLength >= 0);
207
208    request->priv->stream = inputStream;
209    // We use -1 in the API for consistency with soup when the content length is not known, but 0 internally.
210    request->priv->streamLength = streamLength == -1 ? 0 : streamLength;
211    request->priv->cancellable = adoptGRef(g_cancellable_new());
212    request->priv->bytesRead = 0;
213    request->priv->mimeType = mimeType;
214    g_input_stream_read_async(inputStream, request->priv->readBuffer, gReadBufferSize, G_PRIORITY_DEFAULT, request->priv->cancellable.get(),
215        reinterpret_cast<GAsyncReadyCallback>(webkitURISchemeRequestReadCallback), g_object_ref(request));
216}
217
218/**
219 * webkit_uri_scheme_request_finish_error:
220 * @request: a #WebKitURISchemeRequest
221 * @error: a #GError that will be passed to the #WebKitWebView
222 *
223 * Finish a #WebKitURISchemeRequest with a #GError.
224 *
225 * Since: 2.2
226 */
227void webkit_uri_scheme_request_finish_error(WebKitURISchemeRequest* request, GError* error)
228{
229    g_return_if_fail(WEBKIT_IS_URI_SCHEME_REQUEST(request));
230    g_return_if_fail(error);
231
232    WebKitURISchemeRequestPrivate* priv = request->priv;
233
234    WebCore::ResourceError resourceError(g_quark_to_string(error->domain), toWebCoreError(error->code), priv->uri.data(), String::fromUTF8(error->message));
235    priv->webRequestManager->didFailWithError(priv->requestID, resourceError);
236    webkitWebContextDidFinishLoadingCustomProtocol(priv->webContext, priv->requestID);
237}
238