/* * This file is part of Cockpit. * * Copyright (C) 2014 Red Hat, Inc. * * Cockpit 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.1 of the License, or * (at your option) any later version. * * Cockpit 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 Cockpit; If not, see . */ #include "config.h" #include "cockpitwebinject.h" #include "cockpitwebresponse.h" #include "cockpitwebserver.h" #include "mock-io-stream.h" #include "common/cockpittest.h" #include "websocket/websocket.h" #include #include #include 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; CockpitCacheType cache; } 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 = mock_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, NULL, headers); 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\r\nthe 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\r\nthe content"); } static void test_return_error (TestCase *tc, gconstpointer data) { const gchar *resp; cockpit_expect_message ("Returning error-response 500*"); 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" "\r\n" "13\r\n\r\n" "13\r\nReason here: booyah\r\n" "15\r\n\r\n" "13\r\nReason here: booyah\r\n" "f\r\n\n\r\n" "0\r\n\r\n"); } static void test_return_error_headers (TestCase *tc, gconstpointer data) { const gchar *resp; GHashTable *headers; cockpit_expect_message ("Returning error-response 500*"); 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; cockpit_expect_message ("Returning error-response 500*"); 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, 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_return_error_resource (TestCase *tc, gconstpointer user_data) { const gchar *roots[] = { srcdir, NULL }; cockpit_web_failure_resource = "/org/cockpit-project/Cockpit/fail.html"; cockpit_web_response_file (tc->response, "/non-existant", roots); cockpit_assert_strmatch (output_as_string (tc), "HTTP/1.1 404 Not Found*response, "/non-existant", 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_breakout_non_existant (TestCase *tc, gconstpointer user_data) { gchar *root = realpath ( SRCDIR "/src", NULL); const gchar *roots[] = { root, NULL }; const gchar *breakout = "/../non-existant"; 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 = { .path = "/pkg/shell/index.html" }; static void test_content_type (TestCase *tc, gconstpointer user_data) { const gchar *roots[] = { srcdir, NULL }; GHashTable *headers; const gchar *resp; gsize length; guint status; gssize off; g_assert (user_data == &content_type_fixture); 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"), ==, "text/html"); 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/common/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\r\n17\r\n#brand {\n content: \"\r\n4\r\ntest\r\n4\r\n \r\n5\r\nVALUE\r\n9\r\n\";\n}\n\r\n0\r\n\r\n"); g_hash_table_unref (data); } static const TestFixture cache_forever_fixture = { .path = "/pkg/shell/index.html", .cache = COCKPIT_WEB_RESPONSE_CACHE_FOREVER, }; static const TestFixture cache_none_fixture = { .path = "/pkg/shell/index.html", .cache = COCKPIT_WEB_RESPONSE_NO_CACHE }; static const TestFixture cache_private_fixture = { .path = "/pkg/shell/index.html", .cache = COCKPIT_WEB_RESPONSE_CACHE_PRIVATE }; 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_PRIVATE) 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_CACHE_FOREVER) g_assert_cmpstr (g_hash_table_lookup (headers, "Cache-Control"), ==, "max-age=31556926, public"); else 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_PRIVATE) 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" "Content-Length: 50\r\nTransfer-Encoding: chunked\r\n\r\n" "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\r\nthe content"); } static void test_head (TestCase *tc, gconstpointer data) { const gchar *resp; GBytes *content; cockpit_web_response_set_method (tc->response, "HEAD"); 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\r\n"); } 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\r\n" "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\r\n" "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 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 (""); filter = cockpit_web_inject_new ("", inject, 1); cockpit_web_response_add_filter (tc->response, filter); g_object_unref (filter); g_bytes_unref (inject); content = bytes_static ("The Title"); 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\r\n" "c\r\n\r\n" "d\r\n\r\n" "26\r\nThe Title\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 (""); filter = cockpit_web_inject_new ("", inject, 1); cockpit_web_response_add_filter (tc->response, filter); g_object_unref (filter); g_bytes_unref (inject); inject = bytes_static ("Body"); filter = cockpit_web_inject_new ("", 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 ("", 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"); 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\r\n" "6\r\n\r\n" "1\r\n \r\n" "6\r\n\r\n" "1\r\n \r\n" "d\r\n\r\n" "1\r\n \r\n" "7\r\n\r\n" "7\r\nPrefix \r\n" "18\r\nThe Title\r\n" "11\r\nBody\r\n" "7\r\n\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 (""); filter = cockpit_web_inject_new ("", inject, 1); cockpit_web_response_add_filter (tc->response, filter); g_object_unref (filter); g_bytes_unref (inject); inject = bytes_static ("Body"); filter = cockpit_web_inject_new ("", 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 ("", 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"; 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\r\n" "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\r\n" "3\r\n\r\n" "7\r\nPrefix \r\n" "4\r\nThe \r\n" "4\r\nTitl\r\n" "4\r\ne\r\n" "11\r\nBody\r\n" "4\r\n\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); /* Total content is foofoofn and split after the first 4 characters */ 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\r\n" "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); /* Total content is foofoofn and split across multiple packets after the first 4 characters */ 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\r\n" "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 (""); filter = cockpit_web_inject_new ("", inject, 1); cockpit_web_response_add_filter (tc->response, filter); g_object_unref (filter); g_bytes_unref (inject); content = bytes_static ("The Title"); 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\r\n" "32\r\nThe Title\r\n" "0\r\n\r\n"); } static void on_response_done_not_resuable (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_resuable), 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\r\n"); } 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_resuable), 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\r\nthe content"); } 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 = mock_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, NULL, tc->headers); 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, "/", "/", NULL, tc->headers); 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, NULL, tc->headers); 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, "/", "/", NULL, tc->headers); 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, "/", "/", NULL, tc->headers); 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/", NULL, tc->headers); 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/", NULL, tc->headers); 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/", NULL, tc->headers); 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, NULL, tc->headers); 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_gunzip_small (void) { GError *error = NULL; GMappedFile *file; GBytes *compressed; GBytes *bytes; file = g_mapped_file_new (SRCDIR "/src/common/mock-content/test-file.txt.gz", FALSE, &error); g_assert_no_error (error); compressed = g_mapped_file_get_bytes (file); g_mapped_file_unref (file); bytes = cockpit_web_response_gunzip (compressed, &error); g_assert_no_error (error); g_bytes_unref (compressed); cockpit_assert_bytes_eq (bytes, "A small test file\n", -1); g_bytes_unref (bytes); } static void test_gunzip_large (void) { GError *error = NULL; GMappedFile *file; GBytes *compressed; GBytes *bytes; gchar *checksum; file = g_mapped_file_new (SRCDIR "/src/common/mock-content/large.min.js.gz", FALSE, &error); g_assert_no_error (error); compressed = g_mapped_file_get_bytes (file); g_mapped_file_unref (file); bytes = cockpit_web_response_gunzip (compressed, &error); g_assert_no_error (error); g_bytes_unref (compressed); checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes); g_assert_cmpstr (checksum, ==, "5ca7582261c421482436dfdf3af9bffe"); g_free (checksum); g_bytes_unref (bytes); } static void test_gunzip_invalid (void) { GError *error = NULL; GBytes *compressed; GBytes *bytes; compressed = g_bytes_new_static ("invalid", 7); bytes = cockpit_web_response_gunzip (compressed, &error); g_assert (bytes == NULL); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_error_free (error); g_bytes_unref (compressed); } static void test_negotiation_first (void) { gchar *chosen = NULL; GError *error = NULL; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/test-file.txt", NULL, NULL, &chosen, &error); cockpit_assert_bytes_eq (bytes, "A small test file\n", -1); g_assert_no_error (error); g_bytes_unref (bytes); g_assert_cmpstr (chosen, ==, SRCDIR "/src/common/mock-content/test-file.txt"); g_free (chosen); } static void test_negotiation_last (void) { gchar *chosen = NULL; GError *error = NULL; gchar *checksum; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/large.js", NULL, NULL, &chosen, &error); g_assert_no_error (error); g_assert_cmpstr (chosen, ==, SRCDIR "/src/common/mock-content/large.min.js.gz"); g_free (chosen); 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) { gchar *chosen = NULL; GError *error = NULL; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/test-file.extra.extension.txt", NULL, NULL, &chosen, &error); cockpit_assert_bytes_eq (bytes, "A small test file\n", -1); g_assert_no_error (error); g_bytes_unref (bytes); g_assert_cmpstr (chosen, ==, SRCDIR "/src/common/mock-content/test-file.txt"); g_free (chosen); } static void test_negotiation_with_listing (void) { GHashTable *existing; GError *error = NULL; GBytes *bytes; /* Lie and say that only the .gz file exists */ existing = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_add (existing, SRCDIR "/src/common/mock-content/test-file.txt.gz"); bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/test-file.txt", existing, 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) { gchar *chosen = NULL; GError *error = NULL; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/test-file.txt", NULL, "zh-cn", &chosen, &error); cockpit_assert_bytes_eq (bytes, "A translated test file\n", -1); g_assert_no_error (error); g_bytes_unref (bytes); g_assert_cmpstr (chosen, ==, SRCDIR "/src/common/mock-content/test-file.zh_CN.txt"); g_free (chosen); } static void test_negotiation_notfound (void) { gchar *chosen = NULL; GError *error = NULL; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/non-existant", NULL, NULL, &chosen, &error); g_assert_no_error (error); g_assert (bytes == NULL); g_assert (chosen == NULL); } static void test_negotiation_failure (void) { gchar *chosen = NULL; GError *error = NULL; GBytes *bytes; bytes = cockpit_web_response_negotiation (SRCDIR "/src/common/mock-content/directory", NULL, NULL, &chosen, &error); g_assert (error != NULL); g_error_free (error); g_assert (bytes == NULL); g_assert (chosen == NULL); } int main (int argc, char *argv[]) { 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/return-error-resource", TestCase, NULL, setup, test_return_error_resource, 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/breakout-non-existant", TestCase, NULL, setup, test_file_breakout_non_existant, teardown); g_test_add ("/web-reponse/file/template", TestCase, &template_fixture, setup, test_template, teardown); g_test_add ("/web-response/content-type", TestCase, &content_type_fixture, 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/head", TestCase, NULL, setup, test_head, 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/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/cache-forever", TestCase, &cache_forever_fixture, setup, test_cache, teardown); g_test_add ("/web-response/cache-private", TestCase, &cache_private_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/gunzip/small", test_gunzip_small); g_test_add_func ("/web-response/gunzip/large", test_gunzip_large); g_test_add_func ("/web-response/gunzip/invalid", test_gunzip_invalid); 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; }