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
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)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
unpause_msg(gpointer data)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
content_sniffed(SoupMessage * msg,char * content_type,GHashTable * params,gpointer data)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
got_headers(SoupMessage * msg,gpointer data)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
got_chunk(SoupMessage * msg,SoupBuffer * chunk,gpointer data)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
do_signals_test(gboolean should_content_sniff,gboolean should_pause,gboolean should_accumulate,gboolean chunked_encoding,gboolean empty_response)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
do_signals_tests(gconstpointer data)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
sniffing_content_sniffed(SoupMessage * msg,const char * content_type,GHashTable * params,gpointer data)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
test_sniffing(const char * path,const char * expected_type)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
do_sniffing_test(gconstpointer data)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
test_disabled(gconstpointer data)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 g_assert_false (soup_message_is_feature_disabled (msg, SOUP_TYPE_CONTENT_SNIFFER));
407 soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
408 g_assert_true (soup_message_is_feature_disabled (msg, SOUP_TYPE_CONTENT_SNIFFER));
409
410 g_signal_connect (msg, "content-sniffed",
411 G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
412
413 soup_session_send_message (session, msg);
414
415 g_assert_null (sniffed_type);
416 g_object_unref (msg);
417
418 req = soup_session_request_uri (session, uri, NULL);
419 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
420 g_assert_false (soup_message_is_feature_disabled (msg, SOUP_TYPE_CONTENT_SNIFFER));
421 soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
422 g_assert_true (soup_message_is_feature_disabled (msg, SOUP_TYPE_CONTENT_SNIFFER));
423 g_object_unref (msg);
424 stream = soup_test_request_send (req, NULL, 0, &error);
425 if (stream) {
426 soup_test_request_close_stream (req, stream, NULL, &error);
427 g_object_unref (stream);
428 }
429 g_assert_no_error (error);
430
431 sniffed_content_type = soup_request_get_content_type (req);
432 g_assert_cmpstr (sniffed_content_type, ==, NULL);
433
434 g_object_unref (req);
435
436 soup_uri_free (uri);
437 }
438
439 int
main(int argc,char ** argv)440 main (int argc, char **argv)
441 {
442 SoupServer *server;
443 int ret;
444
445 test_init (argc, argv, NULL);
446
447 server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
448 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
449 base_uri = soup_test_server_get_uri (server, "http", NULL);
450
451 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
452 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
453 NULL);
454 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
455
456 g_test_add_data_func ("/sniffing/signals/no-sniffer",
457 GINT_TO_POINTER (FALSE),
458 do_signals_tests);
459 g_test_add_data_func ("/sniffing/signals/with-sniffer",
460 GINT_TO_POINTER (TRUE),
461 do_signals_tests);
462
463 /* Test the apache bug sniffing path */
464 g_test_add_data_func ("/sniffing/apache-bug/binary",
465 "/apache_bug/text_binary.txt => application/octet-stream",
466 do_sniffing_test);
467 g_test_add_data_func ("/sniffing/apache-bug/text",
468 "/apache_bug/text.txt => text/plain",
469 do_sniffing_test);
470
471 /* X-Content-Type-Options: nosniff */
472 g_test_add_data_func ("/sniffing/nosniff",
473 "nosniff/home.gif => no/sniffing-allowed",
474 do_sniffing_test);
475
476 /* GIF is a 'safe' type */
477 g_test_add_data_func ("/sniffing/type/gif",
478 "text_or_binary/home.gif => image/gif",
479 do_sniffing_test);
480
481 /* With our current code, no sniffing is done using GIO, so
482 * the mbox will be identified as text/plain; should we change
483 * this?
484 */
485 g_test_add_data_func ("/sniffing/type/mbox",
486 "text_or_binary/mbox => text/plain",
487 do_sniffing_test);
488
489 /* HTML is considered unsafe for this algorithm, since it is
490 * scriptable, so going from text/plain to text/html is
491 * considered 'privilege escalation'
492 */
493 g_test_add_data_func ("/sniffing/type/html-in-text-context",
494 "text_or_binary/test.html => text/plain",
495 do_sniffing_test);
496
497 /* text/plain with binary content and unknown pattern should be
498 * application/octet-stream
499 */
500 g_test_add_data_func ("/sniffing/type/text-binary",
501 "text_or_binary/text_binary.txt => application/octet-stream",
502 do_sniffing_test);
503
504 /* text/html with binary content and scriptable pattern should be
505 * application/octet-stream to avoid 'privilege escalation'
506 */
507 g_test_add_data_func ("/sniffing/type/html-binary",
508 "text_or_binary/html_binary.html => application/octet-stream",
509 do_sniffing_test);
510
511 /* text/plain with binary content and non scriptable known pattern should
512 * be the given type
513 */
514 g_test_add_data_func ("/sniffing/type/ps",
515 "text_or_binary/ps_binary.ps => application/postscript",
516 do_sniffing_test);
517
518 /* Test the unknown sniffing path */
519 g_test_add_data_func ("/sniffing/type/unknown-html",
520 "unknown/test.html => text/html",
521 do_sniffing_test);
522 g_test_add_data_func ("/sniffing/type/unknown-gif",
523 "unknown/home.gif => image/gif",
524 do_sniffing_test);
525 g_test_add_data_func ("/sniffing/type/unknown-mbox",
526 "unknown/mbox => text/plain",
527 do_sniffing_test);
528 g_test_add_data_func ("/sniffing/type/unknown-binary",
529 "unknown/text_binary.txt => application/octet-stream",
530 do_sniffing_test);
531 /* FIXME g_test_bug ("715126") */
532 g_test_add_data_func ("/sniffing/type/unknown-leading-space",
533 "unknown/leading_space.html => text/html",
534 do_sniffing_test);
535 /* https://bugs.webkit.org/show_bug.cgi?id=173923 */
536 g_test_add_data_func ("/sniffing/type/unknown-xml",
537 "unknown/misc.xml => text/xml",
538 do_sniffing_test);
539
540 /* Test the XML sniffing path */
541 g_test_add_data_func ("/sniffing/type/xml",
542 "type/text_xml/home.gif => text/xml",
543 do_sniffing_test);
544 g_test_add_data_func ("/sniffing/type/xml+xml",
545 "type/anice_type+xml/home.gif => anice/type+xml",
546 do_sniffing_test);
547 g_test_add_data_func ("/sniffing/type/application-xml",
548 "type/application_xml/home.gif => application/xml",
549 do_sniffing_test);
550
551 /* Test the feed or html path */
552 g_test_add_data_func ("/sniffing/type/html/html",
553 "type/text_html/test.html => text/html",
554 do_sniffing_test);
555 g_test_add_data_func ("/sniffing/type/html/rss",
556 "type/text_html/rss20.xml => application/rss+xml",
557 do_sniffing_test);
558 g_test_add_data_func ("/sniffing/type/html/atom",
559 "type/text_html/atom.xml => application/atom+xml",
560 do_sniffing_test);
561 g_test_add_data_func ("/sniffing/type/html/rdf",
562 "type/text_html/feed.rdf => application/rss+xml",
563 do_sniffing_test);
564
565 /* Test the image sniffing path */
566 g_test_add_data_func ("/sniffing/type/image/gif",
567 "type/image_png/home.gif => image/gif",
568 do_sniffing_test);
569 g_test_add_data_func ("/sniffing/type/image/png",
570 "type/image_gif/home.png => image/png",
571 do_sniffing_test);
572 g_test_add_data_func ("/sniffing/type/image/jpeg",
573 "type/image_png/home.jpg => image/jpeg",
574 do_sniffing_test);
575 g_test_add_data_func ("/sniffing/type/image/webp",
576 "type/image_png/tux.webp => image/webp",
577 do_sniffing_test);
578
579 /* Test audio and video sniffing path */
580 g_test_add_data_func ("/sniffing/type/audio/wav",
581 "type/audio_mpeg/test.wav => audio/wave",
582 do_sniffing_test);
583 g_test_add_data_func ("/sniffing/type/audio/aiff",
584 "type/audio_mpeg/test.aiff => audio/aiff",
585 do_sniffing_test);
586 g_test_add_data_func ("/sniffing/type/audio/ogg",
587 "type/audio_mpeg/test.ogg => application/ogg",
588 do_sniffing_test);
589 g_test_add_data_func ("/sniffing/type/video/webm",
590 "type/video_theora/test.webm => video/webm",
591 do_sniffing_test);
592
593 /* Test the MP4 sniffing path */
594 g_test_add_data_func ("/sniffing/type/video/mp4",
595 "unknown/test.mp4 => video/mp4",
596 do_sniffing_test);
597
598 /* The spec tells us to only use the last Content-Type header */
599 g_test_add_data_func ("/sniffing/multiple-headers",
600 "multiple_headers/home.gif => image/gif",
601 do_sniffing_test);
602
603 /* Test that we keep the parameters when sniffing */
604 g_test_add_data_func ("/sniffing/parameters",
605 "type/text_html; charset=UTF-8/test.html => text/html; charset=UTF-8",
606 do_sniffing_test);
607
608 /* Test that disabling the sniffer works correctly */
609 g_test_add_data_func ("/sniffing/disabled",
610 "/text_or_binary/home.gif",
611 test_disabled);
612
613 ret = g_test_run ();
614
615 soup_uri_free (base_uri);
616
617 soup_test_session_abort_unref (session);
618 soup_test_server_quit_unref (server);
619
620 test_cleanup ();
621 return ret;
622 }
623