add missing slash in %configure
[platform/upstream/libsoup.git] / tests / sniffing-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>.
4  */
5
6 #include "test-utils.h"
7
8 SoupSession *session;
9 SoupURI *base_uri;
10 SoupMessageBody *chunk_data;
11
12 static void
13 server_callback (SoupServer *server, SoupMessage *msg,
14                  const char *path, GHashTable *query,
15                  SoupClientContext *context, gpointer data)
16 {
17         GError *error = NULL;
18         char *query_key;
19         SoupBuffer *response = NULL;
20         gsize offset;
21         gboolean empty_response = FALSE;
22
23         if (msg->method != SOUP_METHOD_GET) {
24                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
25                 return;
26         }
27
28         soup_message_set_status (msg, SOUP_STATUS_OK);
29
30         if (query) {
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);
35                 }
36
37                 query_key = g_hash_table_lookup (query, "empty_response");
38                 if (query_key && g_str_equal (query_key, "yes"))
39                         empty_response = TRUE;
40         }
41
42         if (!strcmp (path, "/mbox")) {
43                 if (!empty_response) {
44                         response = soup_test_load_resource ("mbox", &error);
45                         g_assert_no_error (error);
46                 }
47
48                 soup_message_headers_append (msg->response_headers,
49                                              "Content-Type", "text/plain");
50         }
51
52         if (g_str_has_prefix (path, "/nosniff/")) {
53                 char *base_name = g_path_get_basename (path);
54
55                 response = soup_test_load_resource (base_name, &error);
56                 g_assert_no_error (error);
57                 g_free (base_name);
58
59                 soup_message_headers_append (msg->response_headers,
60                                              "X-Content-Type-Options", "nosniff");
61
62                 soup_message_headers_append (msg->response_headers,
63                                              "Content-Type", "no/sniffing-allowed");
64         }
65
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);
68
69                 response = soup_test_load_resource (base_name, &error);
70                 g_assert_no_error (error);
71                 g_free (base_name);
72
73                 soup_message_headers_append (msg->response_headers,
74                                              "Content-Type", "text/plain");
75         }
76
77         if (g_str_has_prefix (path, "/unknown/")) {
78                 char *base_name = g_path_get_basename (path);
79
80                 response = soup_test_load_resource (base_name, &error);
81                 g_assert_no_error (error);
82                 g_free (base_name);
83
84                 soup_message_headers_append (msg->response_headers,
85                                              "Content-Type", "UNKNOWN/unknown");
86         }
87
88         if (g_str_has_prefix (path, "/type/")) {
89                 char **components = g_strsplit (path, "/", 4);
90                 char *ptr;
91
92                 char *base_name = g_path_get_basename (path);
93
94                 response = soup_test_load_resource (base_name, &error);
95                 g_assert_no_error (error);
96                 g_free (base_name);
97
98                 /* Hack to allow passing type in the URI */
99                 ptr = g_strrstr (components[2], "_");
100                 *ptr = '/';
101
102                 soup_message_headers_append (msg->response_headers,
103                                              "Content-Type", components[2]);
104                 g_strfreev (components);
105         }
106
107         if (g_str_has_prefix (path, "/multiple_headers/")) {
108                 char *base_name = g_path_get_basename (path);
109
110                 response = soup_test_load_resource (base_name, &error);
111                 g_assert_no_error (error);
112                 g_free (base_name);
113
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");
118         }
119
120         if (response) {
121                 for (offset = 0; offset < response->length; offset += 500) {
122                         soup_message_body_append (msg->response_body,
123                                                   SOUP_MEMORY_COPY,
124                                                   response->data + offset,
125                                                   MIN (500, response->length - offset));
126                 }
127
128                 soup_buffer_free (response);
129         }
130
131         soup_message_body_complete (msg->response_body);
132 }
133
134 static gboolean
135 unpause_msg (gpointer data)
136 {
137         SoupMessage *msg = (SoupMessage*)data;
138         debug_printf (2, "  unpause\n");
139         soup_session_unpause_message (session, msg);
140         return FALSE;
141 }
142
143
144 static void
145 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data)
146 {
147         gboolean should_pause = GPOINTER_TO_INT (data);
148
149         debug_printf (2, "  content-sniffed -> %s\n", content_type);
150
151         soup_test_assert (g_object_get_data (G_OBJECT (msg), "got-chunk") == NULL,
152                           "got-chunk got emitted before content-sniffed");
153
154         g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
155
156         if (should_pause) {
157                 debug_printf (2, "  pause\n");
158                 soup_session_pause_message (session, msg);
159                 g_idle_add (unpause_msg, msg);
160         }
161 }
162
163 static void
164 got_headers (SoupMessage *msg, gpointer data)
165 {
166         gboolean should_pause = GPOINTER_TO_INT (data);
167
168         debug_printf (2, "  got-headers\n");
169
170         soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL,
171                           "content-sniffed got emitted before got-headers");
172
173         g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE));
174
175         if (should_pause) {
176                 debug_printf (2, "  pause\n");
177                 soup_session_pause_message (session, msg);
178                 g_idle_add (unpause_msg, msg);
179         }
180 }
181
182 static void
183 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data)
184 {
185         gboolean should_accumulate = GPOINTER_TO_INT (data);
186
187         debug_printf (2, "  got-chunk\n");
188
189         g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE));
190
191         if (!should_accumulate) {
192                 if (!chunk_data)
193                         chunk_data = soup_message_body_new ();
194                 soup_message_body_append_buffer (chunk_data, chunk);
195         }
196 }
197
198 static void
199 do_signals_test (gboolean should_content_sniff,
200                  gboolean should_pause,
201                  gboolean should_accumulate,
202                  gboolean chunked_encoding,
203                  gboolean empty_response)
204 {
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;
210
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 ? "" : "!");
217
218         if (chunked_encoding)
219                 soup_uri_set_query (uri, "chunked=yes");
220
221         if (empty_response) {
222                 if (uri->query) {
223                         char *tmp = uri->query;
224                         uri->query = g_strdup_printf ("%s&empty_response=yes", tmp);
225                         g_free (tmp);
226                 } else
227                         soup_uri_set_query (uri, "empty_response=yes");
228         }
229
230         soup_message_set_uri (msg, uri);
231
232         soup_message_body_set_accumulate (msg->response_body, should_accumulate);
233
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),
238                           NULL);
239
240         soup_session_send_message (session, msg);
241
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");
245         } else {
246                 soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL,
247                                   "content-sniffed got emitted without a sniffer");
248         }
249
250         if (empty_response)
251                 expected = soup_buffer_new (SOUP_MEMORY_STATIC, "", 0);
252         else {
253                 expected = soup_test_load_resource ("mbox", &error);
254                 g_assert_no_error (error);
255         }
256
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);
261
262         if (body) {
263                 soup_assert_cmpmem (body->data, body->length,
264                                     expected->data, expected->length);
265         }
266
267         soup_buffer_free (expected);
268         if (body)
269                 soup_buffer_free (body);
270         if (chunk_data) {
271                 soup_message_body_free (chunk_data);
272                 chunk_data = NULL;
273         }
274
275         soup_uri_free (uri);
276         g_object_unref (msg);
277 }
278
279 static void
280 do_signals_tests (gconstpointer data)
281 {
282         gboolean should_content_sniff = GPOINTER_TO_INT (data);
283
284         if (!should_content_sniff)
285                 soup_session_remove_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
286
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);
295
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);
304
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);
310
311         if (!should_content_sniff)
312                 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
313 }
314
315 static void
316 sniffing_content_sniffed (SoupMessage *msg, const char *content_type,
317                           GHashTable *params, gpointer data)
318 {
319         char **sniffed_type = (char **)data;
320         GString *full_header;
321         GHashTableIter iter;
322         gpointer key, value;
323
324         full_header = g_string_new (content_type);
325
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,
331                                                    (const char *) key,
332                                                    (const char *) value);
333         }
334
335         *sniffed_type = g_string_free (full_header, FALSE);
336 }
337
338 static void
339 test_sniffing (const char *path, const char *expected_type)
340 {
341         SoupURI *uri;
342         SoupMessage *msg;
343         SoupRequest *req;
344         GInputStream *stream;
345         char *sniffed_type = NULL;
346         const char *req_sniffed_type;
347         GError *error = NULL;
348
349         uri = soup_uri_new_with_base (base_uri, path);
350         msg = soup_message_new_from_uri ("GET", uri);
351
352         g_signal_connect (msg, "content-sniffed",
353                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
354
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);
359
360         req = soup_session_request_uri (session, uri, NULL);
361         stream = soup_test_request_send (req, NULL, 0, &error);
362         if (stream) {
363                 soup_test_request_close_stream (req, stream, NULL, &error);
364                 g_object_unref (stream);
365         }
366         g_assert_no_error (error);
367         g_clear_error (&error);
368
369         req_sniffed_type = soup_request_get_content_type (req);
370         g_assert_cmpstr (req_sniffed_type, ==, expected_type);
371         g_object_unref (req);
372
373         soup_uri_free (uri);
374 }
375
376 static void
377 do_sniffing_test (gconstpointer data)
378 {
379         const char *path_and_result = data;
380         char **parts;
381
382         parts = g_strsplit (path_and_result, " => ", -1);
383         g_assert (parts && parts[0] && parts[1] && !parts[2]);
384
385         test_sniffing (parts[0], parts[1]);
386         g_strfreev (parts);
387 }
388
389 static void
390 test_disabled (gconstpointer data)
391 {
392         const char *path = data;
393         SoupURI *uri;
394         SoupMessage *msg;
395         SoupRequest *req;
396         GInputStream *stream;
397         char *sniffed_type = NULL;
398         const char *sniffed_content_type;
399         GError *error = NULL;
400
401         g_test_bug ("574773");
402
403         uri = soup_uri_new_with_base (base_uri, path);
404
405         msg = soup_message_new_from_uri ("GET", uri);
406         soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
407
408         g_signal_connect (msg, "content-sniffed",
409                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
410
411         soup_session_send_message (session, msg);
412
413         g_assert_null (sniffed_type);
414         g_object_unref (msg);
415
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);
421         if (stream) {
422                 soup_test_request_close_stream (req, stream, NULL, &error);
423                 g_object_unref (stream);
424         }
425         g_assert_no_error (error);
426
427         sniffed_content_type = soup_request_get_content_type (req);
428         g_assert_cmpstr (sniffed_content_type, ==, NULL);
429
430         g_object_unref (req);
431
432         soup_uri_free (uri);
433 }
434
435 int
436 main (int argc, char **argv)
437 {
438         SoupServer *server;
439         int ret;
440
441         test_init (argc, argv, NULL);
442
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));
447
448         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
449                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
450                                          NULL);
451         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
452
453         g_test_add_data_func ("/sniffing/signals/no-sniffer",
454                               GINT_TO_POINTER (FALSE),
455                               do_signals_tests);
456         g_test_add_data_func ("/sniffing/signals/with-sniffer",
457                               GINT_TO_POINTER (TRUE),
458                               do_signals_tests);
459
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",
463                               do_sniffing_test);
464         g_test_add_data_func ("/sniffing/apache-bug/text",
465                               "/apache_bug/text.txt => text/plain",
466                               do_sniffing_test);
467
468         /* X-Content-Type-Options: nosniff */
469         g_test_add_data_func ("/sniffing/nosniff",
470                               "nosniff/home.gif => no/sniffing-allowed",
471                               do_sniffing_test);
472
473         /* GIF is a 'safe' type */
474         g_test_add_data_func ("/sniffing/type/gif",
475                               "text_or_binary/home.gif => image/gif",
476                               do_sniffing_test);
477
478         /* With our current code, no sniffing is done using GIO, so
479          * the mbox will be identified as text/plain; should we change
480          * this?
481          */
482         g_test_add_data_func ("/sniffing/type/mbox",
483                               "text_or_binary/mbox => text/plain",
484                               do_sniffing_test);
485
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'
489          */
490         g_test_add_data_func ("/sniffing/type/html-in-text-context",
491                               "text_or_binary/test.html => text/plain",
492                               do_sniffing_test);
493
494         /* text/plain with binary content and unknown pattern should be
495          * application/octet-stream
496          */
497         g_test_add_data_func ("/sniffing/type/text-binary",
498                               "text_or_binary/text_binary.txt => application/octet-stream",
499                               do_sniffing_test);
500
501         /* text/html with binary content and scriptable pattern should be
502          * application/octet-stream to avoid 'privilege escalation'
503          */
504         g_test_add_data_func ("/sniffing/type/html-binary",
505                               "text_or_binary/html_binary.html => application/octet-stream",
506                               do_sniffing_test);
507
508         /* text/plain with binary content and non scriptable known pattern should
509          * be the given type
510          */
511         g_test_add_data_func ("/sniffing/type/ps",
512                               "text_or_binary/ps_binary.ps => application/postscript",
513                               do_sniffing_test);
514
515         /* Test the unknown sniffing path */
516         g_test_add_data_func ("/sniffing/type/unknown-html",
517                               "unknown/test.html => text/html",
518                               do_sniffing_test);
519         g_test_add_data_func ("/sniffing/type/unknown-gif",
520                               "unknown/home.gif => image/gif",
521                               do_sniffing_test);
522         g_test_add_data_func ("/sniffing/type/unknown-mbox",
523                               "unknown/mbox => text/plain",
524                               do_sniffing_test);
525         g_test_add_data_func ("/sniffing/type/unknown-binary",
526                               "unknown/text_binary.txt => application/octet-stream",
527                               do_sniffing_test);
528         /* FIXME g_test_bug ("715126") */
529         g_test_add_data_func ("/sniffing/type/unknown-leading-space",
530                               "unknown/leading_space.html => text/html",
531                               do_sniffing_test);
532
533         /* Test the XML sniffing path */
534         g_test_add_data_func ("/sniffing/type/xml",
535                               "type/text_xml/home.gif => text/xml",
536                               do_sniffing_test);
537         g_test_add_data_func ("/sniffing/type/xml+xml",
538                               "type/anice_type+xml/home.gif => anice/type+xml",
539                               do_sniffing_test);
540         g_test_add_data_func ("/sniffing/type/application-xml",
541                               "type/application_xml/home.gif => application/xml",
542                               do_sniffing_test);
543
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",
547                               do_sniffing_test);
548         g_test_add_data_func ("/sniffing/type/html/rss",
549                               "type/text_html/rss20.xml => application/rss+xml",
550                               do_sniffing_test);
551         g_test_add_data_func ("/sniffing/type/html/atom",
552                               "type/text_html/atom.xml => application/atom+xml",
553                               do_sniffing_test);
554         g_test_add_data_func ("/sniffing/type/html/rdf",
555                               "type/text_html/feed.rdf => application/rss+xml",
556                               do_sniffing_test);
557
558         /* Test the image sniffing path */
559         g_test_add_data_func ("/sniffing/type/image/gif",
560                               "type/image_png/home.gif => image/gif",
561                               do_sniffing_test);
562         g_test_add_data_func ("/sniffing/type/image/png",
563                               "type/image_gif/home.png => image/png",
564                               do_sniffing_test);
565         g_test_add_data_func ("/sniffing/type/image/jpeg",
566                               "type/image_png/home.jpg => image/jpeg",
567                               do_sniffing_test);
568         g_test_add_data_func ("/sniffing/type/image/webp",
569                               "type/image_png/tux.webp => image/webp",
570                               do_sniffing_test);
571
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",
575                               do_sniffing_test);
576         g_test_add_data_func ("/sniffing/type/audio/aiff",
577                               "type/audio_mpeg/test.aiff => audio/aiff",
578                               do_sniffing_test);
579         g_test_add_data_func ("/sniffing/type/audio/ogg",
580                               "type/audio_mpeg/test.ogg => application/ogg",
581                               do_sniffing_test);
582         g_test_add_data_func ("/sniffing/type/video/webm",
583                               "type/video_theora/test.webm => video/webm",
584                               do_sniffing_test);
585
586         /* Test the MP4 sniffing path */
587         g_test_add_data_func ("/sniffing/type/video/mp4",
588                               "unknown/test.mp4 => video/mp4",
589                               do_sniffing_test);
590
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",
594                               do_sniffing_test);
595
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",
599                               do_sniffing_test);
600
601         /* Test that disabling the sniffer works correctly */
602         g_test_add_data_func ("/sniffing/disabled",
603                               "/text_or_binary/home.gif",
604                               test_disabled);
605
606         ret = g_test_run ();
607
608         soup_uri_free (base_uri);
609
610         soup_test_session_abort_unref (session);
611         soup_test_server_quit_unref (server);
612
613         test_cleanup ();
614         return ret;
615 }