Update to version 2.39.3
[profile/ivi/libsoup2.4.git] / tests / requester-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2011 Red Hat, Inc.
4  */
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
11 #include <libsoup/soup.h>
12 #include <libsoup/soup-requester.h>
13 #include <libsoup/soup-request-http.h>
14
15 #include "test-utils.h"
16
17 SoupServer *server;
18 GMainLoop *loop;
19 char buf[1024];
20
21 SoupBuffer *response, *auth_response;
22
23 #define REDIRECT_HTML_BODY "<html><body>Try again</body></html>\r\n"
24 #define AUTH_HTML_BODY "<html><body>Unauthorized</body></html>\r\n"
25
26 static void
27 get_index (void)
28 {
29         char *contents;
30         gsize length;
31         GError *error = NULL;
32
33         if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) {
34                 fprintf (stderr, "Could not read index.txt: %s\n",
35                          error->message);
36                 exit (1);
37         }
38
39         response = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length);
40
41         auth_response = soup_buffer_new (SOUP_MEMORY_STATIC,
42                                          AUTH_HTML_BODY,
43                                          strlen (AUTH_HTML_BODY));
44 }
45
46 static void
47 server_callback (SoupServer *server, SoupMessage *msg,
48                  const char *path, GHashTable *query,
49                  SoupClientContext *context, gpointer data)
50 {
51         gboolean chunked = FALSE;
52         int i;
53
54         if (strcmp (path, "/auth") == 0) {
55                 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
56                 soup_message_set_response (msg, "text/html",
57                                            SOUP_MEMORY_STATIC,
58                                            AUTH_HTML_BODY,
59                                            strlen (AUTH_HTML_BODY));
60                 soup_message_headers_append (msg->response_headers,
61                                              "WWW-Authenticate",
62                                              "Basic: realm=\"requester-test\"");
63                 return;
64         } else if (strcmp (path, "/foo") == 0) {
65                 soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
66                 /* Make the response HTML so if we sniff that instead of the
67                  * real body, we'll notice.
68                  */
69                 soup_message_set_response (msg, "text/html",
70                                            SOUP_MEMORY_STATIC,
71                                            REDIRECT_HTML_BODY,
72                                            strlen (REDIRECT_HTML_BODY));
73                 return;
74         } else if (strcmp (path, "/chunked") == 0) {
75                 chunked = TRUE;
76         } else if (strcmp (path, "/non-persistent") == 0) {
77                 soup_message_headers_append (msg->response_headers,
78                                              "Connection", "close");
79         }
80
81         soup_message_set_status (msg, SOUP_STATUS_OK);
82
83         if (chunked) {
84                 soup_message_headers_set_encoding (msg->response_headers,
85                                                    SOUP_ENCODING_CHUNKED);
86
87                 for (i = 0; i < response->length; i += 8192) {
88                         SoupBuffer *tmp;
89
90                         tmp = soup_buffer_new_subbuffer (response, i,
91                                                          MIN (8192, response->length - i));
92                         soup_message_body_append_buffer (msg->response_body, tmp);
93                         soup_buffer_free (tmp);
94                 }
95                 soup_message_body_complete (msg->response_body);
96         } else
97                 soup_message_body_append_buffer (msg->response_body, response);
98 }
99
100 typedef struct {
101         GString *body;
102         gboolean cancel;
103 } RequestData;
104
105 static void
106 stream_closed (GObject *source, GAsyncResult *res, gpointer user_data)
107 {
108         GInputStream *stream = G_INPUT_STREAM (source);
109         GError *error = NULL;
110
111         if (!g_input_stream_close_finish (stream, res, &error)) {
112                 debug_printf (1, "    close failed: %s", error->message);
113                 g_error_free (error);
114                 errors++;
115         }
116         g_main_loop_quit (loop);
117         g_object_unref (stream);
118 }
119
120 static void
121 test_read_ready (GObject *source, GAsyncResult *res, gpointer user_data)
122 {
123         GInputStream *stream = G_INPUT_STREAM (source);
124         RequestData *data = user_data;
125         GString *body = data->body;
126         GError *error = NULL;
127         gsize nread;
128
129         nread = g_input_stream_read_finish (stream, res, &error);
130         if (nread == -1) {
131                 debug_printf (1, "    read_async failed: %s", error->message);
132                 g_error_free (error);
133                 errors++;
134                 g_input_stream_close (stream, NULL, NULL);
135                 g_object_unref (stream);
136                 g_main_loop_quit (loop);
137                 return;
138         } else if (nread == 0) {
139                 g_input_stream_close_async (stream,
140                                             G_PRIORITY_DEFAULT, NULL,
141                                             stream_closed, NULL);
142                 return;
143         }
144
145         g_string_append_len (body, buf, nread);
146         g_input_stream_read_async (stream, buf, sizeof (buf),
147                                    G_PRIORITY_DEFAULT, NULL,
148                                    test_read_ready, data);
149 }
150
151 static void
152 auth_test_sent (GObject *source, GAsyncResult *res, gpointer user_data)
153 {
154         RequestData *data = user_data;
155         GInputStream *stream;
156         GError *error = NULL;
157         SoupMessage *msg;
158         const char *content_type;
159
160         stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error);
161         if (!stream) {
162                 debug_printf (1, "    send_async failed: %s\n", error->message);
163                 errors++;
164                 g_main_loop_quit (loop);
165                 return;
166         }
167
168         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (source));
169         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
170                 debug_printf (1, "    GET failed: %d %s\n", msg->status_code,
171                               msg->reason_phrase);
172                 errors++;
173                 g_main_loop_quit (loop);
174                 return;
175         }
176         g_object_unref (msg);
177
178         content_type = soup_request_get_content_type (SOUP_REQUEST (source));
179         if (g_strcmp0 (content_type, "text/html") != 0) {
180                 debug_printf (1, "    failed to sniff Content-Type: got %s\n",
181                               content_type ? content_type : "(NULL)");
182                 errors++;
183         }
184
185         g_input_stream_read_async (stream, buf, sizeof (buf),
186                                    G_PRIORITY_DEFAULT, NULL,
187                                    test_read_ready, data);
188 }
189
190 static void
191 test_sent (GObject *source, GAsyncResult *res, gpointer user_data)
192 {
193         RequestData *data = user_data;
194         GInputStream *stream;
195         GError *error = NULL;
196         const char *content_type;
197
198         stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error);
199         if (data->cancel) {
200                 if (stream) {
201                         debug_printf (1, "    send_async succeeded??\n");
202                         errors++;
203                         g_input_stream_close (stream, NULL, NULL);
204                         g_object_unref (stream);
205                 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
206                         debug_printf (1, "    send_async failed with wrong error: %s\n", error->message);
207                         errors++;
208                         g_clear_error (&error);
209                 }
210                 g_main_loop_quit (loop);
211                 return;
212         } else {
213                 if (!stream) {
214                         debug_printf (1, "    send_async failed: %s\n", error->message);
215                         errors++;
216                         g_main_loop_quit (loop);
217                         g_clear_error (&error);
218                         return;
219                 }
220         }
221
222         content_type = soup_request_get_content_type (SOUP_REQUEST (source));
223         if (g_strcmp0 (content_type, "text/plain") != 0) {
224                 debug_printf (1, "    failed to sniff Content-Type: got %s\n",
225                               content_type ? content_type : "(NULL)");
226                 errors++;
227         }
228
229         g_input_stream_read_async (stream, buf, sizeof (buf),
230                                    G_PRIORITY_DEFAULT, NULL,
231                                    test_read_ready, data);
232 }
233
234 static void
235 cancel_message (SoupMessage *msg, gpointer session)
236 {
237         soup_session_cancel_message (session, msg, SOUP_STATUS_FORBIDDEN);
238 }
239
240 static void
241 request_started (SoupSession *session, SoupMessage *msg,
242                  SoupSocket *socket, gpointer user_data)
243 {
244         SoupSocket **save_socket = user_data;
245
246         *save_socket = g_object_ref (socket);
247 }
248
249 static void
250 do_async_test (SoupSession *session, SoupURI *uri,
251                GAsyncReadyCallback callback, guint expected_status,
252                SoupBuffer *expected_response,
253                gboolean persistent, gboolean cancel)
254 {
255         SoupRequester *requester;
256         SoupRequest *request;
257         guint started_id;
258         SoupSocket *socket = NULL;
259         SoupMessage *msg;
260         RequestData data;
261
262         requester = SOUP_REQUESTER (soup_session_get_feature (session, SOUP_TYPE_REQUESTER));
263
264         data.body = g_string_new (NULL);
265         data.cancel = cancel;
266         request = soup_requester_request_uri (requester, uri, NULL);
267         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
268
269         if (cancel) {
270                 g_signal_connect (msg, "got-headers",
271                                   G_CALLBACK (cancel_message), session);
272         }
273
274         started_id = g_signal_connect (session, "request-started",
275                                        G_CALLBACK (request_started),
276                                        &socket);
277
278         soup_request_send_async (request, NULL, callback, &data);
279         g_object_unref (request);
280
281         loop = g_main_loop_new (soup_session_get_async_context (session), TRUE);
282         g_main_loop_run (loop);
283         g_main_loop_unref (loop);
284
285         g_signal_handler_disconnect (session, started_id);
286
287         if (msg->status_code != expected_status) {
288                 debug_printf (1, "    GET failed: %d %s (expected %d)\n",
289                               msg->status_code, msg->reason_phrase,
290                               expected_status);
291                 g_object_unref (msg);
292                 errors++;
293                 return;
294         }
295         g_object_unref (msg);
296
297         if (!expected_response) {
298                 if (data.body->len) {
299                         debug_printf (1, "    body length mismatch: expected 0, got %d\n",
300                                       (int)data.body->len);
301                         errors++;
302                 }
303         } else if (data.body->len != expected_response->length) {
304                 debug_printf (1, "    body length mismatch: expected %d, got %d\n",
305                               (int)expected_response->length, (int)data.body->len);
306                 errors++;
307         } else if (memcmp (data.body->str, expected_response->data,
308                            expected_response->length) != 0) {
309                 debug_printf (1, "    body data mismatch\n");
310                 errors++;
311         }
312
313         if (persistent) {
314                 if (!soup_socket_is_connected (socket)) {
315                         debug_printf (1, "    socket not still connected!\n");
316                         errors++;
317                 }
318         } else {
319                 if (soup_socket_is_connected (socket)) {
320                         debug_printf (1, "    socket still connected!\n");
321                         errors++;
322                 }
323         }
324         g_object_unref (socket);
325
326         g_string_free (data.body, TRUE);
327 }
328
329 static void
330 do_test_for_thread_and_context (SoupSession *session, const char *base_uri)
331 {
332         SoupRequester *requester;
333         SoupURI *uri;
334
335         requester = soup_requester_new ();
336         soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester));
337         g_object_unref (requester);
338         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
339
340         debug_printf (1, "  basic test\n");
341         uri = soup_uri_new (base_uri);
342         do_async_test (session, uri, test_sent,
343                        SOUP_STATUS_OK, response,
344                        TRUE, FALSE);
345         soup_uri_free (uri);
346
347         debug_printf (1, "  chunked test\n");
348         uri = soup_uri_new (base_uri);
349         soup_uri_set_path (uri, "/chunked");
350         do_async_test (session, uri, test_sent,
351                        SOUP_STATUS_OK, response,
352                        TRUE, FALSE);
353         soup_uri_free (uri);
354
355         debug_printf (1, "  auth test\n");
356         uri = soup_uri_new (base_uri);
357         soup_uri_set_path (uri, "/auth");
358         do_async_test (session, uri, auth_test_sent,
359                        SOUP_STATUS_UNAUTHORIZED, auth_response,
360                        TRUE, FALSE);
361         soup_uri_free (uri);
362
363         debug_printf (1, "  non-persistent test\n");
364         uri = soup_uri_new (base_uri);
365         soup_uri_set_path (uri, "/non-persistent");
366         do_async_test (session, uri, test_sent,
367                        SOUP_STATUS_OK, response,
368                        FALSE, FALSE);
369         soup_uri_free (uri);
370
371         debug_printf (1, "  cancellation test\n");
372         uri = soup_uri_new (base_uri);
373         soup_uri_set_path (uri, "/");
374         do_async_test (session, uri, test_sent,
375                        SOUP_STATUS_FORBIDDEN, NULL,
376                        FALSE, TRUE);
377         soup_uri_free (uri);
378 }
379
380 static void
381 do_simple_test (const char *uri)
382 {
383         SoupSession *session;
384
385         debug_printf (1, "Simple streaming test\n");
386
387         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
388                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
389                                          NULL);
390         do_test_for_thread_and_context (session, uri);
391         soup_test_session_abort_unref (session);
392 }
393
394 static gpointer
395 do_test_with_context (const char *uri)
396 {
397         GMainContext *async_context;
398         SoupSession *session;
399
400         async_context = g_main_context_new ();
401         g_main_context_push_thread_default (async_context);
402
403         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
404                                          SOUP_SESSION_ASYNC_CONTEXT, async_context,
405                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
406                                          NULL);
407
408         do_test_for_thread_and_context (session, uri);
409         soup_test_session_abort_unref (session);
410
411         g_main_context_pop_thread_default (async_context);
412         g_main_context_unref (async_context);
413         return NULL;
414 }
415
416 static void
417 do_context_test (const char *uri)
418 {
419         debug_printf (1, "Streaming with a non-default-context\n");
420         do_test_with_context (uri);
421 }
422
423 static void
424 do_thread_test (const char *uri)
425 {
426         GThread *thread;
427
428         debug_printf (1, "Streaming in another thread\n");
429
430         thread = g_thread_new ("do_test_with_context",
431                                (GThreadFunc)do_test_with_context,
432                                (gpointer)uri);
433         g_thread_join (thread);
434 }
435
436 static void
437 do_sync_request (SoupSession *session, SoupRequest *request,
438                  guint expected_status, SoupBuffer *expected_response,
439                  gboolean persistent, gboolean cancel)
440 {
441         GInputStream *in;
442         SoupMessage *msg;
443         GError *error = NULL;
444         GString *body;
445         char buf[1024];
446         gssize nread;
447         guint started_id;
448         SoupSocket *socket = NULL;
449
450         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
451         if (cancel) {
452                 g_signal_connect (msg, "got-headers",
453                                   G_CALLBACK (cancel_message), session);
454         }
455
456         started_id = g_signal_connect (session, "request-started",
457                                        G_CALLBACK (request_started),
458                                        &socket);
459
460         in = soup_request_send (request, NULL, &error);
461         g_signal_handler_disconnect (session, started_id);
462         if (cancel) {
463                 if (in) {
464                         debug_printf (1, "    send succeeded??\n");
465                         errors++;
466                         g_input_stream_close (in, NULL, NULL);
467                         g_object_unref (in);
468                 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
469                         debug_printf (1, "    send failed with wrong error: %s\n", error->message);
470                         errors++;
471                         g_clear_error (&error);
472                 }
473                 g_object_unref (msg);
474                 return;
475         } else if (!in) {
476                 debug_printf (1, "    soup_request_send failed: %s\n",
477                               error->message);
478                 g_object_unref (msg);
479                 g_clear_error (&error);
480                 errors++;
481                 return;
482         }
483
484         if (msg->status_code != expected_status) {
485                 debug_printf (1, "    GET failed: %d %s\n", msg->status_code,
486                               msg->reason_phrase);
487                 g_object_unref (msg);
488                 g_object_unref (in);
489                 errors++;
490                 return;
491         }
492         g_object_unref (msg);
493
494         body = g_string_new (NULL);
495         do {
496                 nread = g_input_stream_read (in, buf, sizeof (buf),
497                                              NULL, &error);
498                 if (nread == -1) {
499                         debug_printf (1, "    g_input_stream_read failed: %s\n",
500                                       error->message);
501                         g_clear_error (&error);
502                         errors++;
503                         break;
504                 }
505                 g_string_append_len (body, buf, nread);
506         } while (nread > 0);
507
508         if (!g_input_stream_close (in, NULL, &error)) {
509                 debug_printf (1, "    g_input_stream_close failed: %s\n",
510                               error->message);
511                 g_clear_error (&error);
512                 errors++;
513         }
514         g_object_unref (in);
515
516         if (!expected_response) {
517                 if (body->len) {
518                         debug_printf (1, "    body length mismatch: expected 0, got %d\n",
519                                       (int)body->len);
520                         errors++;
521                 }
522         } else if (body->len != expected_response->length) {
523                 debug_printf (1, "    body length mismatch: expected %d, got %d\n",
524                               (int)expected_response->length, (int)body->len);
525                 errors++;
526         } else if (memcmp (body->str, expected_response->data, body->len) != 0) {
527                 debug_printf (1, "    body data mismatch\n");
528                 errors++;
529         }
530
531         if (persistent) {
532                 if (!soup_socket_is_connected (socket)) {
533                         debug_printf (1, "    socket not still connected!\n");
534                         errors++;
535                 }
536         } else {
537                 if (soup_socket_is_connected (socket)) {
538                         debug_printf (1, "    socket still connected!\n");
539                         errors++;
540                 }
541         }
542         g_object_unref (socket);
543
544         g_string_free (body, TRUE);
545 }
546
547 static void
548 do_sync_test (const char *uri_string)
549 {
550         SoupSession *session;
551         SoupRequester *requester;
552         SoupRequest *request;
553         SoupURI *uri;
554
555         debug_printf (1, "Sync streaming\n");
556
557         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
558         requester = soup_requester_new ();
559         soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester));
560         g_object_unref (requester);
561
562         uri = soup_uri_new (uri_string);
563
564         debug_printf (1, "  basic test\n");
565         request = soup_requester_request_uri (requester, uri, NULL);
566         do_sync_request (session, request,
567                          SOUP_STATUS_OK, response,
568                          TRUE, FALSE);
569         g_object_unref (request);
570
571         debug_printf (1, "  chunked test\n");
572         soup_uri_set_path (uri, "/chunked");
573         request = soup_requester_request_uri (requester, uri, NULL);
574         do_sync_request (session, request,
575                          SOUP_STATUS_OK, response,
576                          TRUE, FALSE);
577         g_object_unref (request);
578
579         debug_printf (1, "  auth test\n");
580         soup_uri_set_path (uri, "/auth");
581         request = soup_requester_request_uri (requester, uri, NULL);
582         do_sync_request (session, request,
583                          SOUP_STATUS_UNAUTHORIZED, auth_response,
584                          TRUE, FALSE);
585         g_object_unref (request);
586
587         debug_printf (1, "  non-persistent test\n");
588         soup_uri_set_path (uri, "/non-persistent");
589         request = soup_requester_request_uri (requester, uri, NULL);
590         do_sync_request (session, request,
591                          SOUP_STATUS_OK, response,
592                          FALSE, FALSE);
593         g_object_unref (request);
594
595         debug_printf (1, "  cancel test\n");
596         soup_uri_set_path (uri, "/");
597         request = soup_requester_request_uri (requester, uri, NULL);
598         do_sync_request (session, request,
599                          SOUP_STATUS_FORBIDDEN, NULL,
600                          TRUE, TRUE);
601         g_object_unref (request);
602
603         soup_test_session_abort_unref (session);
604         soup_uri_free (uri);
605 }
606
607 int
608 main (int argc, char **argv)
609 {
610         char *uri;
611
612         test_init (argc, argv, NULL);
613         get_index ();
614
615         server = soup_test_server_new (TRUE);
616         soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
617
618         uri = g_strdup_printf ("http://127.0.0.1:%u/foo", soup_server_get_port (server));
619
620         do_simple_test (uri);
621         do_thread_test (uri);
622         do_context_test (uri);
623         do_sync_test (uri);
624
625         g_free (uri);
626         soup_buffer_free (response);
627         soup_buffer_free (auth_response);
628         soup_test_server_quit_unref (server);
629
630         test_cleanup ();
631         return errors != 0;
632 }