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 "WebData.h"
24#include "WebFrameProxy.h"
25#include "WebKitMarshal.h"
26#include "WebKitURIRequest.h"
27#include "WebKitWebResourcePrivate.h"
28#include <glib/gi18n-lib.h>
29#include <wtf/gobject/GRefPtr.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
57    LAST_SIGNAL
58};
59
60enum {
61    PROP_0,
62
63    PROP_URI,
64    PROP_RESPONSE
65};
66
67
68struct _WebKitWebResourcePrivate {
69    RefPtr<WebFrameProxy> frame;
70    CString uri;
71    GRefPtr<WebKitURIResponse> response;
72    bool isMainResource;
73};
74
75WEBKIT_DEFINE_TYPE(WebKitWebResource, webkit_web_resource, G_TYPE_OBJECT)
76
77static guint signals[LAST_SIGNAL] = { 0, };
78
79static void webkitWebResourceGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
80{
81    WebKitWebResource* resource = WEBKIT_WEB_RESOURCE(object);
82
83    switch (propId) {
84    case PROP_URI:
85        g_value_set_string(value, webkit_web_resource_get_uri(resource));
86        break;
87    case PROP_RESPONSE:
88        g_value_set_object(value, webkit_web_resource_get_response(resource));
89        break;
90    default:
91        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
92    }
93}
94
95static void webkit_web_resource_class_init(WebKitWebResourceClass* resourceClass)
96{
97    GObjectClass* objectClass = G_OBJECT_CLASS(resourceClass);
98    objectClass->get_property = webkitWebResourceGetProperty;
99
100    /**
101     * WebKitWebResource:uri:
102     *
103     * The current active URI of the #WebKitWebResource.
104     * See webkit_web_resource_get_uri() for more details.
105     */
106    g_object_class_install_property(objectClass,
107                                    PROP_URI,
108                                    g_param_spec_string("uri",
109                                                        _("URI"),
110                                                        _("The current active URI of the resource"),
111                                                        0,
112                                                        WEBKIT_PARAM_READABLE));
113
114    /**
115     * WebKitWebResource:response:
116     *
117     * The #WebKitURIResponse associated with this resource.
118     */
119    g_object_class_install_property(objectClass,
120                                    PROP_RESPONSE,
121                                    g_param_spec_object("response",
122                                                        _("Response"),
123                                                        _("The response of the resource"),
124                                                        WEBKIT_TYPE_URI_RESPONSE,
125                                                        WEBKIT_PARAM_READABLE));
126
127    /**
128     * WebKitWebResource::sent-request:
129     * @resource: the #WebKitWebResource
130     * @request: a #WebKitURIRequest
131     * @redirected_response: a #WebKitURIResponse, or %NULL
132     *
133     * This signal is emitted when @request has been sent to the
134     * server. In case of a server redirection this signal is
135     * emitted again with the @request argument containing the new
136     * request sent to the server due to the redirection and the
137     * @redirected_response parameter containing the response
138     * received by the server for the initial request.
139     */
140    signals[SENT_REQUEST] =
141        g_signal_new("sent-request",
142                     G_TYPE_FROM_CLASS(objectClass),
143                     G_SIGNAL_RUN_LAST,
144                     0, 0, 0,
145                     webkit_marshal_VOID__OBJECT_OBJECT,
146                     G_TYPE_NONE, 2,
147                     WEBKIT_TYPE_URI_REQUEST,
148                     WEBKIT_TYPE_URI_RESPONSE);
149
150    /**
151     * WebKitWebResource::received-data:
152     * @resource: the #WebKitWebResource
153     * @data_length: the length of data received in bytes
154     *
155     * This signal is emitted after response is received,
156     * every time new data has been received. It's
157     * useful to know the progress of the resource load operation.
158     */
159    signals[RECEIVED_DATA] =
160        g_signal_new("received-data",
161                     G_TYPE_FROM_CLASS(objectClass),
162                     G_SIGNAL_RUN_LAST,
163                     0, 0, 0,
164                     webkit_marshal_VOID__UINT64,
165                     G_TYPE_NONE, 1,
166                     G_TYPE_UINT64);
167
168    /**
169     * WebKitWebResource::finished:
170     * @resource: the #WebKitWebResource
171     *
172     * This signal is emitted when the resource load finishes successfully
173     * or due to an error. In case of errors #WebKitWebResource::failed signal
174     * is emitted before this one.
175     */
176    signals[FINISHED] =
177        g_signal_new("finished",
178                     G_TYPE_FROM_CLASS(objectClass),
179                     G_SIGNAL_RUN_LAST,
180                     0, 0, 0,
181                     g_cclosure_marshal_VOID__VOID,
182                     G_TYPE_NONE, 0);
183
184    /**
185     * WebKitWebResource::failed:
186     * @resource: the #WebKitWebResource
187     * @error: the #GError that was triggered
188     *
189     * This signal is emitted when an error occurs during the resource
190     * load operation.
191     */
192    signals[FAILED] =
193        g_signal_new("failed",
194                     G_TYPE_FROM_CLASS(objectClass),
195                     G_SIGNAL_RUN_LAST,
196                     0, 0, 0,
197                     g_cclosure_marshal_VOID__POINTER,
198                     G_TYPE_NONE, 1,
199                     G_TYPE_POINTER);
200}
201
202static void webkitWebResourceUpdateURI(WebKitWebResource* resource, const CString& requestURI)
203{
204    if (resource->priv->uri == requestURI)
205        return;
206
207    resource->priv->uri = requestURI;
208    g_object_notify(G_OBJECT(resource), "uri");
209}
210
211WebKitWebResource* webkitWebResourceCreate(WebFrameProxy* frame, WebKitURIRequest* request, bool isMainResource)
212{
213    ASSERT(frame);
214    WebKitWebResource* resource = WEBKIT_WEB_RESOURCE(g_object_new(WEBKIT_TYPE_WEB_RESOURCE, NULL));
215    resource->priv->frame = frame;
216    resource->priv->uri = webkit_uri_request_get_uri(request);
217    resource->priv->isMainResource = isMainResource;
218    return resource;
219}
220
221void webkitWebResourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
222{
223    webkitWebResourceUpdateURI(resource, webkit_uri_request_get_uri(request));
224    g_signal_emit(resource, signals[SENT_REQUEST], 0, request, redirectResponse);
225}
226
227void webkitWebResourceSetResponse(WebKitWebResource* resource, WebKitURIResponse* response)
228{
229    resource->priv->response = response;
230    g_object_notify(G_OBJECT(resource), "response");
231}
232
233void webkitWebResourceNotifyProgress(WebKitWebResource* resource, guint64 bytesReceived)
234{
235    g_signal_emit(resource, signals[RECEIVED_DATA], 0, bytesReceived);
236}
237
238void webkitWebResourceFinished(WebKitWebResource* resource)
239{
240    g_signal_emit(resource, signals[FINISHED], 0, NULL);
241}
242
243void webkitWebResourceFailed(WebKitWebResource* resource, GError* error)
244{
245    g_signal_emit(resource, signals[FAILED], 0, error);
246    g_signal_emit(resource, signals[FINISHED], 0, NULL);
247}
248
249WebFrameProxy* webkitWebResourceGetFrame(WebKitWebResource* resource)
250{
251    return resource->priv->frame.get();
252}
253
254/**
255 * webkit_web_resource_get_uri:
256 * @resource: a #WebKitWebResource
257 *
258 * Returns the current active URI of @resource. The active URI might change during
259 * a load operation:
260 *
261 * <orderedlist>
262 * <listitem><para>
263 *   When the resource load starts, the active URI is the requested URI
264 * </para></listitem>
265 * <listitem><para>
266 *   When the initial request is sent to the server, #WebKitWebResource::sent-request
267 *   signal is emitted without a redirected response, the active URI is the URI of
268 *   the request sent to the server.
269 * </para></listitem>
270 * <listitem><para>
271 *   In case of a server redirection, #WebKitWebResource::sent-request signal
272 *   is emitted again with a redirected response, the active URI is the URI the request
273 *   was redirected to.
274 * </para></listitem>
275 * <listitem><para>
276 *   When the response is received from the server, the active URI is the final
277 *   one and it will not change again.
278 * </para></listitem>
279 * </orderedlist>
280 *
281 * You can monitor the active URI by connecting to the notify::uri
282 * signal of @resource.
283 *
284 * Returns: the current active URI of @resource
285 */
286const char* webkit_web_resource_get_uri(WebKitWebResource* resource)
287{
288    g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
289
290    return resource->priv->uri.data();
291}
292
293/**
294 * webkit_web_resource_get_response:
295 * @resource: a #WebKitWebResource
296 *
297 * Retrieves the #WebKitURIResponse of the resource load operation.
298 * This method returns %NULL if called before the response
299 * is received from the server. You can connect to notify::response
300 * signal to be notified when the response is received.
301 *
302 * Returns: (transfer none): the #WebKitURIResponse, or %NULL if
303 *     the response hasn't been received yet.
304 */
305WebKitURIResponse* webkit_web_resource_get_response(WebKitWebResource* resource)
306{
307    g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
308
309    return resource->priv->response.get();
310}
311
312struct ResourceGetDataAsyncData {
313    RefPtr<WebData> webData;
314    GRefPtr<GCancellable> cancellable;
315};
316WEBKIT_DEFINE_ASYNC_DATA_STRUCT(ResourceGetDataAsyncData)
317
318static void resourceDataCallback(WKDataRef wkData, WKErrorRef, void* context)
319{
320    GRefPtr<GSimpleAsyncResult> result = adoptGRef(G_SIMPLE_ASYNC_RESULT(context));
321    ResourceGetDataAsyncData* data = static_cast<ResourceGetDataAsyncData*>(g_simple_async_result_get_op_res_gpointer(result.get()));
322    GError* error = 0;
323    if (g_cancellable_set_error_if_cancelled(data->cancellable.get(), &error))
324        g_simple_async_result_take_error(result.get(), error);
325    else
326        data->webData = toImpl(wkData);
327    g_simple_async_result_complete(result.get());
328}
329
330/**
331 * webkit_web_resource_get_data:
332 * @resource: a #WebKitWebResource
333 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
334 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
335 * @user_data: (closure): the data to pass to callback function
336 *
337 * Asynchronously get the raw data for @resource.
338 *
339 * When the operation is finished, @callback will be called. You can then call
340 * webkit_web_resource_get_data_finish() to get the result of the operation.
341 */
342void webkit_web_resource_get_data(WebKitWebResource* resource, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
343{
344    g_return_if_fail(WEBKIT_IS_WEB_RESOURCE(resource));
345
346    GSimpleAsyncResult* result = g_simple_async_result_new(G_OBJECT(resource), callback, userData,
347                                                           reinterpret_cast<gpointer>(webkit_web_resource_get_data));
348    ResourceGetDataAsyncData* data = createResourceGetDataAsyncData();
349    data->cancellable = cancellable;
350    g_simple_async_result_set_op_res_gpointer(result, data, reinterpret_cast<GDestroyNotify>(destroyResourceGetDataAsyncData));
351    if (resource->priv->isMainResource)
352        resource->priv->frame->getMainResourceData(DataCallback::create(result, resourceDataCallback));
353    else {
354        String url = String::fromUTF8(resource->priv->uri.data());
355        resource->priv->frame->getResourceData(WebURL::create(url).get(), DataCallback::create(result, resourceDataCallback));
356    }
357}
358
359/**
360 * webkit_web_resource_get_data_finish:
361 * @resource: a #WebKitWebResource
362 * @result: a #GAsyncResult
363 * @length: (out): return location for the length of the resource data
364 * @error: return location for error or %NULL to ignore
365 *
366 * Finish an asynchronous operation started with webkit_web_resource_get_data().
367 *
368 * Returns: (transfer full): a string with the data of @resource, or %NULL in case
369 *    of error. if @length is not %NULL, the size of the data will be assigned to it.
370 */
371guchar* webkit_web_resource_get_data_finish(WebKitWebResource* resource, GAsyncResult* result, gsize* length, GError** error)
372{
373    g_return_val_if_fail(WEBKIT_IS_WEB_RESOURCE(resource), 0);
374    g_return_val_if_fail(G_IS_ASYNC_RESULT(result), 0);
375
376    GSimpleAsyncResult* simple = G_SIMPLE_ASYNC_RESULT(result);
377    g_warn_if_fail(g_simple_async_result_get_source_tag(simple) == webkit_web_resource_get_data);
378
379    if (g_simple_async_result_propagate_error(simple, error))
380        return 0;
381
382    ResourceGetDataAsyncData* data = static_cast<ResourceGetDataAsyncData*>(g_simple_async_result_get_op_res_gpointer(simple));
383    if (length)
384        *length = data->webData->size();
385    return static_cast<guchar*>(g_memdup(data->webData->bytes(), data->webData->size()));
386}
387