Git init
[profile/ivi/libsoup2.4.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 <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9
10 #include <libsoup/soup.h>
11
12 #include "test-utils.h"
13
14 SoupSession *session;
15 SoupURI *base_uri;
16 SoupMessageBody *chunk_data;
17
18 static void
19 server_callback (SoupServer *server, SoupMessage *msg,
20                  const char *path, GHashTable *query,
21                  SoupClientContext *context, gpointer data)
22 {
23         GError *error = NULL;
24         char *query_key;
25         char *contents;
26         gsize length, offset;
27         gboolean empty_response = FALSE;
28
29         if (msg->method != SOUP_METHOD_GET) {
30                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
31                 return;
32         }
33
34         soup_message_set_status (msg, SOUP_STATUS_OK);
35
36         if (query) {
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);
41                 }
42
43                 query_key = g_hash_table_lookup (query, "empty_response");
44                 if (query_key && g_str_equal (query_key, "yes"))
45                         empty_response = TRUE;
46         }
47
48         if (!strcmp (path, "/mbox")) {
49                 if (empty_response) {
50                         contents = g_strdup ("");
51                         length = 0;
52                 } else {
53                         g_file_get_contents (SRCDIR "/resources/mbox",
54                                              &contents, &length,
55                                              &error);
56                 }
57
58                 if (error) {
59                         g_error ("%s", error->message);
60                         g_error_free (error);
61                         exit (1);
62                 }
63
64                 soup_message_headers_append (msg->response_headers,
65                                              "Content-Type", "text/plain");
66         }
67
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);
71
72                 g_file_get_contents (file_name,
73                                      &contents, &length,
74                                      &error);
75
76                 g_free (base_name);
77                 g_free (file_name);
78
79                 if (error) {
80                         g_error ("%s", error->message);
81                         g_error_free (error);
82                         exit (1);
83                 }
84
85                 soup_message_headers_append (msg->response_headers,
86                                              "Content-Type", "text/plain");
87         }
88
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);
92
93                 g_file_get_contents (file_name,
94                                      &contents, &length,
95                                      &error);
96
97                 g_free (base_name);
98                 g_free (file_name);
99
100                 if (error) {
101                         g_error ("%s", error->message);
102                         g_error_free (error);
103                         exit (1);
104                 }
105
106                 soup_message_headers_append (msg->response_headers,
107                                              "Content-Type", "UNKNOWN/unknown");
108         }
109
110         if (g_str_has_prefix (path, "/type/")) {
111                 char **components = g_strsplit (path, "/", 4);
112                 char *ptr;
113
114                 char *base_name = g_path_get_basename (path);
115                 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
116
117                 g_file_get_contents (file_name,
118                                      &contents, &length,
119                                      &error);
120
121                 g_free (base_name);
122                 g_free (file_name);
123
124                 if (error) {
125                         g_error ("%s", error->message);
126                         g_error_free (error);
127                         exit (1);
128                 }
129
130                 /* Hack to allow passing type in the URI */
131                 ptr = g_strrstr (components[2], "_");
132                 *ptr = '/';
133
134                 soup_message_headers_append (msg->response_headers,
135                                              "Content-Type", components[2]);
136                 g_strfreev (components);
137         }
138
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);
142
143                 g_file_get_contents (file_name,
144                                      &contents, &length,
145                                      &error);
146
147                 g_free (base_name);
148                 g_free (file_name);
149
150                 if (error) {
151                         g_error ("%s", error->message);
152                         g_error_free (error);
153                         exit (1);
154                 }
155
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");
160         }
161
162         for (offset = 0; offset < length; offset += 500) {
163                 soup_message_body_append (msg->response_body,
164                                           SOUP_MEMORY_COPY,
165                                           contents + offset,
166                                           MIN(500, length - offset));
167         }
168         soup_message_body_complete (msg->response_body);
169
170         g_free (contents);
171 }
172
173 static gboolean
174 unpause_msg (gpointer data)
175 {
176         SoupMessage *msg = (SoupMessage*)data;
177         debug_printf (2, "  unpause\n");
178         soup_session_unpause_message (session, msg);
179         return FALSE;
180 }
181
182
183 static void
184 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data)
185 {
186         gboolean should_pause = GPOINTER_TO_INT (data);
187
188         debug_printf (2, "  content-sniffed -> %s\n", content_type);
189
190         if (g_object_get_data (G_OBJECT (msg), "got-chunk")) {
191                 debug_printf (1, "  got-chunk got emitted before content-sniffed\n");
192                 errors++;
193         }
194
195         g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
196
197         if (should_pause) {
198                 debug_printf (2, "  pause\n");
199                 soup_session_pause_message (session, msg);
200                 g_idle_add (unpause_msg, msg);
201         }
202 }
203
204 static void
205 got_headers (SoupMessage *msg, gpointer data)
206 {
207         gboolean should_pause = GPOINTER_TO_INT (data);
208
209         debug_printf (2, "  got-headers\n");
210
211         if (g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
212                 debug_printf (1, "  content-sniffed got emitted before got-headers\n");
213                 errors++;
214         }
215
216         g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE));
217
218         if (should_pause) {
219                 debug_printf (2, "  pause\n");
220                 soup_session_pause_message (session, msg);
221                 g_idle_add (unpause_msg, msg);
222         }
223 }
224
225 static void
226 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data)
227 {
228         gboolean should_accumulate = GPOINTER_TO_INT (data);
229
230         debug_printf (2, "  got-chunk\n");
231
232         g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE));
233
234         if (!should_accumulate) {
235                 if (!chunk_data)
236                         chunk_data = soup_message_body_new ();
237                 soup_message_body_append_buffer (chunk_data, chunk);
238         }
239 }
240
241 static void
242 finished (SoupSession *session, SoupMessage *msg, gpointer data)
243 {
244         GMainLoop *loop = (GMainLoop*)data;
245         g_main_loop_quit (loop);
246 }
247
248 static void
249 do_signals_test (gboolean should_content_sniff,
250                  gboolean should_pause,
251                  gboolean should_accumulate,
252                  gboolean chunked_encoding,
253                  gboolean empty_response)
254 {
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);
258         char *contents;
259         gsize length;
260         GError *error = NULL;
261         SoupBuffer *body = NULL;
262
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 ? "" : "!");
269
270         if (chunked_encoding)
271                 soup_uri_set_query (uri, "chunked=yes");
272
273         if (empty_response) {
274                 if (uri->query) {
275                         char *tmp = uri->query;
276                         uri->query = g_strdup_printf ("%s&empty_response=yes", tmp);
277                         g_free (tmp);
278                 } else
279                         soup_uri_set_query (uri, "empty_response=yes");
280         }
281
282         soup_message_set_uri (msg, uri);
283
284         soup_message_body_set_accumulate (msg->response_body, should_accumulate);
285
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),
290                           NULL);
291
292         g_object_ref (msg);
293         soup_session_queue_message (session, msg, finished, loop);
294
295         g_main_loop_run (loop);
296
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");
300                 errors++;
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");
304                 errors++;
305         }
306
307         if (empty_response) {
308                 contents = g_strdup ("");
309                 length = 0;
310         } else {
311                 g_file_get_contents (SRCDIR "/resources/mbox",
312                                      &contents, &length,
313                                      &error);
314         }
315
316         if (error) {
317                 g_error ("%s", error->message);
318                 g_error_free (error);
319                 exit (1);
320         }
321
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);
326
327         if (body && body->length != length) {
328                 debug_printf (1, "  lengths do not match\n");
329                 errors++;
330         }
331
332         if (body && memcmp (body->data, contents, length)) {
333                 debug_printf (1, "  downloaded data does not match\n");
334                 errors++;
335         }
336
337         g_free (contents);
338         if (body)
339                 soup_buffer_free (body);
340         if (chunk_data) {
341                 soup_message_body_free (chunk_data);
342                 chunk_data = NULL;
343         }
344
345         soup_uri_free (uri);
346         g_object_unref (msg);
347         g_main_loop_unref (loop);
348 }
349
350 static void
351 sniffing_content_sniffed (SoupMessage *msg, const char *content_type,
352                           GHashTable *params, gpointer data)
353 {
354         char **sniffed_type = (char **)data;
355         GString *full_header;
356         GList *keys;
357         GList *iter;
358
359         if (params == NULL) {
360                 *sniffed_type = g_strdup (content_type);
361                 return;
362         }
363
364         full_header = g_string_new (content_type);
365         g_string_append (full_header, "; ");
366
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);
370
371                 soup_header_g_string_append_param (full_header,
372                                                    (const gchar*) iter->data,
373                                                    value);
374         }
375
376         *sniffed_type = full_header->str;
377
378         g_string_free (full_header, FALSE);
379         g_list_free (keys);
380 }
381
382 static void
383 test_sniffing (const char *path, const char *expected_type)
384 {
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;
389
390         debug_printf (1, "test_sniffing(\"%s\", \"%s\")\n", path, expected_type);
391
392         g_signal_connect (msg, "content-sniffed",
393                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
394
395         g_object_ref (msg);
396
397         soup_session_queue_message (session, msg, finished, loop);
398
399         g_main_loop_run (loop);
400
401         if (!sniffed_type) {
402                 debug_printf (1, "  message was not sniffed!\n");
403                 errors++;
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);
407                 errors++;
408         }
409         g_free (sniffed_type);
410
411         soup_uri_free (uri);
412         g_object_unref (msg);
413         g_main_loop_unref (loop);
414 }
415
416 static void
417 test_disabled (const char *path)
418 {
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;
423
424         soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
425
426         debug_printf (1, "test_disabled(\"%s\")\n", path);
427
428         g_signal_connect (msg, "content-sniffed",
429                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
430
431         g_object_ref (msg);
432
433         soup_session_queue_message (session, msg, finished, loop);
434
435         g_main_loop_run (loop);
436
437         if (sniffed_type) {
438                 debug_printf (1, "  message was sniffed!\n");
439                 errors++;
440                 g_free (sniffed_type);
441         }
442
443         soup_uri_free (uri);
444         g_object_unref (msg);
445         g_main_loop_unref (loop);
446 }
447
448 int
449 main (int argc, char **argv)
450 {
451         SoupServer *server;
452
453         test_init (argc, argv, NULL);
454
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));
459
460         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
461
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);
467
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);
472
473         /* Tests that the signals are correctly emitted for empty
474          * responses; see
475          * http://bugzilla.gnome.org/show_bug.cgi?id=587907 */
476
477         do_signals_test (FALSE, TRUE, TRUE, FALSE, TRUE);
478         do_signals_test (FALSE, TRUE, TRUE, TRUE, TRUE);
479
480         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
481
482         /* Now, with a sniffer, content_sniffed must be emitted after
483          * got-headers, and before got-chunk.
484          */
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);
489
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);
494
495         /* Empty response tests */
496         do_signals_test (TRUE, TRUE, TRUE, FALSE, TRUE);
497         do_signals_test (TRUE, TRUE, TRUE, TRUE, TRUE);
498
499         /* Test the text_or_binary sniffing path */
500
501         /* GIF is a 'safe' type */
502         test_sniffing ("/text_or_binary/home.gif", "image/gif");
503
504         /* With our current code, no sniffing is done using GIO, so
505          * the mbox will be identified as text/plain; should we change
506          * this?
507          */
508         test_sniffing ("/text_or_binary/mbox", "text/plain");
509
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'
513          */
514         test_sniffing ("/text_or_binary/test.html", "text/plain");
515
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");
519
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");
523
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");
527
528         /* Test the unknown sniffing path */
529
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");
534
535         /* Test the XML sniffing path */
536
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");
540
541         /* Test the image sniffing path */
542
543         test_sniffing ("/type/image_png/home.gif", "image/gif");
544
545         /* Test the feed or html path */
546
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");
550
551         /* The spec tells us to only use the last Content-Type header */
552
553         test_sniffing ("/multiple_headers/home.gif", "image/gif");
554
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");
557
558         /* Test that disabling the sniffer works correctly */
559
560         test_disabled ("/text_or_binary/home.gif");
561
562         soup_uri_free (base_uri);
563
564         soup_test_session_abort_unref (session);
565         soup_test_server_quit_unref (server);
566         test_cleanup ();
567         return errors != 0;
568 }