Fix the retry-on-broken-connection codepath for SoupRequest
[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         char *contents;
20         gsize length = 0, 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                         contents = g_strdup ("");
45                         length = 0;
46                 } else {
47                         g_file_get_contents (SRCDIR "/resources/mbox",
48                                              &contents, &length,
49                                              &error);
50                 }
51
52                 if (error) {
53                         g_error ("%s", error->message);
54                         g_error_free (error);
55                         exit (1);
56                 }
57
58                 soup_message_headers_append (msg->response_headers,
59                                              "Content-Type", "text/plain");
60         }
61
62         if (g_str_has_prefix (path, "/text_or_binary/")) {
63                 char *base_name = g_path_get_basename (path);
64                 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
65
66                 g_file_get_contents (file_name,
67                                      &contents, &length,
68                                      &error);
69
70                 g_free (base_name);
71                 g_free (file_name);
72
73                 if (error) {
74                         g_error ("%s", error->message);
75                         g_error_free (error);
76                         exit (1);
77                 }
78
79                 soup_message_headers_append (msg->response_headers,
80                                              "Content-Type", "text/plain");
81         }
82
83         if (g_str_has_prefix (path, "/unknown/")) {
84                 char *base_name = g_path_get_basename (path);
85                 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
86
87                 g_file_get_contents (file_name,
88                                      &contents, &length,
89                                      &error);
90
91                 g_free (base_name);
92                 g_free (file_name);
93
94                 if (error) {
95                         g_error ("%s", error->message);
96                         g_error_free (error);
97                         exit (1);
98                 }
99
100                 soup_message_headers_append (msg->response_headers,
101                                              "Content-Type", "UNKNOWN/unknown");
102         }
103
104         if (g_str_has_prefix (path, "/type/")) {
105                 char **components = g_strsplit (path, "/", 4);
106                 char *ptr;
107
108                 char *base_name = g_path_get_basename (path);
109                 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
110
111                 g_file_get_contents (file_name,
112                                      &contents, &length,
113                                      &error);
114
115                 g_free (base_name);
116                 g_free (file_name);
117
118                 if (error) {
119                         g_error ("%s", error->message);
120                         g_error_free (error);
121                         exit (1);
122                 }
123
124                 /* Hack to allow passing type in the URI */
125                 ptr = g_strrstr (components[2], "_");
126                 *ptr = '/';
127
128                 soup_message_headers_append (msg->response_headers,
129                                              "Content-Type", components[2]);
130                 g_strfreev (components);
131         }
132
133         if (g_str_has_prefix (path, "/multiple_headers/")) {
134                 char *base_name = g_path_get_basename (path);
135                 char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name);
136
137                 g_file_get_contents (file_name,
138                                      &contents, &length,
139                                      &error);
140
141                 g_free (base_name);
142                 g_free (file_name);
143
144                 if (error) {
145                         g_error ("%s", error->message);
146                         g_error_free (error);
147                         exit (1);
148                 }
149
150                 soup_message_headers_append (msg->response_headers,
151                                              "Content-Type", "text/xml");
152                 soup_message_headers_append (msg->response_headers,
153                                              "Content-Type", "text/plain");
154         }
155
156         for (offset = 0; offset < length; offset += 500) {
157                 soup_message_body_append (msg->response_body,
158                                           SOUP_MEMORY_COPY,
159                                           contents + offset,
160                                           MIN(500, length - offset));
161         }
162         soup_message_body_complete (msg->response_body);
163
164         g_free (contents);
165 }
166
167 static gboolean
168 unpause_msg (gpointer data)
169 {
170         SoupMessage *msg = (SoupMessage*)data;
171         debug_printf (2, "  unpause\n");
172         soup_session_unpause_message (session, msg);
173         return FALSE;
174 }
175
176
177 static void
178 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data)
179 {
180         gboolean should_pause = GPOINTER_TO_INT (data);
181
182         debug_printf (2, "  content-sniffed -> %s\n", content_type);
183
184         if (g_object_get_data (G_OBJECT (msg), "got-chunk")) {
185                 debug_printf (1, "  got-chunk got emitted before content-sniffed\n");
186                 errors++;
187         }
188
189         g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
190
191         if (should_pause) {
192                 debug_printf (2, "  pause\n");
193                 soup_session_pause_message (session, msg);
194                 g_idle_add (unpause_msg, msg);
195         }
196 }
197
198 static void
199 got_headers (SoupMessage *msg, gpointer data)
200 {
201         gboolean should_pause = GPOINTER_TO_INT (data);
202
203         debug_printf (2, "  got-headers\n");
204
205         if (g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
206                 debug_printf (1, "  content-sniffed got emitted before got-headers\n");
207                 errors++;
208         }
209
210         g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE));
211
212         if (should_pause) {
213                 debug_printf (2, "  pause\n");
214                 soup_session_pause_message (session, msg);
215                 g_idle_add (unpause_msg, msg);
216         }
217 }
218
219 static void
220 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data)
221 {
222         gboolean should_accumulate = GPOINTER_TO_INT (data);
223
224         debug_printf (2, "  got-chunk\n");
225
226         g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE));
227
228         if (!should_accumulate) {
229                 if (!chunk_data)
230                         chunk_data = soup_message_body_new ();
231                 soup_message_body_append_buffer (chunk_data, chunk);
232         }
233 }
234
235 static void
236 finished (SoupSession *session, SoupMessage *msg, gpointer data)
237 {
238         GMainLoop *loop = (GMainLoop*)data;
239         g_main_loop_quit (loop);
240 }
241
242 static void
243 do_signals_test (gboolean should_content_sniff,
244                  gboolean should_pause,
245                  gboolean should_accumulate,
246                  gboolean chunked_encoding,
247                  gboolean empty_response)
248 {
249         SoupURI *uri = soup_uri_new_with_base (base_uri, "/mbox");
250         SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
251         GMainLoop *loop = g_main_loop_new (NULL, TRUE);
252         char *contents;
253         gsize length;
254         GError *error = NULL;
255         SoupBuffer *body = NULL;
256
257         debug_printf (1, "do_signals_test(%ssniff, %spause, %saccumulate, %schunked, %sempty)\n",
258                       should_content_sniff ? "" : "!",
259                       should_pause ? "" : "!",
260                       should_accumulate ? "" : "!",
261                       chunked_encoding ? "" : "!",
262                       empty_response ? "" : "!");
263
264         if (chunked_encoding)
265                 soup_uri_set_query (uri, "chunked=yes");
266
267         if (empty_response) {
268                 if (uri->query) {
269                         char *tmp = uri->query;
270                         uri->query = g_strdup_printf ("%s&empty_response=yes", tmp);
271                         g_free (tmp);
272                 } else
273                         soup_uri_set_query (uri, "empty_response=yes");
274         }
275
276         soup_message_set_uri (msg, uri);
277
278         soup_message_body_set_accumulate (msg->response_body, should_accumulate);
279
280         g_object_connect (msg,
281                           "signal::got-headers", got_headers, GINT_TO_POINTER (should_pause),
282                           "signal::got-chunk", got_chunk, GINT_TO_POINTER (should_accumulate),
283                           "signal::content_sniffed", content_sniffed, GINT_TO_POINTER (should_pause),
284                           NULL);
285
286         g_object_ref (msg);
287         soup_session_queue_message (session, msg, finished, loop);
288
289         g_main_loop_run (loop);
290
291         if (!should_content_sniff &&
292             g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
293                 debug_printf (1, "  content-sniffed got emitted without a sniffer\n");
294                 errors++;
295         } else if (should_content_sniff &&
296                    !g_object_get_data (G_OBJECT (msg), "content-sniffed")) {
297                 debug_printf (1, "  content-sniffed did not get emitted\n");
298                 errors++;
299         }
300
301         if (empty_response) {
302                 contents = g_strdup ("");
303                 length = 0;
304         } else {
305                 g_file_get_contents (SRCDIR "/resources/mbox",
306                                      &contents, &length,
307                                      &error);
308         }
309
310         if (error) {
311                 g_error ("%s", error->message);
312                 g_error_free (error);
313                 exit (1);
314         }
315
316         if (!should_accumulate && chunk_data)
317                 body = soup_message_body_flatten (chunk_data);
318         else if (msg->response_body)
319                 body = soup_message_body_flatten (msg->response_body);
320
321         if (body && body->length != length) {
322                 debug_printf (1, "  lengths do not match\n");
323                 errors++;
324         }
325
326         if (body && memcmp (body->data, contents, length)) {
327                 debug_printf (1, "  downloaded data does not match\n");
328                 errors++;
329         }
330
331         g_free (contents);
332         if (body)
333                 soup_buffer_free (body);
334         if (chunk_data) {
335                 soup_message_body_free (chunk_data);
336                 chunk_data = NULL;
337         }
338
339         soup_uri_free (uri);
340         g_object_unref (msg);
341         g_main_loop_unref (loop);
342 }
343
344 static void
345 sniffing_content_sniffed (SoupMessage *msg, const char *content_type,
346                           GHashTable *params, gpointer data)
347 {
348         char **sniffed_type = (char **)data;
349         GString *full_header;
350         GList *keys;
351         GList *iter;
352
353         if (params == NULL) {
354                 *sniffed_type = g_strdup (content_type);
355                 return;
356         }
357
358         full_header = g_string_new (content_type);
359         g_string_append (full_header, "; ");
360
361         keys = g_hash_table_get_keys (params);
362         for (iter = keys; iter != NULL; iter = iter->next) {
363                 const gchar *value = (const gchar*) g_hash_table_lookup (params, iter->data);
364
365                 soup_header_g_string_append_param (full_header,
366                                                    (const gchar*) iter->data,
367                                                    value);
368         }
369
370         *sniffed_type = full_header->str;
371
372         g_string_free (full_header, FALSE);
373         g_list_free (keys);
374 }
375
376 static void
377 test_sniffing (const char *path, const char *expected_type)
378 {
379         SoupURI *uri = soup_uri_new_with_base (base_uri, path);
380         SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
381         GMainLoop *loop = g_main_loop_new (NULL, TRUE);
382         char *sniffed_type = NULL;
383
384         debug_printf (1, "test_sniffing(\"%s\", \"%s\")\n", path, expected_type);
385
386         g_signal_connect (msg, "content-sniffed",
387                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
388
389         g_object_ref (msg);
390
391         soup_session_queue_message (session, msg, finished, loop);
392
393         g_main_loop_run (loop);
394
395         if (!sniffed_type) {
396                 debug_printf (1, "  message was not sniffed!\n");
397                 errors++;
398         } else if (strcmp (sniffed_type, expected_type) != 0) {
399                 debug_printf (1, "  sniffing failed! expected %s, got %s\n",
400                               expected_type, sniffed_type);
401                 errors++;
402         }
403         g_free (sniffed_type);
404
405         soup_uri_free (uri);
406         g_object_unref (msg);
407         g_main_loop_unref (loop);
408 }
409
410 static void
411 test_disabled (const char *path)
412 {
413         SoupURI *uri = soup_uri_new_with_base (base_uri, path);
414         SoupMessage *msg = soup_message_new_from_uri ("GET", uri);
415         GMainLoop *loop = g_main_loop_new (NULL, TRUE);
416         char *sniffed_type = NULL;
417
418         soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER);
419
420         debug_printf (1, "test_disabled(\"%s\")\n", path);
421
422         g_signal_connect (msg, "content-sniffed",
423                           G_CALLBACK (sniffing_content_sniffed), &sniffed_type);
424
425         g_object_ref (msg);
426
427         soup_session_queue_message (session, msg, finished, loop);
428
429         g_main_loop_run (loop);
430
431         if (sniffed_type) {
432                 debug_printf (1, "  message was sniffed!\n");
433                 errors++;
434                 g_free (sniffed_type);
435         }
436
437         soup_uri_free (uri);
438         g_object_unref (msg);
439         g_main_loop_unref (loop);
440 }
441
442 int
443 main (int argc, char **argv)
444 {
445         SoupServer *server;
446
447         test_init (argc, argv, NULL);
448
449         server = soup_test_server_new (TRUE);
450         soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
451         base_uri = soup_uri_new ("http://127.0.0.1/");
452         soup_uri_set_port (base_uri, soup_server_get_port (server));
453
454         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
455
456         /* No sniffer, no content_sniffed should be emitted */
457         do_signals_test (FALSE, FALSE, FALSE, FALSE, FALSE);
458         do_signals_test (FALSE, FALSE, FALSE, TRUE, FALSE);
459         do_signals_test (FALSE, FALSE, TRUE, FALSE, FALSE);
460         do_signals_test (FALSE, FALSE, TRUE, TRUE, FALSE);
461
462         do_signals_test (FALSE, TRUE, TRUE, FALSE, FALSE);
463         do_signals_test (FALSE, TRUE, TRUE, TRUE, FALSE);
464         do_signals_test (FALSE, TRUE, FALSE, FALSE, FALSE);
465         do_signals_test (FALSE, TRUE, FALSE, TRUE, FALSE);
466
467         /* Tests that the signals are correctly emitted for empty
468          * responses; see
469          * http://bugzilla.gnome.org/show_bug.cgi?id=587907 */
470
471         do_signals_test (FALSE, TRUE, TRUE, FALSE, TRUE);
472         do_signals_test (FALSE, TRUE, TRUE, TRUE, TRUE);
473
474         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
475
476         /* Now, with a sniffer, content_sniffed must be emitted after
477          * got-headers, and before got-chunk.
478          */
479         do_signals_test (TRUE, FALSE, FALSE, FALSE, FALSE);
480         do_signals_test (TRUE, FALSE, FALSE, TRUE, FALSE);
481         do_signals_test (TRUE, FALSE, TRUE, FALSE, FALSE);
482         do_signals_test (TRUE, FALSE, TRUE, TRUE, FALSE);
483
484         do_signals_test (TRUE, TRUE, TRUE, FALSE, FALSE);
485         do_signals_test (TRUE, TRUE, TRUE, TRUE, FALSE);
486         do_signals_test (TRUE, TRUE, FALSE, FALSE, FALSE);
487         do_signals_test (TRUE, TRUE, FALSE, TRUE, FALSE);
488
489         /* Empty response tests */
490         do_signals_test (TRUE, TRUE, TRUE, FALSE, TRUE);
491         do_signals_test (TRUE, TRUE, TRUE, TRUE, TRUE);
492
493         /* Test the text_or_binary sniffing path */
494
495         /* GIF is a 'safe' type */
496         test_sniffing ("/text_or_binary/home.gif", "image/gif");
497
498         /* With our current code, no sniffing is done using GIO, so
499          * the mbox will be identified as text/plain; should we change
500          * this?
501          */
502         test_sniffing ("/text_or_binary/mbox", "text/plain");
503
504         /* HTML is considered unsafe for this algorithm, since it is
505          * scriptable, so going from text/plain to text/html is
506          * considered 'privilege escalation'
507          */
508         test_sniffing ("/text_or_binary/test.html", "text/plain");
509
510         /* text/plain with binary content and unknown pattern should be
511          * application/octet-stream */
512         test_sniffing ("/text_or_binary/text_binary.txt", "application/octet-stream");
513
514         /* text/plain with binary content and scriptable pattern should be
515          * application/octet-stream to avoid 'privilege escalation' */
516         test_sniffing ("/text_or_binary/html_binary.html", "application/octet-stream");
517
518         /* text/plain with binary content and non scriptable known pattern should
519          * be the given type */
520         test_sniffing ("/text_or_binary/ps_binary.ps", "application/postscript");
521
522         /* Test the unknown sniffing path */
523
524         test_sniffing ("/unknown/test.html", "text/html");
525         test_sniffing ("/unknown/home.gif", "image/gif");
526         test_sniffing ("/unknown/mbox", "text/plain");
527         test_sniffing ("/unknown/text_binary.txt", "application/octet-stream");
528
529         /* Test the XML sniffing path */
530
531         test_sniffing ("/type/text_xml/home.gif", "text/xml");
532         test_sniffing ("/type/anice_type+xml/home.gif", "anice/type+xml");
533         test_sniffing ("/type/application_xml/home.gif", "application/xml");
534
535         /* Test the image sniffing path */
536
537         test_sniffing ("/type/image_png/home.gif", "image/gif");
538
539         /* Test the feed or html path */
540
541         test_sniffing ("/type/text_html/test.html", "text/html");
542         test_sniffing ("/type/text_html/rss20.xml", "application/rss+xml");
543         test_sniffing ("/type/text_html/atom.xml", "application/atom+xml");
544
545         /* The spec tells us to only use the last Content-Type header */
546
547         test_sniffing ("/multiple_headers/home.gif", "image/gif");
548
549         /* Test that we keep the parameters when sniffing */
550         test_sniffing ("/type/text_html; charset=UTF-8/test.html", "text/html; charset=UTF-8");
551
552         /* Test that disabling the sniffer works correctly */
553
554         test_disabled ("/text_or_binary/home.gif");
555
556         soup_uri_free (base_uri);
557
558         soup_test_session_abort_unref (session);
559         soup_test_server_quit_unref (server);
560         test_cleanup ();
561         return errors != 0;
562 }