+2010-10-13 Sergio Villar Senin <svillar@igalia.com>
+
+ Reviewed by Martin Robinson.
+
+ [GTK] Add HTTP caching support
+ https://bugs.webkit.org/show_bug.cgi?id=44261
+
+ Bump the required version of GLib. The serialization/deserialization
+ code of libsoup caching code uses GVariant, which was added to GLib in
+ version 2.24.
+
+ * GNUmakefile.am: Add paths for soup HTTP cache code.
+ * autotools/webkit.m4: Increase required glib version.
+
2010-10-13 Xan Lopez <xlopez@igalia.com>
Reviewed by Gustavo Noronha.
endif
webkitgtk_static_h_api += \
+ $(srcdir)/WebCore/platform/network/soup/cache/webkit/soup-cache.h \
$(srcdir)/WebKit/gtk/webkit/webkit.h \
$(srcdir)/WebKit/gtk/webkit/webkitdefines.h \
$(srcdir)/WebKit/gtk/webkit/webkitdownload.h \
$(srcdir)/WebKit/gtk/webkit/webkiterror.h \
+ $(srcdir)/WebKit/gtk/webkit/webkitgeolocationpolicydecision.h \
$(srcdir)/WebKit/gtk/webkit/webkithittestresult.h \
$(srcdir)/WebKit/gtk/webkit/webkitnetworkrequest.h \
$(srcdir)/WebKit/gtk/webkit/webkitnetworkresponse.h \
+ $(srcdir)/WebKit/gtk/webkit/webkitsecurityorigin.h \
$(srcdir)/WebKit/gtk/webkit/webkitsoupauthdialog.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebbackforwardlist.h \
+ $(srcdir)/WebKit/gtk/webkit/webkitwebdatabase.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebdatasource.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebframe.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebhistoryitem.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebinspector.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebnavigationaction.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebpolicydecision.h \
- $(srcdir)/WebKit/gtk/webkit/webkitgeolocationpolicydecision.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebresource.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebsettings.h \
$(srcdir)/WebKit/gtk/webkit/webkitwebwindowfeatures.h \
- $(srcdir)/WebKit/gtk/webkit/webkitwebview.h \
- $(srcdir)/WebKit/gtk/webkit/webkitwebdatabase.h \
- $(srcdir)/WebKit/gtk/webkit/webkitsecurityorigin.h
+ $(srcdir)/WebKit/gtk/webkit/webkitwebview.h
webkitgtk_built_h_api += \
WebKit/gtk/webkit/webkitversion.h
webkit_tests_cflags = \
-fno-strict-aliasing \
-I$(srcdir)/JavaScriptCore/ForwardingHeaders \
+ -I$(srcdir)/WebCore/platform/network/soup/cache \
-I$(srcdir)/WebKit/gtk \
-I$(top_builddir)/WebKit/gtk \
-I$(top_builddir)/DerivedSources \
+2010-10-13 Sergio Villar Senin <svillar@igalia.com>
+
+ Reviewed by Martin Robinson.
+
+ [GTK] Add HTTP caching support
+ https://bugs.webkit.org/show_bug.cgi?id=44261
+
+ This patch adds HTTP caching support to libsoup networking backend.
+ This code will eventually be part of libsoup, but has been temporarily
+ imported into the WebCore tree for testing purposes. All libsoup code
+ is located in WebCore/platform/network/soup/cache. The webkit prefix
+ was added to all the symbols to prevent problems when this lands in
+ libsoup. Some external API was also added under webkit/ directory and
+ will be used by library clients to enable HTTP caching.
+
+ There are two parts to this imported code, the first one is the code
+ under WebCore/platform/network/soup/cache/ that contains all the
+ SoupURILoader code that supports the development of the cache. The
+ second part is the HTTP cache implementation located in
+ WebCore/platform/network/soup/cache/webkit/.
+
+ * GNUmakefile.am:
+ * platform/network/soup/cache/soup-directory-input-stream.c: Added.
+ (webkit_soup_directory_input_stream_parse_info):
+ (webkit_soup_directory_input_stream_read_next_file):
+ (webkit_soup_directory_input_stream_read):
+ (webkit_soup_directory_input_stream_close):
+ (webkit_soup_directory_input_stream_class_init):
+ (webkit_soup_directory_input_stream_init):
+ (webkit_soup_directory_input_stream_new):
+ * platform/network/soup/cache/soup-directory-input-stream.h: Added.
+ * platform/network/soup/cache/soup-http-input-stream.c: Added.
+ (webkit_soup_http_input_stream_finalize):
+ (webkit_soup_http_input_stream_class_init):
+ (webkit_soup_http_input_stream_seekable_iface_init):
+ (webkit_soup_http_input_stream_init):
+ (webkit_soup_http_input_stream_queue_message):
+ (webkit_soup_http_input_stream_new):
+ (webkit_soup_http_input_stream_got_headers):
+ (webkit_soup_http_input_stream_got_chunk):
+ (webkit_soup_http_input_stream_finished):
+ (webkit_soup_http_input_stream_cancelled):
+ (webkit_soup_http_input_stream_prepare_for_io):
+ (webkit_soup_http_input_stream_done_io):
+ (set_error_if_http_failed):
+ (read_from_leftover):
+ (webkit_soup_http_input_stream_send_internal):
+ (send_sync_finished):
+ (webkit_soup_http_input_stream_send):
+ (webkit_soup_http_input_stream_read):
+ (webkit_soup_http_input_stream_close):
+ (wrapper_callback):
+ (send_async_thread):
+ (webkit_soup_http_input_stream_send_async_in_thread):
+ (send_async_finished):
+ (webkit_soup_http_input_stream_send_async_internal):
+ (webkit_soup_http_input_stream_send_async):
+ (webkit_soup_http_input_stream_send_finish):
+ (read_async_done):
+ (webkit_soup_http_input_stream_read_async):
+ (webkit_soup_http_input_stream_read_finish):
+ (webkit_soup_http_input_stream_close_async):
+ (webkit_soup_http_input_stream_close_finish):
+ (webkit_soup_http_input_stream_tell):
+ (webkit_soup_http_input_stream_can_seek):
+ (webkit_soup_http_input_stream_seek):
+ (webkit_soup_http_input_stream_can_truncate):
+ (webkit_soup_http_input_stream_truncate):
+ (webkit_soup_http_input_stream_get_message):
+ * platform/network/soup/cache/soup-http-input-stream.h: Added.
+ * platform/network/soup/cache/soup-request-data.c: Added.
+ (webkit_soup_request_data_init):
+ (webkit_soup_request_data_finalize):
+ (webkit_soup_request_data_check_uri):
+ (uri_decoded_copy):
+ (webkit_soup_request_data_send):
+ (webkit_soup_request_data_get_content_length):
+ (webkit_soup_request_data_get_content_type):
+ (webkit_soup_request_data_class_init):
+ * platform/network/soup/cache/soup-request-data.h: Added.
+ * platform/network/soup/cache/soup-request-file.c: Added.
+ (webkit_soup_request_file_get_file):
+ (webkit_soup_request_file_init):
+ (webkit_soup_request_file_finalize):
+ (webkit_soup_request_file_check_uri):
+ (webkit_soup_request_file_ftp_main_loop_quit):
+ (webkit_soup_request_file_ensure_file_ftp):
+ (webkit_soup_request_file_ensure_file):
+ (webkit_soup_request_file_send):
+ (webkit_soup_request_file_send_async_thread):
+ (webkit_soup_request_file_send_async):
+ (webkit_soup_request_file_send_finish):
+ (webkit_soup_request_file_get_content_length):
+ (webkit_soup_request_file_get_content_type):
+ (webkit_soup_request_file_class_init):
+ * platform/network/soup/cache/soup-request-file.h: Added.
+ * platform/network/soup/cache/soup-request-http.c: Added.
+ (webkit_soup_request_http_get_message):
+ (webkit_soup_request_http_init):
+ (webkit_soup_request_http_check_uri):
+ (webkit_soup_request_http_finalize):
+ (webkit_soup_request_http_send):
+ (sent_async):
+ (conditional_get_ready_cb):
+ (send_async_cb):
+ (webkit_soup_request_http_send_async):
+ (webkit_soup_request_http_send_finish):
+ (webkit_soup_request_http_get_content_length):
+ (webkit_soup_request_http_get_content_type):
+ (webkit_soup_request_http_class_init):
+ * platform/network/soup/cache/soup-request-http.h: Added.
+ * platform/network/soup/cache/soup-request.c: Added.
+ (webkit_soup_request_init):
+ (webkit_soup_request_finalize):
+ (webkit_soup_request_set_property):
+ (webkit_soup_request_get_property):
+ (webkit_soup_request_initable_init):
+ (webkit_soup_request_default_check_uri):
+ (webkit_soup_request_default_send_async):
+ (webkit_soup_request_default_send_finish):
+ (webkit_soup_request_send):
+ (webkit_soup_request_send_async):
+ (webkit_soup_request_send_finish):
+ (webkit_soup_request_class_init):
+ (webkit_soup_request_initable_interface_init):
+ (webkit_soup_request_get_uri):
+ (webkit_soup_request_get_session):
+ (webkit_soup_request_get_content_length):
+ (webkit_soup_request_get_content_type):
+ * platform/network/soup/cache/soup-request.h: Added.
+ * platform/network/soup/cache/soup-requester.c: Added.
+ (webkit_soup_requester_init):
+ (finalize):
+ (webkit_soup_requester_class_init):
+ (init_request_types):
+ (webkit_soup_requester_new):
+ (webkit_soup_requester_request):
+ (webkit_soup_requester_request_uri):
+ (webkit_soup_scheme_is_valid):
+ (webkit_soup_requester_add_protocol):
+ (webkit_soup_requester_remove_protocol):
+ (webkit_soup_error_quark):
+ * platform/network/soup/cache/soup-requester.h: Added.
+ * platform/network/soup/cache/webkit/soup-cache-private.h: Added.
+ * platform/network/soup/cache/webkit/soup-cache.c: Added.
+ (get_cacheability):
+ (webkit_soup_cache_entry_free):
+ (copy_headers):
+ (update_headers):
+ (webkit_soup_cache_entry_get_current_age):
+ (webkit_soup_cache_entry_is_fresh_enough):
+ (webkit_soup_message_get_cache_key):
+ (webkit_soup_cache_entry_set_freshness):
+ (webkit_soup_cache_entry_new):
+ (webkit_soup_cache_writing_fixture_free):
+ (close_ready_cb):
+ (write_ready_cb):
+ (msg_got_chunk_cb):
+ (msg_got_body_cb):
+ (webkit_soup_cache_entry_delete):
+ (lru_compare_func):
+ (cache_accepts_entries_of_size):
+ (make_room_for_new_entry):
+ (webkit_soup_cache_entry_insert_by_key):
+ (msg_restarted_cb):
+ (append_to_ready_cb):
+ (msg_got_headers_cb):
+ (webkit_soup_cache_send_response):
+ (request_started):
+ (attach):
+ (webkit_soup_cache_session_feature_init):
+ (webkit_soup_cache_init):
+ (webkit_soup_cache_finalize):
+ (webkit_soup_cache_set_property):
+ (webkit_soup_cache_get_property):
+ (webkit_soup_cache_constructed):
+ (webkit_soup_cache_type_get_type):
+ (webkit_soup_cache_class_init):
+ (webkit_soup_cache_new):
+ (webkit_soup_cache_has_response):
+ (webkit_soup_cache_get_cacheability):
+ (force_flush_timeout):
+ (webkit_soup_cache_flush):
+ (remove_cache_item):
+ (webkit_soup_cache_clear):
+ (webkit_soup_cache_generate_conditional_request):
+ (pack_entry):
+ (webkit_soup_cache_dump):
+ (webkit_soup_cache_load):
+ (webkit_soup_cache_set_max_size):
+ (webkit_soup_cache_get_max_size):
+ * platform/network/soup/cache/webkit/soup-cache.h: Added.
+
2010-10-13 Gavin Barraclough <barraclough@apple.com>
Chromium build fix.
-I$(srcdir)/WebCore/platform/graphics/gstreamer \
-I$(srcdir)/WebCore/platform/graphics/gtk \
-I$(srcdir)/WebCore/platform/gtk \
- -I$(srcdir)/WebCore/platform/network/soup
+ -I$(srcdir)/WebCore/platform/network/soup \
+ -I$(srcdir)/WebCore/platform/network/soup/cache \
+ -I$(srcdir)/WebCore/platform/network/soup/cache/webkit
webcore_built_nosources += \
DerivedSources/WebCore/tokenizer.cpp
WebCore/platform/network/soup/SocketStreamError.h \
WebCore/platform/network/soup/SocketStreamHandle.h \
WebCore/platform/network/soup/SocketStreamHandleSoup.cpp \
+ WebCore/platform/network/soup/cache/soup-directory-input-stream.c \
+ WebCore/platform/network/soup/cache/soup-directory-input-stream.h \
+ WebCore/platform/network/soup/cache/soup-http-input-stream.c \
+ WebCore/platform/network/soup/cache/soup-http-input-stream.h \
+ WebCore/platform/network/soup/cache/soup-request-data.c \
+ WebCore/platform/network/soup/cache/soup-request-data.h \
+ WebCore/platform/network/soup/cache/soup-request-file.c \
+ WebCore/platform/network/soup/cache/soup-request-file.h \
+ WebCore/platform/network/soup/cache/soup-request-http.c \
+ WebCore/platform/network/soup/cache/soup-request-http.h \
+ WebCore/platform/network/soup/cache/soup-request.c \
+ WebCore/platform/network/soup/cache/soup-request.h \
+ WebCore/platform/network/soup/cache/soup-requester.c \
+ WebCore/platform/network/soup/cache/soup-requester.h \
+ WebCore/platform/network/soup/cache/webkit/soup-cache.c \
+ WebCore/platform/network/soup/cache/webkit/soup-cache.h \
WebCore/plugins/gtk/PluginDataGtk.cpp \
WebCore/plugins/gtk/PluginPackageGtk.cpp \
WebCore/plugins/gtk/PluginViewGtk.cpp
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-directory-input-stream.h"
+
+#include <libsoup/soup.h>
+#include <stdio.h>
+#include <string.h>
+
+#define INIT_STRING "<html><head><title>OMG!</title></head><body><table>"
+#define EXIT_STRING "</table></html>"
+
+G_DEFINE_TYPE (WebKitSoupDirectoryInputStream, webkit_soup_directory_input_stream, G_TYPE_INPUT_STREAM)
+
+static SoupBuffer *
+webkit_soup_directory_input_stream_parse_info (WebKitSoupDirectoryInputStream * stream,
+ GFileInfo * info)
+{
+ SoupBuffer *buffer;
+ GString *string;
+ const char *s;
+ char *escaped, *path, *xml_string;
+
+ if (!g_file_info_get_name (info))
+ return NULL;
+
+ s = g_file_info_get_display_name (info);
+ if (!s) {
+ s = g_file_info_get_name (info);
+ /* FIXME: convert somehow? */
+ if (!g_utf8_validate (s, -1, NULL))
+ return NULL;
+ }
+ string = g_string_new ("<tr>");
+
+ xml_string = g_markup_escape_text (s, -1);
+ escaped = g_uri_escape_string (g_file_info_get_name (info), NULL, FALSE);
+ path = g_strconcat (stream->uri, "/", escaped, NULL);
+ g_free (escaped);
+ g_string_append_printf (string, "<td><a href=\"%s\">%s</a></td>", path, xml_string);
+ g_free (path);
+ g_free (xml_string);
+ g_string_append (string, "</tr>");
+
+ buffer = soup_buffer_new (SOUP_MEMORY_TAKE, string->str, string->len);
+ g_string_free (string, FALSE);
+
+ return buffer;
+}
+
+static SoupBuffer *
+webkit_soup_directory_input_stream_read_next_file (WebKitSoupDirectoryInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFileInfo *info;
+ SoupBuffer *buffer;
+ GError *err = NULL;
+
+ do {
+ info = g_file_enumerator_next_file (stream->enumerator, cancellable, &err);
+ if (info == NULL) {
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ } else if (!stream->done) {
+ stream->done = TRUE;
+ return soup_buffer_new (SOUP_MEMORY_STATIC,
+ EXIT_STRING,
+ sizeof (EXIT_STRING));
+ } else {
+ return NULL;
+ }
+ }
+
+ buffer = webkit_soup_directory_input_stream_parse_info (stream, info);
+ } while (buffer == NULL);
+
+ return buffer;
+}
+
+static gssize
+webkit_soup_directory_input_stream_read (GInputStream *input,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupDirectoryInputStream *stream = WEBKIT_SOUP_DIRECTORY_INPUT_STREAM (input);
+ gssize total, size;
+
+ for (total = 0; total < count; total += size) {
+ if (stream->buffer == NULL) {
+ stream->buffer = webkit_soup_directory_input_stream_read_next_file (stream, cancellable, error);
+ if (stream->buffer == NULL) {
+ /* FIXME: Is this correct or should we forward the error? */
+ if (total)
+ g_clear_error (error);
+ return total;
+ }
+ }
+
+ size = MIN (stream->buffer->length, count - total);
+ memcpy ((char *)buffer + total, stream->buffer->data, size);
+ if (size == stream->buffer->length) {
+ soup_buffer_free (stream->buffer);
+ stream->buffer = NULL;
+ } else {
+ SoupBuffer *sub = soup_buffer_new_subbuffer (stream->buffer,
+ size,
+ stream->buffer->length - size);
+ soup_buffer_free (stream->buffer);
+ stream->buffer = sub;
+ }
+ }
+
+ return total;
+}
+
+static gboolean
+webkit_soup_directory_input_stream_close (GInputStream *input,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupDirectoryInputStream *stream = WEBKIT_SOUP_DIRECTORY_INPUT_STREAM (input);
+ gboolean result;
+
+ if (stream->buffer) {
+ soup_buffer_free (stream->buffer);
+ stream->buffer = NULL;
+ }
+
+ result = g_file_enumerator_close (stream->enumerator,
+ cancellable,
+ error);
+ g_object_unref (stream->enumerator);
+ stream->enumerator = NULL;
+
+ g_free (stream->uri);
+ stream->uri = NULL;
+
+ return result;
+}
+
+static void
+webkit_soup_directory_input_stream_class_init (WebKitSoupDirectoryInputStreamClass *stream_class)
+{
+ GInputStreamClass *inputstream_class = G_INPUT_STREAM_CLASS (stream_class);
+
+ inputstream_class->read_fn = webkit_soup_directory_input_stream_read;
+ inputstream_class->close_fn = webkit_soup_directory_input_stream_close;
+}
+
+static void
+webkit_soup_directory_input_stream_init (WebKitSoupDirectoryInputStream *stream)
+{
+ stream->buffer = soup_buffer_new (SOUP_MEMORY_STATIC,
+ INIT_STRING,
+ sizeof (INIT_STRING));
+}
+
+GInputStream *
+webkit_soup_directory_input_stream_new (GFileEnumerator *enumerator,
+ SoupURI *uri)
+{
+ GInputStream *stream;
+
+ g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ stream = g_object_new (WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM, NULL);
+
+ WEBKIT_SOUP_DIRECTORY_INPUT_STREAM (stream)->enumerator = g_object_ref (enumerator);
+ WEBKIT_SOUP_DIRECTORY_INPUT_STREAM (stream)->uri = soup_uri_to_string (uri, FALSE);
+
+ return stream;
+}
+
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_DIRECTORY_INPUT_STREAM_H
+#define WEBKIT_SOUP_DIRECTORY_INPUT_STREAM_H 1
+
+#include <gio/gio.h>
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-message-body.h>
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM (webkit_soup_directory_input_stream_get_type ())
+#define WEBKIT_SOUP_DIRECTORY_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM, WebKitSoupDirectoryInputStream))
+#define WEBKIT_SOUP_DIRECTORY_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM, WebKitSoupDirectoryInputStreamClass))
+#define WEBKIT_IS_SOUP_DIRECTORY_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM))
+#define WEBKIT_IS_SOUP_DIRECTORY_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM))
+#define WEBKIT_SOUP_DIRECTORY_INPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_DIRECTORY_INPUT_STREAM, WebKitSoupDirectoryInputStreamClass))
+
+typedef struct _WebKitSoupDirectoryInputStream WebKitSoupDirectoryInputStream;
+typedef struct _WebKitSoupDirectoryInputStreamClass WebKitSoupDirectoryInputStreamClass;
+
+struct _WebKitSoupDirectoryInputStream {
+ GInputStream parent;
+
+ GFileEnumerator *enumerator;
+ char *uri;
+ SoupBuffer *buffer;
+ gboolean done;
+};
+
+struct _WebKitSoupDirectoryInputStreamClass {
+ GInputStreamClass parent_class;
+};
+
+GType webkit_soup_directory_input_stream_get_type (void);
+
+GInputStream *webkit_soup_directory_input_stream_new (GFileEnumerator *enumerator,
+ SoupURI *uri);
+
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_DIRECTORY_INPUT_STREAM_H */
--- /dev/null
+/* soup-input-stream.c, based on gsocketinputstream.c
+ *
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <libsoup/soup.h>
+
+#include "soup-http-input-stream.h"
+
+static void webkit_soup_http_input_stream_seekable_iface_init (GSeekableIface *seekable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (WebKitSoupHTTPInputStream, webkit_soup_http_input_stream, G_TYPE_INPUT_STREAM,
+ G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,
+ webkit_soup_http_input_stream_seekable_iface_init))
+
+typedef void (*WebKitSoupHTTPInputStreamCallback)(GInputStream *);
+
+typedef struct {
+ SoupSession *session;
+ GMainContext *async_context;
+ SoupMessage *msg;
+ gboolean got_headers, finished;
+ goffset offset;
+
+ GCancellable *cancellable;
+ GSource *cancel_watch;
+ WebKitSoupHTTPInputStreamCallback got_headers_cb;
+ WebKitSoupHTTPInputStreamCallback got_chunk_cb;
+ WebKitSoupHTTPInputStreamCallback finished_cb;
+ WebKitSoupHTTPInputStreamCallback cancelled_cb;
+
+ guchar *leftover_buffer;
+ gsize leftover_bufsize, leftover_offset;
+
+ guchar *caller_buffer;
+ gsize caller_bufsize, caller_nread;
+ GAsyncReadyCallback outstanding_callback;
+ GSimpleAsyncResult *result;
+} WebKitSoupHTTPInputStreamPrivate;
+#define WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM, WebKitSoupHTTPInputStreamPrivate))
+
+
+static gssize webkit_soup_http_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean webkit_soup_http_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+static void webkit_soup_http_input_stream_read_async (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data);
+static gssize webkit_soup_http_input_stream_read_finish (GInputStream *stream,
+ GAsyncResult *result,
+ GError **error);
+static void webkit_soup_http_input_stream_close_async (GInputStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data);
+static gboolean webkit_soup_http_input_stream_close_finish (GInputStream *stream,
+ GAsyncResult *result,
+ GError **error);
+
+static goffset webkit_soup_http_input_stream_tell (GSeekable *seekable);
+
+static gboolean webkit_soup_http_input_stream_can_seek (GSeekable *seekable);
+static gboolean webkit_soup_http_input_stream_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error);
+
+static gboolean webkit_soup_http_input_stream_can_truncate (GSeekable *seekable);
+static gboolean webkit_soup_http_input_stream_truncate (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error);
+
+static void webkit_soup_http_input_stream_got_headers (SoupMessage *msg, gpointer stream);
+static void webkit_soup_http_input_stream_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer stream);
+static void webkit_soup_http_input_stream_finished (SoupMessage *msg, gpointer stream);
+
+static void
+webkit_soup_http_input_stream_finalize (GObject *object)
+{
+ WebKitSoupHTTPInputStream *stream = WEBKIT_SOUP_HTTP_INPUT_STREAM (object);
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ g_object_unref (priv->session);
+
+ g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (webkit_soup_http_input_stream_got_headers), stream);
+ g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (webkit_soup_http_input_stream_got_chunk), stream);
+ g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (webkit_soup_http_input_stream_finished), stream);
+ g_object_unref (priv->msg);
+ g_free (priv->leftover_buffer);
+
+ if (G_OBJECT_CLASS (webkit_soup_http_input_stream_parent_class)->finalize)
+ (*G_OBJECT_CLASS (webkit_soup_http_input_stream_parent_class)->finalize)(object);
+}
+
+static void
+webkit_soup_http_input_stream_class_init (WebKitSoupHTTPInputStreamClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (WebKitSoupHTTPInputStreamPrivate));
+
+ gobject_class->finalize = webkit_soup_http_input_stream_finalize;
+
+ stream_class->read_fn = webkit_soup_http_input_stream_read;
+ stream_class->close_fn = webkit_soup_http_input_stream_close;
+ stream_class->read_async = webkit_soup_http_input_stream_read_async;
+ stream_class->read_finish = webkit_soup_http_input_stream_read_finish;
+ stream_class->close_async = webkit_soup_http_input_stream_close_async;
+ stream_class->close_finish = webkit_soup_http_input_stream_close_finish;
+}
+
+static void
+webkit_soup_http_input_stream_seekable_iface_init (GSeekableIface *seekable_iface)
+{
+ seekable_iface->tell = webkit_soup_http_input_stream_tell;
+ seekable_iface->can_seek = webkit_soup_http_input_stream_can_seek;
+ seekable_iface->seek = webkit_soup_http_input_stream_seek;
+ seekable_iface->can_truncate = webkit_soup_http_input_stream_can_truncate;
+ seekable_iface->truncate_fn = webkit_soup_http_input_stream_truncate;
+}
+
+static void
+webkit_soup_http_input_stream_init (WebKitSoupHTTPInputStream *stream)
+{
+ ;
+}
+
+static void
+webkit_soup_http_input_stream_queue_message (WebKitSoupHTTPInputStream *stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ priv->got_headers = priv->finished = FALSE;
+
+ /* Add an extra ref since soup_session_queue_message steals one */
+ g_object_ref (priv->msg);
+ soup_session_queue_message (priv->session, priv->msg, NULL, NULL);
+}
+
+/**
+ * webkit_soup_http_input_stream_new:
+ * @session: the #SoupSession to use
+ * @msg: the #SoupMessage whose response will be streamed
+ *
+ * Prepares to send @msg over @session, and returns a #GInputStream
+ * that can be used to read the response.
+ *
+ * @msg may not be sent until the first read call; if you need to look
+ * at the status code or response headers before reading the body, you
+ * can use webkit_soup_http_input_stream_send() or webkit_soup_http_input_stream_send_async()
+ * to force the message to be sent and the response headers read.
+ *
+ * If @msg gets a non-2xx result, the first read (or send) will return
+ * an error with type %WEBKIT_SOUP_HTTP_INPUT_STREAM_HTTP_ERROR.
+ *
+ * Internally, #WebKitSoupHTTPInputStream is implemented using asynchronous I/O,
+ * so if you are using the synchronous API (eg,
+ * g_input_stream_read()), you should create a new #GMainContext and
+ * set it as the %SOUP_SESSION_ASYNC_CONTEXT property on @session. (If
+ * you don't, then synchronous #GInputStream calls will cause the main
+ * loop to be run recursively.) The async #GInputStream API works fine
+ * with %SOUP_SESSION_ASYNC_CONTEXT either set or unset.
+ *
+ * Returns: a new #GInputStream.
+ **/
+WebKitSoupHTTPInputStream *
+webkit_soup_http_input_stream_new (SoupSession *session, SoupMessage *msg)
+{
+ WebKitSoupHTTPInputStream *stream;
+ WebKitSoupHTTPInputStreamPrivate *priv;
+
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+ stream = g_object_new (WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM, NULL);
+ priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ priv->session = g_object_ref (session);
+ priv->async_context = soup_session_get_async_context (session);
+ priv->msg = g_object_ref (msg);
+
+ g_signal_connect (msg, "got_headers",
+ G_CALLBACK (webkit_soup_http_input_stream_got_headers), stream);
+ g_signal_connect (msg, "got_chunk",
+ G_CALLBACK (webkit_soup_http_input_stream_got_chunk), stream);
+ g_signal_connect (msg, "finished",
+ G_CALLBACK (webkit_soup_http_input_stream_finished), stream);
+
+ webkit_soup_http_input_stream_queue_message (stream);
+ return stream;
+}
+
+static void
+webkit_soup_http_input_stream_got_headers (SoupMessage *msg, gpointer stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ /* If the status is unsuccessful, we just ignore the signal and let
+ * libsoup keep going (eventually either it will requeue the request
+ * (after handling authentication/redirection), or else the
+ * "finished" handler will run).
+ */
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
+ return;
+
+ priv->got_headers = TRUE;
+ if (!priv->caller_buffer) {
+ /* Not ready to read the body yet */
+ soup_session_pause_message (priv->session, msg);
+ }
+
+ if (priv->got_headers_cb)
+ priv->got_headers_cb (stream);
+}
+
+static void
+webkit_soup_http_input_stream_got_chunk (SoupMessage *msg, SoupBuffer *chunk_buffer,
+ gpointer stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ const gchar *chunk = chunk_buffer->data;
+ gsize chunk_size = chunk_buffer->length;
+
+ /* We only pay attention to the chunk if it's part of a successful
+ * response.
+ */
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
+ return;
+
+ /* Sanity check */
+ if (priv->caller_bufsize == 0 || priv->leftover_bufsize != 0)
+ g_warning ("webkit_soup_http_input_stream_got_chunk called again before previous chunk was processed");
+
+ /* Copy what we can into priv->caller_buffer */
+ if (priv->caller_bufsize - priv->caller_nread > 0) {
+ gsize nread = MIN (chunk_size, priv->caller_bufsize - priv->caller_nread);
+
+ memcpy (priv->caller_buffer + priv->caller_nread, chunk, nread);
+ priv->caller_nread += nread;
+ priv->offset += nread;
+ chunk += nread;
+ chunk_size -= nread;
+ }
+
+ if (chunk_size > 0) {
+ /* Copy the rest into priv->leftover_buffer. If
+ * there's already some data there, realloc and
+ * append. Otherwise just copy.
+ */
+ if (priv->leftover_bufsize) {
+ priv->leftover_buffer = g_realloc (priv->leftover_buffer,
+ priv->leftover_bufsize + chunk_size);
+ memcpy (priv->leftover_buffer + priv->leftover_bufsize,
+ chunk, chunk_size);
+ priv->leftover_bufsize += chunk_size;
+ } else {
+ priv->leftover_bufsize = chunk_size;
+ priv->leftover_buffer = g_memdup (chunk, chunk_size);
+ priv->leftover_offset = 0;
+ }
+ }
+
+ soup_session_pause_message (priv->session, msg);
+ if (priv->got_chunk_cb)
+ priv->got_chunk_cb (stream);
+}
+
+static void
+webkit_soup_http_input_stream_finished (SoupMessage *msg, gpointer stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ priv->finished = TRUE;
+
+ if (priv->finished_cb)
+ priv->finished_cb (stream);
+}
+
+static gboolean
+webkit_soup_http_input_stream_cancelled (GIOChannel *chan, GIOCondition condition,
+ gpointer stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ priv->cancel_watch = NULL;
+
+ soup_session_pause_message (priv->session, priv->msg);
+ if (priv->cancelled_cb)
+ priv->cancelled_cb (stream);
+
+ return FALSE;
+}
+
+static void
+webkit_soup_http_input_stream_prepare_for_io (GInputStream *stream,
+ GCancellable *cancellable,
+ guchar *buffer,
+ gsize count)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ int cancel_fd;
+
+ priv->cancellable = cancellable;
+ cancel_fd = g_cancellable_get_fd (cancellable);
+ if (cancel_fd != -1) {
+ GIOChannel *chan = g_io_channel_unix_new (cancel_fd);
+ priv->cancel_watch = soup_add_io_watch (priv->async_context, chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ webkit_soup_http_input_stream_cancelled,
+ stream);
+ g_io_channel_unref (chan);
+ }
+
+ priv->caller_buffer = buffer;
+ priv->caller_bufsize = count;
+ priv->caller_nread = 0;
+
+ if (priv->got_headers)
+ soup_session_unpause_message (priv->session, priv->msg);
+}
+
+static void
+webkit_soup_http_input_stream_done_io (GInputStream *stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ if (priv->cancel_watch) {
+ g_source_destroy (priv->cancel_watch);
+ priv->cancel_watch = NULL;
+ g_cancellable_release_fd (priv->cancellable);
+ }
+ priv->cancellable = NULL;
+
+ priv->caller_buffer = NULL;
+ priv->caller_bufsize = 0;
+}
+
+static gboolean
+set_error_if_http_failed (SoupMessage *msg, GError **error)
+{
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+ g_set_error_literal (error, SOUP_HTTP_ERROR,
+ msg->status_code, msg->reason_phrase);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gsize
+read_from_leftover (WebKitSoupHTTPInputStreamPrivate *priv,
+ gpointer buffer, gsize bufsize)
+{
+ gsize nread;
+
+ if (priv->leftover_bufsize - priv->leftover_offset <= bufsize) {
+ nread = priv->leftover_bufsize - priv->leftover_offset;
+ memcpy (buffer, priv->leftover_buffer + priv->leftover_offset, nread);
+
+ g_free (priv->leftover_buffer);
+ priv->leftover_buffer = NULL;
+ priv->leftover_bufsize = priv->leftover_offset = 0;
+ } else {
+ nread = bufsize;
+ memcpy (buffer, priv->leftover_buffer + priv->leftover_offset, nread);
+ priv->leftover_offset += nread;
+ }
+
+ priv->offset += nread;
+ return nread;
+}
+
+/* This does the work of webkit_soup_http_input_stream_send(), assuming that the
+ * GInputStream pending flag has already been set. It is also used by
+ * webkit_soup_http_input_stream_send_async() in some circumstances.
+ */
+static gboolean
+webkit_soup_http_input_stream_send_internal (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ webkit_soup_http_input_stream_prepare_for_io (stream, cancellable, NULL, 0);
+ while (!priv->finished && !priv->got_headers &&
+ !g_cancellable_is_cancelled (cancellable))
+ g_main_context_iteration (priv->async_context, TRUE);
+ webkit_soup_http_input_stream_done_io (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+ else if (set_error_if_http_failed (priv->msg, error))
+ return FALSE;
+ return TRUE;
+}
+
+static void
+send_sync_finished (GInputStream *stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ GError *error = NULL;
+
+ if (!g_cancellable_set_error_if_cancelled (priv->cancellable, &error))
+ set_error_if_http_failed (priv->msg, &error);
+
+ priv->got_headers_cb = NULL;
+ priv->finished_cb = NULL;
+
+ /* Wake up the main context iteration */
+ g_source_attach (g_idle_source_new (), NULL);
+}
+
+/**
+ * webkit_soup_http_input_stream_send:
+ * @httpstream: a #WebKitSoupHTTPInputStream
+ * @cancellable: optional #GCancellable object, %NULL to ignore.
+ * @error: location to store the error occuring, or %NULL to ignore
+ *
+ * Synchronously sends the HTTP request associated with @stream, and
+ * reads the response headers. Call this after webkit_soup_http_input_stream_new()
+ * and before the first g_input_stream_read() if you want to check the
+ * HTTP status code before you start reading.
+ *
+ * Return value: %TRUE if msg has a successful (2xx) status, %FALSE if
+ * not.
+ **/
+gboolean
+webkit_soup_http_input_stream_send (WebKitSoupHTTPInputStream *httpstream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (httpstream);
+ GInputStream *istream = (GInputStream *)httpstream;
+ gboolean result;
+
+ g_return_val_if_fail (WEBKIT_IS_SOUP_HTTP_INPUT_STREAM (httpstream), FALSE);
+
+ if (!g_input_stream_set_pending (istream, error))
+ return FALSE;
+
+ priv->got_headers_cb = send_sync_finished;
+ priv->finished_cb = send_sync_finished;
+
+ result = webkit_soup_http_input_stream_send_internal (istream, cancellable, error);
+ g_input_stream_clear_pending (istream);
+
+ return result;
+}
+
+static gssize
+webkit_soup_http_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ if (priv->finished)
+ return 0;
+
+ /* If there is data leftover from a previous read, return it. */
+ if (priv->leftover_bufsize)
+ return read_from_leftover (priv, buffer, count);
+
+ /* No leftover data, accept one chunk from the network */
+ webkit_soup_http_input_stream_prepare_for_io (stream, cancellable, buffer, count);
+ while (!priv->finished && priv->caller_nread == 0 &&
+ !g_cancellable_is_cancelled (cancellable))
+ g_main_context_iteration (priv->async_context, TRUE);
+ webkit_soup_http_input_stream_done_io (stream);
+
+ if (priv->caller_nread > 0)
+ return priv->caller_nread;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+ else if (set_error_if_http_failed (priv->msg, error))
+ return -1;
+ else
+ return 0;
+}
+
+static gboolean
+webkit_soup_http_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ if (!priv->finished)
+ soup_session_cancel_message (priv->session, priv->msg, SOUP_STATUS_CANCELLED);
+
+ return TRUE;
+}
+
+static void
+wrapper_callback (GObject *source_object, GAsyncResult *res,
+ gpointer user_data)
+{
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ g_input_stream_clear_pending (stream);
+ if (priv->outstanding_callback)
+ (*priv->outstanding_callback)(source_object, res, user_data);
+ priv->outstanding_callback = NULL;
+ g_object_unref (stream);
+}
+
+static void
+send_async_thread (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ gboolean success;
+
+ success = webkit_soup_http_input_stream_send_internal (G_INPUT_STREAM (object),
+ cancellable, &error);
+ g_simple_async_result_set_op_res_gboolean (res, success);
+ if (error) {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ }
+}
+
+static void
+webkit_soup_http_input_stream_send_async_in_thread (GInputStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data,
+ webkit_soup_http_input_stream_send_async_in_thread);
+ g_simple_async_result_run_in_thread (res, send_async_thread,
+ io_priority, cancellable);
+ g_object_unref (res);
+}
+
+static void
+send_async_finished (GInputStream *stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ GSimpleAsyncResult *result;
+ GError *error = NULL;
+
+ if (!g_cancellable_set_error_if_cancelled (priv->cancellable, &error))
+ set_error_if_http_failed (priv->msg, &error);
+
+ priv->got_headers_cb = NULL;
+ priv->finished_cb = NULL;
+ webkit_soup_http_input_stream_done_io (stream);
+
+ result = priv->result;
+ priv->result = NULL;
+
+ g_simple_async_result_set_op_res_gboolean (result, error == NULL);
+ if (error) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+ g_simple_async_result_complete (result);
+}
+
+static void
+webkit_soup_http_input_stream_send_async_internal (GInputStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+ g_object_ref (stream);
+ priv->outstanding_callback = callback;
+
+ /* If the session uses the default GMainContext, then we can do
+ * async I/O directly. But if it has its own main context, it's
+ * easier to just run it in another thread.
+ */
+ if (soup_session_get_async_context (priv->session)) {
+ webkit_soup_http_input_stream_send_async_in_thread (stream, io_priority, cancellable,
+ wrapper_callback, user_data);
+ return;
+ }
+
+ priv->got_headers_cb = send_async_finished;
+ priv->finished_cb = send_async_finished;
+
+ webkit_soup_http_input_stream_prepare_for_io (stream, cancellable, NULL, 0);
+ priv->result = g_simple_async_result_new (G_OBJECT (stream),
+ wrapper_callback, user_data,
+ webkit_soup_http_input_stream_send_async);
+}
+
+/**
+ * webkit_soup_http_input_stream_send_async:
+ * @httpstream: a #WebKitSoupHTTPInputStream
+ * @io_priority: the io priority of the request.
+ * @cancellable: optional #GCancellable object, %NULL to ignore.
+ * @callback: callback to call when the request is satisfied
+ * @user_data: the data to pass to callback function
+ *
+ * Asynchronously sends the HTTP request associated with @stream, and
+ * reads the response headers. Call this after webkit_soup_http_input_stream_new()
+ * and before the first g_input_stream_read_async() if you want to
+ * check the HTTP status code before you start reading.
+ **/
+void
+webkit_soup_http_input_stream_send_async (WebKitSoupHTTPInputStream *httpstream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GInputStream *istream = (GInputStream *)httpstream;
+ GError *error = NULL;
+
+ g_return_if_fail (WEBKIT_IS_SOUP_HTTP_INPUT_STREAM (httpstream));
+
+ if (!g_input_stream_set_pending (istream, &error)) {
+ g_simple_async_report_gerror_in_idle (G_OBJECT (httpstream),
+ callback,
+ user_data,
+ error);
+ g_error_free (error);
+ return;
+ }
+ webkit_soup_http_input_stream_send_async_internal (istream, io_priority, cancellable,
+ callback, user_data);
+}
+
+/**
+ * webkit_soup_http_input_stream_send_finish:
+ * @httpstream: a #WebKitSoupHTTPInputStream
+ * @result: a #GAsyncResult.
+ * @error: a #GError location to store the error occuring, or %NULL to
+ * ignore.
+ *
+ * Finishes a webkit_soup_http_input_stream_send_async() operation.
+ *
+ * Return value: %TRUE if the message was sent successfully and
+ * received a successful status code, %FALSE if not.
+ **/
+gboolean
+webkit_soup_http_input_stream_send_finish (WebKitSoupHTTPInputStream *httpstream,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ g_return_val_if_fail (g_simple_async_result_get_source_tag (simple) == webkit_soup_http_input_stream_send_async, FALSE);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (simple);
+}
+
+static void
+read_async_done (GInputStream *stream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ GSimpleAsyncResult *result;
+ GError *error = NULL;
+
+ result = priv->result;
+ priv->result = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (priv->cancellable, &error) ||
+ set_error_if_http_failed (priv->msg, &error)) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ } else
+ g_simple_async_result_set_op_res_gssize (result, priv->caller_nread);
+
+ priv->got_chunk_cb = NULL;
+ priv->finished_cb = NULL;
+ priv->cancelled_cb = NULL;
+ webkit_soup_http_input_stream_done_io (stream);
+
+ g_simple_async_result_complete (result);
+ g_object_unref (result);
+}
+
+static void
+webkit_soup_http_input_stream_read_async (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+ GSimpleAsyncResult *result;
+
+ /* If the session uses the default GMainContext, then we can do
+ * async I/O directly. But if it has its own main context, we fall
+ * back to the async-via-sync-in-another-thread implementation.
+ */
+ if (soup_session_get_async_context (priv->session)) {
+ G_INPUT_STREAM_CLASS (webkit_soup_http_input_stream_parent_class)->
+ read_async (stream, buffer, count, io_priority,
+ cancellable, callback, user_data);
+ return;
+ }
+
+ result = g_simple_async_result_new (G_OBJECT (stream),
+ callback, user_data,
+ webkit_soup_http_input_stream_read_async);
+
+ if (priv->finished) {
+ g_simple_async_result_set_op_res_gssize (result, 0);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ if (priv->leftover_bufsize) {
+ gsize nread = read_from_leftover (priv, buffer, count);
+ g_simple_async_result_set_op_res_gssize (result, nread);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ priv->result = result;
+
+ priv->got_chunk_cb = read_async_done;
+ priv->finished_cb = read_async_done;
+ priv->cancelled_cb = read_async_done;
+ webkit_soup_http_input_stream_prepare_for_io (stream, cancellable, buffer, count);
+}
+
+static gssize
+webkit_soup_http_input_stream_read_finish (GInputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), -1);
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ g_return_val_if_fail (g_simple_async_result_get_source_tag (simple) == webkit_soup_http_input_stream_read_async, -1);
+
+ return g_simple_async_result_get_op_res_gssize (simple);
+}
+
+static void
+webkit_soup_http_input_stream_close_async (GInputStream *stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ gboolean success;
+ GError *error = NULL;
+
+ result = g_simple_async_result_new (G_OBJECT (stream),
+ callback, user_data,
+ webkit_soup_http_input_stream_close_async);
+ success = webkit_soup_http_input_stream_close (stream, cancellable, &error);
+ g_simple_async_result_set_op_res_gboolean (result, success);
+ if (error) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static gboolean
+webkit_soup_http_input_stream_close_finish (GInputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ /* Failures handled in generic close_finish code */
+ return TRUE;
+}
+
+static goffset
+webkit_soup_http_input_stream_tell (GSeekable *seekable)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (seekable);
+
+ return priv->offset;
+}
+
+static gboolean
+webkit_soup_http_input_stream_can_seek (GSeekable *seekable)
+{
+ return TRUE;
+}
+
+extern void soup_message_io_cleanup (SoupMessage *msg);
+
+static gboolean
+webkit_soup_http_input_stream_seek (GSeekable *seekable,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInputStream *stream = G_INPUT_STREAM (seekable);
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (seekable);
+ char *range;
+
+ if (type == G_SEEK_END) {
+ /* FIXME: we could send "bytes=-offset", but unless we
+ * know the Content-Length, we wouldn't be able to
+ * answer a tell() properly. We could find the
+ * Content-Length by doing a HEAD...
+ */
+
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "G_SEEK_END not currently supported");
+ return FALSE;
+ }
+
+ if (!g_input_stream_set_pending (stream, error))
+ return FALSE;
+
+ soup_session_cancel_message (priv->session, priv->msg, SOUP_STATUS_CANCELLED);
+ soup_message_io_cleanup (priv->msg);
+
+ switch (type) {
+ case G_SEEK_CUR:
+ offset += priv->offset;
+ /* fall through */
+
+ case G_SEEK_SET:
+ range = g_strdup_printf ("bytes=%" G_GUINT64_FORMAT "-", (guint64)offset);
+ priv->offset = offset;
+ break;
+
+ case G_SEEK_END:
+ range = NULL; /* keep compilers happy */
+ g_return_val_if_reached (FALSE);
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ soup_message_headers_remove (priv->msg->request_headers, "Range");
+ soup_message_headers_append (priv->msg->request_headers, "Range", range);
+ g_free (range);
+
+ webkit_soup_http_input_stream_queue_message (WEBKIT_SOUP_HTTP_INPUT_STREAM (stream));
+
+ g_input_stream_clear_pending (stream);
+ return TRUE;
+}
+
+static gboolean
+webkit_soup_http_input_stream_can_truncate (GSeekable *seekable)
+{
+ return FALSE;
+}
+
+static gboolean
+webkit_soup_http_input_stream_truncate (GSeekable *seekable,
+ goffset offset,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Truncate not allowed on input stream");
+ return FALSE;
+}
+
+SoupMessage *
+webkit_soup_http_input_stream_get_message (WebKitSoupHTTPInputStream *httpstream)
+{
+ WebKitSoupHTTPInputStreamPrivate *priv = WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (httpstream);
+ return priv->msg ? g_object_ref (priv->msg) : NULL;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006, 2007, 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __WEBKIT_SOUP_HTTP_INPUT_STREAM_H__
+#define __WEBKIT_SOUP_HTTP_INPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM (webkit_soup_http_input_stream_get_type ())
+#define WEBKIT_SOUP_HTTP_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM, WebKitSoupHTTPInputStream))
+#define WEBKIT_SOUP_HTTP_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM, WebKitSoupHTTPInputStreamClass))
+#define WEBKIT_IS_SOUP_HTTP_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM))
+#define WEBKIT_IS_SOUP_HTTP_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM))
+#define WEBKIT_SOUP_HTTP_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WEBKIT_TYPE_SOUP_HTTP_INPUT_STREAM, WebKitSoupHTTPInputStreamClass))
+
+typedef struct WebKitSoupHTTPInputStream WebKitSoupHTTPInputStream;
+typedef struct WebKitSoupHTTPInputStreamClass WebKitSoupHTTPInputStreamClass;
+
+struct WebKitSoupHTTPInputStream {
+ GInputStream parent;
+};
+
+struct WebKitSoupHTTPInputStreamClass {
+ GInputStreamClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_g_reserved1)(void);
+ void (*_g_reserved2)(void);
+ void (*_g_reserved3)(void);
+ void (*_g_reserved4)(void);
+ void (*_g_reserved5)(void);
+};
+
+GType webkit_soup_http_input_stream_get_type (void) G_GNUC_CONST;
+
+WebKitSoupHTTPInputStream *webkit_soup_http_input_stream_new (SoupSession *session,
+ SoupMessage *msg);
+
+gboolean webkit_soup_http_input_stream_send (WebKitSoupHTTPInputStream *httpstream,
+ GCancellable *cancellable,
+ GError **error);
+
+void webkit_soup_http_input_stream_send_async (WebKitSoupHTTPInputStream *httpstream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean webkit_soup_http_input_stream_send_finish (WebKitSoupHTTPInputStream *httpstream,
+ GAsyncResult *result,
+ GError **error);
+
+SoupMessage *webkit_soup_http_input_stream_get_message (WebKitSoupHTTPInputStream *httpstream);
+
+G_END_DECLS
+
+#endif /* __WEBKIT_SOUP_HTTP_INPUT_STREAM_H__ */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-data.c: data: URI request object
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "soup-request-data.h"
+
+#include "soup-requester.h"
+#include <libsoup/soup.h>
+#include <glib/gi18n.h>
+
+G_DEFINE_TYPE (WebKitSoupRequestData, webkit_soup_request_data, WEBKIT_TYPE_SOUP_REQUEST)
+
+struct _WebKitSoupRequestDataPrivate {
+ gsize content_length;
+ char *content_type;
+};
+
+static void
+webkit_soup_request_data_init (WebKitSoupRequestData *data)
+{
+ data->priv = G_TYPE_INSTANCE_GET_PRIVATE (data, WEBKIT_TYPE_SOUP_REQUEST_DATA, WebKitSoupRequestDataPrivate);
+}
+
+static void
+webkit_soup_request_data_finalize (GObject *object)
+{
+ WebKitSoupRequestData *data = WEBKIT_SOUP_REQUEST_DATA (object);
+
+ g_free (data->priv->content_type);
+
+ G_OBJECT_CLASS (webkit_soup_request_data_parent_class)->finalize (object);
+}
+
+static gboolean
+webkit_soup_request_data_check_uri (WebKitSoupRequest *request,
+ SoupURI *uri,
+ GError **error)
+{
+ return uri->host == NULL;
+}
+
+static GInputStream *
+webkit_soup_request_data_send (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupRequestData *data = WEBKIT_SOUP_REQUEST_DATA (request);
+ SoupURI *uri = webkit_soup_request_get_uri (request);
+ GInputStream *memstream;
+ const char *comma, *semi, *start, *end;
+ gboolean base64 = FALSE;
+
+ gchar *uristr = soup_uri_to_string (uri, FALSE);
+ comma = strchr (uristr, ',');
+ if (comma && comma != uristr) {
+ /* Deal with MIME type / params */
+ semi = memchr (uristr, ';', comma - uristr);
+ end = semi ? semi : comma;
+
+ if (semi && !g_ascii_strncasecmp (semi, ";base64", MAX (comma - semi, strlen (";base64"))))
+ base64 = TRUE;
+
+ if (end != uristr)
+ if (base64)
+ data->priv->content_type = g_strndup (uristr, end - uristr);
+ else
+ data->priv->content_type =
+ webkit_soup_request_uri_decoded_copy (uristr, end - uristr);
+ }
+
+ memstream = g_memory_input_stream_new ();
+
+ start = comma ? comma + 1 : uristr;
+
+ if (*start) {
+ guchar *buf;
+
+ if (base64) {
+ int inlen, state = 0;
+ guint save = 0;
+
+ inlen = strlen (start);
+ buf = g_malloc (inlen * 3 / 4);
+ data->priv->content_length =
+ g_base64_decode_step (start, inlen, buf,
+ &state, &save);
+ if (state != 0) {
+ g_free (buf);
+ goto fail;
+ }
+ } else {
+ /* Cannot use g_uri_unescape_string nor
+ soup_uri_decode because we don't want to
+ fail for things like "%3E%%3C" -> ">%<" */
+ buf = (guchar *)webkit_soup_request_uri_decoded_copy (start, strlen (start));
+ if (!buf)
+ goto fail;
+ data->priv->content_length = strlen ((char *)buf);
+ }
+
+ g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (memstream),
+ buf, data->priv->content_length,
+ g_free);
+ }
+ g_free (uristr);
+
+ return memstream;
+
+ fail:
+ g_free (uristr);
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_BAD_URI,
+ _ ("Unable to decode URI: %s"), start);
+ g_object_unref (memstream);
+ return NULL;
+}
+
+static goffset
+webkit_soup_request_data_get_content_length (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestData *data = WEBKIT_SOUP_REQUEST_DATA (request);
+
+ return data->priv->content_length;
+}
+
+static const char *
+webkit_soup_request_data_get_content_type (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestData *data = WEBKIT_SOUP_REQUEST_DATA (request);
+
+ return data->priv->content_type;
+}
+
+static void
+webkit_soup_request_data_class_init (WebKitSoupRequestDataClass *request_data_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (request_data_class);
+ WebKitSoupRequestClass *request_class =
+ WEBKIT_SOUP_REQUEST_CLASS (request_data_class);
+
+ g_type_class_add_private (request_data_class, sizeof (WebKitSoupRequestDataPrivate));
+
+ object_class->finalize = webkit_soup_request_data_finalize;
+
+ request_class->check_uri = webkit_soup_request_data_check_uri;
+ request_class->send = webkit_soup_request_data_send;
+ request_class->get_content_length = webkit_soup_request_data_get_content_length;
+ request_class->get_content_type = webkit_soup_request_data_get_content_type;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_REQUEST_DATA_H
+#define WEBKIT_SOUP_REQUEST_DATA_H 1
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_REQUEST_DATA (webkit_soup_request_data_get_type ())
+#define WEBKIT_SOUP_REQUEST_DATA(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WEBKIT_TYPE_SOUP_REQUEST_DATA, WebKitSoupRequestData))
+#define WEBKIT_SOUP_REQUEST_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_REQUEST_DATA, WebKitSoupRequestDataClass))
+#define WEBKIT_IS_SOUP_REQUEST_DATA(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WEBKIT_TYPE_SOUP_REQUEST_DATA))
+#define WEBKIT_IS_SOUP_REQUEST_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_SOUP_REQUEST_DATA))
+#define WEBKIT_SOUP_REQUEST_DATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_REQUEST_DATA, WebKitSoupRequestDataClass))
+
+typedef struct _WebKitSoupRequestDataPrivate WebKitSoupRequestDataPrivate;
+
+typedef struct {
+ WebKitSoupRequest parent;
+
+ WebKitSoupRequestDataPrivate *priv;
+} WebKitSoupRequestData;
+
+typedef struct {
+ WebKitSoupRequestClass parent;
+} WebKitSoupRequestDataClass;
+
+GType webkit_soup_request_data_get_type (void);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_REQUEST_DATA_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-file.c: file: URI request object
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-request-file.h"
+#include "soup-directory-input-stream.h"
+#include "soup-requester.h"
+#include <glib/gi18n.h>
+
+G_DEFINE_TYPE (WebKitSoupRequestFile, webkit_soup_request_file, WEBKIT_TYPE_SOUP_REQUEST)
+
+struct _WebKitSoupRequestFilePrivate {
+ GFile *gfile;
+
+ char *mime_type;
+ goffset size;
+};
+
+GFile *
+webkit_soup_request_file_get_file (WebKitSoupRequestFile *file)
+{
+ return g_object_ref (file->priv->gfile);
+}
+
+static void
+webkit_soup_request_file_init (WebKitSoupRequestFile *file)
+{
+ file->priv = G_TYPE_INSTANCE_GET_PRIVATE (file, WEBKIT_TYPE_SOUP_REQUEST_FILE, WebKitSoupRequestFilePrivate);
+
+ file->priv->size = -1;
+}
+
+static void
+webkit_soup_request_file_finalize (GObject *object)
+{
+ WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (object);
+
+ if (file->priv->gfile)
+ g_object_unref (file->priv->gfile);
+ g_free (file->priv->mime_type);
+
+ G_OBJECT_CLASS (webkit_soup_request_file_parent_class)->finalize (object);
+}
+
+static gboolean
+webkit_soup_request_file_check_uri (WebKitSoupRequest *request,
+ SoupURI *uri,
+ GError **error)
+{
+ /* "file:/foo" is not valid */
+ if (!uri->host)
+ return FALSE;
+
+ /* but it must be "file:///..." or "file://localhost/..." */
+ if (uri->scheme == SOUP_URI_SCHEME_FILE &&
+ *uri->host &&
+ g_ascii_strcasecmp (uri->host, "localhost") != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+webkit_soup_request_file_ftp_main_loop_quit (GObject *object,
+ GAsyncResult *result,
+ gpointer loop)
+{
+ g_main_loop_quit (loop);
+}
+
+/* This is a somewhat hacky way to get FTP to almost work. The proper way to
+ * get FTP to _really_ work involves hacking GIO to have APIs to handle
+ * canoncial URLs.
+ */
+static GFile *
+webkit_soup_request_file_ensure_file_ftp (SoupURI *uri,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupURI *host;
+ char *s;
+ GFile *file, *result;
+ GMount *mount;
+
+ host = soup_uri_copy_host (uri);
+ s = soup_uri_to_string (host, FALSE);
+ file = g_file_new_for_uri (s);
+ soup_uri_free (host);
+ g_free (s);
+
+ mount = g_file_find_enclosing_mount (file, cancellable, error);
+ if (mount == NULL && g_file_supports_thread_contexts (file)) {
+ GMainContext *context = g_main_context_new ();
+ GMainLoop *loop = g_main_loop_new (context, FALSE);
+
+ g_clear_error (error);
+ g_main_context_push_thread_default (context);
+ g_file_mount_enclosing_volume (file,
+ G_MOUNT_MOUNT_NONE,
+ NULL, /* FIXME! */
+ cancellable,
+ webkit_soup_request_file_ftp_main_loop_quit,
+ loop);
+ g_main_loop_run (loop);
+ g_main_context_pop_thread_default (context);
+ g_main_loop_unref (loop);
+ g_main_context_unref (context);
+ mount = g_file_find_enclosing_mount (file, cancellable, error);
+ }
+ if (mount == NULL)
+ return NULL;
+ g_object_unref (file);
+
+ file = g_mount_get_default_location (mount);
+ g_object_unref (mount);
+
+ s = g_strdup (uri->path);
+ if (strchr (s, ';'))
+ *strchr (s, ';') = 0;
+
+ result = g_file_resolve_relative_path (file, s);
+ g_free (s);
+ g_object_unref (file);
+
+ return result;
+}
+
+static gboolean
+webkit_soup_request_file_ensure_file (WebKitSoupRequestFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupURI *uri;
+
+ if (file->priv->gfile)
+ return TRUE;
+
+ uri = webkit_soup_request_get_uri (WEBKIT_SOUP_REQUEST (file));
+ if (uri->scheme == SOUP_URI_SCHEME_FILE) {
+ /* We cannot use soup_uri_decode as it incorrectly
+ * returns NULL for incorrectly encoded URIs (that
+ * could be valid filenames). This will be hopefully
+ * shipped in libsoup 2.32.1 but we want to land this
+ * first. TODO: replace uri_decoded_copy by
+ * soup_uri_decode when the required libsoup version
+ * is bumped out to 2.32.1
+ */
+ gchar *uri_path = soup_uri_get_path (uri);
+ gchar *decoded_uri = webkit_soup_request_uri_decoded_copy (uri_path, strlen (uri_path));
+
+ if (decoded_uri) {
+ /* Do not use new_for_uri() as the decoded URI
+ * could not be a valid URI
+ */
+ file->priv->gfile = g_file_new_for_path (decoded_uri);
+ g_free (decoded_uri);
+ }
+
+ return TRUE;
+ } else if (uri->scheme == SOUP_URI_SCHEME_FTP) {
+ file->priv->gfile = webkit_soup_request_file_ensure_file_ftp (uri,
+ cancellable,
+ error);
+ return file->priv->gfile != NULL;
+ }
+
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_UNSUPPORTED_URI_SCHEME,
+ _ ("Unsupported URI scheme '%s'"), uri->scheme);
+ return FALSE;
+}
+
+static GInputStream *
+webkit_soup_request_file_send (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
+ GInputStream *stream;
+ GError *my_error = NULL;
+
+ if (!webkit_soup_request_file_ensure_file (file, cancellable, error))
+ return NULL;
+
+ stream = G_INPUT_STREAM (g_file_read (file->priv->gfile,
+ cancellable, &my_error));
+ if (stream == NULL) {
+ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) {
+ GFileEnumerator *enumerator;
+ g_clear_error (&my_error);
+ enumerator = g_file_enumerate_children (file->priv->gfile,
+ "*",
+ G_FILE_QUERY_INFO_NONE,
+ cancellable,
+ error);
+ if (enumerator) {
+ stream = webkit_soup_directory_input_stream_new (enumerator,
+ webkit_soup_request_get_uri (request));
+ g_object_unref (enumerator);
+ file->priv->mime_type = g_strdup ("text/html");
+ }
+ } else {
+ g_propagate_error (error, my_error);
+ }
+ } else {
+ GFileInfo *info = g_file_query_info (file->priv->gfile,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ 0, cancellable, NULL);
+ if (info) {
+ const char *content_type;
+ file->priv->size = g_file_info_get_size (info);
+ content_type = g_file_info_get_content_type (info);
+
+ if (content_type)
+ file->priv->mime_type = g_content_type_get_mime_type (content_type);
+ g_object_unref (info);
+ }
+ }
+
+ return stream;
+}
+
+static void
+webkit_soup_request_file_send_async_thread (GSimpleAsyncResult *res,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GInputStream *stream;
+ WebKitSoupRequest *request;
+ GError *error = NULL;
+
+ request = WEBKIT_SOUP_REQUEST (object);
+
+ stream = webkit_soup_request_file_send (request, cancellable, &error);
+
+ if (stream == NULL) {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ } else {
+ g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref);
+ }
+}
+
+static void
+webkit_soup_request_file_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (request), callback, user_data, webkit_soup_request_file_send_async);
+
+ g_simple_async_result_run_in_thread (res, webkit_soup_request_file_send_async_thread, G_PRIORITY_DEFAULT, cancellable);
+ g_object_unref (res);
+}
+
+static GInputStream *
+webkit_soup_request_file_send_finish (WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == webkit_soup_request_file_send_async);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+}
+
+static goffset
+webkit_soup_request_file_get_content_length (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
+
+ return file->priv->size;
+}
+
+static const char *
+webkit_soup_request_file_get_content_type (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
+
+ if (!file->priv->mime_type)
+ return "application/octet-stream";
+
+ return file->priv->mime_type;
+}
+
+static void
+webkit_soup_request_file_class_init (WebKitSoupRequestFileClass *request_file_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (request_file_class);
+ WebKitSoupRequestClass *request_class =
+ WEBKIT_SOUP_REQUEST_CLASS (request_file_class);
+
+ g_type_class_add_private (request_file_class, sizeof (WebKitSoupRequestFilePrivate));
+
+ object_class->finalize = webkit_soup_request_file_finalize;
+
+ request_class->check_uri = webkit_soup_request_file_check_uri;
+ request_class->send = webkit_soup_request_file_send;
+ request_class->send_async = webkit_soup_request_file_send_async;
+ request_class->send_finish = webkit_soup_request_file_send_finish;
+ request_class->get_content_length = webkit_soup_request_file_get_content_length;
+ request_class->get_content_type = webkit_soup_request_file_get_content_type;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_REQUEST_FILE_H
+#define WEBKIT_SOUP_REQUEST_FILE_H 1
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_REQUEST_FILE (webkit_soup_request_file_get_type ())
+#define WEBKIT_SOUP_REQUEST_FILE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WEBKIT_TYPE_SOUP_REQUEST_FILE, WebKitSoupRequestFile))
+#define WEBKIT_SOUP_REQUEST_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_REQUEST_FILE, WebKitSoupRequestFileClass))
+#define WEBKIT_IS_SOUP_REQUEST_FILE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WEBKIT_TYPE_SOUP_REQUEST_FILE))
+#define WEBKIT_IS_SOUP_REQUEST_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_SOUP_REQUEST_FILE))
+#define WEBKIT_SOUP_REQUEST_FILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_REQUEST_FILE, WebKitSoupRequestFileClass))
+
+typedef struct _WebKitSoupRequestFilePrivate WebKitSoupRequestFilePrivate;
+
+typedef struct {
+ WebKitSoupRequest parent;
+
+ WebKitSoupRequestFilePrivate *priv;
+} WebKitSoupRequestFile;
+
+typedef struct {
+ WebKitSoupRequestClass parent;
+} WebKitSoupRequestFileClass;
+
+GType webkit_soup_request_file_get_type (void);
+
+GFile *webkit_soup_request_file_get_file (WebKitSoupRequestFile *file);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_REQUEST_FILE_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-http.c: http: URI request object
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "soup-cache.h"
+#include "soup-cache-private.h"
+#include "soup-http-input-stream.h"
+#include "soup-request-http.h"
+
+G_DEFINE_TYPE (WebKitSoupRequestHTTP, webkit_soup_request_http, WEBKIT_TYPE_SOUP_REQUEST)
+
+struct _WebKitSoupRequestHTTPPrivate {
+ SoupMessage *msg;
+};
+
+/**
+ * webkit_soup_request_http_get_message:
+ * @http: a #WebKitSoupRequestHTTP object
+ *
+ * Gets a new reference to the #SoupMessage associated to this SoupRequest
+ *
+ * Returns: a new reference to the #SoupMessage
+ **/
+SoupMessage *
+webkit_soup_request_http_get_message (WebKitSoupRequestHTTP *http)
+{
+ g_return_val_if_fail (WEBKIT_IS_SOUP_REQUEST_HTTP (http), NULL);
+
+ return g_object_ref (http->priv->msg);
+}
+
+static void
+webkit_soup_request_http_init (WebKitSoupRequestHTTP *http)
+{
+ http->priv = G_TYPE_INSTANCE_GET_PRIVATE (http, WEBKIT_TYPE_SOUP_REQUEST_HTTP, WebKitSoupRequestHTTPPrivate);
+}
+
+static gboolean
+webkit_soup_request_http_check_uri (WebKitSoupRequest *request,
+ SoupURI *uri,
+ GError **error)
+{
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (request);
+
+ if (!SOUP_URI_VALID_FOR_HTTP (uri))
+ return FALSE;
+
+ http->priv->msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+ return TRUE;
+}
+
+static void
+webkit_soup_request_http_finalize (GObject *object)
+{
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (object);
+
+ if (http->priv->msg)
+ g_object_unref (http->priv->msg);
+
+ G_OBJECT_CLASS (webkit_soup_request_http_parent_class)->finalize (object);
+}
+
+static GInputStream *
+webkit_soup_request_http_send (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupHTTPInputStream *httpstream;
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (request);
+
+ httpstream = webkit_soup_http_input_stream_new (webkit_soup_request_get_session (request), http->priv->msg);
+ if (!webkit_soup_http_input_stream_send (httpstream, cancellable, error)) {
+ g_object_unref (httpstream);
+ return NULL;
+ }
+ return (GInputStream *)httpstream;
+}
+
+
+static void
+sent_async (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ WebKitSoupHTTPInputStream *httpstream = WEBKIT_SOUP_HTTP_INPUT_STREAM (source);
+ GSimpleAsyncResult *simple = user_data;
+ GError *error = NULL;
+
+ if (webkit_soup_http_input_stream_send_finish (httpstream, result, &error)) {
+ g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
+ } else {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ g_object_unref (httpstream);
+ }
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+
+typedef struct {
+ WebKitSoupRequestHTTP *req;
+ SoupMessage *original;
+ GCancellable *cancellable;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} ConditionalHelper;
+
+
+static void
+conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ ConditionalHelper *helper = (ConditionalHelper *)user_data;
+ GSimpleAsyncResult *simple;
+ WebKitSoupHTTPInputStream *httpstream;
+
+ simple = g_simple_async_result_new (G_OBJECT (helper->req),
+ helper->callback, helper->user_data,
+ conditional_get_ready_cb);
+
+ if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
+ WebKitSoupCache *cache = (WebKitSoupCache *)soup_session_get_feature (session, WEBKIT_TYPE_SOUP_CACHE);
+
+ httpstream = (WebKitSoupHTTPInputStream *)webkit_soup_cache_send_response (cache, msg);
+ if (httpstream) {
+ const gchar *content_type;
+
+ g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
+
+ soup_message_got_headers (helper->original);
+ content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
+ soup_message_content_sniffed (helper->original, content_type, NULL);
+
+ g_simple_async_result_complete (simple);
+
+ soup_message_finished (helper->original);
+
+ g_object_unref (simple);
+ } else {
+ /* Ask again for the resource, somehow the cache cannot locate it */
+ httpstream = webkit_soup_http_input_stream_new (session, helper->original);
+ webkit_soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
+ helper->cancellable, sent_async, simple);
+ }
+ } else {
+ /* It is in the cache but it was modified remotely */
+ httpstream = webkit_soup_http_input_stream_new (session, helper->original);
+ webkit_soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
+ helper->cancellable, sent_async, simple);
+ }
+
+ g_object_unref (helper->req);
+ g_object_unref (helper->original);
+ g_slice_free (ConditionalHelper, helper);
+}
+
+typedef struct {
+ WebKitSoupRequestHTTP *http;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} SendAsyncHelper;
+
+static void webkit_soup_request_http_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+static gboolean
+send_async_cb (gpointer data)
+{
+ GSimpleAsyncResult *simple;
+ WebKitSoupHTTPInputStream *httpstream;
+ SoupSession *session;
+ WebKitSoupCache *cache;
+ SendAsyncHelper *helper = (SendAsyncHelper *)data;
+
+ session = webkit_soup_request_get_session (WEBKIT_SOUP_REQUEST (helper->http));
+ cache = (WebKitSoupCache *)soup_session_get_feature (session, WEBKIT_TYPE_SOUP_CACHE);
+
+ httpstream = (WebKitSoupHTTPInputStream *)webkit_soup_cache_send_response (cache, SOUP_MESSAGE (helper->http->priv->msg));
+
+ if (httpstream) {
+ const gchar *content_type;
+
+ simple = g_simple_async_result_new (G_OBJECT (helper->http),
+ helper->callback, helper->user_data,
+ webkit_soup_request_http_send_async);
+ g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
+
+ /* Update message status */
+ soup_message_set_status (helper->http->priv->msg, SOUP_STATUS_OK);
+
+ /* Issue signals */
+ soup_message_got_headers (helper->http->priv->msg);
+ content_type = soup_message_headers_get_content_type (helper->http->priv->msg->response_headers, NULL);
+ soup_message_content_sniffed (helper->http->priv->msg, content_type, NULL);
+
+ g_simple_async_result_complete (simple);
+
+ soup_message_finished (helper->http->priv->msg);
+
+ g_object_unref (simple);
+ }
+
+ g_slice_free (SendAsyncHelper, helper);
+
+ return FALSE;
+}
+
+static void
+webkit_soup_request_http_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (request);
+ WebKitSoupHTTPInputStream *httpstream;
+ GSimpleAsyncResult *simple;
+ SoupSession *session;
+ WebKitSoupCache *cache;
+
+ session = webkit_soup_request_get_session (request);
+ cache = (WebKitSoupCache *)soup_session_get_feature (session, WEBKIT_TYPE_SOUP_CACHE);
+
+ if (cache) {
+ WebKitSoupCacheResponse response;
+
+ response = webkit_soup_cache_has_response (cache, http->priv->msg);
+ if (response == WEBKIT_SOUP_CACHE_RESPONSE_FRESH) {
+ /* Do return the stream asynchronously as in
+ the other cases. It's not enough to use
+ g_simple_async_result_complete_in_idle as
+ the signals must be also emitted
+ asynchronously */
+ SendAsyncHelper *helper = g_slice_new (SendAsyncHelper);
+ helper->http = http;
+ helper->callback = callback;
+ helper->user_data = user_data;
+ g_timeout_add (0, send_async_cb, helper);
+ return;
+ } else if (response == WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) {
+ SoupMessage *conditional_msg;
+ ConditionalHelper *helper;
+
+ conditional_msg = webkit_soup_cache_generate_conditional_request (cache, http->priv->msg);
+
+ helper = g_slice_new0 (ConditionalHelper);
+ helper->req = g_object_ref (http);
+ helper->original = g_object_ref (http->priv->msg);
+ helper->cancellable = cancellable;
+ helper->callback = callback;
+ helper->user_data = user_data;
+ soup_session_queue_message (session, conditional_msg,
+ conditional_get_ready_cb,
+ helper);
+ return;
+ }
+ }
+
+ simple = g_simple_async_result_new (G_OBJECT (http),
+ callback, user_data,
+ webkit_soup_request_http_send_async);
+ httpstream = webkit_soup_http_input_stream_new (webkit_soup_request_get_session (request),
+ http->priv->msg);
+ webkit_soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
+ cancellable, sent_async, simple);
+}
+
+static GInputStream *
+webkit_soup_request_http_send_finish (WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), webkit_soup_request_http_send_async) || g_simple_async_result_is_valid (result, G_OBJECT (request), conditional_get_ready_cb), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+ return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+}
+
+static goffset
+webkit_soup_request_http_get_content_length (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (request);
+
+ return soup_message_headers_get_content_length (http->priv->msg->response_headers);
+}
+
+static const char *
+webkit_soup_request_http_get_content_type (WebKitSoupRequest *request)
+{
+ WebKitSoupRequestHTTP *http = WEBKIT_SOUP_REQUEST_HTTP (request);
+
+ return soup_message_headers_get_content_type (http->priv->msg->response_headers, NULL);
+}
+
+static void
+webkit_soup_request_http_class_init (WebKitSoupRequestHTTPClass *request_http_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (request_http_class);
+ WebKitSoupRequestClass *request_class =
+ WEBKIT_SOUP_REQUEST_CLASS (request_http_class);
+
+ g_type_class_add_private (request_http_class, sizeof (WebKitSoupRequestHTTPPrivate));
+
+ object_class->finalize = webkit_soup_request_http_finalize;
+
+ request_class->check_uri = webkit_soup_request_http_check_uri;
+ request_class->send = webkit_soup_request_http_send;
+ request_class->send_async = webkit_soup_request_http_send_async;
+ request_class->send_finish = webkit_soup_request_http_send_finish;
+ request_class->get_content_length = webkit_soup_request_http_get_content_length;
+ request_class->get_content_type = webkit_soup_request_http_get_content_type;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_REQUEST_HTTP_H
+#define WEBKIT_SOUP_REQUEST_HTTP_H 1
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_REQUEST_HTTP (webkit_soup_request_http_get_type ())
+#define WEBKIT_SOUP_REQUEST_HTTP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), WEBKIT_TYPE_SOUP_REQUEST_HTTP, WebKitSoupRequestHTTP))
+#define WEBKIT_SOUP_REQUEST_HTTP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_REQUEST_HTTP, WebKitSoupRequestHTTPClass))
+#define WEBKIT_IS_SOUP_REQUEST_HTTP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), WEBKIT_TYPE_SOUP_REQUEST_HTTP))
+#define WEBKIT_IS_SOUP_REQUEST_HTTP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_SOUP_REQUEST_HTTP))
+#define WEBKIT_SOUP_REQUEST_HTTP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_REQUEST_HTTP, WebKitSoupRequestHTTPClass))
+
+typedef struct _WebKitSoupRequestHTTPPrivate WebKitSoupRequestHTTPPrivate;
+
+typedef struct {
+ WebKitSoupRequest parent;
+
+ WebKitSoupRequestHTTPPrivate *priv;
+} WebKitSoupRequestHTTP;
+
+typedef struct {
+ WebKitSoupRequestClass parent;
+} WebKitSoupRequestHTTPClass;
+
+GType webkit_soup_request_http_get_type (void);
+
+SoupMessage *webkit_soup_request_http_get_message (WebKitSoupRequestHTTP *http);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_REQUEST_HTTP_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request.c: Protocol-independent streaming request interface
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010, Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "soup-request.h"
+#include "soup-requester.h"
+
+/**
+ * SECTION:soup-request
+ * @short_description: Protocol-independent streaming request interface
+ *
+ * FIXME
+ **/
+
+/**
+ * WebKitSoupRequest:
+ *
+ * FIXME
+ *
+ * Since: 2.30
+ **/
+
+static void webkit_soup_request_initable_interface_init (GInitableIface *initable_interface);
+
+G_DEFINE_TYPE_WITH_CODE (WebKitSoupRequest, webkit_soup_request, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ webkit_soup_request_initable_interface_init))
+
+enum {
+ PROP_0,
+ PROP_URI,
+ PROP_SESSION
+};
+
+struct _WebKitSoupRequestPrivate {
+ SoupURI *uri;
+ SoupSession *session;
+};
+
+static void
+webkit_soup_request_init (WebKitSoupRequest *request)
+{
+ request->priv = G_TYPE_INSTANCE_GET_PRIVATE (request, WEBKIT_TYPE_SOUP_REQUEST, WebKitSoupRequestPrivate);
+}
+
+static void
+webkit_soup_request_finalize (GObject *object)
+{
+ WebKitSoupRequest *request = WEBKIT_SOUP_REQUEST (object);
+
+ if (request->priv->uri)
+ soup_uri_free (request->priv->uri);
+ if (request->priv->session)
+ g_object_unref (request->priv->session);
+
+ G_OBJECT_CLASS (webkit_soup_request_parent_class)->finalize (object);
+}
+
+static void
+webkit_soup_request_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WebKitSoupRequest *request = WEBKIT_SOUP_REQUEST (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ if (request->priv->uri)
+ soup_uri_free (request->priv->uri);
+ request->priv->uri = g_value_dup_boxed (value);
+ break;
+ case PROP_SESSION:
+ if (request->priv->session)
+ g_object_unref (request->priv->session);
+ request->priv->session = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+webkit_soup_request_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WebKitSoupRequest *request = WEBKIT_SOUP_REQUEST (object);
+
+ switch (prop_id) {
+ case PROP_URI:
+ g_value_set_boxed (value, request->priv->uri);
+ break;
+ case PROP_SESSION:
+ g_value_set_object (value, request->priv->session);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+webkit_soup_request_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WebKitSoupRequest *request = WEBKIT_SOUP_REQUEST (initable);
+ gboolean ok;
+
+ if (!request->priv->uri) {
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_BAD_URI,
+ _ ("No URI provided"));
+ return FALSE;
+ }
+
+ ok = WEBKIT_SOUP_REQUEST_GET_CLASS (initable)->
+ check_uri (request, request->priv->uri, error);
+
+ if (!ok && error) {
+ char *uri_string = soup_uri_to_string (request->priv->uri, FALSE);
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_BAD_URI,
+ _ ("Invalid '%s' URI: %s"),
+ request->priv->uri->scheme,
+ uri_string);
+ g_free (uri_string);
+ }
+
+ return ok;
+}
+
+static gboolean
+webkit_soup_request_default_check_uri (WebKitSoupRequest *request,
+ SoupURI *uri,
+ GError **error)
+{
+ return TRUE;
+}
+
+/* Default implementation: assume the sync implementation doesn't block */
+static void
+webkit_soup_request_default_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new (G_OBJECT (request),
+ callback, user_data,
+ webkit_soup_request_default_send_async);
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static GInputStream *
+webkit_soup_request_default_send_finish (WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), webkit_soup_request_default_send_async), NULL);
+
+ return webkit_soup_request_send (request, NULL, error);
+}
+
+GInputStream *
+webkit_soup_request_send (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return WEBKIT_SOUP_REQUEST_GET_CLASS (request)->
+ send (request, cancellable, error);
+}
+
+void
+webkit_soup_request_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WEBKIT_SOUP_REQUEST_GET_CLASS (request)->
+ send_async (request, cancellable, callback, user_data);
+}
+
+GInputStream *
+webkit_soup_request_send_finish (WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error)
+{
+ return WEBKIT_SOUP_REQUEST_GET_CLASS (request)->
+ send_finish (request, result, error);
+}
+
+static void
+webkit_soup_request_class_init (WebKitSoupRequestClass *request_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (request_class);
+
+ g_type_class_add_private (request_class, sizeof (WebKitSoupRequestPrivate));
+
+ request_class->check_uri = webkit_soup_request_default_check_uri;
+ request_class->send_async = webkit_soup_request_default_send_async;
+ request_class->send_finish = webkit_soup_request_default_send_finish;
+
+ object_class->finalize = webkit_soup_request_finalize;
+ object_class->set_property = webkit_soup_request_set_property;
+ object_class->get_property = webkit_soup_request_get_property;
+
+ g_object_class_install_property (
+ object_class, PROP_URI,
+ g_param_spec_boxed (WEBKIT_SOUP_REQUEST_URI,
+ "URI",
+ "The request URI",
+ SOUP_TYPE_URI,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (
+ object_class, PROP_SESSION,
+ g_param_spec_object (WEBKIT_SOUP_REQUEST_SESSION,
+ "Session",
+ "The request's session",
+ SOUP_TYPE_SESSION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+webkit_soup_request_initable_interface_init (GInitableIface *initable_interface)
+{
+ initable_interface->init = webkit_soup_request_initable_init;
+}
+
+SoupURI *
+webkit_soup_request_get_uri (WebKitSoupRequest *request)
+{
+ return request->priv->uri;
+}
+
+SoupSession *
+webkit_soup_request_get_session (WebKitSoupRequest *request)
+{
+ return request->priv->session;
+}
+
+goffset
+webkit_soup_request_get_content_length (WebKitSoupRequest *request)
+{
+ return WEBKIT_SOUP_REQUEST_GET_CLASS (request)->get_content_length (request);
+}
+
+const char *
+webkit_soup_request_get_content_type (WebKitSoupRequest *request)
+{
+ return WEBKIT_SOUP_REQUEST_GET_CLASS (request)->get_content_type (request);
+}
+
+#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
+#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
+
+/* Copy&pasted from libsoup's soup-uri.c after applying the patch in
+ * https://bugzilla.gnome.org/show_bug.cgi?id=630540. We need this
+ * instead of soup_uri_decode() as it incorrectly returns NULL for
+ * incorrectly encoded URLs. TODO: remove this when required libsoup
+ * version is bumped out to 2.32.1
+ */
+gchar *
+webkit_soup_request_uri_decoded_copy (const char *part, int length)
+{
+ unsigned char *s, *d;
+ char *decoded = g_strndup (part, length);
+
+ s = d = (unsigned char *)decoded;
+ do {
+ if (*s == '%') {
+ if (!g_ascii_isxdigit (s[1]) ||
+ !g_ascii_isxdigit (s[2])) {
+ *d++ = *s;
+ continue;
+ }
+ *d++ = HEXCHAR (s);
+ s += 2;
+ } else
+ *d++ = *s;
+ } while (*s++);
+
+ return decoded;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_REQUEST_H
+#define WEBKIT_SOUP_REQUEST_H 1
+
+#include <libsoup/soup.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_REQUEST (webkit_soup_request_get_type ())
+#define WEBKIT_SOUP_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_SOUP_REQUEST, WebKitSoupRequest))
+#define WEBKIT_SOUP_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_REQUEST, WebKitSoupRequestClass))
+#define WEBKIT_IS_SOUP_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_SOUP_REQUEST))
+#define WEBKIT_IS_SOUP_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_SOUP_REQUEST))
+#define WEBKIT_SOUP_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_REQUEST, WebKitSoupRequestClass))
+
+typedef struct _WebKitSoupRequest WebKitSoupRequest;
+typedef struct _WebKitSoupRequestPrivate WebKitSoupRequestPrivate;
+typedef struct _WebKitSoupRequestClass WebKitSoupRequestClass;
+
+struct _WebKitSoupRequest {
+ GObject parent;
+
+ WebKitSoupRequestPrivate *priv;
+};
+
+struct _WebKitSoupRequestClass {
+ GObjectClass parent;
+
+ gboolean (*check_uri)(WebKitSoupRequest *req_base,
+ SoupURI *uri,
+ GError **error);
+
+ GInputStream * (*send)(WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error);
+ void (*send_async)(WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GInputStream * (*send_finish)(WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error);
+
+ goffset (*get_content_length)(WebKitSoupRequest *request);
+ const char * (*get_content_type)(WebKitSoupRequest *request);
+};
+
+GType webkit_soup_request_get_type (void);
+
+#define WEBKIT_SOUP_REQUEST_URI "uri"
+#define WEBKIT_SOUP_REQUEST_SESSION "session"
+
+GInputStream *webkit_soup_request_send (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GError **error);
+void webkit_soup_request_send_async (WebKitSoupRequest *request,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GInputStream *webkit_soup_request_send_finish (WebKitSoupRequest *request,
+ GAsyncResult *result,
+ GError **error);
+
+SoupURI *webkit_soup_request_get_uri (WebKitSoupRequest *request);
+SoupSession *webkit_soup_request_get_session (WebKitSoupRequest *request);
+
+goffset webkit_soup_request_get_content_length (WebKitSoupRequest *request);
+const char *webkit_soup_request_get_content_type (WebKitSoupRequest *request);
+
+/* Used by WebKitSoupRequestFile and WebKitSoupRequestData. Ideally
+ * should be located in some util file but I'll place it here as it
+ * will be removed with libsoup 2.32.1 that will ship fixed versions
+ * of soup_uri_decode/normalize
+ */
+gchar *webkit_soup_request_uri_decoded_copy (const char *part, int length);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_REQUEST_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-requester.c:
+ *
+ * Copyright (C) 2010, Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "soup-requester.h"
+
+#include "soup-request-data.h"
+#include "soup-request-file.h"
+#include "soup-request-http.h"
+#include <glib/gi18n.h>
+#include <libsoup/soup.h>
+
+struct _WebKitSoupRequesterPrivate {
+ GHashTable *request_types;
+};
+
+#define WEBKIT_SOUP_REQUESTER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), WEBKIT_TYPE_SOUP_REQUESTER, WebKitSoupRequesterPrivate))
+
+G_DEFINE_TYPE (WebKitSoupRequester, webkit_soup_requester, G_TYPE_OBJECT)
+
+static void webkit_soup_requester_init (WebKitSoupRequester *requester)
+{
+ requester->priv = WEBKIT_SOUP_REQUESTER_GET_PRIVATE (requester);
+
+ requester->priv->request_types = 0;
+}
+
+static void finalize (GObject *object)
+{
+ WebKitSoupRequester *requester = WEBKIT_SOUP_REQUESTER (object);
+
+ if (requester->priv->request_types)
+ g_hash_table_destroy (requester->priv->request_types);
+
+ G_OBJECT_CLASS (webkit_soup_requester_parent_class)->finalize (object);
+}
+
+static void webkit_soup_requester_class_init (WebKitSoupRequesterClass *requester_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (requester_class);
+
+ g_type_class_add_private (requester_class, sizeof (WebKitSoupRequesterPrivate));
+
+ /* virtual method override */
+ object_class->finalize = finalize;
+}
+
+static void init_request_types (WebKitSoupRequesterPrivate *priv)
+{
+ if (priv->request_types)
+ return;
+
+ priv->request_types = g_hash_table_new_full (soup_str_case_hash,
+ soup_str_case_equal,
+ g_free, 0);
+ g_hash_table_insert (priv->request_types, g_strdup ("file"),
+ GSIZE_TO_POINTER (WEBKIT_TYPE_SOUP_REQUEST_FILE));
+ g_hash_table_insert (priv->request_types, g_strdup ("data"),
+ GSIZE_TO_POINTER (WEBKIT_TYPE_SOUP_REQUEST_DATA));
+ g_hash_table_insert (priv->request_types, g_strdup ("http"),
+ GSIZE_TO_POINTER (WEBKIT_TYPE_SOUP_REQUEST_HTTP));
+ g_hash_table_insert (priv->request_types, g_strdup ("https"),
+ GSIZE_TO_POINTER (WEBKIT_TYPE_SOUP_REQUEST_HTTP));
+ g_hash_table_insert (priv->request_types, g_strdup ("ftp"),
+ GSIZE_TO_POINTER (WEBKIT_TYPE_SOUP_REQUEST_FILE));
+}
+
+WebKitSoupRequester *webkit_soup_requester_new (void)
+{
+ return (WebKitSoupRequester *)g_object_new (WEBKIT_TYPE_SOUP_REQUESTER, 0);
+}
+
+WebKitSoupRequest *webkit_soup_requester_request (WebKitSoupRequester *requester, const char *uriString, SoupSession *session, GError **error)
+{
+ SoupURI *uri = NULL;
+ WebKitSoupRequest *req;
+
+ uri = soup_uri_new (uriString);
+ if (!uri) {
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_BAD_URI,
+ _ ("Could not parse URI '%s'"), uriString);
+ return 0;
+ }
+
+ req = webkit_soup_requester_request_uri (requester, uri, session, error);
+ soup_uri_free (uri);
+ return req;
+}
+
+WebKitSoupRequest *webkit_soup_requester_request_uri (WebKitSoupRequester *requester, SoupURI *uri, SoupSession *session, GError **error)
+{
+ GType requestType;
+
+ g_return_val_if_fail (WEBKIT_IS_SOUP_REQUESTER (requester), 0);
+
+ init_request_types (requester->priv);
+ requestType = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (requester->priv->request_types, uri->scheme));
+ if (!requestType) {
+ g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_UNSUPPORTED_URI_SCHEME,
+ _ ("Unsupported URI scheme '%s'"), uri->scheme);
+ return 0;
+ }
+
+ if (g_type_is_a (requestType, G_TYPE_INITABLE)) {
+ return (WebKitSoupRequest *)g_initable_new (requestType, 0, error,
+ "uri", uri,
+ "session", session,
+ 0);
+ } else {
+ return (WebKitSoupRequest *)g_object_new (requestType,
+ "uri", uri,
+ "session", session,
+ 0);
+ }
+}
+
+/* RFC 2396, 3.1 */
+static gboolean
+soup_scheme_is_valid (const char *scheme)
+{
+ if (scheme == NULL ||
+ !g_ascii_isalpha (*scheme))
+ return FALSE;
+
+ scheme++;
+ while (*scheme) {
+ if (!g_ascii_isalpha (*scheme) &&
+ !g_ascii_isdigit (*scheme) &&
+ *scheme != '+' &&
+ *scheme != '-' &&
+ *scheme != '.')
+ return FALSE;
+ scheme++;
+ }
+ return TRUE;
+}
+
+void
+webkit_soup_requester_add_protocol (WebKitSoupRequester *requester,
+ const char *scheme,
+ GType request_type)
+{
+ g_return_if_fail (WEBKIT_IS_SOUP_REQUESTER (requester));
+ g_return_if_fail (soup_scheme_is_valid (scheme));
+
+ init_request_types (requester->priv);
+ g_hash_table_insert (requester->priv->request_types, g_strdup (scheme),
+ GSIZE_TO_POINTER (request_type));
+}
+
+void
+webkit_soup_requester_remove_protocol (WebKitSoupRequester *requester,
+ const char *scheme)
+{
+ g_return_if_fail (WEBKIT_IS_SOUP_REQUESTER (requester));
+ g_return_if_fail (soup_scheme_is_valid (scheme));
+
+ init_request_types (requester->priv);
+ g_hash_table_remove (requester->priv->request_types, scheme);
+}
+
+GQuark
+webkit_soup_error_quark (void)
+{
+ static GQuark error;
+ if (!error)
+ error = g_quark_from_static_string ("webkit_soup_error_quark");
+ return error;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_REQUESTER_H
+#define WEBKIT_SOUP_REQUESTER_H 1
+
+#include "soup-request.h"
+#include <libsoup/soup.h>
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_REQUESTER (webkit_soup_requester_get_type ())
+#define WEBKIT_SOUP_REQUESTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_SOUP_REQUESTER, WebKitSoupRequester))
+#define WEBKIT_SOUP_REQUESTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_REQUESTER, WebKitSoupRequesterClass))
+#define WEBKIT_IS_SOUP_REQUESTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_SOUP_REQUESTER))
+#define WEBKIT_IS_SOUP_REQUESTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), WEBKIT_TYPE_SOUP_REQUESTER))
+#define WEBKIT_SOUP_REQUESTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_REQUESTER, WebKitSoupRequesterClass))
+
+#define WEBKIT_SOUP_ERROR webkit_soup_error_quark ()
+
+typedef enum {
+ WEBKIT_SOUP_ERROR_BAD_URI,
+ WEBKIT_SOUP_ERROR_UNSUPPORTED_URI_SCHEME
+} WebKitSoupError;
+
+typedef struct _WebKitSoupRequester WebKitSoupRequester;
+typedef struct _WebKitSoupRequesterPrivate WebKitSoupRequesterPrivate;
+
+struct _WebKitSoupRequester {
+ GObject parent;
+
+ WebKitSoupRequesterPrivate *priv;
+};
+
+typedef struct {
+ GObjectClass parent_class;
+} WebKitSoupRequesterClass;
+
+GType webkit_soup_requester_get_type (void);
+
+WebKitSoupRequester *webkit_soup_requester_new (void);
+
+GQuark webkit_soup_error_quark (void);
+
+WebKitSoupRequest *webkit_soup_requester_request (WebKitSoupRequester *requester,
+ const char *uriString,
+ SoupSession *session,
+ GError **error);
+
+WebKitSoupRequest *webkit_soup_requester_request_uri (WebKitSoupRequester *requester,
+ SoupURI *uri,
+ SoupSession *session,
+ GError **error);
+
+void webkit_soup_requester_add_protocol (WebKitSoupRequester *requester,
+ const char *scheme,
+ GType request_type);
+
+void webkit_soup_requester_remove_protocol (WebKitSoupRequester *requester,
+ const char *scheme);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_REQUESTER_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cache-private.h:
+ *
+ * Copyright (C) 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_CACHE_PRIVATE_H
+#define WEBKIT_SOUP_CACHE_PRIVATE_H 1
+
+#include "soup-cache.h"
+#include <libsoup/soup-message.h>
+
+G_BEGIN_DECLS
+
+WebKitSoupCacheResponse webkit_soup_cache_has_response (WebKitSoupCache *cache,
+ SoupMessage *msg);
+GInputStream *webkit_soup_cache_send_response (WebKitSoupCache *cache,
+ SoupMessage *msg);
+WebKitSoupCacheability webkit_soup_cache_get_cacheability (WebKitSoupCache *cache,
+ SoupMessage *msg);
+SoupMessage *webkit_soup_cache_generate_conditional_request (WebKitSoupCache *cache,
+ SoupMessage *original);
+
+G_END_DECLS
+
+#endif /* WEBKIT_SOUP_CACHE_PRIVATE_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cache.c
+ *
+ * Copyright (C) 2009, 2010 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* TODO:
+ * - Need to hook the feature in the sync SoupSession.
+ * - Need more tests.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-cache.h"
+#include "soup-cache-private.h"
+#include <libsoup/soup.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+
+static SoupSessionFeatureInterface *webkit_soup_cache_default_feature_interface;
+static void webkit_soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+
+#define DEFAULT_MAX_SIZE 50 * 1024 * 1024
+#define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
+ of the cache that can be
+ filled by a single entry */
+
+typedef struct _WebKitSoupCacheEntry {
+ char *key;
+ char *filename;
+ guint freshness_lifetime;
+ gboolean must_revalidate;
+ GString *data;
+ gsize pos;
+ gsize length;
+ time_t corrected_initial_age;
+ time_t response_time;
+ gboolean writing;
+ gboolean dirty;
+ gboolean got_body;
+ gboolean being_validated;
+ SoupMessageHeaders *headers;
+ GOutputStream *stream;
+ GError *error;
+ guint hits;
+ GCancellable *cancellable;
+} WebKitSoupCacheEntry;
+
+struct _WebKitSoupCachePrivate {
+ char *cache_dir;
+ GHashTable *cache;
+ guint n_pending;
+ SoupSession *session;
+ WebKitSoupCacheType cache_type;
+ guint size;
+ guint max_size;
+ guint max_entry_data_size; /* Computed value. Here for performance reasons */
+ GList *lru_start;
+};
+
+typedef struct {
+ WebKitSoupCache *cache;
+ WebKitSoupCacheEntry *entry;
+ SoupMessage *msg;
+ gulong got_chunk_handler;
+ gulong got_body_handler;
+ gulong restarted_handler;
+} WebKitSoupCacheWritingFixture;
+
+enum {
+ PROP_0,
+ PROP_CACHE_DIR,
+ PROP_CACHE_TYPE
+};
+
+#define WEBKIT_SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), WEBKIT_TYPE_SOUP_CACHE, WebKitSoupCachePrivate))
+
+G_DEFINE_TYPE_WITH_CODE (WebKitSoupCache, webkit_soup_cache, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+ webkit_soup_cache_session_feature_init))
+
+static gboolean webkit_soup_cache_entry_remove (WebKitSoupCache *cache, WebKitSoupCacheEntry *entry);
+static void make_room_for_new_entry (WebKitSoupCache *cache, guint length_to_add);
+static gboolean cache_accepts_entries_of_size (WebKitSoupCache *cache, guint length_to_add);
+
+static WebKitSoupCacheability
+get_cacheability (WebKitSoupCache *cache, SoupMessage *msg)
+{
+ WebKitSoupCacheability cacheability;
+ const char *cache_control;
+
+ /* 1. The request method must be cacheable */
+ if (msg->method == SOUP_METHOD_GET)
+ cacheability = WEBKIT_SOUP_CACHE_CACHEABLE;
+ else if (msg->method == SOUP_METHOD_HEAD ||
+ msg->method == SOUP_METHOD_TRACE ||
+ msg->method == SOUP_METHOD_CONNECT)
+ return WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ else
+ return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
+
+ cache_control = soup_message_headers_get (msg->response_headers, "Cache-Control");
+ if (cache_control) {
+ GHashTable *hash;
+ WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
+
+ hash = soup_header_parse_param_list (cache_control);
+
+ /* Shared caches MUST NOT store private resources */
+ if (priv->cache_type == WEBKIT_SOUP_CACHE_SHARED) {
+ if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
+ soup_header_free_param_list (hash);
+ return WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ }
+ }
+
+ /* 2. The 'no-store' cache directive does not appear in the
+ * headers
+ */
+ if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
+ soup_header_free_param_list (hash);
+ return WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ }
+
+ /* This does not appear in section 2.1, but I think it makes
+ * sense to check it too?
+ */
+ if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
+ soup_header_free_param_list (hash);
+ return WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ }
+ }
+
+ switch (msg->status_code) {
+ case SOUP_STATUS_PARTIAL_CONTENT:
+ /* We don't cache partial responses, but they only
+ * invalidate cached full responses if the headers
+ * don't match.
+ */
+ cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ break;
+
+ case SOUP_STATUS_NOT_MODIFIED:
+ /* A 304 response validates an existing cache entry */
+ cacheability = WEBKIT_SOUP_CACHE_VALIDATES;
+ break;
+
+ case SOUP_STATUS_MULTIPLE_CHOICES:
+ case SOUP_STATUS_MOVED_PERMANENTLY:
+ case SOUP_STATUS_GONE:
+ /* FIXME: cacheable unless indicated otherwise */
+ cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ break;
+
+ case SOUP_STATUS_FOUND:
+ case SOUP_STATUS_TEMPORARY_REDIRECT:
+ /* FIXME: cacheable if explicitly indicated */
+ cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
+ break;
+
+ case SOUP_STATUS_SEE_OTHER:
+ case SOUP_STATUS_FORBIDDEN:
+ case SOUP_STATUS_NOT_FOUND:
+ case SOUP_STATUS_METHOD_NOT_ALLOWED:
+ return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
+
+ default:
+ /* Any 5xx status or any 4xx status not handled above
+ * is uncacheable but doesn't break the cache.
+ */
+ if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
+ msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
+ msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
+ return WEBKIT_SOUP_CACHE_UNCACHEABLE;
+
+ /* An unrecognized 2xx, 3xx, or 4xx response breaks
+ * the cache.
+ */
+ if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
+ msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
+ (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
+ msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
+ return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
+ break;
+ }
+
+ return cacheability;
+}
+
+static void
+webkit_soup_cache_entry_free (WebKitSoupCacheEntry *entry, gboolean purge)
+{
+ if (purge) {
+ GFile *file = g_file_new_for_path (entry->filename);
+ g_file_delete (file, NULL, NULL);
+ g_object_unref (file);
+ }
+
+ g_free (entry->filename);
+ entry->filename = NULL;
+ g_free (entry->key);
+ entry->key = NULL;
+
+ if (entry->headers) {
+ soup_message_headers_free (entry->headers);
+ entry->headers = NULL;
+ }
+
+ if (entry->data) {
+ g_string_free (entry->data, TRUE);
+ entry->data = NULL;
+ }
+ if (entry->error) {
+ g_error_free (entry->error);
+ entry->error = NULL;
+ }
+ if (entry->cancellable) {
+ g_object_unref (entry->cancellable);
+ entry->cancellable = NULL;
+ }
+
+ g_slice_free (WebKitSoupCacheEntry, entry);
+}
+
+static void
+copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
+{
+ soup_message_headers_append (headers, name, value);
+}
+
+static void
+update_headers (const char *name, const char *value, SoupMessageHeaders *headers)
+{
+ if (soup_message_headers_get (headers, name))
+ soup_message_headers_replace (headers, name, value);
+ else
+ soup_message_headers_append (headers, name, value);
+}
+
+static guint
+webkit_soup_cache_entry_get_current_age (WebKitSoupCacheEntry *entry)
+{
+ time_t now = time (NULL);
+ time_t resident_time;
+
+ resident_time = now - entry->response_time;
+ return entry->corrected_initial_age + resident_time;
+}
+
+static gboolean
+webkit_soup_cache_entry_is_fresh_enough (WebKitSoupCacheEntry *entry, int min_fresh)
+{
+ unsigned limit = (min_fresh == -1) ? webkit_soup_cache_entry_get_current_age (entry) : min_fresh;
+ return entry->freshness_lifetime > limit;
+}
+
+static char *
+soup_message_get_cache_key (SoupMessage *msg)
+{
+ SoupURI *uri = soup_message_get_uri (msg);
+ return soup_uri_to_string (uri, FALSE);
+}
+
+static void
+webkit_soup_cache_entry_set_freshness (WebKitSoupCacheEntry *entry, SoupMessage *msg, WebKitSoupCache *cache)
+{
+ const char *cache_control;
+ const char *expires, *date, *last_modified;
+ GHashTable *hash;
+
+ hash = NULL;
+
+ cache_control = soup_message_headers_get (entry->headers, "Cache-Control");
+ if (cache_control) {
+ const char *max_age, *s_maxage;
+ gint64 freshness_lifetime = 0;
+ WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
+
+ hash = soup_header_parse_param_list (cache_control);
+
+ /* Should we re-validate the entry when it goes stale */
+ entry->must_revalidate = (gboolean)g_hash_table_lookup (hash, "must-revalidate");
+
+ /* Section 2.3.1 */
+ if (priv->cache_type == WEBKIT_SOUP_CACHE_SHARED) {
+ s_maxage = g_hash_table_lookup (hash, "s-maxage");
+ if (s_maxage) {
+ freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
+ if (freshness_lifetime) {
+ /* Implies proxy-revalidate. TODO: is it true? */
+ entry->must_revalidate = TRUE;
+ soup_header_free_param_list (hash);
+ return;
+ }
+ }
+ }
+
+ /* If 'max-age' cache directive is present, use that */
+ max_age = g_hash_table_lookup (hash, "max-age");
+ if (max_age)
+ freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
+
+ if (freshness_lifetime) {
+ entry->freshness_lifetime = (guint)MIN (freshness_lifetime, G_MAXUINT32);
+ soup_header_free_param_list (hash);
+ return;
+ }
+ }
+
+ if (hash != NULL)
+ soup_header_free_param_list (hash);
+
+ /* If the 'Expires' response header is present, use its value
+ * minus the value of the 'Date' response header
+ */
+ expires = soup_message_headers_get (entry->headers, "Expires");
+ date = soup_message_headers_get (entry->headers, "Date");
+ if (expires && date) {
+ SoupDate *expires_d, *date_d;
+ time_t expires_t, date_t;
+
+ expires_d = soup_date_new_from_string (expires);
+ if (expires_d) {
+ date_d = soup_date_new_from_string (date);
+
+ expires_t = soup_date_to_time_t (expires_d);
+ date_t = soup_date_to_time_t (date_d);
+
+ soup_date_free (expires_d);
+ soup_date_free (date_d);
+
+ if (expires_t && date_t) {
+ entry->freshness_lifetime = (guint)MAX (expires_t - date_t, 0);
+ return;
+ }
+ } else {
+ /* If Expires is not a valid date we should
+ treat it as already expired, see section
+ 3.3 */
+ entry->freshness_lifetime = 0;
+ return;
+ }
+ }
+
+ /* Otherwise an heuristic may be used */
+
+ /* Heuristics MUST NOT be used with these status codes
+ (section 2.3.1.1) */
+ if (msg->status_code != SOUP_STATUS_OK &&
+ msg->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
+ msg->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
+ msg->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
+ msg->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
+ msg->status_code != SOUP_STATUS_GONE)
+ goto expire;
+
+ /* TODO: attach warning 113 if response's current_age is more
+ than 24h (section 2.3.1.1) when using heuristics */
+
+ /* Last-Modified based heuristic */
+ last_modified = soup_message_headers_get (entry->headers, "Last-Modified");
+ if (last_modified) {
+ SoupDate *soup_date;
+ time_t now, last_modified_t;
+
+ soup_date = soup_date_new_from_string (last_modified);
+ last_modified_t = soup_date_to_time_t (soup_date);
+ now = time (NULL);
+
+#define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
+
+ entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
+ soup_date_free (soup_date);
+ }
+
+ return;
+
+ expire:
+ /* If all else fails, make the entry expire immediately */
+ entry->freshness_lifetime = 0;
+}
+
+static WebKitSoupCacheEntry *
+webkit_soup_cache_entry_new (WebKitSoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
+{
+ WebKitSoupCacheEntry *entry;
+ SoupMessageHeaders *headers;
+ const char *date;
+ char *md5;
+
+ entry = g_slice_new0 (WebKitSoupCacheEntry);
+ entry->dirty = FALSE;
+ entry->writing = FALSE;
+ entry->got_body = FALSE;
+ entry->being_validated = FALSE;
+ entry->data = g_string_new (NULL);
+ entry->pos = 0;
+ entry->error = NULL;
+
+ /* key & filename */
+ entry->key = soup_message_get_cache_key (msg);
+ md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, entry->key, -1);
+ entry->filename = g_build_filename (cache->priv->cache_dir, md5, NULL);
+ g_free (md5);
+
+ /* Headers */
+ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+ soup_message_headers_foreach (msg->response_headers,
+ (SoupMessageHeadersForeachFunc)copy_headers,
+ headers);
+ entry->headers = headers;
+
+ /* LRU list */
+ entry->hits = 0;
+
+ /* Section 2.3.1, Freshness Lifetime */
+ webkit_soup_cache_entry_set_freshness (entry, msg, cache);
+
+ /* Section 2.3.2, Calculating Age */
+ date = soup_message_headers_get (entry->headers, "Date");
+
+ if (date) {
+ SoupDate *soup_date;
+ const char *age;
+ time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
+
+ soup_date = soup_date_new_from_string (date);
+ date_value = soup_date_to_time_t (soup_date);
+ soup_date_free (soup_date);
+
+ age = soup_message_headers_get (entry->headers, "Age");
+ if (age)
+ age_value = g_ascii_strtoll (age, NULL, 10);
+
+ entry->response_time = response_time;
+ apparent_age = MAX (0, entry->response_time - date_value);
+ corrected_received_age = MAX (apparent_age, age_value);
+ response_delay = entry->response_time - request_time;
+ entry->corrected_initial_age = corrected_received_age + response_delay;
+ } else {
+ /* Is this correct ? */
+ entry->corrected_initial_age = time (NULL);
+ }
+
+ return entry;
+}
+
+static void
+webkit_soup_cache_writing_fixture_free (WebKitSoupCacheWritingFixture *fixture)
+{
+ /* Free fixture. And disconnect signals, we don't want to
+ listen to more SoupMessage events as we're finished with
+ this resource */
+ if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler))
+ g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler);
+ if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler))
+ g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
+ if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
+ g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
+ g_object_unref (fixture->msg);
+ g_object_unref (fixture->cache);
+ g_slice_free (WebKitSoupCacheWritingFixture, fixture);
+}
+
+static void
+close_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
+{
+ WebKitSoupCacheEntry *entry = fixture->entry;
+ WebKitSoupCache *cache = fixture->cache;
+ GOutputStream *stream = G_OUTPUT_STREAM (source);
+ goffset content_length;
+
+ g_warn_if_fail (entry->error == NULL);
+
+ /* FIXME: what do we do on error ? */
+
+ if (stream) {
+ g_output_stream_close_finish (stream, result, NULL);
+ g_object_unref (stream);
+ }
+ entry->stream = NULL;
+
+ content_length = soup_message_headers_get_content_length (entry->headers);
+
+ /* If the process was cancelled, then delete the entry from
+ the cache. Do it also if the size of a chunked resource is
+ too much for the cache */
+ if (g_cancellable_is_cancelled (entry->cancellable)) {
+ entry->dirty = FALSE;
+ webkit_soup_cache_entry_remove (cache, entry);
+ webkit_soup_cache_entry_free (entry, TRUE);
+ entry = NULL;
+ } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
+ entry->length != content_length) {
+ /** Two options here:
+ *
+ * 1. "chunked" data, entry was temporarily added to
+ * cache (as content-length is 0) and now that we have
+ * the actual size we have to evaluate if we want it
+ * in the cache or not
+ *
+ * 2. Content-Length has a different value than actual
+ * length, means that the content was encoded for
+ * transmission (typically compressed) and thus we
+ * have to substract the content-length value that was
+ * added to the cache and add the unencoded length
+ **/
+ gint length_to_add = entry->length - content_length;
+
+ /* Make room in cache if needed */
+ if (cache_accepts_entries_of_size (cache, length_to_add)) {
+ make_room_for_new_entry (cache, length_to_add);
+
+ cache->priv->size += length_to_add;
+ } else {
+ entry->dirty = FALSE;
+ webkit_soup_cache_entry_remove (cache, entry);
+ webkit_soup_cache_entry_free (entry, TRUE);
+ entry = NULL;
+ }
+ }
+
+ if (entry) {
+ /* Get rid of the GString in memory for the resource now */
+ if (entry->data) {
+ g_string_free (entry->data, TRUE);
+ entry->data = NULL;
+ }
+
+ entry->dirty = FALSE;
+ entry->writing = FALSE;
+ entry->got_body = FALSE;
+ entry->pos = 0;
+
+ g_object_unref (entry->cancellable);
+ entry->cancellable = NULL;
+ }
+
+ cache->priv->n_pending--;
+
+ /* Frees */
+ webkit_soup_cache_writing_fixture_free (fixture);
+}
+
+static void
+write_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
+{
+ GOutputStream *stream = G_OUTPUT_STREAM (source);
+ GError *error = NULL;
+ gssize write_size;
+ WebKitSoupCacheEntry *entry = fixture->entry;
+
+ if (g_cancellable_is_cancelled (entry->cancellable)) {
+ g_output_stream_close_async (stream,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)close_ready_cb,
+ fixture);
+ return;
+ }
+
+ write_size = g_output_stream_write_finish (stream, result, &error);
+ if (write_size <= 0 || error) {
+ if (error)
+ entry->error = error;
+ g_output_stream_close_async (stream,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)close_ready_cb,
+ fixture);
+ /* FIXME: We should completely stop caching the
+ resource at this point */
+ } else {
+ entry->pos += write_size;
+
+ /* Are we still writing and is there new data to write
+ already ? */
+ if (entry->data && entry->pos < entry->data->len) {
+ g_output_stream_write_async (entry->stream,
+ entry->data->str + entry->pos,
+ entry->data->len - entry->pos,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)write_ready_cb,
+ fixture);
+ } else {
+ entry->writing = FALSE;
+
+ if (entry->got_body) {
+ /* If we already received 'got-body'
+ and we have written all the data,
+ we can close the stream */
+ g_output_stream_close_async (entry->stream,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)close_ready_cb,
+ fixture);
+ }
+ }
+ }
+}
+
+static void
+msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, WebKitSoupCacheWritingFixture *fixture)
+{
+ WebKitSoupCacheEntry *entry = fixture->entry;
+
+ g_return_if_fail (chunk->data && chunk->length);
+ g_return_if_fail (entry);
+
+ /* Ignore this if the writing or appending was cancelled */
+ if (!g_cancellable_is_cancelled (entry->cancellable)) {
+ g_string_append_len (entry->data, chunk->data, chunk->length);
+ entry->length = entry->data->len;
+
+ if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
+ /* Quickly cancel the caching of the resource */
+ g_cancellable_cancel (entry->cancellable);
+ }
+ }
+
+ /* FIXME: remove the error check when we cancel the caching at
+ the first write error */
+ /* Only write if the entry stream is ready */
+ if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
+ GString *data = entry->data;
+ entry->writing = TRUE;
+ g_output_stream_write_async (entry->stream,
+ data->str + entry->pos,
+ data->len - entry->pos,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)write_ready_cb,
+ fixture);
+ }
+}
+
+static void
+msg_got_body_cb (SoupMessage *msg, WebKitSoupCacheWritingFixture *fixture)
+{
+ WebKitSoupCacheEntry *entry = fixture->entry;
+ g_return_if_fail (entry);
+
+ entry->got_body = TRUE;
+
+ if (!entry->stream && entry->pos != entry->length)
+ /* The stream is not ready to be written but we still
+ have data to write, we'll write it when the stream
+ is opened for writing */
+ return;
+
+
+ if (entry->pos != entry->length) {
+ /* If we still have data to write, write it,
+ write_ready_cb will close the stream */
+ if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
+ g_output_stream_write_async (entry->stream,
+ entry->data->str + entry->pos,
+ entry->data->len - entry->pos,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)write_ready_cb,
+ fixture);
+ }
+ return;
+ }
+
+ if (entry->stream && !entry->writing)
+ g_output_stream_close_async (entry->stream,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)close_ready_cb,
+ fixture);
+}
+
+static gboolean
+webkit_soup_cache_entry_remove (WebKitSoupCache *cache, WebKitSoupCacheEntry *entry)
+{
+ GList *lru_item;
+
+ /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */
+ if (entry->dirty) {
+ g_cancellable_cancel (entry->cancellable);
+ return FALSE;
+ }
+
+ g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
+
+ /* Remove from cache */
+ if (!g_hash_table_remove (cache->priv->cache, entry->key))
+ return FALSE;
+
+ /* Remove from LRU */
+ lru_item = g_list_find (cache->priv->lru_start, entry);
+ cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
+
+ /* Adjust cache size */
+ cache->priv->size -= entry->length;
+
+ g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
+
+ return TRUE;
+}
+
+static gint
+lru_compare_func (gconstpointer a, gconstpointer b)
+{
+ WebKitSoupCacheEntry *entry_a = (WebKitSoupCacheEntry *)a;
+ WebKitSoupCacheEntry *entry_b = (WebKitSoupCacheEntry *)b;
+
+ /** The rationale of this sorting func is
+ *
+ * 1. sort by hits -> LRU algorithm, then
+ *
+ * 2. sort by freshness lifetime, we better discard first
+ * entries that are close to expire
+ *
+ * 3. sort by size, replace first small size resources as they
+ * are cheaper to download
+ **/
+
+ /* Sort by hits */
+ if (entry_a->hits != entry_b->hits)
+ return entry_a->hits - entry_b->hits;
+
+ /* Sort by freshness_lifetime */
+ if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
+ return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
+
+ /* Sort by size */
+ return entry_a->length - entry_b->length;
+}
+
+static gboolean
+cache_accepts_entries_of_size (WebKitSoupCache *cache, guint length_to_add)
+{
+ /* We could add here some more heuristics. TODO: review how
+ this is done by other HTTP caches */
+
+ return length_to_add <= cache->priv->max_entry_data_size;
+}
+
+static void
+make_room_for_new_entry (WebKitSoupCache *cache, guint length_to_add)
+{
+ GList *lru_entry = cache->priv->lru_start;
+
+ /* Check that there is enough room for the new entry. This is
+ an approximation as we're not working out the size of the
+ cache file or the size of the headers for performance
+ reasons. TODO: check if that would be really that expensive */
+
+ while (lru_entry &&
+ (length_to_add + cache->priv->size > cache->priv->max_size)) {
+ WebKitSoupCacheEntry *old_entry = (WebKitSoupCacheEntry *)lru_entry->data;
+
+ /* Discard entries. Once cancelled resources will be
+ * freed in close_ready_cb
+ */
+ if (webkit_soup_cache_entry_remove (cache, old_entry)) {
+ webkit_soup_cache_entry_free (old_entry, TRUE);
+ lru_entry = cache->priv->lru_start;
+ } else
+ lru_entry = g_list_next (lru_entry);
+ }
+}
+
+static gboolean
+webkit_soup_cache_entry_insert_by_key (WebKitSoupCache *cache,
+ const char *key,
+ WebKitSoupCacheEntry *entry,
+ gboolean sort)
+{
+ guint length_to_add = 0;
+
+ if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED)
+ length_to_add = soup_message_headers_get_content_length (entry->headers);
+
+ /* Check if we are going to store the resource depending on its size */
+ if (length_to_add) {
+ if (!cache_accepts_entries_of_size (cache, length_to_add))
+ return FALSE;
+
+ /* Make room for new entry if needed */
+ make_room_for_new_entry (cache, length_to_add);
+ }
+
+ g_hash_table_insert (cache->priv->cache, g_strdup (key), entry);
+
+ /* Compute new cache size */
+ cache->priv->size += length_to_add;
+
+ /* Update LRU */
+ if (sort)
+ cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
+ else
+ cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
+
+ g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
+
+ return TRUE;
+}
+
+static void
+msg_restarted_cb (SoupMessage *msg, WebKitSoupCacheEntry *entry)
+{
+ /* FIXME: What should we do here exactly? */
+}
+
+static void
+append_to_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
+{
+ GFile *file = (GFile *)source;
+ GOutputStream *stream;
+ WebKitSoupCacheEntry *entry = fixture->entry;
+
+ stream = (GOutputStream *)g_file_append_to_finish (file, result, &entry->error);
+
+ if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) {
+ fixture->cache->priv->n_pending--;
+ entry->dirty = FALSE;
+ webkit_soup_cache_entry_remove (fixture->cache, entry);
+ webkit_soup_cache_entry_free (entry, TRUE);
+ webkit_soup_cache_writing_fixture_free (fixture);
+ return;
+ }
+
+ entry->stream = g_object_ref (stream);
+ g_object_unref (file);
+
+ /* If we already got all the data we have to initiate the
+ writing here, since we won't get more 'got-chunk'
+ signals */
+ if (entry->got_body) {
+ GString *data = entry->data;
+
+ /* It could happen that reading the data from server
+ was completed before this happens. In that case
+ there is no data */
+ if (data) {
+ entry->writing = TRUE;
+ g_output_stream_write_async (entry->stream,
+ data->str + entry->pos,
+ data->len - entry->pos,
+ G_PRIORITY_LOW,
+ entry->cancellable,
+ (GAsyncReadyCallback)write_ready_cb,
+ fixture);
+ }
+ }
+}
+
+typedef struct {
+ time_t request_time;
+ SoupSessionFeature *feature;
+ gulong got_headers_handler;
+} RequestHelper;
+
+static void
+msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
+{
+ WebKitSoupCache *cache;
+ WebKitSoupCacheability cacheable;
+ RequestHelper *helper;
+ time_t request_time, response_time;
+
+ response_time = time (NULL);
+
+ helper = (RequestHelper *)user_data;
+ cache = WEBKIT_SOUP_CACHE (helper->feature);
+ request_time = helper->request_time;
+ g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
+ g_slice_free (RequestHelper, helper);
+
+ cacheable = webkit_soup_cache_get_cacheability (cache, msg);
+
+ if (cacheable & WEBKIT_SOUP_CACHE_CACHEABLE) {
+ WebKitSoupCacheEntry *entry;
+ char *key;
+ GFile *file;
+ WebKitSoupCacheWritingFixture *fixture;
+
+ /* Check if we are already caching this resource */
+ key = soup_message_get_cache_key (msg);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+ g_free (key);
+
+ if (entry && entry->dirty)
+ return;
+
+ /* Create a new entry, deleting any old one if present */
+ if (entry) {
+ webkit_soup_cache_entry_remove (cache, entry);
+ webkit_soup_cache_entry_free (entry, TRUE);
+ }
+
+ entry = webkit_soup_cache_entry_new (cache, msg, request_time, response_time);
+ entry->hits = 1;
+
+ /* Do not continue if it can not be stored */
+ if (!webkit_soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, TRUE)) {
+ webkit_soup_cache_entry_free (entry, TRUE);
+ return;
+ }
+
+ fixture = g_slice_new0 (WebKitSoupCacheWritingFixture);
+ fixture->cache = g_object_ref (cache);
+ fixture->entry = entry;
+ fixture->msg = g_object_ref (msg);
+
+ /* We connect now to these signals and buffer the data
+ if it comes before the file is ready for writing */
+ fixture->got_chunk_handler =
+ g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture);
+ fixture->got_body_handler =
+ g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture);
+ fixture->restarted_handler =
+ g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry);
+
+ /* Prepare entry */
+ file = g_file_new_for_path (entry->filename);
+ cache->priv->n_pending++;
+
+ entry->dirty = TRUE;
+ entry->cancellable = g_cancellable_new ();
+ g_file_append_to_async (file, 0,
+ G_PRIORITY_LOW, entry->cancellable,
+ (GAsyncReadyCallback)append_to_ready_cb,
+ fixture);
+ } else if (cacheable & WEBKIT_SOUP_CACHE_INVALIDATES) {
+ char *key;
+ WebKitSoupCacheEntry *entry;
+
+ key = soup_message_get_cache_key (msg);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+ g_free (key);
+
+ if (entry) {
+ if (webkit_soup_cache_entry_remove (cache, entry))
+ webkit_soup_cache_entry_free (entry, TRUE);
+ }
+ } else if (cacheable & WEBKIT_SOUP_CACHE_VALIDATES) {
+ char *key;
+ WebKitSoupCacheEntry *entry;
+
+ key = soup_message_get_cache_key (msg);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+ g_free (key);
+
+ g_return_if_fail (entry);
+
+ entry->being_validated = FALSE;
+
+ /* We update the headers of the existing cache item,
+ plus its age */
+ soup_message_headers_foreach (msg->response_headers,
+ (SoupMessageHeadersForeachFunc)update_headers,
+ entry->headers);
+ webkit_soup_cache_entry_set_freshness (entry, msg, cache);
+ }
+}
+
+GInputStream *
+webkit_soup_cache_send_response (WebKitSoupCache *cache, SoupMessage *msg)
+{
+ char *key;
+ WebKitSoupCacheEntry *entry;
+ char *current_age;
+ GInputStream *stream = NULL;
+ GFile *file;
+
+ g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+ key = soup_message_get_cache_key (msg);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+ g_return_val_if_fail (entry, NULL);
+
+ /* If we are told to send a response from cache any validation
+ in course is over by now */
+ entry->being_validated = FALSE;
+
+ /* Headers */
+ soup_message_headers_foreach (entry->headers,
+ (SoupMessageHeadersForeachFunc)update_headers,
+ msg->response_headers);
+
+ /* Add 'Age' header with the current age */
+ current_age = g_strdup_printf ("%d", webkit_soup_cache_entry_get_current_age (entry));
+ soup_message_headers_replace (msg->response_headers,
+ "Age",
+ current_age);
+ g_free (current_age);
+
+ /* TODO: the original idea was to save reads, but current code
+ assumes that a stream is always returned. Need to reach
+ some agreement here. Also we have to handle the situation
+ were the file was no longer there (for example files
+ removed without notifying the cache */
+ file = g_file_new_for_path (entry->filename);
+ stream = (GInputStream *)g_file_read (file, NULL, NULL);
+
+ return stream;
+}
+
+static void
+request_started (SoupSessionFeature *feature, SoupSession *session,
+ SoupMessage *msg, SoupSocket *socket)
+{
+ RequestHelper *helper = g_slice_new0 (RequestHelper);
+ helper->request_time = time (NULL);
+ helper->feature = feature;
+ helper->got_headers_handler = g_signal_connect (msg, "got-headers",
+ G_CALLBACK (msg_got_headers_cb),
+ helper);
+}
+
+static void
+attach (SoupSessionFeature *feature, SoupSession *session)
+{
+ WebKitSoupCache *cache = WEBKIT_SOUP_CACHE (feature);
+ cache->priv->session = session;
+
+ webkit_soup_cache_default_feature_interface->attach (feature, session);
+}
+
+static void
+webkit_soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+ gpointer interface_data)
+{
+ webkit_soup_cache_default_feature_interface =
+ g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
+
+ feature_interface->attach = attach;
+ feature_interface->request_started = request_started;
+}
+
+static void
+webkit_soup_cache_init (WebKitSoupCache *cache)
+{
+ WebKitSoupCachePrivate *priv;
+
+ priv = cache->priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
+
+ priv->cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ NULL);
+
+ /* LRU */
+ priv->lru_start = NULL;
+
+ /* */
+ priv->n_pending = 0;
+
+ /* Cache size */
+ priv->max_size = DEFAULT_MAX_SIZE;
+ priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
+ priv->size = 0;
+}
+
+static void
+remove_cache_item (gpointer key,
+ gpointer value,
+ WebKitSoupCache *cache)
+{
+ WebKitSoupCacheEntry *entry = g_hash_table_lookup (cache->priv->cache, (const gchar *)key);
+ if (webkit_soup_cache_entry_remove (cache, entry))
+ webkit_soup_cache_entry_free (entry, FALSE);
+}
+
+static void
+webkit_soup_cache_finalize (GObject *object)
+{
+ WebKitSoupCachePrivate *priv;
+
+ priv = WEBKIT_SOUP_CACHE (object)->priv;
+
+ g_hash_table_foreach (priv->cache, (GHFunc)remove_cache_item, object);
+ g_hash_table_destroy (priv->cache);
+ g_free (priv->cache_dir);
+
+ g_list_free (priv->lru_start);
+ priv->lru_start = NULL;
+
+ G_OBJECT_CLASS (webkit_soup_cache_parent_class)->finalize (object);
+}
+
+static void
+webkit_soup_cache_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE (object)->priv;
+
+ switch (prop_id) {
+ case PROP_CACHE_DIR:
+ priv->cache_dir = g_value_dup_string (value);
+ /* Create directory if it does not exist (FIXME: should we?) */
+ if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+ g_mkdir_with_parents (priv->cache_dir, 0700);
+ break;
+ case PROP_CACHE_TYPE:
+ priv->cache_type = g_value_get_enum (value);
+ /* TODO: clear private entries and issue a warning if moving to shared? */
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+webkit_soup_cache_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE (object)->priv;
+
+ switch (prop_id) {
+ case PROP_CACHE_DIR:
+ g_value_set_string (value, priv->cache_dir);
+ break;
+ case PROP_CACHE_TYPE:
+ g_value_set_enum (value, priv->cache_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+webkit_soup_cache_constructed (GObject *object)
+{
+ WebKitSoupCachePrivate *priv;
+
+ priv = WEBKIT_SOUP_CACHE (object)->priv;
+
+ if (!priv->cache_dir) {
+ /* Set a default cache dir, different for each user */
+ priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
+ "httpcache",
+ NULL);
+ if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+ g_mkdir_with_parents (priv->cache_dir, 0700);
+ }
+
+ if (G_OBJECT_CLASS (webkit_soup_cache_parent_class)->constructed)
+ G_OBJECT_CLASS (webkit_soup_cache_parent_class)->constructed (object);
+}
+
+#define WEBKIT_SOUP_CACHE_TYPE_TYPE (webkit_soup_cache_type_get_type ())
+static GType
+webkit_soup_cache_type_get_type (void)
+{
+ static GType cache_type = 0;
+
+ static const GEnumValue cache_types[] = {
+ { WEBKIT_SOUP_CACHE_SINGLE_USER, "Single user cache", "user" },
+ { WEBKIT_SOUP_CACHE_SHARED, "Shared cache", "shared" },
+ { 0, NULL, NULL }
+ };
+
+ if (!cache_type) {
+ cache_type = g_enum_register_static ("WebKitSoupCacheType", cache_types);
+ }
+ return cache_type;
+}
+
+static void
+webkit_soup_cache_class_init (WebKitSoupCacheClass *cache_class)
+{
+ GObjectClass *gobject_class = (GObjectClass *)cache_class;
+
+ gobject_class->finalize = webkit_soup_cache_finalize;
+ gobject_class->constructed = webkit_soup_cache_constructed;
+ gobject_class->set_property = webkit_soup_cache_set_property;
+ gobject_class->get_property = webkit_soup_cache_get_property;
+
+ cache_class->get_cacheability = get_cacheability;
+
+ g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
+ g_param_spec_string ("cache-dir",
+ "Cache directory",
+ "The directory to store the cache files",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
+ g_param_spec_enum ("cache-type",
+ "Cache type",
+ "Whether the cache is private or shared",
+ WEBKIT_SOUP_CACHE_TYPE_TYPE,
+ WEBKIT_SOUP_CACHE_SINGLE_USER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (cache_class, sizeof (WebKitSoupCachePrivate));
+}
+
+/**
+ * webkit_soup_cache_new:
+ * @cache_dir: the directory to store the cached data, or %NULL to use the default one
+ * @cache_type: the #WebKitSoupCacheType of the cache
+ *
+ * Creates a new #WebKitSoupCache.
+ *
+ * Returns: a new #WebKitSoupCache
+ *
+ * Since: 2.28
+ **/
+WebKitSoupCache *
+webkit_soup_cache_new (const char *cache_dir, WebKitSoupCacheType cache_type)
+{
+ return g_object_new (WEBKIT_TYPE_SOUP_CACHE,
+ "cache-dir", cache_dir,
+ "cache-type", cache_type,
+ NULL);
+}
+
+/**
+ * webkit_soup_cache_has_response:
+ * @cache: a #WebKitSoupCache
+ * @msg: a #SoupMessage
+ *
+ * This function calculates whether the @cache object has a proper
+ * response for the request @msg given the flags both in the request
+ * and the cached reply and the time ellapsed since it was cached.
+ *
+ * Returns: whether or not the @cache has a valid response for @msg
+ **/
+WebKitSoupCacheResponse
+webkit_soup_cache_has_response (WebKitSoupCache *cache, SoupMessage *msg)
+{
+ char *key;
+ WebKitSoupCacheEntry *entry;
+ const char *cache_control;
+ GHashTable *hash;
+ gpointer value;
+ gboolean must_revalidate;
+ int max_age, max_stale, min_fresh;
+ GList *lru_item, *item;
+
+ key = soup_message_get_cache_key (msg);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+
+ /* 1. The presented Request-URI and that of stored response
+ * match
+ */
+ if (!entry)
+ return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
+
+ /* Increase hit count. Take sorting into account */
+ entry->hits++;
+ lru_item = g_list_find (cache->priv->lru_start, entry);
+ item = lru_item;
+ while (item->next && lru_compare_func (item->data, item->next->data) > 0)
+ item = g_list_next (item);
+
+ if (item != lru_item) {
+ cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
+ item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
+ g_list_free (lru_item);
+ }
+
+ if (entry->dirty || entry->being_validated)
+ return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
+
+ /* 2. The request method associated with the stored response
+ * allows it to be used for the presented request
+ */
+
+ /* In practice this means we only return our resource for GET,
+ * cacheability for other methods is a TODO in the RFC
+ * (TODO: although we could return the headers for HEAD
+ * probably).
+ */
+ if (msg->method != SOUP_METHOD_GET)
+ return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
+
+ /* 3. Selecting request-headers nominated by the stored
+ * response (if any) match those presented.
+ */
+
+ /* TODO */
+
+ /* 4. The presented request and stored response are free from
+ * directives that would prevent its use.
+ */
+
+ must_revalidate = FALSE;
+ max_age = max_stale = min_fresh = -1;
+
+ cache_control = soup_message_headers_get (msg->request_headers, "Cache-Control");
+ if (cache_control) {
+ hash = soup_header_parse_param_list (cache_control);
+
+ if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
+ g_hash_table_destroy (hash);
+ return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
+ }
+
+ if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
+ entry->must_revalidate = TRUE;
+ }
+
+ if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
+ max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
+ }
+
+ /* max-stale can have no value set, we need to use _extended */
+ if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
+ if (value)
+ max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
+ else
+ max_stale = G_MAXINT32;
+ }
+
+ value = g_hash_table_lookup (hash, "min-fresh");
+ if (value)
+ min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
+
+ g_hash_table_destroy (hash);
+
+ if (max_age != -1) {
+ guint current_age = webkit_soup_cache_entry_get_current_age (entry);
+
+ /* If we are over max-age and max-stale is not
+ set, do not use the value from the cache
+ without validation */
+ if (max_age <= current_age && max_stale == -1)
+ return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+ }
+ }
+
+ /* 5. The stored response is either: fresh, allowed to be
+ * served stale or succesfully validated
+ */
+ /* TODO consider also proxy-revalidate & s-maxage */
+ if (entry->must_revalidate)
+ return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+
+ if (!webkit_soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
+ /* Not fresh, can it be served stale? */
+ if (max_stale != -1) {
+ /* G_MAXINT32 means we accept any staleness */
+ if (max_stale == G_MAXINT32)
+ return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
+
+ if ((webkit_soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= max_stale)
+ return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
+ }
+
+ return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+ }
+
+ return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
+}
+
+/**
+ * webkit_soup_cache_get_cacheability:
+ * @cache: a #WebKitSoupCache
+ * @msg: a #SoupMessage
+ *
+ * Calculates whether the @msg can be cached or not.
+ *
+ * Returns: a #WebKitSoupCacheability value indicating whether the @msg can be cached or not.
+ **/
+WebKitSoupCacheability
+webkit_soup_cache_get_cacheability (WebKitSoupCache *cache, SoupMessage *msg)
+{
+ g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), WEBKIT_SOUP_CACHE_UNCACHEABLE);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), WEBKIT_SOUP_CACHE_UNCACHEABLE);
+
+ return WEBKIT_SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
+}
+
+static gboolean
+force_flush_timeout (gpointer data)
+{
+ gboolean *forced = (gboolean *)data;
+ *forced = TRUE;
+
+ return FALSE;
+}
+
+/**
+ * webkit_soup_cache_flush:
+ * @cache: a #WebKitSoupCache
+ * @session: the #SoupSession associated with the @cache
+ *
+ * This function will force all pending writes in the @cache to be
+ * committed to disk. For doing so it will iterate the #GMainContext
+ * associated with the @session (which can be the default one) as long
+ * as needed.
+ **/
+void
+webkit_soup_cache_flush (WebKitSoupCache *cache)
+{
+ GMainContext *async_context;
+ SoupSession *session;
+ guint timeout_id;
+ gboolean forced = FALSE;
+
+ g_return_if_fail (WEBKIT_IS_SOUP_CACHE (cache));
+
+ session = cache->priv->session;
+ g_return_if_fail (SOUP_IS_SESSION (session));
+ async_context = soup_session_get_async_context (session);
+
+ /* We give cache 10 secs to finish */
+ timeout_id = g_timeout_add (10000, force_flush_timeout, &forced);
+
+ while (!forced && cache->priv->n_pending > 0)
+ g_main_context_iteration (async_context, FALSE);
+
+ if (!forced)
+ g_source_remove (timeout_id);
+ else
+ g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
+}
+
+static void
+clear_cache_item (gpointer key,
+ gpointer value,
+ WebKitSoupCache *cache)
+{
+ WebKitSoupCacheEntry *entry = g_hash_table_lookup (cache->priv->cache, (const gchar *)key);
+ if (webkit_soup_cache_entry_remove (cache, entry))
+ webkit_soup_cache_entry_free (entry, TRUE);
+}
+
+/**
+ * webkit_soup_cache_clear:
+ * @cache: a #WebKitSoupCache
+ *
+ * Will remove all entries in the @cache plus all the cache files
+ * associated with them.
+ **/
+void
+webkit_soup_cache_clear (WebKitSoupCache *cache)
+{
+ GHashTable *hash;
+
+ g_return_if_fail (WEBKIT_IS_SOUP_CACHE (cache));
+
+ hash = cache->priv->cache;
+ g_return_if_fail (hash);
+
+ g_hash_table_foreach (hash, (GHFunc)clear_cache_item, cache);
+}
+
+SoupMessage *
+webkit_soup_cache_generate_conditional_request (WebKitSoupCache *cache, SoupMessage *original)
+{
+ SoupMessage *msg;
+ SoupURI *uri;
+ WebKitSoupCacheEntry *entry;
+ char *key;
+ const char *value;
+
+ g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
+
+ /* First copy the data we need from the original message */
+ uri = soup_message_get_uri (original);
+ msg = soup_message_new_from_uri (original->method, uri);
+
+ soup_message_headers_foreach (original->request_headers,
+ (SoupMessageHeadersForeachFunc)copy_headers,
+ msg->request_headers);
+
+ /* Now add the validator entries in the header from the cached
+ data */
+ key = soup_message_get_cache_key (original);
+ entry = g_hash_table_lookup (cache->priv->cache, key);
+ g_free (key);
+
+ g_return_val_if_fail (entry, NULL);
+
+ entry->being_validated = TRUE;
+
+ value = soup_message_headers_get (entry->headers, "Last-Modified");
+ if (value)
+ soup_message_headers_append (msg->request_headers,
+ "If-Modified-Since",
+ value);
+ value = soup_message_headers_get (entry->headers, "ETag");
+ if (value)
+ soup_message_headers_append (msg->request_headers,
+ "If-None-Match",
+ value);
+ return msg;
+}
+
+#define WEBKIT_SOUP_CACHE_FILE "soup.cache"
+
+#define WEBKIT_SOUP_CACHE_HEADERS_FORMAT "{ss}"
+#define WEBKIT_SOUP_CACHE_PHEADERS_FORMAT "(ssbuuuuua" WEBKIT_SOUP_CACHE_HEADERS_FORMAT ")"
+#define WEBKIT_SOUP_CACHE_ENTRIES_FORMAT "a" WEBKIT_SOUP_CACHE_PHEADERS_FORMAT
+
+/* Basically the same format than above except that some strings are
+ prepended with &. This way the GVariant returns a pointer to the
+ data instead of duplicating the string */
+#define WEBKIT_SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
+
+static void
+pack_entry (gpointer data,
+ gpointer user_data)
+{
+ WebKitSoupCacheEntry *entry = (WebKitSoupCacheEntry *) data;
+ SoupMessageHeadersIter iter;
+ const gchar *header_key, *header_value;
+ GVariantBuilder *headers_builder;
+ GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
+
+ /* Do not store non-consolidated entries */
+ if (entry->dirty || entry->writing || !entry->key)
+ return;
+
+ /* Pack headers */
+ headers_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ soup_message_headers_iter_init (&iter, entry->headers);
+ while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
+ if (g_utf8_validate (header_value, -1, NULL))
+ g_variant_builder_add (headers_builder, WEBKIT_SOUP_CACHE_HEADERS_FORMAT,
+ header_key, header_value);
+ }
+
+ /* Entry data */
+ g_variant_builder_add (entries_builder, WEBKIT_SOUP_CACHE_PHEADERS_FORMAT,
+ entry->key, entry->filename, entry->must_revalidate,
+ entry->freshness_lifetime, entry->corrected_initial_age,
+ entry->response_time, entry->hits, entry->length, headers_builder);
+
+ g_variant_builder_unref (headers_builder);
+}
+
+void
+webkit_soup_cache_dump (WebKitSoupCache *cache)
+{
+ WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
+ gchar *filename;
+ GVariantBuilder *entries_builder;
+ GVariant *cache_variant;
+
+ if (!g_list_length (cache->priv->lru_start))
+ return;
+
+ /* Create the builder and iterate over all entries */
+ entries_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ g_list_foreach (cache->priv->lru_start, pack_entry, entries_builder);
+
+ /* Serialize and dump */
+ cache_variant = g_variant_new (WEBKIT_SOUP_CACHE_ENTRIES_FORMAT, entries_builder);
+ g_variant_builder_unref (entries_builder);
+
+ filename = g_build_filename (priv->cache_dir, WEBKIT_SOUP_CACHE_FILE, NULL);
+ g_file_set_contents (filename, (const gchar *)g_variant_get_data (cache_variant),
+ g_variant_get_size (cache_variant), NULL);
+ g_free (filename);
+ g_variant_unref (cache_variant);
+}
+
+void
+webkit_soup_cache_load (WebKitSoupCache *cache)
+{
+ gchar *filename = NULL, *contents = NULL;
+ GVariant *cache_variant;
+ GVariantIter *entries_iter, *headers_iter;
+ GVariantType *variant_format;
+ gsize length;
+ WebKitSoupCacheEntry *entry;
+ WebKitSoupCachePrivate *priv = cache->priv;
+
+ filename = g_build_filename (priv->cache_dir, WEBKIT_SOUP_CACHE_FILE, NULL);
+ if (!g_file_get_contents (filename, &contents, &length, NULL)) {
+ g_free (filename);
+ return;
+ }
+ g_free (filename);
+
+ variant_format = g_variant_type_new (WEBKIT_SOUP_CACHE_ENTRIES_FORMAT);
+ cache_variant = g_variant_new_from_data (variant_format, (const gchar *)contents, length, FALSE, NULL, NULL);
+ g_variant_type_free (variant_format);
+
+ g_variant_get (cache_variant, WEBKIT_SOUP_CACHE_ENTRIES_FORMAT, &entries_iter);
+ entry = g_slice_new0 (WebKitSoupCacheEntry);
+
+ while (g_variant_iter_loop (entries_iter, WEBKIT_SOUP_CACHE_PHEADERS_FORMAT,
+ &entry->key, &entry->filename, &entry->must_revalidate,
+ &entry->freshness_lifetime, &entry->corrected_initial_age,
+ &entry->response_time, &entry->hits, &entry->length,
+ &headers_iter)) {
+ const gchar *header_key, *header_value;
+
+ /* SoupMessage Headers */
+ entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+ while (g_variant_iter_loop (headers_iter, WEBKIT_SOUP_CACHE_DECODE_HEADERS_FORMAT, &header_key, &header_value))
+ soup_message_headers_append (entry->headers, header_key, header_value);
+
+ /* Insert in cache */
+ if (!webkit_soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, FALSE))
+ webkit_soup_cache_entry_free (entry, TRUE);
+
+ /* New entry for the next iteration. This creates an
+ extra object the last iteration but it's worth it
+ as we save several if's */
+ entry = g_slice_new0 (WebKitSoupCacheEntry);
+ }
+ /* Remove last created entry */
+ g_slice_free (WebKitSoupCacheEntry, entry);
+
+ /* Sort LRU (shouldn't be needed). First reverse as elements
+ * are always prepended when inserting
+ */
+ cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
+ cache->priv->lru_start = g_list_sort (cache->priv->lru_start, lru_compare_func);
+
+ /* frees */
+ g_variant_iter_free (entries_iter);
+ g_variant_unref (cache_variant);
+}
+
+void
+webkit_soup_cache_set_max_size (WebKitSoupCache *cache,
+ guint max_size)
+{
+ cache->priv->max_size = max_size;
+ cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
+}
+
+guint
+webkit_soup_cache_get_max_size (WebKitSoupCache *cache)
+{
+ return cache->priv->max_size;
+}
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cache.h:
+ *
+ * Copyright (C) 2009, 2010 Igalia, S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WEBKIT_SOUP_CACHE_H
+#define WEBKIT_SOUP_CACHE_H 1
+
+#include <webkit/webkitdefines.h>
+
+#include <libsoup/soup-types.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_SOUP_CACHE (webkit_soup_cache_get_type ())
+#define WEBKIT_SOUP_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_SOUP_CACHE, WebKitSoupCache))
+#define WEBKIT_SOUP_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_SOUP_CACHE, WebKitSoupCacheClass))
+#define WEBKIT_IS_SOUP_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_SOUP_CACHE))
+#define WEBKIT_IS_SOUP_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), WEBKIT_TYPE_SOUP_CACHE))
+#define WEBKIT_SOUP_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_SOUP_CACHE, WebKitSoupCacheClass))
+
+typedef struct _WebKitSoupCache WebKitSoupCache;
+typedef struct _WebKitSoupCachePrivate WebKitSoupCachePrivate;
+
+typedef enum {
+ WEBKIT_SOUP_CACHE_CACHEABLE = (1 << 0),
+ WEBKIT_SOUP_CACHE_UNCACHEABLE = (1 << 1),
+ WEBKIT_SOUP_CACHE_INVALIDATES = (1 << 2),
+ WEBKIT_SOUP_CACHE_VALIDATES = (1 << 3)
+} WebKitSoupCacheability;
+
+typedef enum {
+ WEBKIT_SOUP_CACHE_RESPONSE_FRESH,
+ WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION,
+ WEBKIT_SOUP_CACHE_RESPONSE_STALE
+} WebKitSoupCacheResponse;
+
+typedef enum {
+ WEBKIT_SOUP_CACHE_SINGLE_USER,
+ WEBKIT_SOUP_CACHE_SHARED
+} WebKitSoupCacheType;
+
+struct _WebKitSoupCache {
+ GObject parent_instance;
+
+ WebKitSoupCachePrivate *priv;
+};
+
+typedef struct {
+ GObjectClass parent_class;
+
+ /* methods */
+ WebKitSoupCacheability (*get_cacheability)(WebKitSoupCache *cache, SoupMessage *msg);
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved1)(void);
+ void (*_libsoup_reserved2)(void);
+ void (*_libsoup_reserved3)(void);
+} WebKitSoupCacheClass;
+
+WEBKIT_API GType webkit_soup_cache_get_type (void);
+WEBKIT_API WebKitSoupCache *webkit_soup_cache_new (const char *cache_dir,
+ WebKitSoupCacheType cache_type);
+WEBKIT_API void webkit_soup_cache_flush (WebKitSoupCache *cache);
+WEBKIT_API void webkit_soup_cache_clear (WebKitSoupCache *cache);
+
+WEBKIT_API void webkit_soup_cache_dump (WebKitSoupCache *cache);
+WEBKIT_API void webkit_soup_cache_load (WebKitSoupCache *cache);
+
+WEBKIT_API void webkit_soup_cache_set_max_size (WebKitSoupCache *cache,
+ guint max_size);
+WEBKIT_API guint webkit_soup_cache_get_max_size (WebKitSoupCache *cache);
+
+G_END_DECLS
+
+
+#endif /* WEBKIT_SOUP_CACHE_H */
+
+2010-10-13 Sergio Villar Senin <svillar@igalia.com>
+
+ Reviewed by Martin Robinson.
+
+ [GTK] Add HTTP caching support
+ https://bugs.webkit.org/show_bug.cgi?id=44261
+
+ Add include paths for the new soup HTTP cache code.
+
+ * GNUmakefile.am: Add paths for the new soup HTTP cache code.
+
2010-10-13 Yi Shen <yi.4.shen@nokia.com>
Reviewed by Antonio Gomes.
# GtkLauncher
Programs_GtkLauncher_CPPFLAGS = \
-I$(srcdir)/WebKit/gtk \
+ -I$(srcdir)/WebCore/platform/network/soup/cache/ \
-I$(top_builddir)/WebKit/gtk \
-I$(top_builddir)/DerivedSources \
$(global_cppflags) \
-I$(srcdir)/WebKitTools/DumpRenderTree/gtk \
-I$(srcdir)/WebKit/gtk \
-I$(srcdir)/WebCore/platform/gtk \
+ -I$(srcdir)/WebCore/platform/network/soup/cache/ \
-I$(top_builddir)/WebKit/gtk \
-I$(top_builddir)/DerivedSources \
$(global_cppflags) \
[dnl
dnl check for glib
# Version requirements
-GLIB_REQUIRED_VERSION=2.21.3
+GLIB_REQUIRED_VERSION=2.24
GOBJECT_REQUIRED_VERSION=2.0
GTHREAD_REQUIRED_VERSION=2.0