[libsoup] Import SoupRequest/cache stuff from new-io branch / WebKit



commit f8494ad794438280c8772e29d1dd178af7ccd257
Author: Dan Winship <danw gnome org>
Date:   Wed Dec 8 10:40:02 2010 +0100

    Import SoupRequest/cache stuff from new-io branch / WebKit
    
    SoupRequest stuff is based on Gabriel Corvalan's Google Summer of Code
    project from 2009, as further hacked on at the 2009 WebKitGTK Hackfest.
    
    The cache stuff was written on top of that by Sergio Villar and
    initially committed to WebKit since it only works with the SoupRequest
    infrastructure, and the SoupRequest stuff was never fully completed
    (eg, the synchronous versions never worked right).
    
    Move all of this here now as a first step to finishing it, but hide it
    behind #ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API.

 libsoup/Makefile.am                   |   21 +-
 libsoup/soup-cache-private.h          |   42 +
 libsoup/soup-cache.c                  | 1667 +++++++++++++++++++++++++++++++++
 libsoup/soup-cache.h                  |   98 ++
 libsoup/soup-directory-input-stream.c |  200 ++++
 libsoup/soup-directory-input-stream.h |   62 ++
 libsoup/soup-enum-types.c.tmpl        |    3 +
 libsoup/soup-http-input-stream.c      |  921 ++++++++++++++++++
 libsoup/soup-http-input-stream.h      |   77 ++
 libsoup/soup-request-data.c           |  169 ++++
 libsoup/soup-request-data.h           |   56 ++
 libsoup/soup-request-file.c           |  253 +++++
 libsoup/soup-request-file.h           |   58 ++
 libsoup/soup-request-http.c           |  348 +++++++
 libsoup/soup-request-http.h           |   58 ++
 libsoup/soup-request.c                |  284 ++++++
 libsoup/soup-request.h                |   98 ++
 libsoup/soup-requester.c              |  188 ++++
 libsoup/soup-requester.h              |   84 ++
 19 files changed, 4685 insertions(+), 2 deletions(-)
---
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 38c0a38..5ed63f1 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -54,8 +54,9 @@ soup_headers =			\
 	soup-auth-domain.h	\
 	soup-auth-domain-basic.h  \
 	soup-auth-domain-digest.h \
-	soup-content-decoder.h  \
-	soup-content-sniffer.h  \
+	soup-cache.h		\
+	soup-content-decoder.h	\
+	soup-content-sniffer.h	\
 	soup-cookie.h		\
 	soup-cookie-jar.h	\
 	soup-cookie-jar-text.h	\
@@ -73,6 +74,11 @@ soup_headers =			\
 	soup-portability.h	\
 	soup-proxy-resolver.h	\
 	soup-proxy-uri-resolver.h \
+	soup-request.h		\
+	soup-request-data.h	\
+	soup-request-file.h	\
+	soup-request-http.h	\
+	soup-requester.h	\
 	soup-server.h		\
 	soup-session.h		\
 	soup-session-async.h	\
@@ -117,6 +123,8 @@ libsoup_2_4_la_SOURCES =		\
 	soup-auth-manager.c		\
 	soup-auth-manager-ntlm.h	\
 	soup-auth-manager-ntlm.c	\
+	soup-cache.c			\
+	soup-cache-private.h		\
 	soup-coding.h			\
 	soup-coding.c			\
 	soup-coding-gzip.h		\
@@ -129,8 +137,12 @@ libsoup_2_4_la_SOURCES =		\
 	soup-cookie-jar.c		\
 	soup-cookie-jar-text.c		\
 	soup-date.c			\
+	soup-directory-input-stream.h	\
+	soup-directory-input-stream.c	\
 	soup-form.c			\
 	soup-headers.c			\
+	soup-http-input-stream.h	\
+	soup-http-input-stream.c	\
 	soup-logger.c			\
 	soup-message.c			\
 	soup-message-body.c		\
@@ -151,6 +163,11 @@ libsoup_2_4_la_SOURCES =		\
 	soup-proxy-resolver-static.h	\
 	soup-proxy-resolver-static.c	\
 	soup-proxy-uri-resolver.c	\
+	soup-request.c			\
+	soup-request-data.c		\
+	soup-request-file.c		\
+	soup-request-http.c		\
+	soup-requester.c		\
 	soup-server.c			\
 	soup-session.c			\
 	soup-session-async.c		\
diff --git a/libsoup/soup-cache-private.h b/libsoup/soup-cache-private.h
new file mode 100644
index 0000000..3843e8e
--- /dev/null
+++ b/libsoup/soup-cache-private.h
@@ -0,0 +1,42 @@
+/* -*- 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 SOUP_CACHE_PRIVATE_H
+#define SOUP_CACHE_PRIVATE_H 1
+
+#include "soup-cache.h"
+#include <libsoup/soup-message.h>
+
+G_BEGIN_DECLS
+
+SoupCacheResponse  soup_cache_has_response                 (SoupCache   *cache,
+							    SoupMessage *msg);
+GInputStream      *soup_cache_send_response                (SoupCache   *cache,
+							    SoupMessage *msg);
+SoupCacheability   soup_cache_get_cacheability             (SoupCache   *cache,
+							    SoupMessage *msg);
+SoupMessage       *soup_cache_generate_conditional_request (SoupCache   *cache,
+							    SoupMessage *original);
+
+G_END_DECLS
+
+#endif /* SOUP_CACHE_PRIVATE_H */
diff --git a/libsoup/soup-cache.c b/libsoup/soup-cache.c
new file mode 100644
index 0000000..0506348
--- /dev/null
+++ b/libsoup/soup-cache.c
@@ -0,0 +1,1667 @@
+/* -*- 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 <stdlib.h>
+#include <gio/gio.h>
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-cache.h"
+#include "soup-cache-private.h"
+#include "soup-date.h"
+#include "soup-enum-types.h"
+#include "soup-headers.h"
+#include "soup-session.h"
+#include "soup-session-feature.h"
+#include "soup-uri.h"
+
+static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
+static void 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 _SoupCacheEntry {
+	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;
+} SoupCacheEntry;
+
+struct _SoupCachePrivate {
+	char *cache_dir;
+	GHashTable *cache;
+	guint n_pending;
+	SoupSession *session;
+	SoupCacheType cache_type;
+	guint size;
+	guint max_size;
+	guint max_entry_data_size; /* Computed value. Here for performance reasons */
+	GList *lru_start;
+};
+
+typedef struct {
+	SoupCache *cache;
+	SoupCacheEntry *entry;
+	SoupMessage *msg;
+	gulong got_chunk_handler;
+	gulong got_body_handler;
+	gulong restarted_handler;
+} SoupCacheWritingFixture;
+
+enum {
+	PROP_0,
+	PROP_CACHE_DIR,
+	PROP_CACHE_TYPE
+};
+
+#define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
+
+G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+						soup_cache_session_feature_init))
+
+static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry);
+static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
+static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
+
+static SoupCacheability
+get_cacheability (SoupCache *cache, SoupMessage *msg)
+{
+	SoupCacheability cacheability;
+	const char *cache_control;
+
+	/* 1. The request method must be cacheable */
+	if (msg->method == SOUP_METHOD_GET)
+		cacheability = SOUP_CACHE_CACHEABLE;
+	else if (msg->method == SOUP_METHOD_HEAD ||
+		 msg->method == SOUP_METHOD_TRACE ||
+		 msg->method == SOUP_METHOD_CONNECT)
+		return SOUP_CACHE_UNCACHEABLE;
+	else
+		return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
+
+	cache_control = soup_message_headers_get (msg->response_headers, "Cache-Control");
+	if (cache_control) {
+		GHashTable *hash;
+		SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
+
+		hash = soup_header_parse_param_list (cache_control);
+
+		/* Shared caches MUST NOT store private resources */
+		if (priv->cache_type == SOUP_CACHE_SHARED) {
+			if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
+				soup_header_free_param_list (hash);
+				return 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 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 SOUP_CACHE_UNCACHEABLE;
+		}
+
+		soup_header_free_param_list (hash);
+	}
+
+	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 = SOUP_CACHE_UNCACHEABLE;
+		break;
+
+	case SOUP_STATUS_NOT_MODIFIED:
+		/* A 304 response validates an existing cache entry */
+		cacheability = SOUP_CACHE_VALIDATES;
+		break;
+
+	case SOUP_STATUS_MULTIPLE_CHOICES:
+	case SOUP_STATUS_MOVED_PERMANENTLY:
+	case SOUP_STATUS_GONE:
+		/* FIXME: cacheable unless indicated otherwise */
+		cacheability = SOUP_CACHE_UNCACHEABLE;
+		break;
+
+	case SOUP_STATUS_FOUND:
+	case SOUP_STATUS_TEMPORARY_REDIRECT:
+		/* FIXME: cacheable if explicitly indicated */
+		cacheability = 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 (SOUP_CACHE_UNCACHEABLE | 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 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 (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
+		break;
+	}
+
+	return cacheability;
+}
+
+static void
+soup_cache_entry_free (SoupCacheEntry *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 (SoupCacheEntry, 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
+soup_cache_entry_get_current_age (SoupCacheEntry *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
+soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
+{
+	guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) 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
+soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
+{
+	const char *cache_control;
+	const char *expires, *date, *last_modified;
+
+	cache_control = soup_message_headers_get (entry->headers, "Cache-Control");
+	if (cache_control) {
+		const char *max_age, *s_maxage;
+		gint64 freshness_lifetime = 0;
+		GHashTable *hash;
+		SoupCachePrivate *priv = 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 = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
+
+		/* Section 2.3.1 */
+		if (priv->cache_type == 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;
+		}
+
+		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 SoupCacheEntry *
+soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
+{
+	SoupCacheEntry *entry;
+	SoupMessageHeaders *headers;
+	const char *date;
+	char *md5;
+
+	entry = g_slice_new0 (SoupCacheEntry);
+	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 */
+	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
+soup_cache_writing_fixture_free (SoupCacheWritingFixture *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 (SoupCacheWritingFixture, fixture);
+}
+
+static void
+close_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
+{
+	SoupCacheEntry *entry = fixture->entry;
+	SoupCache *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;
+		soup_cache_entry_remove (cache, entry);
+		soup_cache_entry_free (entry, TRUE);
+		entry = NULL;
+	} else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
+		   entry->length != (gsize) 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;
+			soup_cache_entry_remove (cache, entry);
+			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 */
+	soup_cache_writing_fixture_free (fixture);
+}
+
+static void
+write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
+{
+	GOutputStream *stream = G_OUTPUT_STREAM (source);
+	GError *error = NULL;
+	gssize write_size;
+	SoupCacheEntry *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, SoupCacheWritingFixture *fixture)
+{
+	SoupCacheEntry *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, SoupCacheWritingFixture *fixture)
+{
+	SoupCacheEntry *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
+soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *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 (!entry->dirty);
+	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)
+{
+	SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
+	SoupCacheEntry *entry_b = (SoupCacheEntry *)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 (SoupCache *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 (SoupCache *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)) {
+		SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
+
+		/* Discard entries. Once cancelled resources will be
+		 * freed in close_ready_cb
+		 */
+		if (soup_cache_entry_remove (cache, old_entry)) {
+			soup_cache_entry_free (old_entry, TRUE);
+			lru_entry = cache->priv->lru_start;
+		} else
+			lru_entry = g_list_next (lru_entry);
+	}
+}
+
+static gboolean
+soup_cache_entry_insert_by_key (SoupCache *cache,
+				const char *key,
+				SoupCacheEntry *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, SoupCacheEntry *entry)
+{
+	/* FIXME: What should we do here exactly? */
+}
+
+static void
+append_to_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
+{
+	GFile *file = (GFile *)source;
+	GOutputStream *stream;
+	SoupCacheEntry *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;
+		soup_cache_entry_remove (fixture->cache, entry);
+		soup_cache_entry_free (entry, TRUE);
+		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)
+{
+	SoupCache *cache;
+	SoupCacheability cacheable;
+	RequestHelper *helper;
+	time_t request_time, response_time;
+
+	response_time = time (NULL);
+
+	helper = (RequestHelper *)user_data;
+	cache = 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 = soup_cache_get_cacheability (cache, msg);
+
+	if (cacheable & SOUP_CACHE_CACHEABLE) {
+		SoupCacheEntry *entry;
+		char *key;
+		GFile *file;
+		SoupCacheWritingFixture *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) {
+			soup_cache_entry_remove (cache, entry);
+			soup_cache_entry_free (entry, TRUE);
+		}
+
+		entry = soup_cache_entry_new (cache, msg, request_time, response_time);
+		entry->hits = 1;
+
+		/* Do not continue if it can not be stored */
+		if (!soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, TRUE)) {
+			soup_cache_entry_free (entry, TRUE);
+			return;
+		}
+
+		fixture = g_slice_new0 (SoupCacheWritingFixture);
+		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 & SOUP_CACHE_INVALIDATES) {
+		char *key;
+		SoupCacheEntry *entry;
+
+		key = soup_message_get_cache_key (msg);
+		entry = g_hash_table_lookup (cache->priv->cache, key);
+		g_free (key);
+
+		if (entry) {
+			if (soup_cache_entry_remove (cache, entry))
+				soup_cache_entry_free (entry, TRUE);
+		}
+	} else if (cacheable & SOUP_CACHE_VALIDATES) {
+		char *key;
+		SoupCacheEntry *entry;
+
+		key = soup_message_get_cache_key (msg);
+		entry = g_hash_table_lookup (cache->priv->cache, key);
+		g_free (key);
+
+		/* It's possible to get a CACHE_VALIDATES with no
+		 * entry in the hash table. This could happen if for
+		 * example the soup client is the one creating the
+		 * conditional request.
+		 */
+		if (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);
+			soup_cache_entry_set_freshness (entry, msg, cache);
+		}
+	}
+}
+
+GInputStream *
+soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
+{
+	char *key;
+	SoupCacheEntry *entry;
+	char *current_age;
+	GInputStream *stream = NULL;
+	GFile *file;
+
+	g_return_val_if_fail (SOUP_IS_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_free (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", 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)
+{
+	SoupCache *cache = SOUP_CACHE (feature);
+	cache->priv->session = session;
+
+	soup_cache_default_feature_interface->attach (feature, session);
+}
+
+static void
+soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+					gpointer interface_data)
+{
+	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
+soup_cache_init (SoupCache *cache)
+{
+	SoupCachePrivate *priv;
+
+	priv = cache->priv = 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 data,
+		   gpointer user_data)
+{
+	SoupCache *cache = (SoupCache *) user_data;
+	SoupCacheEntry *entry = (SoupCacheEntry *) data;
+
+	if (soup_cache_entry_remove (cache, entry))
+		soup_cache_entry_free (entry, FALSE);
+}
+
+static void
+soup_cache_finalize (GObject *object)
+{
+	SoupCachePrivate *priv;
+	GList *entries;
+
+	priv = SOUP_CACHE (object)->priv;
+
+	// Cannot use g_hash_table_foreach as callbacks must not modify the hash table
+	entries = g_hash_table_get_values (priv->cache);
+	g_list_foreach (entries, remove_cache_item, object);
+	g_list_free (entries);
+
+	g_hash_table_destroy (priv->cache);
+	g_free (priv->cache_dir);
+
+	g_list_free (priv->lru_start);
+	priv->lru_start = NULL;
+
+	G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
+}
+
+static void
+soup_cache_set_property (GObject *object, guint prop_id,
+				const GValue *value, GParamSpec *pspec)
+{
+	SoupCachePrivate *priv = 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
+soup_cache_get_property (GObject *object, guint prop_id,
+			 GValue *value, GParamSpec *pspec)
+{
+	SoupCachePrivate *priv = 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
+soup_cache_constructed (GObject *object)
+{
+	SoupCachePrivate *priv;
+
+	priv = 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 (soup_cache_parent_class)->constructed)
+		G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
+}
+
+static void
+soup_cache_class_init (SoupCacheClass *cache_class)
+{
+	GObjectClass *gobject_class = (GObjectClass *)cache_class;
+
+	gobject_class->finalize = soup_cache_finalize;
+	gobject_class->constructed = soup_cache_constructed;
+	gobject_class->set_property = soup_cache_set_property;
+	gobject_class->get_property = 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",
+							    SOUP_TYPE_CACHE_TYPE,
+							    SOUP_CACHE_SINGLE_USER,
+							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
+}
+
+/**
+ * soup_cache_new:
+ * @cache_dir: the directory to store the cached data, or %NULL to use the default one
+ * @cache_type: the #SoupCacheType of the cache
+ *
+ * Creates a new #SoupCache.
+ *
+ * Returns: a new #SoupCache
+ *
+ * Since: 2.28
+ **/
+SoupCache *
+soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
+{
+	return g_object_new (SOUP_TYPE_CACHE,
+			     "cache-dir", cache_dir,
+			     "cache-type", cache_type,
+			     NULL);
+}
+
+/**
+ * soup_cache_has_response:
+ * @cache: a #SoupCache
+ * @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
+ **/
+SoupCacheResponse
+soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
+{
+	char *key;
+	SoupCacheEntry *entry;
+	const char *cache_control;
+	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);
+	g_free (key);
+
+	/* 1. The presented Request-URI and that of stored response
+	 * match
+	 */
+	if (!entry)
+		return 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 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 SOUP_CACHE_RESPONSE_STALE;
+
+	/* 3. Selecting request-headers nominated by the stored
+	 * response (if any) match those presented.
+	 */
+
+	/* TODO */
+
+	/* 4. The request is a conditional request issued by the client.
+	 */
+	if (soup_message_headers_get (msg->request_headers, "If-Modified-Since") ||
+	    soup_message_headers_get (msg->request_headers, "If-None-Match"))
+		return SOUP_CACHE_RESPONSE_STALE;
+
+	/* 5. 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) {
+		GHashTable *hash = soup_header_parse_param_list (cache_control);
+
+		if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
+			soup_header_free_param_list (hash);
+			return 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);
+
+		soup_header_free_param_list (hash);
+
+		if (max_age != -1) {
+			guint current_age = 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 ((guint) max_age <= current_age && max_stale == -1)
+				return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+		}
+	}
+
+	/* 6. 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 SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+
+	if (!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 SOUP_CACHE_RESPONSE_FRESH;
+
+			if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
+				return SOUP_CACHE_RESPONSE_FRESH;
+		}
+
+		return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
+	}
+
+	return SOUP_CACHE_RESPONSE_FRESH;
+}
+
+/**
+ * soup_cache_get_cacheability:
+ * @cache: a #SoupCache
+ * @msg: a #SoupMessage
+ *
+ * Calculates whether the @msg can be cached or not.
+ *
+ * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
+ **/
+SoupCacheability
+soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
+{
+	g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
+	g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
+
+	return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
+}
+
+static gboolean
+force_flush_timeout (gpointer data)
+{
+	gboolean *forced = (gboolean *)data;
+	*forced = TRUE;
+
+	return FALSE;
+}
+
+/**
+ * soup_cache_flush:
+ * @cache: a #SoupCache
+ * @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
+soup_cache_flush (SoupCache *cache)
+{
+	GMainContext *async_context;
+	SoupSession *session;
+	guint timeout_id;
+	gboolean forced = FALSE;
+
+	g_return_if_fail (SOUP_IS_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 data,
+		  gpointer user_data)
+{
+	SoupCache *cache = (SoupCache *) user_data;
+	SoupCacheEntry *entry = (SoupCacheEntry *) data;
+
+	if (soup_cache_entry_remove (cache, entry))
+		soup_cache_entry_free (entry, TRUE);
+}
+
+/**
+ * soup_cache_clear:
+ * @cache: a #SoupCache
+ *
+ * Will remove all entries in the @cache plus all the cache files
+ * associated with them.
+ **/
+void
+soup_cache_clear (SoupCache *cache)
+{
+	GHashTable *hash;
+	GList *entries;
+
+	g_return_if_fail (SOUP_IS_CACHE (cache));
+
+	hash = cache->priv->cache;
+	g_return_if_fail (hash);
+
+	// Cannot use g_hash_table_foreach as callbacks must not modify the hash table
+	entries = g_hash_table_get_values (hash);
+	g_list_foreach (entries, clear_cache_item, cache);
+	g_list_free (entries);
+}
+
+SoupMessage *
+soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
+{
+	SoupMessage *msg;
+	SoupURI *uri;
+	SoupCacheEntry *entry;
+	char *key;
+	const char *value;
+
+	g_return_val_if_fail (SOUP_IS_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 SOUP_CACHE_FILE "soup.cache"
+
+#define SOUP_CACHE_HEADERS_FORMAT "{ss}"
+#define SOUP_CACHE_PHEADERS_FORMAT "(ssbuuuuua" SOUP_CACHE_HEADERS_FORMAT ")"
+#define SOUP_CACHE_ENTRIES_FORMAT "a" 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 SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
+
+static void
+pack_entry (gpointer data,
+	    gpointer user_data)
+{
+	SoupCacheEntry *entry = (SoupCacheEntry *) 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, SOUP_CACHE_HEADERS_FORMAT,
+					       header_key, header_value);
+	}
+
+	/* Entry data */
+	g_variant_builder_add (entries_builder, 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
+soup_cache_dump (SoupCache *cache)
+{
+	SoupCachePrivate *priv = 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 (SOUP_CACHE_ENTRIES_FORMAT, entries_builder);
+	g_variant_builder_unref (entries_builder);
+
+	filename = g_build_filename (priv->cache_dir, 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
+soup_cache_load (SoupCache *cache)
+{
+	gchar *filename = NULL, *contents = NULL;
+	GVariant *cache_variant;
+	GVariantIter *entries_iter, *headers_iter;
+	GVariantType *variant_format;
+	gsize length;
+	SoupCacheEntry *entry;
+	SoupCachePrivate *priv = cache->priv;
+
+	filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
+	if (!g_file_get_contents (filename, &contents, &length, NULL)) {
+		g_free (filename);
+		g_free (contents);
+		return;
+	}
+	g_free (filename);
+
+	variant_format = g_variant_type_new (SOUP_CACHE_ENTRIES_FORMAT);
+	cache_variant = g_variant_new_from_data (variant_format, (const gchar *)contents, length, FALSE, g_free, contents);
+	g_variant_type_free (variant_format);
+
+	g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &entries_iter);
+	entry = g_slice_new0 (SoupCacheEntry);
+
+	while (g_variant_iter_loop (entries_iter, 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, SOUP_CACHE_DECODE_HEADERS_FORMAT, &header_key, &header_value))
+			soup_message_headers_append (entry->headers, header_key, header_value);
+
+		/* Insert in cache */
+		if (!soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, FALSE))
+			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 (SoupCacheEntry);
+	}
+	/* Remove last created entry */
+	g_slice_free (SoupCacheEntry, 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
+soup_cache_set_max_size (SoupCache *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
+soup_cache_get_max_size (SoupCache *cache)
+{
+	return cache->priv->max_size;
+}
diff --git a/libsoup/soup-cache.h b/libsoup/soup-cache.h
new file mode 100644
index 0000000..8585d51
--- /dev/null
+++ b/libsoup/soup-cache.h
@@ -0,0 +1,98 @@
+/* -*- 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 SOUP_CACHE_H
+#define SOUP_CACHE_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include <libsoup/soup-types.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_CACHE            (soup_cache_get_type ())
+#define SOUP_CACHE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CACHE, SoupCache))
+#define SOUP_CACHE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CACHE, SoupCacheClass))
+#define SOUP_IS_CACHE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CACHE))
+#define SOUP_IS_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CACHE))
+#define SOUP_CACHE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CACHE, SoupCacheClass))
+
+typedef struct _SoupCache SoupCache;
+typedef struct _SoupCachePrivate SoupCachePrivate;
+
+typedef enum {
+	SOUP_CACHE_CACHEABLE = (1 << 0),
+	SOUP_CACHE_UNCACHEABLE = (1 << 1),
+	SOUP_CACHE_INVALIDATES = (1 << 2),
+	SOUP_CACHE_VALIDATES = (1 << 3)
+} SoupCacheability;
+
+typedef enum {
+	SOUP_CACHE_RESPONSE_FRESH,
+	SOUP_CACHE_RESPONSE_NEEDS_VALIDATION,
+	SOUP_CACHE_RESPONSE_STALE
+} SoupCacheResponse;
+
+typedef enum {
+	SOUP_CACHE_SINGLE_USER,
+	SOUP_CACHE_SHARED
+} SoupCacheType;
+
+struct _SoupCache {
+	GObject parent_instance;
+
+	SoupCachePrivate *priv;
+};
+
+typedef struct {
+	GObjectClass parent_class;
+
+	/* methods */
+	SoupCacheability (*get_cacheability) (SoupCache   *cache,
+					      SoupMessage *msg);
+
+	/* Padding for future expansion */
+	void (*_libsoup_reserved1)(void);
+	void (*_libsoup_reserved2)(void);
+	void (*_libsoup_reserved3)(void);
+} SoupCacheClass;
+
+GType      soup_cache_get_type     (void);
+SoupCache *soup_cache_new          (const char    *cache_dir,
+				    SoupCacheType  cache_type);
+void       soup_cache_flush        (SoupCache     *cache);
+void       soup_cache_clear        (SoupCache     *cache);
+
+void       soup_cache_dump         (SoupCache     *cache);
+void       soup_cache_load         (SoupCache     *cache);
+
+void       soup_cache_set_max_size (SoupCache     *cache,
+				    guint          max_size);
+guint      soup_cache_get_max_size (SoupCache     *cache);
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_CACHE_H */
+
diff --git a/libsoup/soup-directory-input-stream.c b/libsoup/soup-directory-input-stream.c
new file mode 100644
index 0000000..f127fcc
--- /dev/null
+++ b/libsoup/soup-directory-input-stream.c
@@ -0,0 +1,200 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008, 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.
+ */
+
+#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 (SoupDirectoryInputStream, soup_directory_input_stream, G_TYPE_INPUT_STREAM)
+
+static SoupBuffer *
+soup_directory_input_stream_parse_info (SoupDirectoryInputStream *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 *
+soup_directory_input_stream_read_next_file (SoupDirectoryInputStream  *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 = soup_directory_input_stream_parse_info (stream, info);
+	} while (buffer == NULL);
+
+	return buffer;
+}
+
+static gssize
+soup_directory_input_stream_read (GInputStream  *input,
+				  void          *buffer,
+				  gsize          count,
+				  GCancellable  *cancellable,
+				  GError       **error)
+{
+	SoupDirectoryInputStream *stream = SOUP_DIRECTORY_INPUT_STREAM (input);
+	gsize total, size;
+
+	for (total = 0; total < count; total += size) {
+		if (stream->buffer == NULL) {
+			stream->buffer = 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
+soup_directory_input_stream_close (GInputStream  *input,
+				   GCancellable  *cancellable,
+				   GError       **error)
+{
+	SoupDirectoryInputStream *stream = 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
+soup_directory_input_stream_class_init (SoupDirectoryInputStreamClass *stream_class)
+{
+	GInputStreamClass *inputstream_class = G_INPUT_STREAM_CLASS (stream_class);
+
+	inputstream_class->read_fn = soup_directory_input_stream_read;
+	inputstream_class->close_fn = soup_directory_input_stream_close;
+}
+
+static void
+soup_directory_input_stream_init (SoupDirectoryInputStream *stream)
+{
+	stream->buffer = soup_buffer_new (SOUP_MEMORY_STATIC,
+					  INIT_STRING,
+					  sizeof (INIT_STRING));
+}
+
+GInputStream *
+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 (SOUP_TYPE_DIRECTORY_INPUT_STREAM, NULL);
+
+	SOUP_DIRECTORY_INPUT_STREAM (stream)->enumerator = g_object_ref (enumerator);
+	SOUP_DIRECTORY_INPUT_STREAM (stream)->uri = soup_uri_to_string (uri, FALSE);
+
+	return stream;
+}
+
diff --git a/libsoup/soup-directory-input-stream.h b/libsoup/soup-directory-input-stream.h
new file mode 100644
index 0000000..ae2b4ba
--- /dev/null
+++ b/libsoup/soup-directory-input-stream.h
@@ -0,0 +1,62 @@
+/* -*- 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 SOUP_DIRECTORY_INPUT_STREAM_H
+#define 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 SOUP_TYPE_DIRECTORY_INPUT_STREAM            (soup_directory_input_stream_get_type ())
+#define SOUP_DIRECTORY_INPUT_STREAM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_DIRECTORY_INPUT_STREAM, SoupDirectoryInputStream))
+#define SOUP_DIRECTORY_INPUT_STREAM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_DIRECTORY_INPUT_STREAM, SoupDirectoryInputStreamClass))
+#define SOUP_IS_DIRECTORY_INPUT_STREAM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_DIRECTORY_INPUT_STREAM))
+#define SOUP_IS_DIRECTORY_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_DIRECTORY_INPUT_STREAM))
+#define SOUP_DIRECTORY_INPUT_STREAM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_DIRECTORY_INPUT_STREAM, SoupDirectoryInputStreamClass))
+
+typedef struct _SoupDirectoryInputStream SoupDirectoryInputStream;
+typedef struct _SoupDirectoryInputStreamClass SoupDirectoryInputStreamClass;
+
+struct _SoupDirectoryInputStream {
+	GInputStream parent;
+
+	GFileEnumerator *enumerator;
+	char *uri;
+	SoupBuffer *buffer;
+	gboolean done;
+};
+
+struct _SoupDirectoryInputStreamClass {
+	GInputStreamClass parent_class;
+};
+
+GType         soup_directory_input_stream_get_type (void);
+
+GInputStream *soup_directory_input_stream_new      (GFileEnumerator *enumerator,
+						    SoupURI         *uri);
+
+
+G_END_DECLS
+
+#endif /* SOUP_DIRECTORY_INPUT_STREAM_H */
diff --git a/libsoup/soup-enum-types.c.tmpl b/libsoup/soup-enum-types.c.tmpl
index ea10782..289cef0 100644
--- a/libsoup/soup-enum-types.c.tmpl
+++ b/libsoup/soup-enum-types.c.tmpl
@@ -1,5 +1,8 @@
 /*** BEGIN file-header ***/
 #include "soup.h"
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+#include "soup-cache.h"
+#include "soup-requester.h"
 /*** END file-header ***/
 
 /*** BEGIN file-production ***/
diff --git a/libsoup/soup-http-input-stream.c b/libsoup/soup-http-input-stream.c
new file mode 100644
index 0000000..cee978f
--- /dev/null
+++ b/libsoup/soup-http-input-stream.c
@@ -0,0 +1,921 @@
+/* soup-input-stream.c, based on gsocketinputstream.c
+ *
+ * Copyright (C) 2006-2007, 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 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 "soup-http-input-stream.h"
+#include "soup-session.h"
+
+static void soup_http_input_stream_seekable_iface_init (GSeekableIface *seekable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (SoupHTTPInputStream, soup_http_input_stream, G_TYPE_INPUT_STREAM,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,
+						soup_http_input_stream_seekable_iface_init))
+
+typedef void (*SoupHTTPInputStreamCallback)(GInputStream *);
+
+typedef struct {
+	SoupSession *session;
+	GMainContext *async_context;
+	SoupMessage *msg;
+	gboolean got_headers, finished;
+	goffset offset;
+
+	GCancellable *cancellable;
+	GSource *cancel_watch;
+	SoupHTTPInputStreamCallback got_headers_cb;
+	SoupHTTPInputStreamCallback got_chunk_cb;
+	SoupHTTPInputStreamCallback finished_cb;
+	SoupHTTPInputStreamCallback cancelled_cb;
+
+	guchar *leftover_buffer;
+	gsize leftover_bufsize, leftover_offset;
+
+	guchar *caller_buffer;
+	gsize caller_bufsize, caller_nread;
+	GAsyncReadyCallback outstanding_callback;
+	GSimpleAsyncResult *result;
+} SoupHTTPInputStreamPrivate;
+#define SOUP_HTTP_INPUT_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_HTTP_INPUT_STREAM, SoupHTTPInputStreamPrivate))
+
+
+static gssize   soup_http_input_stream_read (GInputStream         *stream,
+					     void                 *buffer,
+					     gsize count,
+					     GCancellable         *cancellable,
+					     GError              **error);
+static gboolean soup_http_input_stream_close (GInputStream         *stream,
+					      GCancellable         *cancellable,
+					      GError              **error);
+static void     soup_http_input_stream_read_async (GInputStream         *stream,
+						   void                 *buffer,
+						   gsize count,
+						   int io_priority,
+						   GCancellable         *cancellable,
+						   GAsyncReadyCallback callback,
+						   gpointer data);
+static gssize   soup_http_input_stream_read_finish (GInputStream         *stream,
+						    GAsyncResult         *result,
+						    GError              **error);
+static void     soup_http_input_stream_close_async (GInputStream         *stream,
+						    int io_priority,
+						    GCancellable         *cancellable,
+						    GAsyncReadyCallback callback,
+						    gpointer data);
+static gboolean soup_http_input_stream_close_finish (GInputStream         *stream,
+						     GAsyncResult         *result,
+						     GError              **error);
+
+static goffset  soup_http_input_stream_tell (GSeekable            *seekable);
+
+static gboolean soup_http_input_stream_can_seek (GSeekable            *seekable);
+static gboolean soup_http_input_stream_seek (GSeekable            *seekable,
+					     goffset offset,
+					     GSeekType type,
+					     GCancellable         *cancellable,
+					     GError              **error);
+
+static gboolean soup_http_input_stream_can_truncate (GSeekable            *seekable);
+static gboolean soup_http_input_stream_truncate (GSeekable            *seekable,
+						 goffset offset,
+						 GCancellable         *cancellable,
+						 GError              **error);
+
+static void soup_http_input_stream_got_headers (SoupMessage *msg, gpointer stream);
+static void soup_http_input_stream_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer stream);
+static void soup_http_input_stream_finished (SoupMessage *msg, gpointer stream);
+
+static void
+soup_http_input_stream_finalize (GObject *object)
+{
+	SoupHTTPInputStream *stream = SOUP_HTTP_INPUT_STREAM (object);
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+	g_object_unref (priv->session);
+
+	g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (soup_http_input_stream_got_headers), stream);
+	g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (soup_http_input_stream_got_chunk), stream);
+	g_signal_handlers_disconnect_by_func (priv->msg, G_CALLBACK (soup_http_input_stream_finished), stream);
+	g_object_unref (priv->msg);
+	g_free (priv->leftover_buffer);
+
+	if (G_OBJECT_CLASS (soup_http_input_stream_parent_class)->finalize)
+		(*G_OBJECT_CLASS (soup_http_input_stream_parent_class)->finalize)(object);
+}
+
+static void
+soup_http_input_stream_class_init (SoupHTTPInputStreamClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (SoupHTTPInputStreamPrivate));
+
+	gobject_class->finalize = soup_http_input_stream_finalize;
+
+	stream_class->read_fn = soup_http_input_stream_read;
+	stream_class->close_fn = soup_http_input_stream_close;
+	stream_class->read_async = soup_http_input_stream_read_async;
+	stream_class->read_finish = soup_http_input_stream_read_finish;
+	stream_class->close_async = soup_http_input_stream_close_async;
+	stream_class->close_finish = soup_http_input_stream_close_finish;
+}
+
+static void
+soup_http_input_stream_seekable_iface_init (GSeekableIface *seekable_iface)
+{
+	seekable_iface->tell = soup_http_input_stream_tell;
+	seekable_iface->can_seek = soup_http_input_stream_can_seek;
+	seekable_iface->seek = soup_http_input_stream_seek;
+	seekable_iface->can_truncate = soup_http_input_stream_can_truncate;
+	seekable_iface->truncate_fn = soup_http_input_stream_truncate;
+}
+
+static void
+soup_http_input_stream_init (SoupHTTPInputStream *stream)
+{
+	;
+}
+
+static void
+soup_http_input_stream_queue_message (SoupHTTPInputStream *stream)
+{
+	SoupHTTPInputStreamPrivate *priv = 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);
+}
+
+/**
+ * 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 soup_http_input_stream_send() or 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 %SOUP_HTTP_INPUT_STREAM_HTTP_ERROR.
+ *
+ * Internally, #SoupHTTPInputStream 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.
+ **/
+SoupHTTPInputStream *
+soup_http_input_stream_new (SoupSession *session, SoupMessage *msg)
+{
+	SoupHTTPInputStream *stream;
+	SoupHTTPInputStreamPrivate *priv;
+
+	g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+	stream = g_object_new (SOUP_TYPE_HTTP_INPUT_STREAM, NULL);
+	priv = 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 (soup_http_input_stream_got_headers), stream);
+	g_signal_connect (msg, "got_chunk",
+			  G_CALLBACK (soup_http_input_stream_got_chunk), stream);
+	g_signal_connect (msg, "finished",
+			  G_CALLBACK (soup_http_input_stream_finished), stream);
+
+	soup_http_input_stream_queue_message (stream);
+	return stream;
+}
+
+static void
+soup_http_input_stream_got_headers (SoupMessage *msg, gpointer stream)
+{
+	SoupHTTPInputStreamPrivate *priv = 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
+soup_http_input_stream_got_chunk (SoupMessage *msg, SoupBuffer *chunk_buffer,
+					 gpointer stream)
+{
+	SoupHTTPInputStreamPrivate *priv = 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 ("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
+soup_http_input_stream_finished (SoupMessage *msg, gpointer stream)
+{
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+	priv->finished = TRUE;
+
+	if (priv->finished_cb)
+		priv->finished_cb (stream);
+}
+
+static gboolean
+soup_http_input_stream_cancelled (GIOChannel *chan, GIOCondition condition,
+					 gpointer stream)
+{
+	SoupHTTPInputStreamPrivate *priv = 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
+soup_http_input_stream_prepare_for_io (GInputStream *stream,
+				       GCancellable *cancellable,
+				       guchar       *buffer,
+				       gsize count)
+{
+	SoupHTTPInputStreamPrivate *priv = 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,
+							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
+soup_http_input_stream_done_io (GInputStream *stream)
+{
+	SoupHTTPInputStreamPrivate *priv = 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 (SoupHTTPInputStreamPrivate *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 soup_http_input_stream_send(), assuming that the
+ * GInputStream pending flag has already been set. It is also used by
+ * soup_http_input_stream_send_async() in some circumstances.
+ */
+static gboolean
+soup_http_input_stream_send_internal (GInputStream  *stream,
+				      GCancellable  *cancellable,
+				      GError       **error)
+{
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (stream);
+
+	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);
+	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)
+{
+	SoupHTTPInputStreamPrivate *priv = 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);
+}
+
+/**
+ * soup_http_input_stream_send:
+ * @httpstream: a #SoupHTTPInputStream
+ * @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 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
+soup_http_input_stream_send (SoupHTTPInputStream  *httpstream,
+			     GCancellable         *cancellable,
+			     GError              **error)
+{
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (httpstream);
+	GInputStream *istream = (GInputStream *)httpstream;
+	gboolean result;
+
+	g_return_val_if_fail (SOUP_IS_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 = soup_http_input_stream_send_internal (istream, cancellable, error);
+	g_input_stream_clear_pending (istream);
+
+	return result;
+}
+
+static gssize
+soup_http_input_stream_read (GInputStream  *stream,
+			     void          *buffer,
+			     gsize          count,
+			     GCancellable  *cancellable,
+			     GError       **error)
+{
+	SoupHTTPInputStreamPrivate *priv = 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 */
+	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);
+	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
+soup_http_input_stream_close (GInputStream  *stream,
+			      GCancellable  *cancellable,
+			      GError       **error)
+{
+	SoupHTTPInputStreamPrivate *priv = 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);
+	SoupHTTPInputStreamPrivate *priv = 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 = 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
+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,
+					 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)
+{
+	SoupHTTPInputStreamPrivate *priv = 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;
+	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);
+	g_object_unref (result);
+}
+
+static void
+soup_http_input_stream_send_async_internal (GInputStream        *stream,
+					    int                  io_priority,
+					    GCancellable        *cancellable,
+					    GAsyncReadyCallback  callback,
+					    gpointer             user_data)
+{
+	SoupHTTPInputStreamPrivate *priv = 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)) {
+		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;
+
+	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,
+						  soup_http_input_stream_send_async);
+}
+
+/**
+ * soup_http_input_stream_send_async:
+ * @httpstream: a #SoupHTTPInputStream
+ * @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 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
+soup_http_input_stream_send_async (SoupHTTPInputStream *httpstream,
+				   int                  io_priority,
+				   GCancellable        *cancellable,
+				   GAsyncReadyCallback  callback,
+				   gpointer             user_data)
+{
+	GInputStream *istream = (GInputStream *)httpstream;
+	GError *error = NULL;
+
+	g_return_if_fail (SOUP_IS_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;
+	}
+	soup_http_input_stream_send_async_internal (istream, io_priority, cancellable,
+						    callback, user_data);
+}
+
+/**
+ * soup_http_input_stream_send_finish:
+ * @httpstream: a #SoupHTTPInputStream
+ * @result: a #GAsyncResult.
+ * @error: a #GError location to store the error occuring, or %NULL to
+ * ignore.
+ *
+ * Finishes a 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
+soup_http_input_stream_send_finish (SoupHTTPInputStream  *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) == 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)
+{
+	SoupHTTPInputStreamPrivate *priv = 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;
+	soup_http_input_stream_done_io (stream);
+
+	g_simple_async_result_complete (result);
+	g_object_unref (result);
+}
+
+static void
+soup_http_input_stream_read_async (GInputStream        *stream,
+				   void                *buffer,
+				   gsize                count,
+				   int                  io_priority,
+				   GCancellable        *cancellable,
+				   GAsyncReadyCallback  callback,
+				   gpointer             user_data)
+{
+	SoupHTTPInputStreamPrivate *priv = 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 (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,
+					    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;
+	soup_http_input_stream_prepare_for_io (stream, cancellable, buffer, count);
+}
+
+static gssize
+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) == soup_http_input_stream_read_async, -1);
+
+	return g_simple_async_result_get_op_res_gssize (simple);
+}
+
+static void
+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,
+					    soup_http_input_stream_close_async);
+	success = 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
+soup_http_input_stream_close_finish (GInputStream  *stream,
+				     GAsyncResult  *result,
+				     GError       **error)
+{
+	/* Failures handled in generic close_finish code */
+	return TRUE;
+}
+
+static goffset
+soup_http_input_stream_tell (GSeekable *seekable)
+{
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (seekable);
+
+	return priv->offset;
+}
+
+static gboolean
+soup_http_input_stream_can_seek (GSeekable *seekable)
+{
+	return TRUE;
+}
+
+extern void soup_message_io_cleanup (SoupMessage *msg);
+
+static gboolean
+soup_http_input_stream_seek (GSeekable     *seekable,
+			     goffset        offset,
+			     GSeekType      type,
+			     GCancellable  *cancellable,
+			     GError       **error)
+{
+	GInputStream *stream = G_INPUT_STREAM (seekable);
+	SoupHTTPInputStreamPrivate *priv = 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);
+
+	soup_http_input_stream_queue_message (SOUP_HTTP_INPUT_STREAM (stream));
+
+	g_input_stream_clear_pending (stream);
+	return TRUE;
+}
+
+static gboolean
+soup_http_input_stream_can_truncate (GSeekable *seekable)
+{
+	return FALSE;
+}
+
+static gboolean
+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 *
+soup_http_input_stream_get_message (SoupHTTPInputStream *httpstream)
+{
+	SoupHTTPInputStreamPrivate *priv = SOUP_HTTP_INPUT_STREAM_GET_PRIVATE (httpstream);
+	return priv->msg ? g_object_ref (priv->msg) : NULL;
+}
diff --git a/libsoup/soup-http-input-stream.h b/libsoup/soup-http-input-stream.h
new file mode 100644
index 0000000..4f23e93
--- /dev/null
+++ b/libsoup/soup-http-input-stream.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006, 2007, 2009, 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 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 __SOUP_HTTP_INPUT_STREAM_H__
+#define __SOUP_HTTP_INPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_HTTP_INPUT_STREAM         (soup_http_input_stream_get_type ())
+#define SOUP_HTTP_INPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SOUP_TYPE_HTTP_INPUT_STREAM, SoupHTTPInputStream))
+#define SOUP_HTTP_INPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), SOUP_TYPE_HTTP_INPUT_STREAM, SoupHTTPInputStreamClass))
+#define SOUP_IS_HTTP_INPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SOUP_TYPE_HTTP_INPUT_STREAM))
+#define SOUP_IS_HTTP_INPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SOUP_TYPE_HTTP_INPUT_STREAM))
+#define SOUP_HTTP_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SOUP_TYPE_HTTP_INPUT_STREAM, SoupHTTPInputStreamClass))
+
+typedef struct SoupHTTPInputStream SoupHTTPInputStream;
+typedef struct SoupHTTPInputStreamClass SoupHTTPInputStreamClass;
+
+struct SoupHTTPInputStream {
+	GInputStream parent;
+};
+
+struct SoupHTTPInputStreamClass {
+	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 soup_http_input_stream_get_type (void) G_GNUC_CONST;
+
+SoupHTTPInputStream *soup_http_input_stream_new         (SoupSession          *session,
+							 SoupMessage          *msg);
+
+gboolean             soup_http_input_stream_send        (SoupHTTPInputStream  *httpstream,
+							 GCancellable         *cancellable,
+							 GError              **error);
+
+void                 soup_http_input_stream_send_async  (SoupHTTPInputStream  *httpstream,
+							 int                   io_priority,
+							 GCancellable         *cancellable,
+							 GAsyncReadyCallback   callback,
+							 gpointer              user_data);
+gboolean             soup_http_input_stream_send_finish (SoupHTTPInputStream  *httpstream,
+							 GAsyncResult         *result,
+							 GError              **error);
+
+SoupMessage         *soup_http_input_stream_get_message (SoupHTTPInputStream  *httpstream);
+
+G_END_DECLS
+
+#endif /* __SOUP_HTTP_INPUT_STREAM_H__ */
diff --git a/libsoup/soup-request-data.c b/libsoup/soup-request-data.c
new file mode 100644
index 0000000..8449813
--- /dev/null
+++ b/libsoup/soup-request-data.c
@@ -0,0 +1,169 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-data.c: data: URI request object
+ *
+ * Copyright (C) 2009, 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request-data.h"
+
+#include "soup-requester.h"
+#include <libsoup/soup.h>
+#include <glib/gi18n.h>
+
+G_DEFINE_TYPE (SoupRequestData, soup_request_data, SOUP_TYPE_REQUEST)
+
+struct _SoupRequestDataPrivate {
+	gsize content_length;
+	char *content_type;
+};
+
+static void
+soup_request_data_init (SoupRequestData *data)
+{
+	data->priv = G_TYPE_INSTANCE_GET_PRIVATE (data, SOUP_TYPE_REQUEST_DATA, SoupRequestDataPrivate);
+}
+
+static void
+soup_request_data_finalize (GObject *object)
+{
+	SoupRequestData *data = SOUP_REQUEST_DATA (object);
+
+	g_free (data->priv->content_type);
+
+	G_OBJECT_CLASS (soup_request_data_parent_class)->finalize (object);
+}
+
+static gboolean
+soup_request_data_check_uri (SoupRequest  *request,
+			     SoupURI      *uri,
+			     GError      **error)
+{
+	return uri->host == NULL;
+}
+
+static GInputStream *
+soup_request_data_send (SoupRequest   *request,
+			GCancellable  *cancellable,
+			GError       **error)
+{
+	SoupRequestData *data = SOUP_REQUEST_DATA (request);
+	SoupURI *uri = soup_request_get_uri (request);
+	GInputStream *memstream;
+	const char *comma, *semi, *start, *end;
+	gboolean base64 = FALSE;
+	char *uristr;
+
+	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 ((size_t) (comma - semi), strlen (";base64"))))
+			base64 = TRUE;
+
+		if (end != uristr) {
+			data->priv->content_type = g_strndup (uristr, end - uristr);
+			if (!base64)
+				soup_uri_decode (data->priv->content_type);
+		}
+	}
+
+	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_malloc0 (inlen * 3 / 4 + 3);
+			data->priv->content_length =
+				g_base64_decode_step (start, inlen, buf,
+						      &state, &save);
+			if (state != 0) {
+				g_free (buf);
+				goto fail;
+			}
+		} else {
+			soup_uri_decode (start);
+			data->priv->content_length = strlen (start);
+			buf = g_memdup (start, data->priv->content_length);
+		}
+
+		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, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI,
+		     _("Unable to decode URI: %s"), start);
+	g_object_unref (memstream);
+	return NULL;
+}
+
+static goffset
+soup_request_data_get_content_length (SoupRequest *request)
+{
+	SoupRequestData *data = SOUP_REQUEST_DATA (request);
+
+	return data->priv->content_length;
+}
+
+static const char *
+soup_request_data_get_content_type (SoupRequest *request)
+{
+	SoupRequestData *data = SOUP_REQUEST_DATA (request);
+
+	return data->priv->content_type;
+}
+
+static void
+soup_request_data_class_init (SoupRequestDataClass *request_data_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (request_data_class);
+	SoupRequestClass *request_class =
+		SOUP_REQUEST_CLASS (request_data_class);
+
+	g_type_class_add_private (request_data_class, sizeof (SoupRequestDataPrivate));
+
+	object_class->finalize = soup_request_data_finalize;
+
+	request_class->check_uri = soup_request_data_check_uri;
+	request_class->send = soup_request_data_send;
+	request_class->get_content_length = soup_request_data_get_content_length;
+	request_class->get_content_type = soup_request_data_get_content_type;
+}
diff --git a/libsoup/soup-request-data.h b/libsoup/soup-request-data.h
new file mode 100644
index 0000000..aeb9599
--- /dev/null
+++ b/libsoup/soup-request-data.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009, 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 SOUP_REQUEST_DATA_H
+#define SOUP_REQUEST_DATA_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_REQUEST_DATA            (soup_request_data_get_type ())
+#define SOUP_REQUEST_DATA(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_REQUEST_DATA, SoupRequestData))
+#define SOUP_REQUEST_DATA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_REQUEST_DATA, SoupRequestDataClass))
+#define SOUP_IS_REQUEST_DATA(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_REQUEST_DATA))
+#define SOUP_IS_REQUEST_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_REQUEST_DATA))
+#define SOUP_REQUEST_DATA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUEST_DATA, SoupRequestDataClass))
+
+typedef struct _SoupRequestDataPrivate SoupRequestDataPrivate;
+
+typedef struct {
+	SoupRequest parent;
+
+	SoupRequestDataPrivate *priv;
+} SoupRequestData;
+
+typedef struct {
+	SoupRequestClass parent;
+} SoupRequestDataClass;
+
+GType soup_request_data_get_type (void);
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_REQUEST_DATA_H */
diff --git a/libsoup/soup-request-file.c b/libsoup/soup-request-file.c
new file mode 100644
index 0000000..7de3526
--- /dev/null
+++ b/libsoup/soup-request-file.c
@@ -0,0 +1,253 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-file.c: file: URI request object
+ *
+ * Copyright (C) 2009, 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include <glib/gi18n.h>
+
+#include "soup-request-file.h"
+#include "soup-directory-input-stream.h"
+#include "soup-requester.h"
+#include "soup-uri.h"
+
+G_DEFINE_TYPE (SoupRequestFile, soup_request_file, SOUP_TYPE_REQUEST)
+
+struct _SoupRequestFilePrivate {
+	GFile *gfile;
+
+	char *mime_type;
+	goffset size;
+};
+
+static void
+soup_request_file_init (SoupRequestFile *file)
+{
+	file->priv = G_TYPE_INSTANCE_GET_PRIVATE (file, SOUP_TYPE_REQUEST_FILE, SoupRequestFilePrivate);
+
+	file->priv->size = -1;
+}
+
+static void
+soup_request_file_finalize (GObject *object)
+{
+	SoupRequestFile *file = SOUP_REQUEST_FILE (object);
+
+	if (file->priv->gfile)
+		g_object_unref (file->priv->gfile);
+	g_free (file->priv->mime_type);
+
+	G_OBJECT_CLASS (soup_request_file_parent_class)->finalize (object);
+}
+
+static gboolean
+soup_request_file_check_uri (SoupRequest  *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 gboolean
+soup_request_file_ensure_file (SoupRequestFile  *file,
+			       GCancellable     *cancellable,
+			       GError          **error)
+{
+	SoupURI *uri;
+
+	if (file->priv->gfile)
+		return TRUE;
+
+	uri = soup_request_get_uri (SOUP_REQUEST (file));
+	if (uri->scheme == SOUP_URI_SCHEME_FILE) {
+		gchar *decoded_uri = soup_uri_decode (g_strdup (uri->path));
+
+		if (decoded_uri) {
+			file->priv->gfile = g_file_new_for_path (decoded_uri);
+			g_free (decoded_uri);
+		}
+
+		return TRUE;
+	}
+
+	g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME,
+		     _("Unsupported URI scheme '%s'"), uri->scheme);
+	return FALSE;
+}
+
+static GInputStream *
+soup_request_file_send (SoupRequest          *request,
+			GCancellable         *cancellable,
+			GError              **error)
+{
+	SoupRequestFile *file = SOUP_REQUEST_FILE (request);
+	GInputStream *stream;
+	GError *my_error = NULL;
+
+	if (!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 = soup_directory_input_stream_new (enumerator,
+									  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
+soup_request_file_send_async_thread (GSimpleAsyncResult *res,
+				     GObject            *object,
+				     GCancellable       *cancellable)
+{
+	GInputStream *stream;
+	SoupRequest *request;
+	GError *error = NULL;
+
+	request = SOUP_REQUEST (object);
+
+	stream = 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
+soup_request_file_send_async (SoupRequest          *request,
+			      GCancellable         *cancellable,
+			      GAsyncReadyCallback callback,
+			      gpointer user_data)
+{
+	GSimpleAsyncResult *res;
+
+	res = g_simple_async_result_new (G_OBJECT (request), callback, user_data, soup_request_file_send_async);
+
+	g_simple_async_result_run_in_thread (res, soup_request_file_send_async_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (res);
+}
+
+static GInputStream *
+soup_request_file_send_finish (SoupRequest          *request,
+			       GAsyncResult         *result,
+			       GError              **error)
+{
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == 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
+soup_request_file_get_content_length (SoupRequest *request)
+{
+	SoupRequestFile *file = SOUP_REQUEST_FILE (request);
+
+	return file->priv->size;
+}
+
+static const char *
+soup_request_file_get_content_type (SoupRequest *request)
+{
+	SoupRequestFile *file = SOUP_REQUEST_FILE (request);
+
+	if (!file->priv->mime_type)
+		return "application/octet-stream";
+
+	return file->priv->mime_type;
+}
+
+static void
+soup_request_file_class_init (SoupRequestFileClass *request_file_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (request_file_class);
+	SoupRequestClass *request_class =
+		SOUP_REQUEST_CLASS (request_file_class);
+
+	g_type_class_add_private (request_file_class, sizeof (SoupRequestFilePrivate));
+
+	object_class->finalize = soup_request_file_finalize;
+
+	request_class->check_uri = soup_request_file_check_uri;
+	request_class->send = soup_request_file_send;
+	request_class->send_async = soup_request_file_send_async;
+	request_class->send_finish = soup_request_file_send_finish;
+	request_class->get_content_length = soup_request_file_get_content_length;
+	request_class->get_content_type = soup_request_file_get_content_type;
+}
+
+GFile *
+soup_request_file_get_file (SoupRequestFile *file)
+{
+	return g_object_ref (file->priv->gfile);
+}
diff --git a/libsoup/soup-request-file.h b/libsoup/soup-request-file.h
new file mode 100644
index 0000000..acb1a08
--- /dev/null
+++ b/libsoup/soup-request-file.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009, 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 SOUP_REQUEST_FILE_H
+#define SOUP_REQUEST_FILE_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_REQUEST_FILE            (soup_request_file_get_type ())
+#define SOUP_REQUEST_FILE(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_REQUEST_FILE, SoupRequestFile))
+#define SOUP_REQUEST_FILE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_REQUEST_FILE, SoupRequestFileClass))
+#define SOUP_IS_REQUEST_FILE(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_REQUEST_FILE))
+#define SOUP_IS_REQUEST_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_REQUEST_FILE))
+#define SOUP_REQUEST_FILE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUEST_FILE, SoupRequestFileClass))
+
+typedef struct _SoupRequestFilePrivate SoupRequestFilePrivate;
+
+typedef struct {
+	SoupRequest parent;
+
+	SoupRequestFilePrivate *priv;
+} SoupRequestFile;
+
+typedef struct {
+	SoupRequestClass parent;
+} SoupRequestFileClass;
+
+GType  soup_request_file_get_type (void);
+
+GFile *soup_request_file_get_file (SoupRequestFile *file);
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_REQUEST_FILE_H */
diff --git a/libsoup/soup-request-http.c b/libsoup/soup-request-http.c
new file mode 100644
index 0000000..3b195ab
--- /dev/null
+++ b/libsoup/soup-request-http.c
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request-http.c: http: URI request object
+ *
+ * Copyright (C) 2009, 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request-http.h"
+#include "soup-cache.h"
+#include "soup-cache-private.h"
+#include "soup-content-sniffer.h"
+#include "soup-http-input-stream.h"
+#include "soup-message.h"
+#include "soup-session.h"
+#include "soup-uri.h"
+
+G_DEFINE_TYPE (SoupRequestHTTP, soup_request_http, SOUP_TYPE_REQUEST)
+
+struct _SoupRequestHTTPPrivate {
+	SoupMessage *msg;
+};
+
+static void
+soup_request_http_init (SoupRequestHTTP *http)
+{
+	http->priv = G_TYPE_INSTANCE_GET_PRIVATE (http, SOUP_TYPE_REQUEST_HTTP, SoupRequestHTTPPrivate);
+}
+
+static gboolean
+soup_request_http_check_uri (SoupRequest  *request,
+			     SoupURI      *uri,
+			     GError      **error)
+{
+	SoupRequestHTTP *http = 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
+soup_request_http_finalize (GObject *object)
+{
+	SoupRequestHTTP *http = SOUP_REQUEST_HTTP (object);
+
+	if (http->priv->msg)
+		g_object_unref (http->priv->msg);
+
+	G_OBJECT_CLASS (soup_request_http_parent_class)->finalize (object);
+}
+
+static GInputStream *
+soup_request_http_send (SoupRequest          *request,
+			GCancellable         *cancellable,
+			GError              **error)
+{
+	SoupHTTPInputStream *httpstream;
+	SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
+
+	httpstream = soup_http_input_stream_new (soup_request_get_session (request), http->priv->msg);
+	if (!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)
+{
+	SoupHTTPInputStream *httpstream = SOUP_HTTP_INPUT_STREAM (source);
+	GSimpleAsyncResult *simple = user_data;
+	GError *error = NULL;
+
+	if (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 {
+	SoupRequestHTTP *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;
+	SoupHTTPInputStream *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) {
+		SoupCache *cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
+
+		httpstream = (SoupHTTPInputStream *)soup_cache_send_response (cache, msg);
+		if (httpstream) {
+			g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
+
+			soup_message_got_headers (helper->original);
+
+			if (soup_session_get_feature_for_message (session, SOUP_TYPE_CONTENT_SNIFFER, helper->original)) {
+			 	const char *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 = soup_http_input_stream_new (session, helper->original);
+			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 = soup_http_input_stream_new (session, helper->original);
+		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 {
+	SoupRequestHTTP *http;
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+} SendAsyncHelper;
+
+static void soup_request_http_send_async (SoupRequest          *request,
+					  GCancellable         *cancellable,
+					  GAsyncReadyCallback callback,
+					  gpointer user_data);
+
+static gboolean
+send_async_cb (gpointer data)
+{
+	GSimpleAsyncResult *simple;
+	SoupHTTPInputStream *httpstream;
+	SoupSession *session;
+	SoupCache *cache;
+	SendAsyncHelper *helper = (SendAsyncHelper *)data;
+
+	session = soup_request_get_session (SOUP_REQUEST (helper->http));
+	cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
+
+	httpstream = (SoupHTTPInputStream *)soup_cache_send_response (cache, SOUP_MESSAGE (helper->http->priv->msg));
+
+	if (httpstream) {
+		simple = g_simple_async_result_new (G_OBJECT (helper->http),
+						    helper->callback, helper->user_data,
+						    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);
+
+		if (soup_session_get_feature_for_message (session, SOUP_TYPE_CONTENT_SNIFFER, helper->http->priv->msg)) {
+			const char *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_object_unref (helper->http);
+	g_slice_free (SendAsyncHelper, helper);
+
+	return FALSE;
+}
+
+static void
+soup_request_http_send_async (SoupRequest          *request,
+			      GCancellable         *cancellable,
+			      GAsyncReadyCallback   callback,
+			      gpointer              user_data)
+{
+	SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
+	SoupHTTPInputStream *httpstream;
+	GSimpleAsyncResult *simple;
+	SoupSession *session;
+	SoupCache *cache;
+
+	session = soup_request_get_session (request);
+	cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
+
+	if (cache) {
+		SoupCacheResponse response;
+
+		response = soup_cache_has_response (cache, http->priv->msg);
+		if (response == 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 = g_object_ref (http);
+			helper->callback = callback;
+			helper->user_data = user_data;
+			g_timeout_add (0, send_async_cb, helper);
+			return;
+		} else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) {
+			SoupMessage *conditional_msg;
+			ConditionalHelper *helper;
+
+			conditional_msg = 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,
+					    soup_request_http_send_async);
+	httpstream = soup_http_input_stream_new (soup_request_get_session (request),
+							http->priv->msg);
+	soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
+						  cancellable, sent_async, simple);
+}
+
+static GInputStream *
+soup_request_http_send_finish (SoupRequest          *request,
+			       GAsyncResult         *result,
+			       GError              **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), 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
+soup_request_http_get_content_length (SoupRequest *request)
+{
+	SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
+
+	return soup_message_headers_get_content_length (http->priv->msg->response_headers);
+}
+
+static const char *
+soup_request_http_get_content_type (SoupRequest *request)
+{
+	SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
+
+	return soup_message_headers_get_content_type (http->priv->msg->response_headers, NULL);
+}
+
+static void
+soup_request_http_class_init (SoupRequestHTTPClass *request_http_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (request_http_class);
+	SoupRequestClass *request_class =
+		SOUP_REQUEST_CLASS (request_http_class);
+
+	g_type_class_add_private (request_http_class, sizeof (SoupRequestHTTPPrivate));
+
+	object_class->finalize = soup_request_http_finalize;
+
+	request_class->check_uri = soup_request_http_check_uri;
+	request_class->send = soup_request_http_send;
+	request_class->send_async = soup_request_http_send_async;
+	request_class->send_finish = soup_request_http_send_finish;
+	request_class->get_content_length = soup_request_http_get_content_length;
+	request_class->get_content_type = soup_request_http_get_content_type;
+}
+
+/**
+ * soup_request_http_get_message:
+ * @http: a #SoupRequestHTTP object
+ *
+ * Gets a new reference to the #SoupMessage associated to this SoupRequest
+ *
+ * Returns: a new reference to the #SoupMessage
+ **/
+SoupMessage *
+soup_request_http_get_message (SoupRequestHTTP *http)
+{
+	g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (http), NULL);
+
+	return g_object_ref (http->priv->msg);
+}
diff --git a/libsoup/soup-request-http.h b/libsoup/soup-request-http.h
new file mode 100644
index 0000000..6402646
--- /dev/null
+++ b/libsoup/soup-request-http.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009, 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 SOUP_REQUEST_HTTP_H
+#define SOUP_REQUEST_HTTP_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_REQUEST_HTTP            (soup_request_http_get_type ())
+#define SOUP_REQUEST_HTTP(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_REQUEST_HTTP, SoupRequestHTTP))
+#define SOUP_REQUEST_HTTP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_REQUEST_HTTP, SoupRequestHTTPClass))
+#define SOUP_IS_REQUEST_HTTP(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_REQUEST_HTTP))
+#define SOUP_IS_REQUEST_HTTP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_REQUEST_HTTP))
+#define SOUP_REQUEST_HTTP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUEST_HTTP, SoupRequestHTTPClass))
+
+typedef struct _SoupRequestHTTPPrivate SoupRequestHTTPPrivate;
+
+typedef struct {
+	SoupRequest parent;
+
+	SoupRequestHTTPPrivate *priv;
+} SoupRequestHTTP;
+
+typedef struct {
+	SoupRequestClass parent;
+} SoupRequestHTTPClass;
+
+GType soup_request_http_get_type (void);
+
+SoupMessage *soup_request_http_get_message (SoupRequestHTTP *http);
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_REQUEST_HTTP_H */
diff --git a/libsoup/soup-request.c b/libsoup/soup-request.c
new file mode 100644
index 0000000..1ba9550
--- /dev/null
+++ b/libsoup/soup-request.c
@@ -0,0 +1,284 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-request.c: Protocol-independent streaming request interface
+ *
+ * Copyright (C) 2009, 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-request.h"
+#include "soup-requester.h"
+#include "soup-session.h"
+#include "soup-uri.h"
+
+/**
+ * SECTION:soup-request
+ * @short_description: Protocol-independent streaming request interface
+ *
+ * FIXME
+ **/
+
+/**
+ * SoupRequest:
+ *
+ * FIXME
+ *
+ * Since: 2.30
+ **/
+
+static void soup_request_initable_interface_init (GInitableIface *initable_interface);
+
+G_DEFINE_TYPE_WITH_CODE (SoupRequest, soup_request, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+						soup_request_initable_interface_init))
+
+enum {
+	PROP_0,
+	PROP_URI,
+	PROP_SESSION
+};
+
+struct _SoupRequestPrivate {
+	SoupURI *uri;
+	SoupSession *session;
+};
+
+static void
+soup_request_init (SoupRequest *request)
+{
+	request->priv = G_TYPE_INSTANCE_GET_PRIVATE (request, SOUP_TYPE_REQUEST, SoupRequestPrivate);
+}
+
+static void
+soup_request_finalize (GObject *object)
+{
+	SoupRequest *request = 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 (soup_request_parent_class)->finalize (object);
+}
+
+static void
+soup_request_set_property (GObject      *object,
+			   guint prop_id,
+			   const GValue *value,
+			   GParamSpec   *pspec)
+{
+	SoupRequest *request = 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
+soup_request_get_property (GObject    *object,
+			   guint prop_id,
+			   GValue     *value,
+			   GParamSpec *pspec)
+{
+	SoupRequest *request = 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
+soup_request_initable_init (GInitable     *initable,
+			    GCancellable  *cancellable,
+			    GError       **error)
+{
+	SoupRequest *request = SOUP_REQUEST (initable);
+	gboolean ok;
+
+	if (!request->priv->uri) {
+		g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI,
+			     _("No URI provided"));
+		return FALSE;
+	}
+
+	ok = 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, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI,
+			     _("Invalid '%s' URI: %s"),
+			     request->priv->uri->scheme,
+			     uri_string);
+		g_free (uri_string);
+	}
+
+	return ok;
+}
+
+static gboolean
+soup_request_default_check_uri (SoupRequest  *request,
+				SoupURI      *uri,
+				GError      **error)
+{
+	return TRUE;
+}
+
+/* Default implementation: assume the sync implementation doesn't block */
+static void
+soup_request_default_send_async (SoupRequest          *request,
+				 GCancellable         *cancellable,
+				 GAsyncReadyCallback   callback,
+				 gpointer              user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (G_OBJECT (request),
+					    callback, user_data,
+					    soup_request_default_send_async);
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
+}
+
+static GInputStream *
+soup_request_default_send_finish (SoupRequest          *request,
+				  GAsyncResult         *result,
+				  GError              **error)
+{
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), soup_request_default_send_async), NULL);
+
+	return soup_request_send (request, NULL, error);
+}
+
+GInputStream *
+soup_request_send (SoupRequest          *request,
+		   GCancellable         *cancellable,
+		   GError              **error)
+{
+	return SOUP_REQUEST_GET_CLASS (request)->
+		send (request, cancellable, error);
+}
+
+void
+soup_request_send_async (SoupRequest          *request,
+			 GCancellable         *cancellable,
+			 GAsyncReadyCallback callback,
+			 gpointer user_data)
+{
+	SOUP_REQUEST_GET_CLASS (request)->
+		send_async (request, cancellable, callback, user_data);
+}
+
+GInputStream *
+soup_request_send_finish (SoupRequest          *request,
+			  GAsyncResult         *result,
+			  GError              **error)
+{
+	return SOUP_REQUEST_GET_CLASS (request)->
+		send_finish (request, result, error);
+}
+
+static void
+soup_request_class_init (SoupRequestClass *request_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (request_class);
+
+	g_type_class_add_private (request_class, sizeof (SoupRequestPrivate));
+
+	request_class->check_uri = soup_request_default_check_uri;
+	request_class->send_async = soup_request_default_send_async;
+	request_class->send_finish = soup_request_default_send_finish;
+
+	object_class->finalize = soup_request_finalize;
+	object_class->set_property = soup_request_set_property;
+	object_class->get_property = soup_request_get_property;
+
+	g_object_class_install_property (
+		 object_class, PROP_URI,
+		 g_param_spec_boxed (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 (SOUP_REQUEST_SESSION,
+				      "Session",
+				      "The request's session",
+				      SOUP_TYPE_SESSION,
+				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+soup_request_initable_interface_init (GInitableIface *initable_interface)
+{
+	initable_interface->init = soup_request_initable_init;
+}
+
+SoupURI *
+soup_request_get_uri (SoupRequest *request)
+{
+	return request->priv->uri;
+}
+
+SoupSession *
+soup_request_get_session (SoupRequest *request)
+{
+	return request->priv->session;
+}
+
+goffset
+soup_request_get_content_length (SoupRequest *request)
+{
+	return SOUP_REQUEST_GET_CLASS (request)->get_content_length (request);
+}
+
+const char *
+soup_request_get_content_type (SoupRequest  *request)
+{
+	return SOUP_REQUEST_GET_CLASS (request)->get_content_type (request);
+}
diff --git a/libsoup/soup-request.h b/libsoup/soup-request.h
new file mode 100644
index 0000000..5b4fcd2
--- /dev/null
+++ b/libsoup/soup-request.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009, 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 SOUP_REQUEST_H
+#define SOUP_REQUEST_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include <gio/gio.h>
+
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_REQUEST            (soup_request_get_type ())
+#define SOUP_REQUEST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_REQUEST, SoupRequest))
+#define SOUP_REQUEST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_REQUEST, SoupRequestClass))
+#define SOUP_IS_REQUEST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_REQUEST))
+#define SOUP_IS_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_REQUEST))
+#define SOUP_REQUEST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUEST, SoupRequestClass))
+
+typedef struct _SoupRequest SoupRequest;
+typedef struct _SoupRequestPrivate SoupRequestPrivate;
+typedef struct _SoupRequestClass SoupRequestClass;
+
+struct _SoupRequest {
+	GObject parent;
+
+	SoupRequestPrivate *priv;
+};
+
+struct _SoupRequestClass {
+	GObjectClass parent;
+
+	gboolean       (*check_uri)          (SoupRequest          *req_base,
+					      SoupURI              *uri,
+					      GError              **error);
+
+	GInputStream * (*send)               (SoupRequest          *request,
+					      GCancellable         *cancellable,
+					      GError              **error);
+	void           (*send_async)         (SoupRequest          *request,
+					      GCancellable         *cancellable,
+					      GAsyncReadyCallback   callback,
+					      gpointer              user_data);
+	GInputStream * (*send_finish)        (SoupRequest          *request,
+					      GAsyncResult         *result,
+					      GError              **error);
+
+	goffset        (*get_content_length) (SoupRequest          *request);
+	const char *   (*get_content_type)   (SoupRequest          *request);
+};
+
+GType soup_request_get_type (void);
+
+#define SOUP_REQUEST_URI     "uri"
+#define SOUP_REQUEST_SESSION "session"
+
+GInputStream *soup_request_send               (SoupRequest          *request,
+					       GCancellable         *cancellable,
+					       GError              **error);
+void          soup_request_send_async         (SoupRequest          *request,
+					       GCancellable         *cancellable,
+					       GAsyncReadyCallback   callback,
+					       gpointer              user_data);
+GInputStream *soup_request_send_finish        (SoupRequest          *request,
+					       GAsyncResult         *result,
+					       GError              **error);
+
+SoupURI      *soup_request_get_uri            (SoupRequest          *request);
+SoupSession  *soup_request_get_session        (SoupRequest          *request);
+
+goffset       soup_request_get_content_length (SoupRequest          *request);
+const char   *soup_request_get_content_type   (SoupRequest          *request);
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_REQUEST_H */
diff --git a/libsoup/soup-requester.c b/libsoup/soup-requester.c
new file mode 100644
index 0000000..072b8be
--- /dev/null
+++ b/libsoup/soup-requester.c
@@ -0,0 +1,188 @@
+/* -*- 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 <glib/gi18n.h>
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include "soup-requester.h"
+#include "soup-request-data.h"
+#include "soup-request-file.h"
+#include "soup-request-http.h"
+#include "soup-uri.h"
+
+struct _SoupRequesterPrivate {
+	GHashTable *request_types;
+};
+
+G_DEFINE_TYPE (SoupRequester, soup_requester, G_TYPE_OBJECT)
+
+static void
+soup_requester_init (SoupRequester *requester)
+{
+	requester->priv = G_TYPE_INSTANCE_GET_PRIVATE (requester,
+						       SOUP_TYPE_REQUESTER,
+						       SoupRequesterPrivate);
+
+	requester->priv->request_types =
+		g_hash_table_new_full (soup_str_case_hash,
+				       soup_str_case_equal,
+				       g_free, NULL);
+	g_hash_table_insert (requester->priv->request_types, g_strdup ("file"),
+			     GSIZE_TO_POINTER (SOUP_TYPE_REQUEST_FILE));
+	g_hash_table_insert (requester->priv->request_types, g_strdup ("data"),
+			     GSIZE_TO_POINTER (SOUP_TYPE_REQUEST_DATA));
+	g_hash_table_insert (requester->priv->request_types, g_strdup ("http"),
+			     GSIZE_TO_POINTER (SOUP_TYPE_REQUEST_HTTP));
+	g_hash_table_insert (requester->priv->request_types, g_strdup ("https"),
+			     GSIZE_TO_POINTER (SOUP_TYPE_REQUEST_HTTP));
+}
+
+static void
+finalize (GObject *object)
+{
+	SoupRequester *requester = SOUP_REQUESTER (object);
+
+	if (requester->priv->request_types)
+		g_hash_table_destroy (requester->priv->request_types);
+
+	G_OBJECT_CLASS (soup_requester_parent_class)->finalize (object);
+}
+
+static void
+soup_requester_class_init (SoupRequesterClass *requester_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (requester_class);
+
+	g_type_class_add_private (requester_class, sizeof (SoupRequesterPrivate));
+
+	/* virtual method override */
+	object_class->finalize = finalize;
+}
+
+SoupRequester *
+soup_requester_new (void)
+{
+	return g_object_new (SOUP_TYPE_REQUESTER, NULL);
+}
+
+SoupRequest *
+soup_requester_request (SoupRequester *requester, const char *uri_string,
+			SoupSession *session, GError **error)
+{
+	SoupURI *uri;
+	SoupRequest *req;
+
+	uri = soup_uri_new (uri_string);
+	if (!uri) {
+		g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI,
+			     _("Could not parse URI '%s'"), uri_string);
+		return NULL;
+	}
+
+	req = soup_requester_request_uri (requester, uri, session, error);
+	soup_uri_free (uri);
+	return req;
+}
+
+SoupRequest *
+soup_requester_request_uri (SoupRequester *requester, SoupURI *uri,
+			    SoupSession *session, GError **error)
+{
+	GType request_type;
+
+	g_return_val_if_fail (SOUP_IS_REQUESTER (requester), NULL);
+
+	request_type = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (requester->priv->request_types, uri->scheme));
+	if (!request_type) {
+		g_set_error (error, SOUP_REQUESTER_ERROR,
+			     SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME,
+			     _("Unsupported URI scheme '%s'"), uri->scheme);
+		return NULL;
+	}
+
+	if (g_type_is_a (request_type, G_TYPE_INITABLE)) {
+		return g_initable_new (request_type, NULL, error,
+				       "uri", uri,
+				       "session", session,
+				       NULL);
+	} else {
+		return g_object_new (request_type,
+				     "uri", uri,
+				     "session", session,
+				     NULL);
+	}
+}
+
+/* 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
+soup_requester_add_protocol (SoupRequester *requester,
+			     const char    *scheme,
+			     GType          request_type)
+{
+	g_return_if_fail (SOUP_IS_REQUESTER (requester));
+	g_return_if_fail (soup_scheme_is_valid (scheme));
+
+	g_hash_table_insert (requester->priv->request_types, g_strdup (scheme),
+			     GSIZE_TO_POINTER (request_type));
+}
+
+void
+soup_requester_remove_protocol (SoupRequester *requester,
+				       const char  *scheme)
+{
+	g_return_if_fail (SOUP_IS_REQUESTER (requester));
+	g_return_if_fail (soup_scheme_is_valid (scheme));
+
+	g_hash_table_remove (requester->priv->request_types, scheme);
+}
+
+GQuark
+soup_requester_error_quark (void)
+{
+	static GQuark error;
+	if (!error)
+		error = g_quark_from_static_string ("soup_requester_error_quark");
+	return error;
+}
diff --git a/libsoup/soup-requester.h b/libsoup/soup-requester.h
new file mode 100644
index 0000000..68b4cd7
--- /dev/null
+++ b/libsoup/soup-requester.h
@@ -0,0 +1,84 @@
+/* -*- 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 SOUP_REQUESTER_H
+#define SOUP_REQUESTER_H 1
+
+#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-request.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_REQUESTER            (soup_requester_get_type ())
+#define SOUP_REQUESTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_REQUESTER, SoupRequester))
+#define SOUP_REQUESTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_REQUESTER, SoupRequesterClass))
+#define SOUP_IS_REQUESTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_REQUESTER))
+#define SOUP_IS_REQUESTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_REQUESTER))
+#define SOUP_REQUESTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUESTER, SoupRequesterClass))
+
+typedef struct _SoupRequester SoupRequester;
+typedef struct _SoupRequesterPrivate SoupRequesterPrivate;
+
+struct _SoupRequester {
+	GObject parent;
+
+	SoupRequesterPrivate *priv;
+};
+
+typedef struct {
+	GObjectClass parent_class;
+} SoupRequesterClass;
+
+GType          soup_requester_get_type        (void);
+
+SoupRequester *soup_requester_new             (void);
+
+SoupRequest   *soup_requester_request         (SoupRequester  *requester,
+					       const char     *uri_string,
+					       SoupSession    *session,
+					       GError        **error);
+
+SoupRequest   *soup_requester_request_uri     (SoupRequester  *requester,
+					       SoupURI        *uri,
+					       SoupSession    *session,
+					       GError        **error);
+
+void           soup_requester_add_protocol    (SoupRequester  *requester,
+					       const char     *scheme,
+					       GType           request_type);
+
+void           soup_requester_remove_protocol (SoupRequester  *requester,
+					       const char     *scheme);
+
+GQuark soup_requester_error_quark (void);
+#define SOUP_REQUESTER_ERROR soup_requester_error_quark ()
+
+typedef enum {
+	SOUP_REQUESTER_ERROR_BAD_URI,
+	SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME
+} SoupRequesterError;
+
+G_END_DECLS
+
+#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */
+
+#endif /* SOUP_REQUESTER_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]