1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>.
6 #include "test-utils.h"
10 SoupMessageBody *chunk_data;
13 server_callback (SoupServer *server, SoupMessage *msg,
14 const char *path, GHashTable *query,
15 SoupClientContext *context, gpointer data)
19 SoupBuffer *response = NULL;
21 gboolean empty_response = FALSE;
23 if (msg->method != SOUP_METHOD_GET) {
24 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
28 soup_message_set_status (msg, SOUP_STATUS_OK);
31 query_key = g_hash_table_lookup (query, "chunked");
32 if (query_key && g_str_equal (query_key, "yes")) {
33 soup_message_headers_set_encoding (msg->response_headers,
34 SOUP_ENCODING_CHUNKED);
37 query_key = g_hash_table_lookup (query, "empty_response");
38 if (query_key && g_str_equal (query_key, "yes"))
39 empty_response = TRUE;
42 if (!strcmp (path, "/mbox")) {
43 if (!empty_response) {
44 response = soup_test_load_resource ("mbox", &error);
45 g_assert_no_error (error);
48 soup_message_headers_append (msg->response_headers,
49 "Content-Type", "text/plain");
52 if (g_str_has_prefix (path, "/nosniff/")) {
53 char *base_name = g_path_get_basename (path);
55 response = soup_test_load_resource (base_name, &error);
56 g_assert_no_error (error);
59 soup_message_headers_append (msg->response_headers,
60 "X-Content-Type-Options", "nosniff");
62 soup_message_headers_append (msg->response_headers,
63 "Content-Type", "no/sniffing-allowed");
66 if (g_str_has_prefix (path, "/text_or_binary/") || g_str_has_prefix (path, "/apache_bug/")) {
67 char *base_name = g_path_get_basename (path);
69 response = soup_test_load_resource (base_name, &error);
70 g_assert_no_error (error);
73 soup_message_headers_append (msg->response_headers,
74 "Content-Type", "text/plain");
77 if (g_str_has_prefix (path, "/unknown/")) {
78 char *base_name = g_path_get_basename (path);
80 response = soup_test_load_resource (base_name, &error);
81 g_assert_no_error (error);
84 soup_message_headers_append (msg->response_headers,
85 "Content-Type", "UNKNOWN/unknown");
88 if (g_str_has_prefix (path, "/type/")) {
89 char **components = g_strsplit (path, "/", 4);
92 char *base_name = g_path_get_basename (path);
94 response = soup_test_load_resource (base_name, &error);
95 g_assert_no_error (error);
98 /* Hack to allow passing type in the URI */
99 ptr = g_strrstr (components[2], "_");
102 soup_message_headers_append (msg->response_headers,
103 "Content-Type", components[2]);
104 g_strfreev (components);
107 if (g_str_has_prefix (path, "/multiple_headers/")) {
108 char *base_name = g_path_get_basename (path);
110 response = soup_test_load_resource (base_name, &error);
111 g_assert_no_error (error);
114 soup_message_headers_append (msg->response_headers,
115 "Content-Type", "text/xml");
116 soup_message_headers_append (msg->response_headers,
117 "Content-Type", "text/plain");
121 for (offset = 0; offset < response->length; offset += 500) {
122 soup_message_body_append (msg->response_body,
124 response->data + offset,
125 MIN (500, response->length - offset));
128 soup_buffer_free (response);
131 soup_message_body_complete (msg->response_body);
135 unpause_msg (gpointer data)
137 SoupMessage *msg = (SoupMessage*)data;
138 debug_printf (2, " unpause\n");
139 soup_session_unpause_message (session, msg);
145 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data)
147 gboolean should_pause = GPOINTER_TO_INT (data);
149 debug_printf (2, " content-sniffed -> %s\n", content_type);
151 soup_test_assert (g_object_get_data (G_OBJECT (msg), "got-chunk") == NULL,
152 "got-chunk got emitted before content-sniffed");
154 g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
157 debug_printf (2, " pause\n");
158 soup_session_pause_message (session, msg);
159 g_idle_add (unpause_msg, msg);
164 got_headers (SoupMessage *msg, gpointer data)
166 gboolean should_pause = GPOINTER_TO_INT (data);
168 debug_printf (2, " got-headers\n");
170 soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL,
171 "content-sniffed got emitted before got-headers");
173 g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE));
176 debug_printf (2, " pause\n");
177 soup_session_pause_message (session, msg);
178 g_idle_add (unpause_msg, msg);
183 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data)
185 gboolean should_accumulate = GPOINTER_TO_INT (data);
187 debug_printf (2, " got-chunk\n");
189 g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE));
191 if (!should_accumulate) {
193 chunk_data = soup_message_body_new ();
194 soup_message_body_append_buffer (chunk_data, chunk);
199 do_signals_test (gboolean should_content_sniff,
200 gboolean should_pause,
201 gboolean should_accumulate,
202 gboolean chunked_encoding,
203 gboolean empty_response)
205 SoupURI *uri = soup_uri_new_with_base (base_uri, "/mbox");
206 SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
207 SoupBuffer *expected;
208 GError *error = NULL;
209 SoupBuffer *body = NULL;
211 debug_printf (1, "do_signals_test(%ssniff, %spause, %saccumulate, %schunked, %sempty)\n",
212 should_content_sniff ? "" : "!",
213 should_pause ? "" : "!",
214 should_accumulate ? "" : "!",
215 chunked_encoding ? "" : "!",
216 empty_response ? "" : "!");
218 if (chunked_encoding)
219 soup_uri_set_query (uri, "chunked=yes");
221 if (empty_response) {
223 char *tmp = uri->query;
224 uri->query = g_strdup_printf ("%s&empty_response=yes", tmp);
227 soup_uri_set_query (uri, "empty_response=yes");
230 soup_message_set_uri (msg, uri);
232 soup_message_body_set_accumulate (msg->response_body, should_accumulate);
234 g_object_connect (msg,
235 "signal::got-headers", got_headers, GINT_TO_POINTER (should_pause),
236 "signal::got-chunk", got_chunk, GINT_TO_POINTER (should_accumulate),
237 "signal::content_sniffed", content_sniffed, GINT_TO_POINTER (should_pause),
240 soup_session_send_message (session, msg);
242 if (should_content_sniff) {
243 soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") != NULL,
244 "content-sniffed did not get emitted");
246 soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL,
247 "content-sniffed got emitted without a sniffer");
251 expected = soup_buffer_new (SOUP_MEMORY_STATIC, "", 0);
253 expected = soup_test_load_resource ("mbox", &error);
254 g_assert_no_error (error);
257 if (!should_accumulate && chunk_data)
258 body = soup_message_body_flatten (chunk_data);
259 else if (msg->response_body)
260 body = soup_message_body_flatten (msg->response_body);
263 soup_assert_cmpmem (body->data, body->length,
264 expected->data, expected->length);
267 soup_buffer_free (expected);
269 soup_buffer_free (body);
271 soup_message_body_free (chunk_data);
276 g_object_unref (msg);
280 do_signals_tests (gconstpointer data)
282 gboolean should_content_sniff = GPOINTER_TO_INT (data);
284 if (!should_content_sniff)
285 soup_session_remove_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
287 do_signals_test (should_content_sniff,
288 FALSE, FALSE, FALSE, FALSE);
289 do_signals_test (should_content_sniff,
290 FALSE, FALSE, TRUE, FALSE);
291 do_signals_test (should_content_sniff,
292 FALSE, TRUE, FALSE, FALSE);
293 do_signals_test (should_content_sniff,
294 FALSE, TRUE, TRUE, FALSE);
296 do_signals_test (should_content_sniff,
297 TRUE, TRUE, FALSE, FALSE);
298 do_signals_test (should_content_sniff,
299 TRUE, TRUE, TRUE, FALSE);
300 do_signals_test (should_content_sniff,
301 TRUE, FALSE, FALSE, FALSE);
302 do_signals_test (should_content_sniff,
303 TRUE, FALSE, TRUE, FALSE);
305 /* FIXME g_test_bug ("587907") */
306 do_signals_test (should_content_sniff,
307 TRUE, TRUE, FALSE, TRUE);
308 do_signals_test (should_content_sniff,
309 TRUE, TRUE, TRUE, TRUE);
311 if (!should_content_sniff)
312 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
316 sniffing_content_sniffed (SoupMessage *msg, const char *content_type,
317 GHashTable *params, gpointer data)
319 char **sniffed_type = (char **)data;
320 GString *full_header;
324 full_header = g_string_new (content_type);
326 g_hash_table_iter_init (&iter, params);
327 while (g_hash_table_iter_next (&iter, &key, &value)) {
328 if (full_header->len)
329 g_string_append (full_header, "; ");
330 soup_header_g_string_append_param (full_header,
332 (const char *) value);
335 *sniffed_type = g_string_free (full_header, FALSE);
339 test_sniffing (const char *path, const char *expected_type)
344 GInputStream *stream;
345 char *sniffed_type = NULL;
346 const char *req_sniffed_type;
347 GError *error = NULL;
349 uri = soup_uri_new_with_base (base_uri, path);
350 msg = soup_message_new_from_uri ("GET", uri);
352 g_signal_connect (msg, "content-sniffed",
353 G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
355 soup_session_send_message (session, msg);
356 g_assert_cmpstr (sniffed_type, ==, expected_type);
357 g_free (sniffed_type);
358 g_object_unref (msg);
360 req = soup_session_request_uri (session, uri, NULL);
361 stream = soup_test_request_send (req, NULL, 0, &error);
363 soup_test_request_close_stream (req, stream, NULL, &error);
364 g_object_unref (stream);
366 g_assert_no_error (error);
367 g_clear_error (&error);
369 req_sniffed_type = soup_request_get_content_type (req);
370 g_assert_cmpstr (req_sniffed_type, ==, expected_type);
371 g_object_unref (req);
377 do_sniffing_test (gconstpointer data)
379 const char *path_and_result = data;
382 parts = g_strsplit (path_and_result, " => ", -1);
383 g_assert (parts && parts[0] && parts[1] && !parts[2]);
385 test_sniffing (parts[0], parts[1]);
390 test_disabled (gconstpointer data)
392 const char *path = data;
396 GInputStream *stream;
397 char *sniffed_type = NULL;
398 const char *sniffed_content_type;
399 GError *error = NULL;
401 g_test_bug ("574773");
403 uri = soup_uri_new_with_base (base_uri, path);
405 msg = soup_message_new_from_uri ("GET", uri);
406 soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
408 g_signal_connect (msg, "content-sniffed",
409 G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
411 soup_session_send_message (session, msg);
413 g_assert_null (sniffed_type);
414 g_object_unref (msg);
416 req = soup_session_request_uri (session, uri, NULL);
417 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
418 soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
419 g_object_unref (msg);
420 stream = soup_test_request_send (req, NULL, 0, &error);
422 soup_test_request_close_stream (req, stream, NULL, &error);
423 g_object_unref (stream);
425 g_assert_no_error (error);
427 sniffed_content_type = soup_request_get_content_type (req);
428 g_assert_cmpstr (sniffed_content_type, ==, NULL);
430 g_object_unref (req);
436 main (int argc, char **argv)
441 test_init (argc, argv, NULL);
443 server = soup_test_server_new (TRUE);
444 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
445 base_uri = soup_uri_new ("http://127.0.0.1/");
446 soup_uri_set_port (base_uri, soup_server_get_port (server));
448 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
449 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
451 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
453 g_test_add_data_func ("/sniffing/signals/no-sniffer",
454 GINT_TO_POINTER (FALSE),
456 g_test_add_data_func ("/sniffing/signals/with-sniffer",
457 GINT_TO_POINTER (TRUE),
460 /* Test the apache bug sniffing path */
461 g_test_add_data_func ("/sniffing/apache-bug/binary",
462 "/apache_bug/text_binary.txt => application/octet-stream",
464 g_test_add_data_func ("/sniffing/apache-bug/text",
465 "/apache_bug/text.txt => text/plain",
468 /* X-Content-Type-Options: nosniff */
469 g_test_add_data_func ("/sniffing/nosniff",
470 "nosniff/home.gif => no/sniffing-allowed",
473 /* GIF is a 'safe' type */
474 g_test_add_data_func ("/sniffing/type/gif",
475 "text_or_binary/home.gif => image/gif",
478 /* With our current code, no sniffing is done using GIO, so
479 * the mbox will be identified as text/plain; should we change
482 g_test_add_data_func ("/sniffing/type/mbox",
483 "text_or_binary/mbox => text/plain",
486 /* HTML is considered unsafe for this algorithm, since it is
487 * scriptable, so going from text/plain to text/html is
488 * considered 'privilege escalation'
490 g_test_add_data_func ("/sniffing/type/html-in-text-context",
491 "text_or_binary/test.html => text/plain",
494 /* text/plain with binary content and unknown pattern should be
495 * application/octet-stream
497 g_test_add_data_func ("/sniffing/type/text-binary",
498 "text_or_binary/text_binary.txt => application/octet-stream",
501 /* text/html with binary content and scriptable pattern should be
502 * application/octet-stream to avoid 'privilege escalation'
504 g_test_add_data_func ("/sniffing/type/html-binary",
505 "text_or_binary/html_binary.html => application/octet-stream",
508 /* text/plain with binary content and non scriptable known pattern should
511 g_test_add_data_func ("/sniffing/type/ps",
512 "text_or_binary/ps_binary.ps => application/postscript",
515 /* Test the unknown sniffing path */
516 g_test_add_data_func ("/sniffing/type/unknown-html",
517 "unknown/test.html => text/html",
519 g_test_add_data_func ("/sniffing/type/unknown-gif",
520 "unknown/home.gif => image/gif",
522 g_test_add_data_func ("/sniffing/type/unknown-mbox",
523 "unknown/mbox => text/plain",
525 g_test_add_data_func ("/sniffing/type/unknown-binary",
526 "unknown/text_binary.txt => application/octet-stream",
528 /* FIXME g_test_bug ("715126") */
529 g_test_add_data_func ("/sniffing/type/unknown-leading-space",
530 "unknown/leading_space.html => text/html",
533 /* Test the XML sniffing path */
534 g_test_add_data_func ("/sniffing/type/xml",
535 "type/text_xml/home.gif => text/xml",
537 g_test_add_data_func ("/sniffing/type/xml+xml",
538 "type/anice_type+xml/home.gif => anice/type+xml",
540 g_test_add_data_func ("/sniffing/type/application-xml",
541 "type/application_xml/home.gif => application/xml",
544 /* Test the feed or html path */
545 g_test_add_data_func ("/sniffing/type/html/html",
546 "type/text_html/test.html => text/html",
548 g_test_add_data_func ("/sniffing/type/html/rss",
549 "type/text_html/rss20.xml => application/rss+xml",
551 g_test_add_data_func ("/sniffing/type/html/atom",
552 "type/text_html/atom.xml => application/atom+xml",
554 g_test_add_data_func ("/sniffing/type/html/rdf",
555 "type/text_html/feed.rdf => application/rss+xml",
558 /* Test the image sniffing path */
559 g_test_add_data_func ("/sniffing/type/image/gif",
560 "type/image_png/home.gif => image/gif",
562 g_test_add_data_func ("/sniffing/type/image/png",
563 "type/image_gif/home.png => image/png",
565 g_test_add_data_func ("/sniffing/type/image/jpeg",
566 "type/image_png/home.jpg => image/jpeg",
568 g_test_add_data_func ("/sniffing/type/image/webp",
569 "type/image_png/tux.webp => image/webp",
572 /* Test audio and video sniffing path */
573 g_test_add_data_func ("/sniffing/type/audio/wav",
574 "type/audio_mpeg/test.wav => audio/wave",
576 g_test_add_data_func ("/sniffing/type/audio/aiff",
577 "type/audio_mpeg/test.aiff => audio/aiff",
579 g_test_add_data_func ("/sniffing/type/audio/ogg",
580 "type/audio_mpeg/test.ogg => application/ogg",
582 g_test_add_data_func ("/sniffing/type/video/webm",
583 "type/video_theora/test.webm => video/webm",
586 /* Test the MP4 sniffing path */
587 g_test_add_data_func ("/sniffing/type/video/mp4",
588 "unknown/test.mp4 => video/mp4",
591 /* The spec tells us to only use the last Content-Type header */
592 g_test_add_data_func ("/sniffing/multiple-headers",
593 "multiple_headers/home.gif => image/gif",
596 /* Test that we keep the parameters when sniffing */
597 g_test_add_data_func ("/sniffing/parameters",
598 "type/text_html; charset=UTF-8/test.html => text/html; charset=UTF-8",
601 /* Test that disabling the sniffer works correctly */
602 g_test_add_data_func ("/sniffing/disabled",
603 "/text_or_binary/home.gif",
608 soup_uri_free (base_uri);
610 soup_test_session_abort_unref (session);
611 soup_test_server_quit_unref (server);