[GTK] Implement custom URI schemes with CustomProtocols
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / gtk / WebKitURISchemeRequest.cpp
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 "WebKitURISchemeRequestPrivate.h"
25 #include "WebKitWebContextPrivate.h"
26 #include "WebKitWebView.h"
27 #include "WebPageProxy.h"
28 #include <WebCore/GUniquePtrSoup.h>
29 #include <WebCore/ResourceError.h>
30 #include <libsoup/soup.h>
31 #include <wtf/gobject/GOwnPtr.h>
32 #include <wtf/gobject/GRefPtr.h>
33 #include <wtf/text/CString.h>
34
35 using 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
53 static const unsigned int gReadBufferSize = 8192;
54
55 struct _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
71 WEBKIT_DEFINE_TYPE(WebKitURISchemeRequest, webkit_uri_scheme_request, G_TYPE_OBJECT)
72
73 static void webkit_uri_scheme_request_class_init(WebKitURISchemeRequestClass* requestClass)
74 {
75 }
76
77 WebKitURISchemeRequest* webkitURISchemeRequestCreate(uint64_t requestID, WebKitWebContext* webContext, API::URLRequest* urlRequest, WebPageProxy* initiatingPage)
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 = initiatingPage;
84     request->priv->requestID = requestID;
85     return request;
86 }
87
88 void 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  */
102 const 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  */
119 const 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  */
134 const 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  */
151 WebKitWebView* 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
159 static void webkitURISchemeRequestReadCallback(GInputStream* inputStream, GAsyncResult* result, WebKitURISchemeRequest* schemeRequest)
160 {
161     GRefPtr<WebKitURISchemeRequest> request = adoptGRef(schemeRequest);
162     GOwnPtr<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  */
202 void 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  */
227 void 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), error->code, priv->uri.data(), String::fromUTF8(error->message));
235     priv->webRequestManager->didFailWithError(priv->requestID, resourceError);
236     webkitWebContextDidFinishLoadingCustomProtocol(priv->webContext, priv->requestID);
237 }