1 /* GStreamer unit tests for the souphttpsrc element
2 * Copyright (C) 2006-2007 Tim-Philipp Müller <tim centricular net>
3 * Copyright (C) 2008 Wouter Cloetens <wouter@mind.be>
4 * Copyright (C) 2001-2003, Ximian, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
27 #include <glib/gprintf.h>
28 #include <libsoup/soup-address.h>
29 #include <libsoup/soup-message.h>
30 #include <libsoup/soup-misc.h>
31 #include <libsoup/soup-server.h>
32 #include <libsoup/soup-auth-domain.h>
33 #include <libsoup/soup-auth-domain-basic.h>
34 #include <libsoup/soup-auth-domain-digest.h>
35 #include <gst/check/gstcheck.h>
37 static guint http_port = 0, https_port = 0;
39 gboolean redirect = TRUE;
41 static const char **cookies = NULL;
43 /* Variables for authentication tests */
44 static const char *user_id = NULL;
45 static const char *user_pw = NULL;
46 static const char *good_user = "good_user";
47 static const char *bad_user = "bad_user";
48 static const char *good_pw = "good_pw";
49 static const char *bad_pw = "bad_pw";
50 static const char *realm = "SOUPHTTPSRC_REALM";
51 static const char *basic_auth_path = "/basic_auth";
52 static const char *digest_auth_path = "/digest_auth";
54 static int run_server (guint * http_port, guint * https_port);
55 static void stop_server (void);
58 handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
59 GstBuffer ** p_outbuf)
61 GST_LOG ("handoff, buf = %p", buf);
62 if (*p_outbuf == NULL)
63 *p_outbuf = gst_buffer_ref (buf);
67 basic_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
68 const char *username, const char *password, gpointer user_data)
70 /* There is only one good login for testing */
71 return (strcmp (username, good_user) == 0)
72 && (strcmp (password, good_pw) == 0);
77 digest_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
78 const char *username, gpointer user_data)
80 /* There is only one good login for testing */
81 if (strcmp (username, good_user) == 0)
82 return soup_auth_domain_digest_encode_password (good_user, realm, good_pw);
87 run_test (const char *format, ...)
89 GstStateChangeReturn ret;
91 GstElement *pipe, *src, *sink;
93 GstBuffer *buf = NULL;
103 pipe = gst_pipeline_new (NULL);
105 src = gst_element_factory_make ("souphttpsrc", NULL);
106 fail_unless (src != NULL);
108 sink = gst_element_factory_make ("fakesink", NULL);
109 fail_unless (sink != NULL);
111 gst_bin_add (GST_BIN (pipe), src);
112 gst_bin_add (GST_BIN (pipe), sink);
113 fail_unless (gst_element_link (src, sink));
115 if (http_port == 0) {
116 GST_DEBUG ("failed to start soup http server");
118 fail_unless (http_port != 0);
119 va_start (args, format);
120 g_vasprintf (&url, format, args);
122 fail_unless (url != NULL);
123 g_object_set (src, "location", url, NULL);
126 g_object_set (src, "automatic-redirect", redirect, NULL);
128 g_object_set (src, "cookies", cookies, NULL);
129 g_object_set (sink, "signal-handoffs", TRUE, NULL);
130 g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
133 g_object_set (src, "user-id", user_id, NULL);
135 g_object_set (src, "user-pw", user_pw, NULL);
137 ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
138 if (ret != GST_STATE_CHANGE_ASYNC) {
139 GST_DEBUG ("failed to start up soup http src, ret = %d", ret);
143 gst_element_set_state (pipe, GST_STATE_PLAYING);
144 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
145 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
146 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
151 gst_message_parse_error (msg, &err, &debug);
152 GST_INFO ("error: %s", err->message);
153 if (g_str_has_suffix (err->message, "Not Found"))
155 else if (g_str_has_suffix (err->message, "Forbidden"))
157 else if (g_str_has_suffix (err->message, "Unauthorized"))
159 else if (g_str_has_suffix (err->message, "Found"))
161 GST_INFO ("debug: %s", debug);
164 gst_message_unref (msg);
167 gst_message_unref (msg);
169 /* don't wait for more than 10 seconds */
170 ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
171 GST_LOG ("ret = %u", ret);
174 /* we want to test the buffer offset, nothing else; if there's a failure
175 * it might be for lots of reasons (no network connection, whatever), we're
176 * not interested in those */
177 GST_DEBUG ("didn't manage to get data within 10 seconds, skipping test");
181 GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
183 /* first buffer should have a 0 offset */
184 fail_unless (GST_BUFFER_OFFSET (buf) == 0);
185 gst_buffer_unref (buf);
190 gst_element_set_state (pipe, GST_STATE_NULL);
191 gst_object_unref (pipe);
195 GST_START_TEST (test_first_buffer_has_offset)
197 fail_unless (run_test ("http://127.0.0.1:%u/", http_port) == 0);
202 GST_START_TEST (test_not_found)
204 fail_unless (run_test ("http://127.0.0.1:%u/404", http_port) == 404);
209 GST_START_TEST (test_forbidden)
211 fail_unless (run_test ("http://127.0.0.1:%u/403", http_port) == 403);
216 GST_START_TEST (test_redirect_no)
219 fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 302);
224 GST_START_TEST (test_redirect_yes)
227 fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 0);
232 GST_START_TEST (test_https)
235 GST_INFO ("Failed to start an HTTPS server; let's just skip this test.");
237 fail_unless (run_test ("https://127.0.0.1:%u/", https_port) == 0);
242 GST_START_TEST (test_cookies)
244 static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL };
248 rc = run_test ("http://127.0.0.1:%u/", http_port);
250 fail_unless (rc == 0);
255 GST_START_TEST (test_good_user_basic_auth)
261 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
262 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
263 user_id = user_pw = NULL;
264 fail_unless (res == 0);
269 GST_START_TEST (test_bad_user_basic_auth)
275 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
276 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
277 user_id = user_pw = NULL;
278 fail_unless (res == 401);
283 GST_START_TEST (test_bad_password_basic_auth)
289 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
290 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
291 user_id = user_pw = NULL;
292 fail_unless (res == 401);
297 GST_START_TEST (test_good_user_digest_auth)
303 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
304 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
305 user_id = user_pw = NULL;
306 fail_unless (res == 0);
311 GST_START_TEST (test_bad_user_digest_auth)
317 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
318 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
319 user_id = user_pw = NULL;
320 fail_unless (res == 401);
325 GST_START_TEST (test_bad_password_digest_auth)
331 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
332 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
333 user_id = user_pw = NULL;
334 fail_unless (res == 401);
339 static gboolean icy_caps = FALSE;
342 got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
347 /* Caps can be anything if we don't except icy caps */
351 /* Otherwise they _must_ be "application/x-icy" */
352 fail_unless (GST_BUFFER_CAPS (buf) != NULL);
353 s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
354 fail_unless_equals_string (gst_structure_get_name (s), "application/x-icy");
357 GST_START_TEST (test_icy_stream)
359 GstElement *pipe, *src, *sink;
363 pipe = gst_pipeline_new (NULL);
365 src = gst_element_factory_make ("souphttpsrc", NULL);
366 fail_unless (src != NULL);
367 g_object_set (src, "iradio-mode", TRUE, NULL);
369 sink = gst_element_factory_make ("fakesink", NULL);
370 fail_unless (sink != NULL);
371 g_object_set (sink, "signal-handoffs", TRUE, NULL);
372 g_signal_connect (sink, "handoff", G_CALLBACK (got_buffer), NULL);
374 gst_bin_add (GST_BIN (pipe), src);
375 gst_bin_add (GST_BIN (pipe), sink);
376 fail_unless (gst_element_link (src, sink));
378 /* First try Virgin Radio Ogg stream, to see if there's connectivity and all
379 * (which is an attempt to work around the completely horrid error reporting
380 * and that we can't distinguish different types of failures here). */
382 g_object_set (src, "location", "http://ogg2.smgradio.com/vr32.ogg", NULL);
383 g_object_set (src, "num-buffers", 1, NULL);
385 gst_element_set_state (pipe, GST_STATE_PLAYING);
387 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
388 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
389 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
390 GST_INFO ("looks like there's no net connectivity or sgmradio.com is "
391 "down. In any case, let's just skip this test");
392 gst_message_unref (msg);
395 gst_message_unref (msg);
397 gst_element_set_state (pipe, GST_STATE_NULL);
399 /* Now, if the ogg stream works, the mp3 shoutcast stream should work as
400 * well (time will tell if that's true) */
402 /* Virgin Radio 32kbps mp3 shoutcast stream */
403 g_object_set (src, "location", "http://mp3-vr-32.smgradio.com:80/", NULL);
406 /* EOS after the first buffer */
407 g_object_set (src, "num-buffers", 1, NULL);
409 gst_element_set_state (pipe, GST_STATE_PLAYING);
410 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
411 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
413 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
414 GST_DEBUG ("success, we're done here");
415 gst_message_unref (msg);
422 gst_message_parse_error (msg, &err, NULL);
423 gst_message_unref (msg);
424 g_error ("Error with ICY mp3 shoutcast stream: %s", err->message);
431 gst_element_set_state (pipe, GST_STATE_NULL);
432 gst_object_unref (pipe);
438 souphttpsrc_suite (void)
442 TCase *tc_chain, *tc_internet;
445 if (!g_thread_supported ())
446 g_thread_init (NULL);
448 s = suite_create ("souphttpsrc");
449 tc_chain = tcase_create ("general");
450 tc_internet = tcase_create ("internet");
452 suite_add_tcase (s, tc_chain);
453 run_server (&http_port, &https_port);
454 g_atexit (stop_server);
455 tcase_add_test (tc_chain, test_first_buffer_has_offset);
456 tcase_add_test (tc_chain, test_redirect_yes);
457 tcase_add_test (tc_chain, test_redirect_no);
458 tcase_add_test (tc_chain, test_not_found);
459 tcase_add_test (tc_chain, test_forbidden);
460 tcase_add_test (tc_chain, test_cookies);
461 tcase_add_test (tc_chain, test_good_user_basic_auth);
462 tcase_add_test (tc_chain, test_bad_user_basic_auth);
463 tcase_add_test (tc_chain, test_bad_password_basic_auth);
464 tcase_add_test (tc_chain, test_good_user_digest_auth);
465 tcase_add_test (tc_chain, test_bad_user_digest_auth);
466 tcase_add_test (tc_chain, test_bad_password_digest_auth);
467 if (soup_ssl_supported)
468 tcase_add_test (tc_chain, test_https);
470 suite_add_tcase (s, tc_internet);
471 tcase_set_timeout (tc_internet, 250);
472 tcase_add_test (tc_internet, test_icy_stream);
477 GST_CHECK_MAIN (souphttpsrc);
480 do_get (SoupMessage * msg, const char *path)
486 SoupKnownStatusCode status = SOUP_STATUS_OK;
488 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
489 GST_DEBUG ("request: \"%s\"", uri);
491 if (!strcmp (path, "/301"))
492 status = SOUP_STATUS_MOVED_PERMANENTLY;
493 else if (!strcmp (path, "/302"))
494 status = SOUP_STATUS_MOVED_TEMPORARILY;
495 else if (!strcmp (path, "/307"))
496 status = SOUP_STATUS_TEMPORARY_REDIRECT;
497 else if (!strcmp (path, "/403"))
498 status = SOUP_STATUS_FORBIDDEN;
499 else if (!strcmp (path, "/404"))
500 status = SOUP_STATUS_NOT_FOUND;
502 if (SOUP_STATUS_IS_REDIRECTION (status)) {
505 redir_uri = g_strdup_printf ("%s-redirected", uri);
506 soup_message_headers_append (msg->response_headers, "Location", redir_uri);
509 if (status != SOUP_STATUS_OK)
512 if (msg->method == SOUP_METHOD_GET) {
515 buf = g_malloc (buflen);
516 memset (buf, 0, buflen);
517 soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE,
519 } else { /* msg->method == SOUP_METHOD_HEAD */
523 /* We could just use the same code for both GET and
524 * HEAD. But we'll optimize and avoid the extra
527 length = g_strdup_printf ("%lu", (gulong) buflen);
528 soup_message_headers_append (msg->response_headers,
529 "Content-Length", length);
534 soup_message_set_status (msg, status);
539 print_header (const char *name, const char *value, gpointer data)
541 GST_DEBUG ("header: %s: %s", name, value);
545 server_callback (SoupServer * server, SoupMessage * msg,
546 const char *path, GHashTable * query,
547 SoupClientContext * context, gpointer data)
549 GST_DEBUG ("%s %s HTTP/1.%d", msg->method, path,
550 soup_message_get_http_version (msg));
551 soup_message_headers_foreach (msg->request_headers, print_header, NULL);
552 if (msg->request_body->length)
553 GST_DEBUG ("%s", msg->request_body->data);
555 if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
558 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
560 GST_DEBUG (" -> %d %s", msg->status_code, msg->reason_phrase);
563 static SoupServer *server; /* NULL */
564 static SoupServer *ssl_server; /* NULL */
567 run_server (guint * http_port, guint * https_port)
569 guint port = SOUP_ADDRESS_ANY_PORT;
570 guint ssl_port = SOUP_ADDRESS_ANY_PORT;
571 const char *ssl_cert_file = GST_TEST_FILES_PATH "/test-cert.pem";
572 const char *ssl_key_file = GST_TEST_FILES_PATH "/test-key.pem";
573 static int server_running = 0;
575 SoupAuthDomain *domain = NULL;
581 *http_port = *https_port = 0;
583 server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
585 GST_DEBUG ("Unable to bind to server port %u", port);
588 *http_port = soup_server_get_port (server);
589 GST_INFO ("HTTP server listening on port %u", *http_port);
590 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
591 domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, realm,
592 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_cb,
593 SOUP_AUTH_DOMAIN_ADD_PATH, basic_auth_path, NULL);
594 soup_server_add_auth_domain (server, domain);
595 g_object_unref (domain);
596 domain = soup_auth_domain_digest_new (SOUP_AUTH_DOMAIN_REALM, realm,
597 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_cb,
598 SOUP_AUTH_DOMAIN_ADD_PATH, digest_auth_path, NULL);
599 soup_server_add_auth_domain (server, domain);
600 g_object_unref (domain);
601 soup_server_run_async (server);
603 if (ssl_cert_file && ssl_key_file) {
604 ssl_server = soup_server_new (SOUP_SERVER_PORT, ssl_port,
605 SOUP_SERVER_SSL_CERT_FILE, ssl_cert_file,
606 SOUP_SERVER_SSL_KEY_FILE, ssl_key_file, NULL);
609 GST_DEBUG ("Unable to bind to SSL server port %u", ssl_port);
612 *https_port = soup_server_get_port (ssl_server);
613 GST_INFO ("HTTPS server listening on port %u", *https_port);
614 soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL);
615 soup_server_run_async (ssl_server);
624 GST_INFO ("cleaning up");
627 g_object_unref (server);
631 g_object_unref (ssl_server);