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