a7a769f3创建于 2023年11月20日历史提交
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
 * Copyright 2012 Red Hat, Inc.
 */

#include "test-utils.h"
#include <glib/gstdio.h>

static void
server_callback (SoupServer        *server,
		 SoupServerMessage *msg,
		 const char        *path,
		 GHashTable        *query,
		 gpointer           data)
{
	const char *last_modified, *etag;
	const char *header;
	const char *method;
	SoupMessageHeaders *request_headers;
	SoupMessageHeaders *response_headers;
	guint status = SOUP_STATUS_OK;

	method = soup_server_message_get_method (msg);

	if (method != SOUP_METHOD_GET && method != SOUP_METHOD_POST) {
		soup_server_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED, NULL);
		return;
	}

	request_headers = soup_server_message_get_request_headers (msg);
	response_headers = soup_server_message_get_response_headers (msg);
	header = soup_message_headers_get_one (request_headers,
					       "Test-Set-Expires");
	if (header) {
		soup_message_headers_append (response_headers,
					     "Expires",
					     header);
	}

	header = soup_message_headers_get_one (request_headers,
					       "Test-Set-Cache-Control");
	if (header) {
		soup_message_headers_append (response_headers,
					     "Cache-Control",
					     header);
	}

	last_modified = soup_message_headers_get_one (request_headers,
						      "Test-Set-Last-Modified");
	if (last_modified) {
		soup_message_headers_append (response_headers,
					     "Last-Modified",
					     last_modified);
	}

	etag = soup_message_headers_get_one (request_headers,
					     "Test-Set-ETag");
	if (etag) {
		soup_message_headers_append (response_headers,
					     "ETag",
					     etag);
	}


	header = soup_message_headers_get_one (request_headers,
					       "If-Modified-Since");
	if (header && last_modified) {
		GDateTime *modified_date, *header_date;

		modified_date = soup_date_time_new_from_http_string (last_modified);
		header_date = soup_date_time_new_from_http_string (header);

		if (g_date_time_compare (modified_date, header_date) <= 0)
			status = SOUP_STATUS_NOT_MODIFIED;

                g_date_time_unref (modified_date);
                g_date_time_unref (header_date);
	}

	header = soup_message_headers_get_one (request_headers,
					       "If-None-Match");
	if (header && etag) {
		if (!strcmp (header, etag))
			status = SOUP_STATUS_NOT_MODIFIED;
	}

	header = soup_message_headers_get_one (request_headers,
					       "Test-Set-My-Header");
	if (header) {
		soup_message_headers_append (response_headers,
					     "My-Header",
					     header);
	}

	if (status == SOUP_STATUS_OK) {
		GChecksum *sum;
		const char *body;

		sum = g_checksum_new (G_CHECKSUM_SHA256);
		g_checksum_update (sum, (guchar *)path, strlen (path));
		if (last_modified)
			g_checksum_update (sum, (guchar *)last_modified, strlen (last_modified));
		if (etag)
			g_checksum_update (sum, (guchar *)etag, strlen (etag));
		body = g_checksum_get_string (sum);
		soup_server_message_set_response (msg, "text/plain",
						  SOUP_MEMORY_COPY,
						  body, strlen (body) + 1);
		g_checksum_free (sum);
	}
	soup_server_message_set_status (msg, status, NULL);
}

static gboolean
is_network_stream (GInputStream *stream)
{
	while (G_IS_FILTER_INPUT_STREAM (stream))
		stream = G_FILTER_INPUT_STREAM (stream)->base_stream;

	return !G_IS_FILE_INPUT_STREAM (stream);
}

static char *do_request (SoupSession        *session,
			 GUri               *base_uri,
			 const char         *method,
			 const char         *path,
			 SoupMessageHeaders *response_headers,
			 ...) G_GNUC_NULL_TERMINATED;

static gboolean last_request_hit_network;
static gboolean last_request_validated;
static gboolean last_request_unqueued;

static void
copy_headers (const char         *name,
	      const char         *value,
	      gpointer            user_data)
{
	SoupMessageHeaders *headers = (SoupMessageHeaders *) user_data;
	soup_message_headers_append (headers, name, value);
}

static char *
do_request (SoupSession        *session,
	    GUri               *base_uri,
	    const char         *method,
	    const char         *path,
	    SoupMessageHeaders *response_headers,
	    ...)
{
	SoupMessage *msg;
	GInputStream *stream;
	GUri *uri;
	va_list ap;
	const char *header, *value;
	char buf[256];
	gsize nread;
	GError *error = NULL;

	last_request_validated = last_request_hit_network = FALSE;
	last_request_unqueued = FALSE;

	uri = g_uri_parse_relative (base_uri, path, SOUP_HTTP_URI_FLAGS, NULL);
	msg = soup_message_new_from_uri (method, uri);
	g_uri_unref (uri);

	va_start (ap, response_headers);
	while ((header = va_arg (ap, const char *))) {
		value = va_arg (ap, const char *);
		soup_message_headers_append (soup_message_get_request_headers (msg),
					     header, value);
	}
	va_end (ap);

	stream = soup_test_request_send (session, msg, NULL, 0, &error);
	if (!stream) {
		debug_printf (1, "    could not send request: %s\n",
			      error->message);
		g_error_free (error);
		g_object_unref (msg);
		return NULL;
	}

	if (response_headers)
		soup_message_headers_foreach (soup_message_get_response_headers (msg), copy_headers, response_headers);

	g_object_unref (msg);

	if (last_request_validated)
		last_request_unqueued = FALSE;
	else
		soup_test_assert (!last_request_unqueued,
				  "Request unqueued before finishing");

	last_request_hit_network = is_network_stream (stream);

	g_input_stream_read_all (stream, buf, sizeof (buf), &nread,
				 NULL, &error);
	if (error) {
		debug_printf (1, "    could not read response: %s\n",
			      error->message);
		g_clear_error (&error);
	}
	soup_test_request_close_stream (stream, NULL, &error);
	if (error) {
		debug_printf (1, "    could not close stream: %s\n",
			      error->message);
		g_clear_error (&error);
	}
	g_object_unref (stream);

	/* Cache writes are G_PRIORITY_LOW, so they won't have happened yet... */
	soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE));

	return nread ? g_memdup2 (buf, nread) : g_strdup ("");
}

static void
do_request_with_cancel (SoupSession          *session,
			GUri                 *base_uri,
			const char           *method,
			const char           *path,
			SoupTestRequestFlags  flags)
{
	SoupMessage *msg;
	GInputStream *stream;
	GUri *uri;
	GError *error = NULL;
	GCancellable *cancellable;

	last_request_validated = last_request_hit_network = last_request_unqueued = FALSE;

	uri = g_uri_parse_relative (base_uri, path, SOUP_HTTP_URI_FLAGS, NULL);
	msg = soup_message_new_from_uri (method, uri);
	g_uri_unref (uri);
	cancellable = g_cancellable_new ();
	stream = soup_test_request_send (session, msg, cancellable, flags, &error);
	if (stream) {
		debug_printf (1, "    could not cancel the request\n");
		g_object_unref (stream);
		g_object_unref (msg);
		return;
	} else {
		g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
		g_clear_error (&error);
	}

	g_clear_object (&cancellable);
	g_clear_object (&stream);
	g_clear_object (&msg);

	soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE));
}

static void
message_starting (SoupMessage *msg, gpointer data)
{
	if (soup_message_headers_get_one (soup_message_get_request_headers (msg),
					  "If-Modified-Since") ||
	    soup_message_headers_get_one (soup_message_get_request_headers (msg),
					  "If-None-Match")) {
		debug_printf (2, "    Conditional request for %s\n",
			      g_uri_get_path (soup_message_get_uri (msg)));
		last_request_validated = TRUE;
	}
}

static void
request_queued (SoupSession *session, SoupMessage *msg,
		gpointer data)
{
	g_signal_connect (msg, "starting",
			  G_CALLBACK (message_starting),
			  data);
}

static void
request_unqueued (SoupSession *session, SoupMessage *msg,
		  gpointer data)
{
	last_request_unqueued = TRUE;
}

static void
do_basics_test (gconstpointer data)
{
	GUri *base_uri = (GUri *)data;
	SoupSession *session;
	SoupCache *cache;
	char *cache_dir;
	char *body1, *body2, *body3, *body4, *body5, *cmp;

	cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
	debug_printf (2, "  Caching to %s\n", cache_dir);
	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	g_signal_connect (session, "request-queued",
			  G_CALLBACK (request_queued), NULL);
	g_signal_connect (session, "request-unqueued",
			  G_CALLBACK (request_unqueued), NULL);

	debug_printf (2, "  Initial requests\n");
	body1 = do_request (session, base_uri, "GET", "/1", NULL,
			    "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			    NULL);
	body2 = do_request (session, base_uri, "GET", "/2", NULL,
			    "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			    "Test-Set-Cache-Control", "must-revalidate",
			    NULL);
	body3 = do_request (session, base_uri, "GET", "/3", NULL,
			    "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			    "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT",
			    "Test-Set-Cache-Control", "must-revalidate",
			    NULL);
	body4 = do_request (session, base_uri, "GET", "/4", NULL,
			    "Test-Set-ETag", "\"abcdefg\"",
			    "Test-Set-Cache-Control", "must-revalidate",
			    NULL);
	body5 = do_request (session, base_uri, "GET", "/5", NULL,
			    "Test-Set-Cache-Control", "no-cache",
			    NULL);


	/* Resource with future Expires should have been cached */
	debug_printf (1, "  Fresh cached resource\n");
	cmp = do_request (session, base_uri, "GET", "/1", NULL,
			  NULL);
	soup_test_assert (!last_request_hit_network,
			  "Request for /1 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /1 not unqueued");
	g_assert_cmpstr (body1, ==, cmp);
	g_free (cmp);


	/* Resource with long-ago Last-Modified should have been cached */
	debug_printf (1, "  Heuristically-fresh cached resource\n");
	cmp = do_request (session, base_uri, "GET", "/2", NULL,
			  NULL);
	/* Not validated even if it has must-revalidate, because it hasn't expired */
	soup_test_assert (!last_request_validated,
			  "Request for /2 was validated");
	soup_test_assert (!last_request_hit_network,
			  "Request for /2 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /2 not unqueued");
	g_assert_cmpstr (body2, ==, cmp);
	g_free (cmp);


	/* Adding a query string should bypass the cache but not invalidate it */
	debug_printf (1, "  Fresh cached resource with a query\n");
	cmp = do_request (session, base_uri, "GET", "/1?attr=value", NULL,
			  NULL);
	soup_test_assert (last_request_hit_network,
			  "Request for /1?attr=value filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /1?attr=value not unqueued");
	g_free (cmp);
	debug_printf (2, "  Second request\n");
	cmp = do_request (session, base_uri, "GET", "/1", NULL,
			  NULL);
	soup_test_assert (!last_request_hit_network,
			  "Second request for /1 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Request for /1 not unqueued");
	g_assert_cmpstr (body1, ==, cmp);
	g_free (cmp);


	/* Expired + must-revalidate causes a conditional request */
	debug_printf (1, "  Unchanged must-revalidate resource w/ Last-Modified\n");
	cmp = do_request (session, base_uri, "GET", "/3", NULL,
			  "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			  "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT",
			  "Test-Set-Cache-Control", "must-revalidate",
			  NULL);
	soup_test_assert (last_request_validated,
			  "Request for /3 not validated");
	soup_test_assert (!last_request_hit_network,
			  "Request for /3 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /3 not unqueued");
	g_assert_cmpstr (body3, ==, cmp);
	g_free (cmp);


	/* Validation failure should update cache */
	debug_printf (1, "  Changed must-revalidate resource w/ Last-Modified\n");
	cmp = do_request (session, base_uri, "GET", "/3", NULL,
			  "Test-Set-Last-Modified", "Sat, 02 Jan 2010 00:00:00 GMT",
			  "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT",
			  "Test-Set-Cache-Control", "must-revalidate",
			  NULL);
	soup_test_assert (last_request_validated,
			  "Request for /3 not validated");
	soup_test_assert (last_request_hit_network,
			  "Request for /3 filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Request for /3 not unqueued");
	g_assert_cmpstr (body3, !=, cmp);
	g_free (cmp);

	debug_printf (2, "  Second request\n");
	cmp = do_request (session, base_uri, "GET", "/3", NULL,
			  "Test-Set-Last-Modified", "Sat, 02 Jan 2010 00:00:00 GMT",
			  "Test-Set-Cache-Control", "must-revalidate",
			  NULL);
	soup_test_assert (last_request_validated,
			  "Second request for /3 not validated");
	soup_test_assert (!last_request_hit_network,
			  "Second request for /3 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /3 not unqueued");
	g_assert_cmpstr (body3, !=, cmp);
	g_free (cmp);

	/* ETag + must-revalidate causes a conditional request */
	debug_printf (1, "  Unchanged must-revalidate resource w/ ETag\n");
	cmp = do_request (session, base_uri, "GET", "/4", NULL,
			  "Test-Set-ETag", "\"abcdefg\"",
			  NULL);
	soup_test_assert (last_request_validated,
			  "Request for /4 not validated");
	soup_test_assert (!last_request_hit_network,
			  "Request for /4 not filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Cached resource /4 not unqueued");
	g_assert_cmpstr (body4, ==, cmp);
	g_free (cmp);


	/* Cache-Control: no-cache prevents caching */
	debug_printf (1, "  Uncacheable resource\n");
	cmp = do_request (session, base_uri, "GET", "/5", NULL,
			  "Test-Set-Cache-Control", "no-cache",
			  NULL);
	soup_test_assert (last_request_hit_network,
			  "Request for /5 filled from cache");
	soup_test_assert (last_request_unqueued,
			  "Request for /5 not unqueued");
	g_assert_cmpstr (body5, ==, cmp);
	g_free (cmp);


	/* PUT to a URI invalidates the cache entry */
	debug_printf (1, "  Invalidating and re-requesting a cached resource\n");
	cmp = do_request (session, base_uri, "PUT", "/1", NULL,
			  NULL);
	soup_test_assert (last_request_hit_network,
			  "PUT filled from cache");
	g_free (cmp);
	cmp = do_request (session, base_uri, "GET", "/1", NULL,
			  NULL);
	soup_test_assert (last_request_hit_network,
			  "PUT failed to invalidate cache entry");
	g_assert_true (last_request_hit_network);
	g_free (cmp);


	soup_test_session_abort_unref (session);
	g_object_unref (cache);

	g_free (cache_dir);
	g_free (body1);
	g_free (body2);
	g_free (body3);
	g_free (body4);
	g_free (body5);
}

static void
do_cancel_test (gconstpointer data)
{
	GUri *base_uri = (GUri *)data;
	SoupSession *session;
	SoupCache *cache;
	char *cache_dir;
	char *body1, *body2;
	guint flags;

	g_test_bug ("692310");

	cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
	debug_printf (2, "  Caching to %s\n", cache_dir);
	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	g_signal_connect (session, "request-unqueued",
			  G_CALLBACK (request_unqueued), NULL);

	debug_printf (2, "  Initial requests\n");
	body1 = do_request (session, base_uri, "GET", "/1", NULL,
			    "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			    NULL);
	body2 = do_request (session, base_uri, "GET", "/2", NULL,
			    "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			    "Test-Set-Expires", "Fri, 01 Jan 2011 00:00:00 GMT",
			    "Test-Set-Cache-Control", "must-revalidate",
			    NULL);

	/* Check that messages are correctly processed on cancellations. */
	debug_printf (1, "  Cancel fresh resource with g_cancellable_cancel()\n");
	flags = SOUP_TEST_REQUEST_CANCEL_IMMEDIATE;
	do_request_with_cancel (session, base_uri, "GET", "/1", flags);
	soup_test_assert (last_request_unqueued,
			  "Cancelled request /1 not unqueued");

	soup_test_session_abort_unref (session);

	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	g_signal_connect (session, "request-unqueued",
			  G_CALLBACK (request_unqueued), NULL);

	/* Check that messages are correctly processed on cancellations. */
	debug_printf (1, "  Cancel a revalidating resource with g_cancellable_cancel()\n");
	flags = SOUP_TEST_REQUEST_CANCEL_IMMEDIATE;
	do_request_with_cancel (session, base_uri, "GET", "/2", flags);
	soup_test_assert (last_request_unqueued,
			  "Cancelled request /2 not unqueued");

	soup_test_session_abort_unref (session);

	g_object_unref (cache);
	g_free (cache_dir);
	g_free (body1);
	g_free (body2);
}

static gboolean
unref_stream (gpointer stream)
{
	g_object_unref (stream);
	return FALSE;
}

static void
base_stream_unreffed (gpointer loop, GObject *ex_base_stream)
{
	g_main_loop_quit (loop);
}

static void
do_refcounting_test (gconstpointer data)
{
	GUri *base_uri = (GUri *)data;
	SoupSession *session;
	SoupCache *cache;
	char *cache_dir;
	SoupMessage *msg;
	GInputStream *stream, *base_stream;
	GUri *uri;
	GError *error = NULL;
	guint flags;
	GMainLoop *loop;

	g_test_bug ("682527");

	cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
	debug_printf (2, "  Caching to %s\n", cache_dir);
	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	last_request_validated = last_request_hit_network = FALSE;

	uri = g_uri_parse_relative (base_uri, "/1", SOUP_HTTP_URI_FLAGS, NULL);
	msg = soup_message_new_from_uri ("GET", uri);
	g_uri_unref (uri);

	flags = SOUP_TEST_REQUEST_CANCEL_AFTER_SEND_FINISH;
	stream = soup_test_request_send (session, msg, NULL, flags, &error);
	if (!stream) {
		debug_printf (1, "    could not send request: %s\n",
			      error->message);
		g_error_free (error);
		g_object_unref (msg);
		return;
	}
	g_object_unref (msg);

	base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (stream));

	debug_printf (1, " Checking that the base stream is properly unref'ed\n");
	loop = g_main_loop_new (NULL, FALSE);
	g_object_weak_ref (G_OBJECT (base_stream), base_stream_unreffed, loop);
	g_idle_add (unref_stream, stream);
	g_main_loop_run (loop);
	g_main_loop_unref (loop);

	soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE));

	g_object_unref (cache);
	g_free (cache_dir);
	soup_test_session_abort_unref (session);
}

static void
do_headers_test (gconstpointer data)
{
	GUri *base_uri = (GUri *)data;
	SoupSession *session;
	SoupMessageHeaders *headers;
	SoupCache *cache;
	char *cache_dir;
	char *body1, *cmp;
	const char *header_value;

	cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
	debug_printf (2, "  Caching to %s\n", cache_dir);
	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	g_signal_connect (session, "request-queued",
			  G_CALLBACK (request_queued), NULL);

	debug_printf (2, "  Initial requests\n");
	body1 = do_request (session, base_uri, "GET", "/1", NULL,
			    "Test-Set-Last-Modified", "Fri, 01 Jan 2100 00:00:00 GMT",
			    "Test-Set-My-Header", "My header value",
			    NULL);

	/* My-Header new value should be updated in cache */
	debug_printf (2, "  Fresh cached resource which updates My-Header\n");
	cmp = do_request (session, base_uri, "GET", "/1", NULL,
			  "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			  "Test-Set-My-Header", "My header NEW value",
			  NULL);
	soup_test_assert (last_request_validated,
			  "Request for /1 not validated");
	soup_test_assert (!last_request_hit_network,
			  "Request for /1 not filled from cache");
	g_free (cmp);

	/* Check that cache returns the updated header */
	debug_printf (2, "  Fresh cached resource with new value for My-Header\n");
	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
	cmp = do_request (session, base_uri, "GET", "/1", headers,
			  "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
			  NULL);
	soup_test_assert (!last_request_hit_network,
			  "Request for /1 not filled from cache");
	g_free (cmp);

	header_value = soup_message_headers_get_list (headers, "My-Header");
	g_assert_cmpstr (header_value, ==, "My header NEW value");
	soup_message_headers_unref (headers);

	soup_test_session_abort_unref (session);
	g_object_unref (cache);

	g_free (cache_dir);
	g_free (body1);
}

static guint
count_cached_resources_in_dir (const char *cache_dir)
{
	GDir *dir;
	const char *name;
	guint retval = 0;

	dir = g_dir_open (cache_dir, 0, NULL);
	while ((name = g_dir_read_name (dir))) {
		if (g_str_has_prefix (name, "soup."))
			continue;

		retval++;
	}
	g_dir_close (dir);

	return retval;
}

static void
do_leaks_test (gconstpointer data)
{
	GUri *base_uri = (GUri *)data;
	SoupSession *session;
	SoupCache *cache;
	char *cache_dir;
	char *body;

	cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
	debug_printf (2, "  Caching to %s\n", cache_dir);
	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
	session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

	debug_printf (2, "  Initial requests\n");
	body = do_request (session, base_uri, "GET", "/1", NULL,
			   "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			   NULL);
	g_free (body);
	body = do_request (session, base_uri, "GET", "/2", NULL,
			   "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			   NULL);
	g_free (body);
	body = do_request (session, base_uri, "GET", "/3", NULL,
			   "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			   NULL);
	g_free (body);

	debug_printf (2, "  Dumping the cache\n");
	soup_cache_dump (cache);

	g_assert_cmpuint (count_cached_resources_in_dir (cache_dir), ==, 3);

	body = do_request (session, base_uri, "GET", "/4", NULL,
			   "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			   NULL);
	g_free (body);
	body = do_request (session, base_uri, "GET", "/5", NULL,
			   "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
			   NULL);
	g_free (body);

	/* Destroy the cache without dumping the last two resources */
	soup_test_session_abort_unref (session);
	g_object_unref (cache);

	cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);

	debug_printf (2, "  Loading the cache\n");
	g_assert_cmpuint (count_cached_resources_in_dir (cache_dir), ==, 5);
	soup_cache_load (cache);
	g_assert_cmpuint (count_cached_resources_in_dir (cache_dir), ==, 3);

	g_object_unref (cache);
	g_free (cache_dir);
}

static void
do_metrics_test (gconstpointer data)
{
        GUri *base_uri = (GUri *)data;
        SoupSession *session;
        SoupCache *cache;
        char *cache_dir;
        char *body;
        GUri *uri;
        SoupMessage *msg;
        SoupMessageHeaders *request_headers;
        GInputStream *stream;
        char buffer[256];
        gsize nread;
        SoupMessageMetrics *metrics;

        cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
        debug_printf (2, "  Caching to %s\n", cache_dir);
        cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
        session = soup_test_session_new (NULL);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

        g_signal_connect (session, "request-queued",
                          G_CALLBACK (request_queued), NULL);
        g_signal_connect (session, "request-unqueued",
                          G_CALLBACK (request_unqueued), NULL);

        body = do_request (session, base_uri, "GET", "/1", NULL,
                           "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT",
                           NULL);
        g_free (body);

        last_request_validated = last_request_hit_network = last_request_unqueued = FALSE;
        uri = g_uri_parse_relative (base_uri, "/1", SOUP_HTTP_URI_FLAGS, NULL);
        msg = soup_message_new_from_uri ("GET", uri);
        g_uri_unref (uri);

        soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS);
        metrics = soup_message_get_metrics (msg);
        g_assert_nonnull (metrics);
        g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), ==, 0);

        stream = soup_test_request_send (session, msg, NULL, 0, NULL);
        g_assert_true (G_IS_INPUT_STREAM (stream));
        g_assert_false (is_network_stream (stream));
        g_assert_null (soup_message_get_remote_address (msg));

        g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0);
        g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics));
        g_input_stream_read_all (stream, buffer, sizeof (buffer), &nread, NULL, NULL);
        g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics));
        soup_test_request_close_stream (stream, NULL, NULL);
        g_object_unref (stream);
        g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics));
        g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), >, 0);
        g_object_unref (msg);

        body = do_request (session, base_uri, "GET", "/2", NULL,
                           "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT",
                           "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT",
                           "Test-Set-Cache-Control", "must-revalidate",
                           NULL);
        g_free (body);

        last_request_validated = last_request_hit_network = last_request_unqueued = FALSE;
        uri = g_uri_parse_relative (base_uri, "/2", SOUP_HTTP_URI_FLAGS, NULL);
        msg = soup_message_new_from_uri ("GET", uri);
        g_uri_unref (uri);

        request_headers = soup_message_get_request_headers (msg);
        soup_message_headers_append (request_headers,
                                     "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT");
        soup_message_headers_append (request_headers,
                                     "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT");
        soup_message_headers_append (request_headers,
                                     "Test-Set-Cache-Control", "must-revalidate");

        soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS);
        metrics = soup_message_get_metrics (msg);
        g_assert_nonnull (metrics);
        g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), ==, 0);

        stream = soup_test_request_send (session, msg, NULL, 0, NULL);
        g_assert_true (G_IS_INPUT_STREAM (stream));
        g_assert_false (is_network_stream (stream));
        g_assert_null (soup_message_get_remote_address (msg));
        g_assert_true (last_request_validated);

        g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0);
        g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics));
        g_input_stream_read_all (stream, buffer, sizeof (buffer), &nread, NULL, NULL);
        g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics));
        soup_test_request_close_stream (stream, NULL, NULL);
        g_object_unref (stream);
        g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics));
        g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, 0);
        g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), >, 0);
        g_object_unref (msg);

        soup_test_session_abort_unref (session);
        g_object_unref (cache);
        g_free (cache_dir);
}

typedef struct {
        SoupSession *session;
        GUri *uri;
        gboolean must_revalidate;
        gboolean is_expired;
        gboolean hit_network;
        gboolean validated;
        GError *error;
} ThreadTestRequest;

static void
threads_message_starting (SoupMessage       *msg,
                          ThreadTestRequest *request)
{
        SoupMessageHeaders *request_headers;

        if (request->validated)
                return;

        request_headers = soup_message_get_request_headers (msg);
        request->validated = !!soup_message_headers_get_one (request_headers, "If-Modified-Since");
}

static void
threads_request_queued (SoupSession       *session,
                        SoupMessage       *msg,
                        ThreadTestRequest *request)
{
        if (soup_message_get_uri (msg) != request->uri)
                return;

        g_signal_connect (msg, "starting",
                          G_CALLBACK (threads_message_starting),
                          request);
}

static void
task_async_function (GTask        *task,
                     GObject      *source,
                     gpointer      task_data,
                     GCancellable *cancellable)
{
        GMainContext *context;
        SoupMessage *msg;
        SoupMessageHeaders *request_headers;
        GInputStream *stream;
        ThreadTestRequest *request = (ThreadTestRequest *)task_data;

        context = g_main_context_new ();
        g_main_context_push_thread_default (context);

        msg = soup_message_new_from_uri ("GET", request->uri);
        g_signal_connect (request->session, "request-queued",
                          G_CALLBACK (threads_request_queued),
                          request);
        request_headers = soup_message_get_request_headers (msg);
        if (request->must_revalidate) {
                soup_message_headers_append (request_headers,
                                             "Test-Set-Last-Modified",
                                             request->is_expired ? "Sat, 02 Jan 2010 00:00:00 GMT" : "Fri, 01 Jan 2010 00:00:00 GMT");
                soup_message_headers_append (request_headers,
                                             "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT");
                soup_message_headers_append (request_headers,
                                             "Test-Set-Cache-Control", "must-revalidate");
        } else {
                soup_message_headers_append (request_headers,
                                             "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT");
        }

        stream = soup_test_request_send (request->session, msg, NULL, 0, &request->error);
        if (stream) {
                request->hit_network = is_network_stream (stream);
                soup_test_request_read_all (stream, NULL, &request->error);
                g_object_unref (stream);
        }

        g_signal_handlers_disconnect_by_data (request->session, request);

        g_object_unref (msg);

        /* Cache writes are G_PRIORITY_LOW, so they won't have happened yet */
        soup_cache_flush ((SoupCache *)soup_session_get_feature (request->session, SOUP_TYPE_CACHE));

        g_task_return_boolean (task, TRUE);

        g_main_context_pop_thread_default (context);
        g_main_context_unref (context);
}

static void
task_finished_cb (SoupSession  *session,
                  GAsyncResult *result,
                  guint        *finished_count)
{
        g_assert_true (g_task_propagate_boolean (G_TASK (result), NULL));
        g_atomic_int_inc (finished_count);
}

static void
do_threads_test (gconstpointer data)
{
        GUri *base_uri = (GUri *)data;
        SoupSession *session;
        SoupCache *cache;
        char *cache_dir;
        ThreadTestRequest requests[4];
        guint i;
        guint finished_count = 0;

        session = soup_test_session_new (NULL);

        cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL);
        cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER);
        soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache));

        requests[0].session = session;
        requests[0].uri = g_uri_parse_relative (base_uri, "/1", SOUP_HTTP_URI_FLAGS, NULL);
        requests[0].must_revalidate = FALSE;
        requests[0].is_expired = FALSE;
        requests[1].session = session;
        requests[1].uri = g_uri_parse_relative (base_uri, "/2", SOUP_HTTP_URI_FLAGS, NULL);
        requests[1].must_revalidate = TRUE;
        requests[1].is_expired = FALSE;
        requests[2].session = session;
        requests[2].uri = g_uri_parse_relative (base_uri, "/3", SOUP_HTTP_URI_FLAGS, NULL);
        requests[2].must_revalidate = FALSE;
        requests[2].is_expired = FALSE;
        requests[3].session = session;
        requests[3].uri = g_uri_parse_relative (base_uri, "/4", SOUP_HTTP_URI_FLAGS, NULL);
        requests[3].must_revalidate = TRUE;
        requests[3].is_expired = FALSE;

        for (i = 0; i < 4; i++) {
                GTask *task;

                requests[i].hit_network = FALSE;
                requests[i].validated = FALSE;
                requests[i].error = NULL;

                task = g_task_new (NULL, NULL, (GAsyncReadyCallback)task_finished_cb, &finished_count);
                g_task_set_task_data (task, &requests[i], NULL);
                g_task_run_in_thread (task, (GTaskThreadFunc)task_async_function);
                g_object_unref (task);
        }

        while (g_atomic_int_get (&finished_count) != 4)
                g_main_context_iteration (NULL, TRUE);

        /* Initial requests hit the network */
        for (i = 0; i < 4; i++) {
                g_assert_true (requests[i].hit_network);
                g_assert_false (requests[i].validated);
                g_assert_no_error (requests[i].error);
        }

        finished_count = 0;
        for (i = 0; i < 4; i++) {
                GTask *task;

                requests[i].hit_network = FALSE;
                requests[i].validated = FALSE;
                requests[i].error = NULL;

                task = g_task_new (NULL, NULL, (GAsyncReadyCallback)task_finished_cb, &finished_count);
                g_task_set_task_data (task, &requests[i], NULL);
                g_task_run_in_thread (task, (GTaskThreadFunc)task_async_function);
                g_object_unref (task);
        }

        while (g_atomic_int_get (&finished_count) != 4)
                g_main_context_iteration (NULL, TRUE);

        /* None of the requests hit the ntwork */
        for (i = 0; i < 4; i++) {
                g_assert_false (requests[i].hit_network);
                g_assert_no_error (requests[i].error);
        }

        /* The ones including must-revalidate are validated */
        g_assert_false (requests[0].validated);
        g_assert_true (requests[1].validated);
        g_assert_false (requests[2].validated);
        g_assert_true (requests[3].validated);

        /* Try again making the validations fail */
        requests[1].is_expired = TRUE;
        requests[3].is_expired = TRUE;

        finished_count = 0;
        for (i = 0; i < 4; i++) {
                GTask *task;

                requests[i].hit_network = FALSE;
                requests[i].validated = FALSE;
                requests[i].error = NULL;

                task = g_task_new (NULL, NULL, (GAsyncReadyCallback)task_finished_cb, &finished_count);
                g_task_set_task_data (task, &requests[i], NULL);
                g_task_run_in_thread (task, (GTaskThreadFunc)task_async_function);
                g_object_unref (task);
        }

        while (g_atomic_int_get (&finished_count) != 4)
                g_main_context_iteration (NULL, TRUE);

        /* None of the requests failed */
        for (i = 0; i < 4; i++)
                g_assert_no_error (requests[i].error);

        /* The ones including must-revalidate are validated and hit the network this time */
        g_assert_false (requests[0].validated);
        g_assert_false (requests[0].hit_network);
        g_assert_true (requests[1].validated);
        g_assert_true (requests[1].hit_network);
        g_assert_false (requests[2].validated);
        g_assert_false (requests[2].hit_network);
        g_assert_true (requests[3].validated);
        g_assert_true (requests[3].hit_network);

        for (i = 0; i < 4; i++)
                g_uri_unref (requests[i].uri);

        soup_test_session_abort_unref (session);
        soup_cache_clear (cache);
        g_rmdir (cache_dir);
        g_object_unref (cache);
        g_free (cache_dir);
}

int
main (int argc, char **argv)
{
	SoupServer *server;
	GUri *base_uri;
	int ret;

	test_init (argc, argv, NULL);

	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
	soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
	base_uri = soup_test_server_get_uri (server, "http", NULL);

	g_test_add_data_func ("/cache/basics", base_uri, do_basics_test);
	g_test_add_data_func ("/cache/cancellation", base_uri, do_cancel_test);
	g_test_add_data_func ("/cache/refcounting", base_uri, do_refcounting_test);
	g_test_add_data_func ("/cache/headers", base_uri, do_headers_test);
	g_test_add_data_func ("/cache/leaks", base_uri, do_leaks_test);
        g_test_add_data_func ("/cache/metrics", base_uri, do_metrics_test);
        g_test_add_data_func ("/cache/threads", base_uri, do_threads_test);

	ret = g_test_run ();

	g_uri_unref (base_uri);
	soup_test_server_quit_unref (server);

	test_cleanup ();
	return ret;
}