1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>.
10 #include <libsoup/soup.h>
12 #include "test-utils.h"
16 SoupMessageBody *chunk_data;
19 server_callback (SoupServer *server, SoupMessage *msg,
20 const char *path, GHashTable *query,
21 SoupClientContext *context, gpointer data)
27 gboolean empty_response = FALSE;
29 if (msg->method != SOUP_METHOD_GET) {
30 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
34 soup_message_set_status (msg, SOUP_STATUS_OK);
37 query_key = g_hash_table_lookup (query, "chunked");
38 if (query_key && g_str_equal (query_key, "yes")) {
39 soup_message_headers_set_encoding (msg->response_headers,
40 SOUP_ENCODING_CHUNKED);
43 query_key = g_hash_table_lookup (query, "empty_response");
44 if (query_key && g_str_equal (query_key, "yes"))
45 empty_response = TRUE;
48 if (!strcmp (path, "/mbox")) {
50 contents = g_strdup ("");
53 g_file_get_contents (SRCDIR "/resources/mbox",
59 g_error ("%s", error->message);
64 soup_message_headers_append (msg->response_headers,
65 "Content-Type", "text/plain");
68 if (g_str_has_prefix (path, "/text_or_binary/")) {
69 char *base_name = g_path_get_basename (path);
70 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
72 g_file_get_contents (file_name,
80 g_error ("%s", error->message);
85 soup_message_headers_append (msg->response_headers,
86 "Content-Type", "text/plain");
89 if (g_str_has_prefix (path, "/unknown/")) {
90 char *base_name = g_path_get_basename (path);
91 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
93 g_file_get_contents (file_name,
101 g_error ("%s", error->message);
102 g_error_free (error);
106 soup_message_headers_append (msg->response_headers,
107 "Content-Type", "UNKNOWN/unknown");
110 if (g_str_has_prefix (path, "/type/")) {
111 char **components = g_strsplit (path, "/", 4);
114 char *base_name = g_path_get_basename (path);
115 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
117 g_file_get_contents (file_name,
125 g_error ("%s", error->message);
126 g_error_free (error);
130 /* Hack to allow passing type in the URI */
131 ptr = g_strrstr (components[2], "_");
134 soup_message_headers_append (msg->response_headers,
135 "Content-Type", components[2]);
136 g_strfreev (components);
139 if (g_str_has_prefix (path, "/multiple_headers/")) {
140 char *base_name = g_path_get_basename (path);
141 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
143 g_file_get_contents (file_name,
151 g_error ("%s", error->message);
152 g_error_free (error);
156 soup_message_headers_append (msg->response_headers,
157 "Content-Type", "text/xml");
158 soup_message_headers_append (msg->response_headers,
159 "Content-Type", "text/plain");
162 for (offset = 0; offset < length; offset += 500) {
163 soup_message_body_append (msg->response_body,
166 MIN(500, length - offset));
168 soup_message_body_complete (msg->response_body);
174 unpause_msg (gpointer data)
176 SoupMessage *msg = (SoupMessage*)data;
177 debug_printf (2, " unpause\n");
178 soup_session_unpause_message (session, msg);
184 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data)
186 gboolean should_pause = GPOINTER_TO_INT (data);
188 debug_printf (2, " content-sniffed -> %s\n", content_type);
190 if (g_object_get_data (G_OBJECT (msg), "got-chunk")) {
191 debug_printf (1, " got-chunk got emitted before content-sniffed\n");
195 g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
198 debug_printf (2, " pause\n");
199 soup_session_pause_message (session, msg);
200 g_idle_add (unpause_msg, msg);
205 got_headers (SoupMessage *msg, gpointer data)
207 gboolean should_pause = GPOINTER_TO_INT (data);
209 debug_printf (2, " got-headers\n");
211 if (g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
212 debug_printf (1, " content-sniffed got emitted before got-headers\n");
216 g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE));
219 debug_printf (2, " pause\n");
220 soup_session_pause_message (session, msg);
221 g_idle_add (unpause_msg, msg);
226 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data)
228 gboolean should_accumulate = GPOINTER_TO_INT (data);
230 debug_printf (2, " got-chunk\n");
232 g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE));
234 if (!should_accumulate) {
236 chunk_data = soup_message_body_new ();
237 soup_message_body_append_buffer (chunk_data, chunk);
242 finished (SoupSession *session, SoupMessage *msg, gpointer data)
244 GMainLoop *loop = (GMainLoop*)data;
245 g_main_loop_quit (loop);
249 do_signals_test (gboolean should_content_sniff,
250 gboolean should_pause,
251 gboolean should_accumulate,
252 gboolean chunked_encoding,
253 gboolean empty_response)
255 SoupURI *uri = soup_uri_new_with_base (base_uri, "/mbox");
256 SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
257 GMainLoop *loop = g_main_loop_new (NULL, TRUE);
260 GError *error = NULL;
261 SoupBuffer *body = NULL;
263 debug_printf (1, "do_signals_test(%ssniff, %spause, %saccumulate, %schunked, %sempty)\n",
264 should_content_sniff ? "" : "!",
265 should_pause ? "" : "!",
266 should_accumulate ? "" : "!",
267 chunked_encoding ? "" : "!",
268 empty_response ? "" : "!");
270 if (chunked_encoding)
271 soup_uri_set_query (uri, "chunked=yes");
273 if (empty_response) {
275 char *tmp = uri->query;
276 uri->query = g_strdup_printf ("%s&empty_response=yes", tmp);
279 soup_uri_set_query (uri, "empty_response=yes");
282 soup_message_set_uri (msg, uri);
284 soup_message_body_set_accumulate (msg->response_body, should_accumulate);
286 g_object_connect (msg,
287 "signal::got-headers", got_headers, GINT_TO_POINTER (should_pause),
288 "signal::got-chunk", got_chunk, GINT_TO_POINTER (should_accumulate),
289 "signal::content_sniffed", content_sniffed, GINT_TO_POINTER (should_pause),
293 soup_session_queue_message (session, msg, finished, loop);
295 g_main_loop_run (loop);
297 if (!should_content_sniff &&
298 g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
299 debug_printf (1, " content-sniffed got emitted without a sniffer\n");
301 } else if (should_content_sniff &&
302 !g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
303 debug_printf (1, " content-sniffed did not get emitted\n");
307 if (empty_response) {
308 contents = g_strdup ("");
311 g_file_get_contents (SRCDIR "/resources/mbox",
317 g_error ("%s", error->message);
318 g_error_free (error);
322 if (!should_accumulate && chunk_data)
323 body = soup_message_body_flatten (chunk_data);
324 else if (msg->response_body)
325 body = soup_message_body_flatten (msg->response_body);
327 if (body && body->length != length) {
328 debug_printf (1, " lengths do not match\n");
332 if (body && memcmp (body->data, contents, length)) {
333 debug_printf (1, " downloaded data does not match\n");
339 soup_buffer_free (body);
341 soup_message_body_free (chunk_data);
346 g_object_unref (msg);
347 g_main_loop_unref (loop);
351 sniffing_content_sniffed (SoupMessage *msg, const char *content_type,
352 GHashTable *params, gpointer data)
354 char **sniffed_type = (char **)data;
355 GString *full_header;
359 if (params == NULL) {
360 *sniffed_type = g_strdup (content_type);
364 full_header = g_string_new (content_type);
365 g_string_append (full_header, "; ");
367 keys = g_hash_table_get_keys (params);
368 for (iter = keys; iter != NULL; iter = iter->next) {
369 const gchar *value = (const gchar*) g_hash_table_lookup (params, iter->data);
371 soup_header_g_string_append_param (full_header,
372 (const gchar*) iter->data,
376 *sniffed_type = full_header->str;
378 g_string_free (full_header, FALSE);
383 test_sniffing (const char *path, const char *expected_type)
385 SoupURI *uri = soup_uri_new_with_base (base_uri, path);
386 SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
387 GMainLoop *loop = g_main_loop_new (NULL, TRUE);
388 char *sniffed_type = NULL;
390 debug_printf (1, "test_sniffing(\"%s\", \"%s\")\n", path, expected_type);
392 g_signal_connect (msg, "content-sniffed",
393 G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
397 soup_session_queue_message (session, msg, finished, loop);
399 g_main_loop_run (loop);
402 debug_printf (1, " message was not sniffed!\n");
404 } else if (strcmp (sniffed_type, expected_type) != 0) {
405 debug_printf (1, " sniffing failed! expected %s, got %s\n",
406 expected_type, sniffed_type);
409 g_free (sniffed_type);
412 g_object_unref (msg);
413 g_main_loop_unref (loop);
417 test_disabled (const char *path)
419 SoupURI *uri = soup_uri_new_with_base (base_uri, path);
420 SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
421 GMainLoop *loop = g_main_loop_new (NULL, TRUE);
422 char *sniffed_type = NULL;
424 soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
426 debug_printf (1, "test_disabled(\"%s\")\n", path);
428 g_signal_connect (msg, "content-sniffed",
429 G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
433 soup_session_queue_message (session, msg, finished, loop);
435 g_main_loop_run (loop);
438 debug_printf (1, " message was sniffed!\n");
440 g_free (sniffed_type);
444 g_object_unref (msg);
445 g_main_loop_unref (loop);
449 main (int argc, char **argv)
453 test_init (argc, argv, NULL);
455 server = soup_test_server_new (TRUE);
456 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
457 base_uri = soup_uri_new ("http://127.0.0.1/");
458 soup_uri_set_port (base_uri, soup_server_get_port (server));
460 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
462 /* No sniffer, no content_sniffed should be emitted */
463 do_signals_test (FALSE, FALSE, FALSE, FALSE, FALSE);
464 do_signals_test (FALSE, FALSE, FALSE, TRUE, FALSE);
465 do_signals_test (FALSE, FALSE, TRUE, FALSE, FALSE);
466 do_signals_test (FALSE, FALSE, TRUE, TRUE, FALSE);
468 do_signals_test (FALSE, TRUE, TRUE, FALSE, FALSE);
469 do_signals_test (FALSE, TRUE, TRUE, TRUE, FALSE);
470 do_signals_test (FALSE, TRUE, FALSE, FALSE, FALSE);
471 do_signals_test (FALSE, TRUE, FALSE, TRUE, FALSE);
473 /* Tests that the signals are correctly emitted for empty
475 * http://bugzilla.gnome.org/show_bug.cgi?id=587907 */
477 do_signals_test (FALSE, TRUE, TRUE, FALSE, TRUE);
478 do_signals_test (FALSE, TRUE, TRUE, TRUE, TRUE);
480 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
482 /* Now, with a sniffer, content_sniffed must be emitted after
483 * got-headers, and before got-chunk.
485 do_signals_test (TRUE, FALSE, FALSE, FALSE, FALSE);
486 do_signals_test (TRUE, FALSE, FALSE, TRUE, FALSE);
487 do_signals_test (TRUE, FALSE, TRUE, FALSE, FALSE);
488 do_signals_test (TRUE, FALSE, TRUE, TRUE, FALSE);
490 do_signals_test (TRUE, TRUE, TRUE, FALSE, FALSE);
491 do_signals_test (TRUE, TRUE, TRUE, TRUE, FALSE);
492 do_signals_test (TRUE, TRUE, FALSE, FALSE, FALSE);
493 do_signals_test (TRUE, TRUE, FALSE, TRUE, FALSE);
495 /* Empty response tests */
496 do_signals_test (TRUE, TRUE, TRUE, FALSE, TRUE);
497 do_signals_test (TRUE, TRUE, TRUE, TRUE, TRUE);
499 /* Test the text_or_binary sniffing path */
501 /* GIF is a 'safe' type */
502 test_sniffing ("/text_or_binary/home.gif", "image/gif");
504 /* With our current code, no sniffing is done using GIO, so
505 * the mbox will be identified as text/plain; should we change
508 test_sniffing ("/text_or_binary/mbox", "text/plain");
510 /* HTML is considered unsafe for this algorithm, since it is
511 * scriptable, so going from text/plain to text/html is
512 * considered 'privilege escalation'
514 test_sniffing ("/text_or_binary/test.html", "text/plain");
516 /* text/plain with binary content and unknown pattern should be
517 * application/octet-stream */
518 test_sniffing ("/text_or_binary/text_binary.txt", "application/octet-stream");
520 /* text/plain with binary content and scriptable pattern should be
521 * application/octet-stream to avoid 'privilege escalation' */
522 test_sniffing ("/text_or_binary/html_binary.html", "application/octet-stream");
524 /* text/plain with binary content and non scriptable known pattern should
525 * be the given type */
526 test_sniffing ("/text_or_binary/ps_binary.ps", "application/postscript");
528 /* Test the unknown sniffing path */
530 test_sniffing ("/unknown/test.html", "text/html");
531 test_sniffing ("/unknown/home.gif", "image/gif");
532 test_sniffing ("/unknown/mbox", "text/plain");
533 test_sniffing ("/unknown/text_binary.txt", "application/octet-stream");
535 /* Test the XML sniffing path */
537 test_sniffing ("/type/text_xml/home.gif", "text/xml");
538 test_sniffing ("/type/anice_type+xml/home.gif", "anice/type+xml");
539 test_sniffing ("/type/application_xml/home.gif", "application/xml");
541 /* Test the image sniffing path */
543 test_sniffing ("/type/image_png/home.gif", "image/gif");
545 /* Test the feed or html path */
547 test_sniffing ("/type/text_html/test.html", "text/html");
548 test_sniffing ("/type/text_html/rss20.xml", "application/rss+xml");
549 test_sniffing ("/type/text_html/atom.xml", "application/atom+xml");
551 /* The spec tells us to only use the last Content-Type header */
553 test_sniffing ("/multiple_headers/home.gif", "image/gif");
555 /* Test that we keep the parameters when sniffing */
556 test_sniffing ("/type/text_html; charset=UTF-8/test.html", "text/html; charset=UTF-8");
558 /* Test that disabling the sniffer works correctly */
560 test_disabled ("/text_or_binary/home.gif");
562 soup_uri_free (base_uri);
564 soup_test_session_abort_unref (session);
565 soup_test_server_quit_unref (server);