* Copyright (C) 2014 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "cockpitwebinject.h"
#include "cockpitwebresponse.h"
#include "cockpitwebserver.h"
#include "testlib/cockpittest.h"
#include "websocket.h"
#include <glib/gstdio.h>
#include <stdlib.h>
#include <string.h>
#define STATIC_HEADERS "X-DNS-Prefetch-Control: off\r\nReferrer-Policy: no-referrer\r\nX-Content-Type-Options: nosniff\r\nCross-Origin-Resource-Policy: same-origin\r\nX-Frame-Options: sameorigin\r\n\r\n"
static gchar *srcdir;
typedef struct {
CockpitWebResponse *response;
GOutputStream *output;
gchar *scratch;
gboolean response_done;
gulong sig_done;
} TestCase;
typedef struct {
const gchar *path;
const gchar *header;
const gchar *value;
const gchar *method;
const gchar *expected_content_type;
CockpitCacheType cache;
gboolean for_tls_proxy;
} TestFixture;
static void
on_response_done (CockpitWebResponse *response,
gboolean reusable,
gpointer user_data)
{
gboolean *response_done = user_data;
g_assert (response_done != NULL);
g_assert (*response_done == FALSE);
*response_done = TRUE;
}
static void
setup (TestCase *tc,
gconstpointer data)
{
const TestFixture *fixture = data;
const gchar *path = NULL;
GHashTable *headers = NULL;
GInputStream *input;
GIOStream *io;
if (fixture)
path = fixture->path;
input = g_memory_input_stream_new ();
tc->output = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
io = g_simple_io_stream_new (input, tc->output);
g_object_unref (input);
if (fixture && fixture->header)
{
headers = cockpit_web_server_new_table ();
g_hash_table_insert (headers, g_strdup (fixture->header), g_strdup (fixture->value));
}
tc->response = cockpit_web_response_new (io, path, path, headers,
(fixture && fixture->method) ? fixture->method : "GET",
(fixture && fixture->for_tls_proxy) ? "https" : "http");
if (headers)
g_hash_table_unref (headers);
g_object_unref (io);
tc->sig_done = g_signal_connect (tc->response, "done",
G_CALLBACK (on_response_done),
&tc->response_done);
}
static void
teardown (TestCase *tc,
gconstpointer data)
{
while (g_main_context_iteration (NULL, FALSE));
g_assert (tc->response_done);
g_signal_handler_disconnect (tc->response, tc->sig_done);
g_clear_object (&tc->output);
g_clear_object (&tc->response);
g_free (tc->scratch);
}
static const gchar *
output_as_string (TestCase *tc)
{
while (!tc->response_done)
g_main_context_iteration (NULL, TRUE);
g_free (tc->scratch);
tc->scratch = g_strndup (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (tc->output)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (tc->output)));
return tc->scratch;
}
static void
test_return_content (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n" STATIC_HEADERS "the content");
}
static void
test_return_content_headers (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GHashTable *headers;
GBytes *content;
headers = cockpit_web_server_new_table ();
g_hash_table_insert (headers, g_strdup ("My-header"), g_strdup ("my-value"));
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_content (tc->response, headers, content, NULL);
g_bytes_unref (content);
g_hash_table_destroy (headers);
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nMy-header: my-value\r\nContent-Length: 11\r\n" STATIC_HEADERS "the content");
}
static void
test_return_error (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
cockpit_web_response_error (tc->response, 500, NULL, "Reason here: %s", "booyah");
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==,
"HTTP/1.1 500 Reason here: booyah\r\n"
"Content-Type: text/html; charset=utf8\r\n"
"Transfer-Encoding: chunked\r\n"
STATIC_HEADERS
"5d\r\n<html><head><title>Reason here: booyah</title></head><body>Reason here: booyah</body></html>\n\r\n"
"0\r\n\r\n");
}
static void
test_return_error_headers (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GHashTable *headers;
headers = cockpit_web_server_new_table ();
g_hash_table_insert (headers, g_strdup ("Header1"), g_strdup ("value1"));
cockpit_web_response_error (tc->response, 500, headers, "Reason here: %s", "booyah");
g_hash_table_destroy (headers);
resp = output_as_string (tc);
cockpit_assert_strmatch(resp,"HTTP/1.1 500 Reason here: booyah\r*\n"
"Header1: value1\r*\n"
"\r\n");
}
static void
test_return_gerror_headers (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GHashTable *headers;
GError *error;
headers = cockpit_web_server_new_table ();
g_hash_table_insert (headers, g_strdup ("Header1"), g_strdup ("value1"));
error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Reason here: %s", "booyah");
cockpit_web_response_gerror (tc->response, headers, NULL, error);
g_error_free (error);
g_hash_table_destroy (headers);
resp = output_as_string (tc);
cockpit_assert_strmatch(resp,"HTTP/1.1 500 Reason here: booyah\r*\n"
"Header1: value1\r*\n"
"\r\n");
}
static void
test_file_not_found (TestCase *tc,
gconstpointer user_data)
{
const gchar *roots[] = { srcdir, NULL };
cockpit_web_response_file (tc->response, "/non-existent", roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404 Not Found*");
}
static void
test_file_directory_denied (TestCase *tc,
gconstpointer user_data)
{
const gchar *roots[] = { srcdir, NULL };
cockpit_web_response_file (tc->response, "/src", roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 403 Directory Listing Denied*");
}
static void
test_file_access_denied (TestCase *tc,
gconstpointer user_data)
{
const gchar *roots[] = { "/tmp", NULL };
gchar templ[] = "/tmp/test-temp.XXXXXX";
if (!g_mkdtemp_full (templ, 0000))
g_assert_not_reached ();
cockpit_web_response_file (tc->response, templ + 4, roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 403*");
g_unlink (templ);
}
static void
test_file_breakout_denied (TestCase *tc,
gconstpointer user_data)
{
gchar *root = realpath ( SRCDIR "/src", NULL);
const gchar *roots[] = { root, NULL };
const gchar *breakout = "/../Makefile.am";
gchar *check = g_build_filename (roots[0], breakout, NULL);
g_assert (root);
g_assert (g_file_test (check, G_FILE_TEST_EXISTS));
g_free (check);
cockpit_web_response_file (tc->response, breakout, roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404*");
free (root);
}
static void
test_file_encoding_denied (TestCase *tc,
gconstpointer user_data)
{
gchar *root = realpath ( SRCDIR "/src", NULL);
const gchar *roots[] = { root, NULL };
const gchar *breakout = "/common/Makefile-common.am%00";
gchar *check = g_build_filename (roots[0], "common", "Makefile-common.am", NULL);
g_assert (root);
g_assert (g_file_test (check, G_FILE_TEST_EXISTS));
g_free (check);
cockpit_web_response_file (tc->response, breakout, roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404*");
free (root);
}
static void
test_file_slash_denied (TestCase *tc,
gconstpointer user_data)
{
gchar *root = realpath ( SRCDIR "/src", NULL);
const gchar *roots[] = { root, NULL };
const gchar *breakout = "/common%2fMakefile-common.am";
gchar *check = g_build_filename (roots[0], "common", "Makefile-common.am", NULL);
g_assert (root);
g_assert (g_file_test (check, G_FILE_TEST_EXISTS));
g_free (check);
cockpit_web_response_file (tc->response, breakout, roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404*");
free (root);
}
static void
test_file_breakout_non_existent (TestCase *tc,
gconstpointer user_data)
{
gchar *root = realpath ( SRCDIR "/src", NULL);
const gchar *roots[] = { root, NULL };
const gchar *breakout = "/../non-existent";
gchar *check = g_build_filename (roots[0], breakout, NULL);
g_assert (root);
g_assert (!g_file_test (check, G_FILE_TEST_EXISTS));
g_free (check);
cockpit_web_response_file (tc->response, breakout, roots);
cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404*");
free (root);
}
static const TestFixture content_type_fixture_html = {
.path = "/pkg/shell/index.html",
.expected_content_type = "text/html",
};
static const TestFixture content_type_fixture_png = {
.path = "/pkg/shell/images/server-small.png",
.expected_content_type = "image/png",
};
static const TestFixture content_type_fixture_wasm = {
.path = "/src/ws/mock-content/test.wasm",
.expected_content_type = "application/wasm",
};
static const TestFixture fixture_head = {
.method = "HEAD",
};
static const TestFixture fixture_unsupported_method = {
.method = "PATCH",
};
static void
test_content_type (TestCase *tc,
gconstpointer user_data)
{
const TestFixture *fixture = user_data;
const gchar *roots[] = { srcdir, NULL };
GHashTable *headers;
const gchar *resp;
gsize length;
guint status;
gssize off;
cockpit_web_response_file (tc->response, NULL, roots);
resp = output_as_string (tc);
length = strlen (resp);
off = web_socket_util_parse_status_line (resp, length, NULL, &status, NULL);
g_assert_cmpuint (off, >, 0);
g_assert_cmpint (status, ==, 200);
off = web_socket_util_parse_headers (resp + off, length - off, &headers);
g_assert_cmpuint (off, >, 0);
g_assert_cmpstr (g_hash_table_lookup (headers, "Content-Type"), ==, fixture->expected_content_type);
g_hash_table_unref (headers);
}
static const TestFixture template_fixture = {
.path = "/test.css"
};
static void
test_template (TestCase *tc,
gconstpointer user_data)
{
const gchar *roots[] = { SRCDIR "/src/ws/mock-content/", NULL };
const gchar *resp;
GHashTable *data = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (data, "NAME", "test");
g_hash_table_insert (data, "VARIANT", "VALUE");
cockpit_web_response_template (tc->response, NULL, roots, data);
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Type: text/css\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS "17\r\n#brand {\n content: \"\r\n4\r\ntest\r\n4\r\n <b>\r\n5\r\nVALUE\r\n9\r\n</b>\";\n}\n\r\n0\r\n\r\n");
g_hash_table_unref (data);
}
static const TestFixture cache_none_fixture = {
.path = "/pkg/shell/index.html",
.cache = COCKPIT_WEB_RESPONSE_NO_CACHE
};
static const TestFixture cache_fixture = {
.path = "/pkg/shell/index.html",
.cache = COCKPIT_WEB_RESPONSE_CACHE
};
static const TestFixture cache_unset_fixture = {
.path = "/pkg/shell/index.html",
.cache = COCKPIT_WEB_RESPONSE_CACHE_UNSET
};
static void
test_cache (TestCase *tc,
gconstpointer user_data)
{
const TestFixture *fixture = user_data;
const gchar *roots[] = { srcdir, NULL };
GHashTable *headers;
const gchar *resp;
gsize length;
guint status;
gssize off;
cockpit_web_response_set_cache_type (tc->response, fixture->cache);
cockpit_web_response_file (tc->response, NULL, roots);
resp = output_as_string (tc);
length = strlen (resp);
off = web_socket_util_parse_status_line (resp, length, NULL, &status, NULL);
g_assert_cmpuint (off, >, 0);
g_assert_cmpint (status, ==, 200);
off = web_socket_util_parse_headers (resp + off, length - off, &headers);
g_assert_cmpuint (off, >, 0);
if (fixture->cache == COCKPIT_WEB_RESPONSE_CACHE)
g_assert_cmpstr (g_hash_table_lookup (headers, "Vary"), ==, "Cookie");
else
g_assert_null (g_hash_table_lookup (headers, "Vary"));
if (fixture->cache == COCKPIT_WEB_RESPONSE_NO_CACHE)
g_assert_cmpstr (g_hash_table_lookup (headers, "Cache-Control"), ==, "no-cache, no-store");
else if (fixture->cache == COCKPIT_WEB_RESPONSE_CACHE)
g_assert_cmpstr (g_hash_table_lookup (headers, "Cache-Control"), ==, "max-age=86400, private");
else
g_assert_null (g_hash_table_lookup (headers, "Cache-Control"));
g_hash_table_unref (headers);
}
static void
test_content_encoding (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", 50,
"Content-Encoding", "blah",
NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("Cockpit is perfect for new sysadmins, ", 38);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Encoding: blah\r\n"
"Transfer-Encoding: chunked\r\n" STATIC_HEADERS
"26\r\nCockpit is perfect for new sysadmins, \r\n0\r\n\r\n");
}
static void
test_stream (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", 11, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n" STATIC_HEADERS "the content");
}
static void
on_pressure_set_throttle (CockpitWebResponse *response,
gboolean throttle,
gpointer user_data)
{
gint *data = user_data;
g_assert (user_data != NULL);
*data = throttle ? 1 : 0;
}
static void
test_pressure (TestCase *tc,
gconstpointer data)
{
GBytes *sent;
gint throttle = -1;
gint i;
g_signal_connect (tc->response, "pressure", G_CALLBACK (on_pressure_set_throttle), &throttle);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", -1, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
sent = g_bytes_new_take (g_strnfill (10 * 1000, '?'), 10 * 1000);
for (i = 0; i < 1000; i++)
cockpit_web_response_queue (tc->response, sent);
g_bytes_unref (sent);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
* This should have put way too much in the queue, and thus
* emitted the back-pressure signal. This signal would normally
* be used by others to slow down their queueing, but in this
* case we just check that it was fired.
*/
g_assert_cmpint (throttle, ==, 1);
throttle = -1;
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
* Now the queue is getting drained. At some point, it will be
* signaled that back pressure has been turned off
*/
while (throttle == -1)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (throttle, ==, 0);
while (!tc->response_done)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (tc->output)), >, 10 * 1000 * 1000);
}
static void
test_head (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", 19, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("I shall not be seen", 19);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Length: 19\r\n" STATIC_HEADERS);
}
static void
test_unsupported_method (TestCase *tc,
gconstpointer data)
{
cockpit_web_response_error (tc->response, 405, NULL, "Unsupported method");
const gchar *resp = output_as_string (tc);
g_assert (g_str_has_prefix (resp, "HTTP/1.1 405 Unsupported method\r\n"));
g_assert (strstr (resp, "<body>"));
}
static void
test_chunked_transfer_encoding (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", -1, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("Cockpit is perfect for new sysadmins, ", 38);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
content = g_bytes_new_static ("allowing them to easily perform simple tasks such as storage administration, ", 77);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
content = g_bytes_new_static ("inspecting journals and starting and stopping services.", 55);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"26\r\nCockpit is perfect for new sysadmins, \r\n"
"4d\r\nallowing them to easily perform simple tasks such as storage administration, \r\n"
"37\r\ninspecting journals and starting and stopping services.\r\n0\r\n\r\n");
}
static void
test_chunked_zero_length (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", -1, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("Cockpit is perfect for new sysadmins, ", 38);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
content = g_bytes_new_static ("", 0);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
content = g_bytes_new_static ("inspecting journals and starting and stopping services.", 55);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
content = g_bytes_new_static ("", 0);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"26\r\nCockpit is perfect for new sysadmins, \r\n"
"37\r\ninspecting journals and starting and stopping services.\r\n0\r\n\r\n");
}
static void
test_chunked_head (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_READY);
cockpit_web_response_headers (tc->response, 200, "OK", -1, NULL);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
while (g_main_context_iteration (NULL, FALSE));
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_QUEUING);
cockpit_web_response_complete (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_COMPLETE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS);
}
static GBytes *
bytes_static (const gchar *data)
{
return g_bytes_new_static (data, strlen (data));
}
static void
test_web_filter_simple (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *resp;
GBytes *content;
GBytes *inject;
inject = bytes_static ("<meta inject>");
filter = cockpit_web_inject_new ("<head>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
content = bytes_static ("<html><head><title>The Title</title></head></html>");
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"c\r\n<html><head>\r\n"
"d\r\n<meta inject>\r\n"
"26\r\n<title>The Title</title></head></html>\r\n"
"0\r\n\r\n");
}
static void
test_web_filter_multiple (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *resp;
GBytes *content;
GBytes *inject;
inject = bytes_static ("<meta inject>");
filter = cockpit_web_inject_new ("<head>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
inject = bytes_static ("<body>Body</body>");
filter = cockpit_web_inject_new ("</head>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
inject = bytes_static ("Prefix ");
filter = cockpit_web_inject_new ("<title>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
inject = bytes_static (" ");
filter = cockpit_web_inject_new (">", inject, 3);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
content = bytes_static ("<html><head><title>The Title</title></head></html>");
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"6\r\n<html>\r\n"
"1\r\n \r\n"
"6\r\n<head>\r\n"
"1\r\n \r\n"
"d\r\n<meta inject>\r\n"
"1\r\n \r\n"
"7\r\n<title>\r\n"
"7\r\nPrefix \r\n"
"18\r\nThe Title</title></head>\r\n"
"11\r\n<body>Body</body>\r\n"
"7\r\n</html>\r\n"
"0\r\n\r\n");
}
static void
test_web_filter_split (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *string;
const gchar *resp;
GBytes *inject;
GBytes *block;
gsize i, x, len;
inject = bytes_static ("<meta inject>");
filter = cockpit_web_inject_new ("<head>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
inject = bytes_static ("<body>Body</body>");
filter = cockpit_web_inject_new ("</head>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
inject = bytes_static ("Prefix ");
filter = cockpit_web_inject_new ("<title>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
cockpit_web_response_headers (tc->response, 200, "OK", -1, NULL);
string = "<html><head><title>The Title</title></head></html>";
len = strlen (string);
for (i = 0, x = 1; i < len; i += x, x = 1 + (i % 4))
{
block = g_bytes_new_static (string + i, MIN (x, strlen (string + i)));
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
}
cockpit_web_response_complete (tc->response);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"1\r\n<\r\n"
"2\r\nht\r\n"
"4\r\nml><\r\n"
"4\r\nhead\r\n"
"1\r\n>\r\n"
"d\r\n<meta inject>\r\n"
"3\r\n<ti\r\n"
"4\r\ntle>\r\n"
"7\r\nPrefix \r\n"
"4\r\nThe \r\n"
"4\r\nTitl\r\n"
"4\r\ne</t\r\n"
"4\r\nitle\r\n"
"4\r\n></h\r\n"
"4\r\nead>\r\n"
"11\r\n<body>Body</body>\r\n"
"4\r\n</ht\r\n"
"3\r\nml>\r\n"
"0\r\n\r\n");
}
static void
test_web_filter_shift (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *resp;
GBytes *block;
GBytes *inject;
inject = bytes_static ("injected");
filter = cockpit_web_inject_new ("foofn", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
cockpit_web_response_headers_full (tc->response, 200, "OK", -1, NULL);
block = bytes_static ("foof");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
block = bytes_static ("oofn");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
cockpit_web_response_complete (tc->response);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"4\r\nfoof\r\n"
"4\r\noofn\r\n"
"8\r\ninjected\r\n"
"0\r\n\r\n");
}
static void
test_web_filter_shift_three (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *resp;
GBytes *block;
GBytes *inject;
inject = bytes_static ("injected");
filter = cockpit_web_inject_new ("foofn", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
cockpit_web_response_headers_full (tc->response, 200, "OK", -1, NULL);
block = bytes_static ("foof");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
block = bytes_static ("o");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
block = bytes_static ("of");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
block = bytes_static ("n");
g_assert (cockpit_web_response_queue (tc->response, block) == TRUE);
g_bytes_unref (block);
cockpit_web_response_complete (tc->response);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"4\r\nfoof\r\n"
"1\r\no\r\n"
"2\r\nof\r\n"
"1\r\nn\r\n"
"8\r\ninjected\r\n"
"0\r\n\r\n");
}
static void
test_web_filter_passthrough (TestCase *tc,
gconstpointer data)
{
CockpitWebFilter *filter;
const gchar *resp;
GBytes *content;
GBytes *inject;
inject = bytes_static ("<meta inject>");
filter = cockpit_web_inject_new ("<unknown>", inject, 1);
cockpit_web_response_add_filter (tc->response, filter);
g_object_unref (filter);
g_bytes_unref (inject);
content = bytes_static ("<html><head><title>The Title</title></head></html>");
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
while (cockpit_web_response_get_state (tc->response) != COCKPIT_WEB_RESPONSE_COMPLETE)
g_main_context_iteration (NULL, TRUE);
resp = output_as_string (tc);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n" STATIC_HEADERS
"32\r\n<html><head><title>The Title</title></head></html>\r\n"
"0\r\n\r\n");
}
static void
on_response_done_not_reusable (CockpitWebResponse *response,
gboolean reusable,
gpointer user_data)
{
g_assert (reusable == FALSE);
}
static void
test_abort (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
cockpit_web_response_headers (tc->response, 200, "OK", 11, NULL);
g_signal_connect (tc->response, "done", G_CALLBACK (on_response_done_not_reusable), NULL);
while (g_main_context_iteration (NULL, FALSE));
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_queue (tc->response, content);
g_bytes_unref (content);
cockpit_web_response_abort (tc->response);
g_assert_cmpint (cockpit_web_response_get_state (tc->response), ==, COCKPIT_WEB_RESPONSE_SENT);
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n" STATIC_HEADERS);
}
static const TestFixture fixture_connection_close = {
.header = "Connection",
.value = "close",
};
static void
test_connection_close (TestCase *tc,
gconstpointer data)
{
const gchar *resp;
GBytes *content;
g_assert (data == &fixture_connection_close);
g_signal_connect (tc->response, "done", G_CALLBACK (on_response_done_not_reusable), NULL);
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
resp = output_as_string (tc);
g_assert_cmpstr (resp, ==, "HTTP/1.1 200 OK\r\nContent-Length: 11\r\nConnection: close\r\n"
STATIC_HEADERS "the content");
}
static const TestFixture fixture_origin_default = {
.header = "Host",
.value = "somemachine",
.for_tls_proxy = FALSE,
};
static const TestFixture fixture_origin_tls_proxy = {
.header = "Host",
.value = "somemachine",
.for_tls_proxy = TRUE,
};
static void
test_origin (TestCase *tc,
gconstpointer data)
{
const TestFixture *fixture = data;
GBytes *content;
if (fixture->for_tls_proxy)
g_assert_cmpstr (cockpit_web_response_get_origin (tc->response), ==, "https://somemachine");
else
g_assert_cmpstr (cockpit_web_response_get_origin (tc->response), ==, "http://somemachine");
content = g_bytes_new_static ("the content", 11);
cockpit_web_response_content (tc->response, NULL, content, NULL);
g_bytes_unref (content);
output_as_string (tc);
}
typedef struct {
GHashTable *headers;
GIOStream *io;
} TestPlain;
static void
setup_plain (TestPlain *tc,
gconstpointer unused)
{
GInputStream *input;
GOutputStream *output;
input = g_memory_input_stream_new ();
output = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
tc->io = g_simple_io_stream_new (input, output);
g_object_unref (input);
g_object_unref (output);
tc->headers = cockpit_web_server_new_table ();
}
static void
teardown_plain (TestPlain *tc,
gconstpointer unused)
{
g_object_unref (tc->io);
g_hash_table_unref (tc->headers);
}
static void
test_pop_path (TestPlain *tc,
gconstpointer unused)
{
CockpitWebResponse *response;
gchar *part;
const gchar *start = "/cockpit/@localhost/another/test.html";
response = cockpit_web_response_new (tc->io, start, start, tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, start);
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, NULL);
part = cockpit_web_response_pop_path (response);
g_assert_cmpstr (part, ==, "cockpit");
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/@localhost/another/test.html");
g_free (part);
part = cockpit_web_response_pop_path (response);
g_assert_cmpstr (part, ==, "@localhost");
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/another/test.html");
g_free (part);
part = cockpit_web_response_pop_path (response);
g_assert_cmpstr (part, ==, "another");
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/test.html");
g_free (part);
part = cockpit_web_response_pop_path (response);
g_assert_cmpstr (part, ==, "test.html");
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, NULL);
g_free (part);
part = cockpit_web_response_pop_path (response);
g_assert (part == NULL);
g_assert (cockpit_web_response_get_path (response) == NULL);
g_free (part);
cockpit_web_response_abort (response);
g_object_unref (response);
}
static void
test_pop_path_root (TestPlain *tc,
gconstpointer unused)
{
CockpitWebResponse *response;
gchar *part;
response = cockpit_web_response_new (tc->io, "/", "/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/");
part = cockpit_web_response_pop_path (response);
g_assert_cmpstr (part, ==, NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, NULL);
g_free (part);
cockpit_web_response_abort (response);
g_object_unref (response);
}
static void
test_skip_path (TestPlain *tc,
gconstpointer unused)
{
CockpitWebResponse *response;
const gchar *start = "/cockpit/@localhost/another/test.html";
response = cockpit_web_response_new (tc->io, start, start, tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/cockpit/@localhost/another/test.html");
g_assert (cockpit_web_response_skip_path (response) == TRUE);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/@localhost/another/test.html");
g_assert (cockpit_web_response_skip_path (response) == TRUE);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/another/test.html");
g_assert (cockpit_web_response_skip_path (response) == TRUE);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/test.html");
g_assert (cockpit_web_response_skip_path (response) == TRUE);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, NULL);
g_assert (cockpit_web_response_skip_path (response) == FALSE);
g_assert (cockpit_web_response_get_path (response) == NULL);
cockpit_web_response_abort (response);
g_object_unref (response);
}
static void
test_skip_path_root (TestPlain *tc,
gconstpointer unused)
{
CockpitWebResponse *response;
response = cockpit_web_response_new (tc->io, "/", "/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/");
g_assert (cockpit_web_response_skip_path (response) == FALSE);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, NULL);
cockpit_web_response_abort (response);
g_object_unref (response);
}
static void
test_removed_prefix (TestPlain *tc,
gconstpointer unused)
{
CockpitWebResponse *response;
response = cockpit_web_response_new (tc->io, "/", "/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/");
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, NULL);
cockpit_web_response_abort (response);
g_clear_object (&response);
response = cockpit_web_response_new (tc->io, "/path/", "/path/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/path/");
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, NULL);
cockpit_web_response_abort (response);
g_clear_object (&response);
response = cockpit_web_response_new (tc->io, "/path/path2/", "/path2/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/path2/");
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, "/path");
cockpit_web_response_abort (response);
g_clear_object (&response);
response = cockpit_web_response_new (tc->io, "/mis/", "/match/", tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, "/match/");
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, NULL);
cockpit_web_response_abort (response);
g_clear_object (&response);
response = cockpit_web_response_new (tc->io, NULL, NULL, tc->headers, "GET", NULL);
g_assert_cmpstr (cockpit_web_response_get_path (response), ==, NULL);
g_assert_cmpstr (cockpit_web_response_get_url_root (response), ==, NULL);
cockpit_web_response_abort (response);
g_clear_object (&response);
}
static void
test_negotiation_first (void)
{
gboolean is_language_specific, is_compressed;
GError *error = NULL;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/test-file.txt",
NULL, NULL, &is_language_specific, &is_compressed, &error);
cockpit_assert_bytes_eq (bytes, "A small test file\n", -1);
g_assert_no_error (error);
g_bytes_unref (bytes);
g_assert (!is_language_specific);
g_assert (!is_compressed);
}
static void
test_negotiation_last (void)
{
gboolean is_language_specific, is_compressed;
GError *error = NULL;
gchar *checksum;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/large.js",
NULL, NULL, &is_language_specific, &is_compressed, &error);
g_assert_no_error (error);
g_assert (!is_language_specific);
g_assert (is_compressed);
checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes);
g_assert_cmpstr (checksum, ==, "e5284b625b7665fc04e082827de3436c");
g_free (checksum);
g_bytes_unref (bytes);
}
static void
test_negotiation_prune (void)
{
GError *error = NULL;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/test-file.extra.extension.txt",
NULL, NULL, NULL, NULL, &error);
cockpit_assert_bytes_eq (bytes, "A small test file\n", -1);
g_assert_no_error (error);
g_bytes_unref (bytes);
}
static void
test_negotiation_with_listing (void)
{
GHashTable *existing;
GError *error = NULL;
GBytes *bytes;
existing = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_add (existing, SRCDIR "/src/ws/mock-content/test-file.txt.gz");
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/test-file.txt",
existing, NULL, NULL, NULL, &error);
cockpit_assert_bytes_eq (bytes, "\x1F\x8B\x08\x08N1\x03U\x00\x03test-file.txt\x00"
"sT(\xCEM\xCC\xC9Q(I-.QH\xCB\xCCI\xE5\x02\x00>PjG\x12\x00\x00\x00", 52);
g_assert_no_error (error);
g_bytes_unref (bytes);
g_hash_table_unref (existing);
}
static void
test_negotiation_locale (void)
{
gboolean is_language_specific, is_compressed;
GError *error = NULL;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/test-file.txt",
NULL, "zh-cn", &is_language_specific, &is_compressed, &error);
cockpit_assert_bytes_eq (bytes, "A translated test file\n", -1);
g_assert_no_error (error);
g_bytes_unref (bytes);
g_assert (is_language_specific);
g_assert (!is_compressed);
}
static void
test_negotiation_notfound (void)
{
GError *error = NULL;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/non-existent",
NULL, NULL, NULL, NULL, &error);
g_assert_no_error (error);
g_assert (bytes == NULL);
}
static void
test_negotiation_failure (void)
{
GError *error = NULL;
GBytes *bytes;
bytes = cockpit_web_response_negotiation (SRCDIR "/src/ws/mock-content/directory",
NULL, NULL, NULL, NULL, &error);
g_assert (error != NULL);
g_error_free (error);
g_assert (bytes == NULL);
}
int
main (int argc,
char *argv[])
{
extern const gchar *cockpit_webresponse_fail_html_text;
cockpit_webresponse_fail_html_text =
"<html><head><title>@@message@@</title></head><body>@@message@@</body></html>\n";
gint ret;
srcdir = realpath (SRCDIR, NULL);
g_assert (srcdir != NULL);
cockpit_test_init (&argc, &argv);
g_test_add ("/web-response/return-content", TestCase, NULL,
setup, test_return_content, teardown);
g_test_add ("/web-response/return-content-headers", TestCase, NULL,
setup, test_return_content_headers, teardown);
g_test_add ("/web-response/return-error", TestCase, NULL,
setup, test_return_error, teardown);
g_test_add ("/web-response/return-error-headers", TestCase, NULL,
setup, test_return_error_headers, teardown);
g_test_add ("/web-response/return-gerror-headers", TestCase, NULL,
setup, test_return_gerror_headers, teardown);
g_test_add ("/web-response/file/not-found", TestCase, NULL,
setup, test_file_not_found, teardown);
g_test_add ("/web-response/file/directory-denied", TestCase, NULL,
setup, test_file_directory_denied, teardown);
g_test_add ("/web-response/file/access-denied", TestCase, NULL,
setup, test_file_access_denied, teardown);
g_test_add ("/web-response/file/breakout-denied", TestCase, NULL,
setup, test_file_breakout_denied, teardown);
g_test_add ("/web-response/file/invalid-encoding-denied", TestCase, NULL,
setup, test_file_encoding_denied, teardown);
g_test_add ("/web-response/file/file-slash-denied", TestCase, NULL,
setup, test_file_slash_denied, teardown);
g_test_add ("/web-response/file/breakout-non-existent", TestCase, NULL,
setup, test_file_breakout_non_existent, teardown);
g_test_add ("/web-reponse/file/template", TestCase, &template_fixture,
setup, test_template, teardown);
g_test_add ("/web-response/content-type/html", TestCase, &content_type_fixture_html,
setup, test_content_type, teardown);
g_test_add ("/web-response/content-type/png", TestCase, &content_type_fixture_png,
setup, test_content_type, teardown);
g_test_add ("/web-response/content-type/wasm", TestCase, &content_type_fixture_wasm,
setup, test_content_type, teardown);
g_test_add ("/web-response/content-encoding", TestCase, NULL,
setup, test_content_encoding, teardown);
g_test_add ("/web-response/stream", TestCase, NULL,
setup, test_stream, teardown);
g_test_add ("/web-response/pressure", TestCase, NULL,
setup, test_pressure, teardown);
g_test_add ("/web-response/head", TestCase, &fixture_head,
setup, test_head, teardown);
g_test_add ("/web-response/unsupported-method", TestCase, &fixture_unsupported_method,
setup, test_unsupported_method, teardown);
g_test_add ("/web-response/chunked-transfer-encoding", TestCase, NULL,
setup, test_chunked_transfer_encoding, teardown);
g_test_add ("/web-response/chunked-zero-length", TestCase, NULL,
setup, test_chunked_zero_length, teardown);
g_test_add ("/web-response/chunked-head", TestCase, &fixture_head,
setup, test_chunked_head, teardown);
g_test_add ("/web-response/abort", TestCase, NULL,
setup, test_abort, teardown);
g_test_add ("/web-response/connection-close", TestCase, &fixture_connection_close,
setup, test_connection_close, teardown);
g_test_add ("/web-response/origin-default", TestCase, &fixture_origin_default,
setup, test_origin, teardown);
g_test_add ("/web-response/origin-tls-proxy", TestCase, &fixture_origin_tls_proxy,
setup, test_origin, teardown);
g_test_add ("/web-response/cache", TestCase, &cache_fixture,
setup, test_cache, teardown);
g_test_add ("/web-response/cache-none", TestCase, &cache_none_fixture,
setup, test_cache, teardown);
g_test_add ("/web-response/cache-unset", TestCase, &cache_unset_fixture,
setup, test_cache, teardown);
g_test_add ("/web-response/filter/simple", TestCase, NULL,
setup, test_web_filter_simple, teardown);
g_test_add ("/web-response/filter/multiple", TestCase, NULL,
setup, test_web_filter_multiple, teardown);
g_test_add ("/web-response/filter/passthrough", TestCase, NULL,
setup, test_web_filter_passthrough, teardown);
g_test_add ("/web-response/filter/split", TestCase, NULL,
setup, test_web_filter_split, teardown);
g_test_add ("/web-response/filter/shift", TestCase, NULL,
setup, test_web_filter_shift, teardown);
g_test_add ("/web-response/filter/shift_three", TestCase, NULL,
setup, test_web_filter_shift_three, teardown);
g_test_add ("/web-response/path/pop", TestPlain, NULL,
setup_plain, test_pop_path, teardown_plain);
g_test_add ("/web-response/path/pop-root", TestPlain, NULL,
setup_plain, test_pop_path_root, teardown_plain);
g_test_add ("/web-response/path/skip", TestPlain, NULL,
setup_plain, test_skip_path, teardown_plain);
g_test_add ("/web-response/path/skip-root", TestPlain, NULL,
setup_plain, test_skip_path_root, teardown_plain);
g_test_add ("/web-response/path/removed-prefix", TestPlain, NULL,
setup_plain, test_removed_prefix, teardown_plain);
g_test_add_func ("/web-response/negotiation/first", test_negotiation_first);
g_test_add_func ("/web-response/negotiation/last", test_negotiation_last);
g_test_add_func ("/web-response/negotiation/locale", test_negotiation_locale);
g_test_add_func ("/web-response/negotiation/prune", test_negotiation_prune);
g_test_add_func ("/web-response/negotiation/with-listing", test_negotiation_with_listing);
g_test_add_func ("/web-response/negotiation/notfound", test_negotiation_notfound);
g_test_add_func ("/web-response/negotiation/failure", test_negotiation_failure);
ret = g_test_run ();
free (srcdir);
return ret;
}