summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2021-04-22 21:59:52 +0200
committerJens Georg <mail@jensge.org>2021-04-22 21:59:52 +0200
commitc47a46735cf95bb3d19058d698de85aa68888de1 (patch)
treee6e95c0893896946ae43c56b2be7b27a5e1c86c9
parent5f59aa2123c9727497efe24ca2ab9e5318410f92 (diff)
downloadgupnp-wip/missing-checks.tar.gz
service: Check Content-Type in soap callswip/missing-checks
-rw-r--r--libgupnp/gupnp-service.c35
-rw-r--r--tests/gtest/data/TestService.xml15
-rw-r--r--tests/gtest/test-bugs.c425
3 files changed, 466 insertions, 9 deletions
diff --git a/libgupnp/gupnp-service.c b/libgupnp/gupnp-service.c
index b061c34..be3c9cc 100644
--- a/libgupnp/gupnp-service.c
+++ b/libgupnp/gupnp-service.c
@@ -928,7 +928,10 @@ control_server_handler (SoupServer *server,
const char *accept_encoding;
char *action_name;
char *end;
+ const char *content_type;
GUPnPServiceAction *action;
+ GHashTable *content_type_params = NULL;
+ GSSDPUDAVersion uda_version;
service = GUPNP_SERVICE (user_data);
@@ -952,7 +955,39 @@ control_server_handler (SoupServer *server,
"close");
}
+ // Check Content-Type header
+ content_type =
+ soup_message_headers_get_content_type (msg->request_headers,
+ &content_type_params);
+
+ // It has to be there and have text/xml for ALL UDA versions
+ if (content_type == NULL ||
+ g_ascii_strcasecmp (content_type, "text/xml") != 0) {
+ soup_message_set_status (
+ msg,
+ SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+ g_clear_pointer (&content_type_params, g_hash_table_unref);
+ return;
+ }
+
context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
+ uda_version = gssdp_client_get_uda_version (GSSDP_CLIENT (context));
+
+ // For UDA >= 1.1, the encoding must be utf-8
+ if (uda_version >= GSSDP_UDA_VERSION_1_1) {
+ if (content_type_params == NULL ||
+ !g_hash_table_contains (content_type_params, "charset") ||
+ g_ascii_strcasecmp (
+ g_hash_table_lookup (content_type_params,
+ "charset"),
+ "utf-8") != 0) {
+ soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
+ g_clear_pointer (&content_type_params, g_hash_table_destroy);
+ return;
+ }
+ }
+
+ g_clear_pointer (&content_type_params, g_hash_table_destroy);
/* Get action name */
soap_action = soup_message_headers_get_one (msg->request_headers,
diff --git a/tests/gtest/data/TestService.xml b/tests/gtest/data/TestService.xml
index 8f02269..3c29dd1 100644
--- a/tests/gtest/data/TestService.xml
+++ b/tests/gtest/data/TestService.xml
@@ -36,13 +36,28 @@
<name>A_ARG_TYPE_Count</name>
<dataType>ui4</dataType>
</stateVariable>
+ <stateVariable sendEvents="no">
+ <name>A_ARG_TYPE_Tock</name>
+ <dataType>string</dataType>
+ </stateVariable>
<stateVariable sendEvents="yes">
<name>evented_variable</name>
<dataType>string</dataType>
+ <allowedValue>Tock</allowedValue>
</stateVariable>
</serviceStateTable>
<actionList>
<action>
+ <name>Tick</name>
+ <argumentList>
+ <argument>
+ <name>Result</name>
+ <direction>out</direction>
+ <relatedStateVariable>A_ARG_TYPE_Tock</relatedStateVariable>
+ </argument>
+ </argumentList>
+ </action>
+ <action>
<name>Browse</name>
<argumentList>
<argument>
diff --git a/tests/gtest/test-bugs.c b/tests/gtest/test-bugs.c
index db94285..eb80edf 100644
--- a/tests/gtest/test-bugs.c
+++ b/tests/gtest/test-bugs.c
@@ -27,12 +27,16 @@
#include <libgupnp/gupnp-service-private.h>
static GUPnPContext *
-create_context (guint16 port, GError **error) {
+create_context (GSSDPUDAVersion uda_version, guint16 port, GError **error) {
return GUPNP_CONTEXT (g_initable_new (GUPNP_TYPE_CONTEXT,
NULL,
error,
- "host-ip", "127.0.0.1",
- "msearch-port", port,
+ "uda-version",
+ uda_version,
+ "host-ip",
+ "127.0.0.1",
+ "msearch-port",
+ port,
NULL));
}
@@ -186,8 +190,12 @@ static void
test_run_loop (GMainLoop *loop)
{
guint timeout_id = 0;
+ const char *test_timeout = g_getenv ("GUPNP_TEST_TIMEOUT");
- timeout_id = g_timeout_add_seconds (2, test_on_timeout, NULL);
+ timeout_id = g_timeout_add_seconds (
+ test_timeout == NULL ? 2 : atoi (test_timeout),
+ test_on_timeout,
+ NULL);
g_main_loop_run (loop);
g_source_remove (timeout_id);
}
@@ -205,7 +213,7 @@ test_bgo_696762 (void)
data.loop = g_main_loop_new (NULL, FALSE);
- context = create_context (0, &error);
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
g_assert_no_error (error);
g_assert (context != NULL);
@@ -274,7 +282,7 @@ test_bgo_678701 (void)
data.loop = g_main_loop_new (NULL, FALSE);
- context = create_context (0, &error);
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
g_assert_no_error (error);
g_assert (context != NULL);
@@ -331,7 +339,7 @@ test_bgo_690400 (void)
data.loop = g_main_loop_new (NULL, FALSE);
- context = create_context (0, &error);
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
g_assert_no_error (error);
g_assert (context != NULL);
@@ -390,7 +398,7 @@ test_bgo_722696 (void)
char *icon;
int width;
- context = create_context (0, &error);
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
g_assert_no_error (error);
g_assert (context != NULL);
@@ -451,7 +459,7 @@ test_bgo_743233 (void)
GUPnPControlPoint *cp = NULL;
GError *error = NULL;
- context = create_context (0, &error);
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
g_assert_no_error (error);
g_assert (context != NULL);
@@ -469,6 +477,403 @@ test_bgo_743233 (void)
g_object_unref (context);
}
+typedef struct _TestGgo24Data {
+ GMainLoop *loop;
+ GUPnPServiceInfo *service;
+ SoupSession *session;
+ SoupKnownStatusCode result;
+} TestGgo24Data;
+
+static void
+on_message_done (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+
+ data->result = msg->status_code;
+ g_main_loop_quit (data->loop);
+}
+
+#define TICK_CALL \
+ "<?xml version=\"1.0\"?>" \
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
+ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" \
+ "<s:Body>" \
+ "<u:Tick xmlns:u=\"urn:test-gupnp-org:service:TestService:1\">" \
+ "</u:Tick>" \
+ "</s:Body>" \
+ "</s:Envelope>"
+
+static gboolean
+test_ggo_24_1_call_no_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+
+ return FALSE;
+}
+
+static gboolean
+test_ggo_24_1_call_invalid_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+ soup_message_headers_set_content_type (msg->request_headers,
+ "text/plain",
+ NULL);
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+
+ return FALSE;
+}
+
+static gboolean
+test_ggo_24_1_call_plain_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+ soup_message_headers_set_content_type (msg->request_headers,
+ "text/xml",
+ NULL);
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+
+ return FALSE;
+}
+
+static gboolean
+test_ggo_24_1_call_missing_encoding_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+ GHashTable *params =
+ g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+
+
+ g_hash_table_replace (params, "frobnication", "dada");
+ soup_message_headers_set_content_type (msg->request_headers,
+ "text/xml",
+ params);
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+ g_hash_table_unref (params);
+
+ return FALSE;
+}
+
+static gboolean
+test_ggo_24_1_call_wrong_encoding_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+ GHashTable *params =
+ g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+
+
+ g_hash_table_replace (params, "charset⎄", "iso-8859-1");
+ soup_message_headers_set_content_type (msg->request_headers,
+ "text/xml",
+ params);
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+ g_hash_table_unref (params);
+
+ return FALSE;
+}
+
+static gboolean
+test_ggo_24_1_call_proper_encoding_content_type (gpointer user_data)
+{
+ SoupMessage *msg;
+ TestGgo24Data *data = (TestGgo24Data *) user_data;
+ char *control_url;
+ GHashTable *params =
+ g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+ control_url = gupnp_service_info_get_control_url (data->service);
+
+ msg = soup_message_new (SOUP_METHOD_POST, control_url);
+ g_free (control_url);
+ soup_message_headers_append (
+ msg->request_headers,
+ "SOAPAction",
+ "urn:test-gupnp-org:service:TestService:1#Tick");
+
+
+ soup_message_body_append_take (msg->request_body,
+ (guchar *) TICK_CALL,
+ sizeof (TICK_CALL));
+
+
+
+ g_hash_table_replace (params, "charset", "utf-8");
+ soup_message_headers_set_content_type (msg->request_headers,
+ "text/xml",
+ params);
+ g_hash_table_unref (params);
+
+ soup_session_queue_message (data->session,
+ g_object_ref (msg),
+ on_message_done,
+ data);
+
+ return FALSE;
+}
+
+static void
+on_tick_call (GUPnPService *service,
+ GUPnPServiceAction *action,
+ gpointer user_data)
+{
+ gupnp_service_action_set (action,
+ "Result",
+ G_TYPE_STRING,
+ "Tock",
+ NULL);
+ gupnp_service_action_return (action);
+}
+
+static void
+test_ggo_24_1 (void)
+{
+ // Test the correct handling of the Content-Type header in SOAP calls
+ GUPnPContext *context = NULL;
+ GError *error = NULL;
+ TestGgo24Data data = { .loop = g_main_loop_new (NULL, FALSE),
+ .service = NULL,
+ .session = soup_session_new (),
+ .result = SOUP_KNOWN_STATUS_CODE_NONE };
+ GUPnPRootDevice *rd = NULL;
+
+ context = create_context (GSSDP_UDA_VERSION_1_0, 0, &error);
+ g_assert_no_error (error);
+ g_assert (context != NULL);
+
+ rd = gupnp_root_device_new (context,
+ "TestDevice.xml",
+ DATA_PATH,
+ &error);
+ g_assert_no_error (error);
+ g_assert (rd != NULL);
+
+ data.service = gupnp_device_info_get_service (
+ GUPNP_DEVICE_INFO (rd),
+ "urn:test-gupnp-org:service:TestService:1");
+
+ g_signal_connect (data.service,
+ "action-invoked::Tick",
+ G_CALLBACK (on_tick_call),
+ &data);
+
+ gupnp_root_device_set_available (rd, TRUE);
+
+ g_timeout_add_seconds (1, test_ggo_24_1_call_no_content_type, &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result,
+ ==,
+ SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_invalid_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result,
+ ==,
+ SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1, test_ggo_24_1_call_plain_content_type, &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_OK);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_missing_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_OK);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_wrong_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_OK);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_proper_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_OK);
+
+ g_object_unref (rd);
+ g_object_unref (context);
+ g_main_loop_unref (data.loop);
+}
+
+static void
+test_ggo_24_2 (void)
+{
+ // Test the correct handling of the Content-Type header in SOAP calls
+ GUPnPContext *context = NULL;
+ GError *error = NULL;
+ TestGgo24Data data = { .loop = g_main_loop_new (NULL, FALSE),
+ .service = NULL,
+ .session = soup_session_new (),
+ .result = SOUP_KNOWN_STATUS_CODE_NONE };
+ GUPnPRootDevice *rd = NULL;
+
+ context = create_context (GSSDP_UDA_VERSION_1_1, 0, &error);
+ g_assert_no_error (error);
+ g_assert (context != NULL);
+
+ rd = gupnp_root_device_new (context,
+ "TestDevice.xml",
+ DATA_PATH,
+ &error);
+ g_assert_no_error (error);
+ g_assert (rd != NULL);
+
+ data.service = gupnp_device_info_get_service (
+ GUPNP_DEVICE_INFO (rd),
+ "urn:test-gupnp-org:service:TestService:1");
+
+ g_signal_connect (data.service,
+ "action-invoked::Tick",
+ G_CALLBACK (on_tick_call),
+ &data);
+
+ gupnp_root_device_set_available (rd, TRUE);
+
+ g_timeout_add_seconds (1, test_ggo_24_1_call_no_content_type, &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_invalid_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_plain_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_missing_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_wrong_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED);
+
+ // Only the proper content-type with proper encoding should be accepted
+ g_timeout_add_seconds (1,
+ test_ggo_24_1_call_proper_encoding_content_type,
+ &data);
+ test_run_loop (data.loop);
+ g_assert_cmpint (data.result, ==, SOUP_KNOWN_STATUS_CODE_OK);
+
+ g_object_unref (rd);
+ g_object_unref (context);
+ g_main_loop_unref (data.loop);
+}
+
int
main (int argc, char *argv[]) {
g_test_init (&argc, &argv, NULL);
@@ -477,6 +882,8 @@ main (int argc, char *argv[]) {
g_test_add_func ("/bugs/690400", test_bgo_690400);
g_test_add_func ("/bugs/722696", test_bgo_722696);
g_test_add_func ("/bugs/743233", test_bgo_743233);
+ g_test_add_func ("/bugs/24/1", test_ggo_24_1);
+ g_test_add_func ("/bugs/24/2", test_ggo_24_2);
return g_test_run ();
}