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 "WebKitWebResource.h"
22
23#include "APIData.h"
24#include "WebFrameProxy.h"
25#include "WebKitURIRequest.h"
26#include "WebKitWebResourcePrivate.h"
27#include <glib/gi18n-lib.h>
28#include <wtf/glib/GRefPtr.h>
29#include <wtf/glib/WTFGType.h>
30#include <wtf/text/CString.h>
31
32using namespace WebKit;
33
34/**
35 * SECTION: WebKitWebResource
36 * @Short_description: Represents a resource at the end of a URI
37 * @Title: WebKitWebResource
38 *
39 * A #WebKitWebResource encapsulates content for each resource at the
40 * end of a particular URI. For example, one #WebKitWebResource will
41 * be created for each separate image and stylesheet when a page is
42 * loaded.
43 *
44 * You can access the response and the URI for a given
45 * #WebKitWebResource, using webkit_web_resource_get_uri() and
46 * webkit_web_resource_get_response(), as well as the raw data, using
47 * webkit_web_resource_get_data().
48 *
49 */
50
51enum {
52 SENT_REQUEST,
53 RECEIVED_DATA,
54 FINISHED,
55 FAILED,
56 FAILED_WITH_TLS_ERRORS,
57
58 LAST_SIGNAL
59};
60
61enum {
62 PROP_0,
63
64 PROP_URI,
65 PROP_RESPONSE
66};
67
68
69struct _WebKitWebResourcePrivate {
70 RefPtr<WebFrameProxy> frame;
71 CString uri;
72 GRefPtr<WebKitURIResponse> response;
73 bool isMainResource;
74};
75
76WEBKIT_DEFINE_TYPE(WebKitWebResource, webkit_web_resource, G_TYPE_OBJECT)
77
78static guint signals[LAST_SIGNAL] = { 0, };
79
80static void webkitWebResourceGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
81{
82 WebKitWebResource* resource = WEBKIT_WEB_RESOURCE(object);
83
84 switch (propId) {
85 case PROP_URI:
86 g_value_set_string(value, webkit_web_resource_get_uri(resource));
87 break;
88 case PROP_RESPONSE:
89 g_value_set_object(value, webkit_web_resource_get_response(resource));
90 break;
91 default:
92 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
93 }
94}
95
96static void webkit_web_resource_class_init(WebKitWebResourceClass* resourceClass)
97{
98 GObjectClass* objectClass = G_OBJECT_CLASS(resourceClass);
99 objectClass->get_property = webkitWebResourceGetProperty;
100
101 /**
102 * WebKitWebResource:uri:
103 *
104 * The current active URI of the #WebKitWebResource.
105 * See webkit_web_resource_get_uri() for more details.
106 */
107 g_object_class_install_property(objectClass,
108 PROP_URI,
109 g_param_spec_string("uri",
110 _("URI"),
111 _("The current active URI of the resource"),
112 0,
113 WEBKIT_PARAM_READABLE));
114
115 /**
116 * WebKitWebResource:response:
117 *
118 * The #WebKitURIResponse associated with this resource.
119 */
120 g_object_class_install_property(objectClass,
121 PROP_RESPONSE,
122 g_param_spec_object("response",
123 _("Response"),
124 _("The response of the resource"),
125 WEBKIT_TYPE_URI_RESPONSE,
126 WEBKIT_PARAM_READABLE));
127
128 /**
129 * WebKitWebResource::sent-request:
130 * @resource: the #WebKitWebResource
131 * @request: a #WebKitURIRequest
132 * @redirected_response: a #WebKitURIResponse, or %NULL
133 *
134 * This signal is emitted when @request has been sent to the
135 * server. In case of a server redirection this signal is
136 * emitted again with the @request argument containing the new
137 * request sent to the server due to the redirection and the
138 * @redirected_response parameter containing the response
139 * received by the server for the initial request.
140 */
141 signals[SENT_REQUEST] = g_signal_new(
142 "sent-request",
143 G_TYPE_FROM_CLASS(objectClass),
144 G_SIGNAL_RUN_LAST,
145 0, nullptr, nullptr,
146 g_cclosure_marshal_generic,
147 G_TYPE_NONE, 2,
148 WEBKIT_TYPE_URI_REQUEST,
149 WEBKIT_TYPE_URI_RESPONSE);
150
151 /**
152 * WebKitWebResource::received-data:
153 * @resource: the #WebKitWebResource
154 * @data_length: the length of data received in bytes
155 *
156 * This signal is emitted after response is received,
157 * every time new data has been received. It's
158 * useful to know the progress of the resource load operation.
159 */
160 signals[RECEIVED_DATA] = g_signal_new(
161 "received-data",
162 G_TYPE_FROM_CLASS(objectClass),
163 G_SIGNAL_RUN_LAST,
164 0, nullptr, nullptr,
165 g_cclosure_marshal_generic,
166 G_TYPE_NONE, 1,
167 G_TYPE_UINT64);
168
169 /**
170 * WebKitWebResource::finished:
171 * @resource: the #WebKitWebResource
172 *
173 * This signal is emitted when the resource load finishes successfully
174 * or due to an error. In case of errors #WebKitWebResource::failed signal
175 * is emitted before this one.
176 */
177 signals[FINISHED] =
178 g_signal_new("finished",
179 G_TYPE_FROM_CLASS(objectClass),
180 G_SIGNAL_RUN_LAST,
181 0, 0, 0,
182 g_cclosure_marshal_VOID__VOID,
183 G_TYPE_NONE, 0);
184
185 /**
186 * WebKitWebResource::failed:
187 * @resource: the #WebKitWebResource
188 * @error: the #GError that was triggered
189 *
190 * This signal is emitted when an error occurs during the resource
191 * load operation.
192 */
193 signals[FAILED] =
194 g_signal_new(
195 "failed",
196 G_TYPE_FROM_CLASS(objectClass),
197 G_SIGNAL_RUN_LAST,
198 0, 0, 0,
199 g_cclosure_marshal_VOID__BOXED,
200 G_TYPE_NONE, 1,
201 G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);
202
203 /**
204 * WebKitWebResource::failed-with-tls-errors:
205 * @resource: the #WebKitWebResource
206 * @certificate: a #GTlsCertificate
207 * @errors: a #GTlsCertificateFlags with the verification status of @certificate
208 *
209 * This signal is emitted when a TLS error occurs during the resource load operation.
210 *
211 * Since: 2.8
212 */
213 signals[FAILED_WITH_TLS_ERRORS] =
214 g_signal_new("failed-with-tls-errors",
215 G_TYPE_FROM_CLASS(objectClass),
216 G_SIGNAL_RUN_LAST,
217 0, nullptr, nullptr,
218 g_cclosure_marshal_generic,
219 G_TYPE_NONE, 2,
220 G_TYPE_TLS_CERTIFICATE,
221 G_TYPE_TLS_CERTIFICATE_FLAGS);
222}
223
224static void webkitWebResourceUpdateURI(WebKitWebResource* resource, const CString& requestURI)
225{
226 if (resource->priv->uri == requestURI)
227 return;
228
229 resource->priv->uri = requestURI;
230 g_object_notify(G_OBJECT(resource), "uri");
231}
232
233WebKitWebResource* webkitWebResourceCreate(WebFrameProxy* frame, WebKitURIRequest* request, bool isMainResource)
234{
235 ASSERT(frame);
236 WebKitWebResource* resource = WEBKIT_WEB_RESOURCE(g_object_new(WEBKIT_TYPE_WEB_RESOURCE, NULL));
237 resource->priv->frame = frame;
238 resource->priv->uri = webkit_uri_request_get_uri(request);
239 resource->priv->isMainResource = isMainResource;
240 return resource;
241}
242
243void webkitWebResourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
244{
245 webkitWebResourceUpdateURI(resource, webkit_uri_request_get_uri(request));
246 g_signal_emit(resource, signals[SENT_REQUEST], 0, request, redirectResponse);
247}
248
249void webkitWebResourceSetResponse(WebKitWebResource* resource, WebKitURIResponse* response)
250{
251 resource->priv->response = response;
252 g_object_notify(G_OBJECT(resource), "response");
253}
254
255void webkitWebResourceNotifyProgress(WebKitWebResource* resource, guint64 bytesReceived)
256{
257 g_signal_emit(resource, signals[RECEIVED_DATA], 0, bytesReceived);
258}
259
260void webkitWebResourceFinished(WebKitWebResource* resource)
261{
262 g_signal_emit(resource, signals[FINISHED], 0, NULL);
263}
264
265void webkitWebResourceFailed(WebKitWebResource* resource, GError* error)
266{
267 g_signal_emit(resource, signals[FAILED], 0, error);
268 g_signal_emit(resource, signals[FINISHED], 0, NULL);
269}
270
271void webkitWebResourceFailedWithTLSErrors(WebKitWebResource* resource, GTlsCertificateFlags tlsErrors, GTlsCertificate* certificate)
272{
273 g_signal_emit(resource, signals[FAILED_WITH_TLS_ERRORS], 0, certificate, tlsErrors);
274 g_signal_emit(resource, signals[FINISHED], 0, nullptr);
275}
276
277WebFrameProxy* webkitWebResourceGetFrame(WebKitWebResource* resource)
278{
279 return resource->priv->frame.get();
280}
281
282/**
283 * webkit_web_resource_get_uri:
284 * @resource: a #WebKitWebResource
285 *
286 * Returns the current active URI of @resource. The active URI might change during
287 * a load operation:
288 *
289 * <orderedlist>
290 * <listitem><para>
291 * When the resource load starts, the active URI is the requested URI
292 * </para></listitem>
293 * <listitem><para>
294 * When the initial request is sent to the server, #WebKitWebResource::sent-request
295 * signal is emitted without a redirected response, the active URI is the URI of
296 * the request sent to the server.
297 * </para></listitem>
298 * <listitem><para>
299 * In case of a server redirection, #WebKitWebResource::sent-request signal
300 * is emitted again with a redirected response, the active URI is the URI the request
301 * was redirected to.
302 * </para></listitem>
303 * <listitem><para>
304 * When the response is received from the server, the active URI is the final
305 * one and it will not change again.
306 * </para></listitem>
307 * </orderedlist>
308 *
309 * You can monitor the active URI by connecting to the notify::uri
310 * signal of @resource.
311 *
312 * Returns: the current active URI of @resource
313 */
314const char* webkit_web_resource_get_uri(WebKitWebResource* resource)
315{
316 g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
317
318 return resource->priv->uri.data();
319}
320
321/**
322 * webkit_web_resource_get_response:
323 * @resource: a #WebKitWebResource
324 *
325 * Retrieves the #WebKitURIResponse of the resource load operation.
326 * This method returns %NULL if called before the response
327 * is received from the server. You can connect to notify::response
328 * signal to be notified when the response is received.
329 *
330 * Returns: (transfer none): the #WebKitURIResponse, or %NULL if
331 * the response hasn't been received yet.
332 */
333WebKitURIResponse* webkit_web_resource_get_response(WebKitWebResource* resource)
334{
335 g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
336
337 return resource->priv->response.get();
338}
339
340struct ResourceGetDataAsyncData {
341 RefPtr<API::Data> webData;
342};
343WEBKIT_DEFINE_ASYNC_DATA_STRUCT(ResourceGetDataAsyncData)
344
345static void resourceDataCallback(API::Data* wkData, CallbackBase::Error error, GTask* task)
346{
347 if (error != CallbackBase::Error::None) {
348 // This fails when the page is closed or frame is destroyed, so we can just cancel the operation.
349 g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled"));
350 return;
351 }
352 ResourceGetDataAsyncData* data = static_cast<ResourceGetDataAsyncData*>(g_task_get_task_data(task));
353 data->webData = wkData;
354 if (!wkData->bytes())
355 data->webData = API::Data::create(reinterpret_cast<const unsigned char*>(""), 1);
356 g_task_return_boolean(task, TRUE);
357}
358
359/**
360 * webkit_web_resource_get_data:
361 * @resource: a #WebKitWebResource
362 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
363 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
364 * @user_data: (closure): the data to pass to callback function
365 *
366 * Asynchronously get the raw data for @resource.
367 *
368 * When the operation is finished, @callback will be called. You can then call
369 * webkit_web_resource_get_data_finish() to get the result of the operation.
370 */
371void webkit_web_resource_get_data(WebKitWebResource* resource, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
372{
373 g_return_if_fail(WEBKIT_IS_WEB_RESOURCE(resource));
374
375 GRefPtr<GTask> task = adoptGRef(g_task_new(resource, cancellable, callback, userData));
376 g_task_set_task_data(task.get(), createResourceGetDataAsyncData(), reinterpret_cast<GDestroyNotify>(destroyResourceGetDataAsyncData));
377 if (resource->priv->isMainResource)
378 resource->priv->frame->getMainResourceData([task = WTFMove(task)](API::Data* data, CallbackBase::Error error) {
379 resourceDataCallback(data, error, task.get());
380 });
381 else {
382 String url = String::fromUTF8(resource->priv->uri.data());
383 resource->priv->frame->getResourceData(API::URL::create(url).ptr(), [task = WTFMove(task)](API::Data* data, CallbackBase::Error error) {
384 resourceDataCallback(data, error, task.get());
385 });
386 }
387}
388
389/**
390 * webkit_web_resource_get_data_finish:
391 * @resource: a #WebKitWebResource
392 * @result: a #GAsyncResult
393 * @length: (out) (allow-none): return location for the length of the resource data
394 * @error: return location for error or %NULL to ignore
395 *
396 * Finish an asynchronous operation started with webkit_web_resource_get_data().
397 *
398 * Returns: (transfer full) (array length=length) (element-type guint8): a
399 * string with the data of @resource, or %NULL in case of error. if @length
400 * is not %NULL, the size of the data will be assigned to it.
401 */
402guchar* webkit_web_resource_get_data_finish(WebKitWebResource* resource, GAsyncResult* result, gsize* length, GError** error)
403{
404 g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
405 g_return_val_if_fail(g_task_is_valid(result, resource), 0);
406
407 GTask* task = G_TASK(result);
408 if (!g_task_propagate_boolean(task, error))
409 return 0;
410
411 ResourceGetDataAsyncData* data = static_cast<ResourceGetDataAsyncData*>(g_task_get_task_data(task));
412 if (length)
413 *length = data->webData->size();
414 return static_cast<guchar*>(g_memdup(data->webData->bytes(), data->webData->size()));
415}
416