diff options
author | Jens Georg <mail@jensge.org> | 2021-04-22 21:59:52 +0200 |
---|---|---|
committer | Jens Georg <mail@jensge.org> | 2021-04-22 21:59:52 +0200 |
commit | c47a46735cf95bb3d19058d698de85aa68888de1 (patch) | |
tree | e6e95c0893896946ae43c56b2be7b27a5e1c86c9 | |
parent | 5f59aa2123c9727497efe24ca2ab9e5318410f92 (diff) | |
download | gupnp-wip/missing-checks.tar.gz |
service: Check Content-Type in soap callswip/missing-checks
-rw-r--r-- | libgupnp/gupnp-service.c | 35 | ||||
-rw-r--r-- | tests/gtest/data/TestService.xml | 15 | ||||
-rw-r--r-- | tests/gtest/test-bugs.c | 425 |
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 (); } |