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.
29 #include <glib/gprintf.h>
30 #include <libsoup/soup-address.h>
31 #include <libsoup/soup-message.h>
32 #include <libsoup/soup-misc.h>
33 #include <libsoup/soup-server.h>
34 #include <libsoup/soup-auth-domain.h>
35 #include <libsoup/soup-auth-domain-basic.h>
36 #include <libsoup/soup-auth-domain-digest.h>
37 #include <gst/check/gstcheck.h>
39 static guint http_port = 0, https_port = 0;
41 gboolean redirect = TRUE;
43 static const char **cookies = NULL;
45 /* Variables for authentication tests */
46 static const char *user_id = NULL;
47 static const char *user_pw = NULL;
48 static const char *good_user = "good_user";
49 static const char *bad_user = "bad_user";
50 static const char *good_pw = "good_pw";
51 static const char *bad_pw = "bad_pw";
52 static const char *realm = "SOUPHTTPSRC_REALM";
53 static const char *basic_auth_path = "/basic_auth";
54 static const char *digest_auth_path = "/digest_auth";
56 static int run_server (guint * http_port, guint * https_port);
57 static void stop_server (void);
60 handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
61 GstBuffer ** p_outbuf)
63 GST_LOG ("handoff, buf = %p", buf);
64 if (*p_outbuf == NULL)
65 *p_outbuf = gst_buffer_ref (buf);
69 basic_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
70 const char *username, const char *password, gpointer user_data)
72 /* There is only one good login for testing */
73 return (strcmp (username, good_user) == 0)
74 && (strcmp (password, good_pw) == 0);
79 digest_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
80 const char *username, gpointer user_data)
82 /* There is only one good login for testing */
83 if (strcmp (username, good_user) == 0)
84 return soup_auth_domain_digest_encode_password (good_user, realm, good_pw);
89 run_test (const char *format, ...)
91 GstStateChangeReturn ret;
93 GstElement *pipe, *src, *sink;
95 GstBuffer *buf = NULL;
105 pipe = gst_pipeline_new (NULL);
107 src = gst_element_factory_make ("souphttpsrc", NULL);
108 fail_unless (src != NULL);
110 sink = gst_element_factory_make ("fakesink", NULL);
111 fail_unless (sink != NULL);
113 gst_bin_add (GST_BIN (pipe), src);
114 gst_bin_add (GST_BIN (pipe), sink);
115 fail_unless (gst_element_link (src, sink));
117 if (http_port == 0) {
118 GST_DEBUG ("failed to start soup http server");
120 fail_unless (http_port != 0);
121 va_start (args, format);
122 g_vasprintf (&url, format, args);
124 fail_unless (url != NULL);
125 g_object_set (src, "location", url, NULL);
128 g_object_set (src, "automatic-redirect", redirect, NULL);
130 g_object_set (src, "cookies", cookies, NULL);
131 g_object_set (sink, "signal-handoffs", TRUE, NULL);
132 g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
135 g_object_set (src, "user-id", user_id, NULL);
137 g_object_set (src, "user-pw", user_pw, NULL);
139 ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
140 if (ret != GST_STATE_CHANGE_ASYNC) {
141 GST_DEBUG ("failed to start up soup http src, ret = %d", ret);
145 gst_element_set_state (pipe, GST_STATE_PLAYING);
146 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
147 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
148 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
153 gst_message_parse_error (msg, &err, &debug);
154 GST_INFO ("error: %s", err->message);
155 if (g_str_has_suffix (err->message, "Not Found"))
157 else if (g_str_has_suffix (err->message, "Forbidden"))
159 else if (g_str_has_suffix (err->message, "Unauthorized"))
161 else if (g_str_has_suffix (err->message, "Found"))
163 GST_INFO ("debug: %s", debug);
166 gst_message_unref (msg);
169 gst_message_unref (msg);
171 /* don't wait for more than 10 seconds */
172 ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
173 GST_LOG ("ret = %u", ret);
176 /* we want to test the buffer offset, nothing else; if there's a failure
177 * it might be for lots of reasons (no network connection, whatever), we're
178 * not interested in those */
179 GST_DEBUG ("didn't manage to get data within 10 seconds, skipping test");
183 GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
185 /* first buffer should have a 0 offset */
186 fail_unless (GST_BUFFER_OFFSET (buf) == 0);
187 gst_buffer_unref (buf);
192 gst_element_set_state (pipe, GST_STATE_NULL);
193 gst_object_unref (pipe);
197 GST_START_TEST (test_first_buffer_has_offset)
199 fail_unless (run_test ("http://127.0.0.1:%u/", http_port) == 0);
204 GST_START_TEST (test_not_found)
206 fail_unless (run_test ("http://127.0.0.1:%u/404", http_port) == 404);
211 GST_START_TEST (test_forbidden)
213 fail_unless (run_test ("http://127.0.0.1:%u/403", http_port) == 403);
218 GST_START_TEST (test_redirect_no)
221 fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 302);
226 GST_START_TEST (test_redirect_yes)
229 fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 0);
234 GST_START_TEST (test_https)
237 GST_INFO ("Failed to start an HTTPS server; let's just skip this test.");
239 fail_unless (run_test ("https://127.0.0.1:%u/", https_port) == 0);
244 GST_START_TEST (test_cookies)
246 static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL };
250 rc = run_test ("http://127.0.0.1:%u/", http_port);
252 fail_unless (rc == 0);
257 GST_START_TEST (test_good_user_basic_auth)
263 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
264 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
265 user_id = user_pw = NULL;
266 fail_unless (res == 0);
271 GST_START_TEST (test_bad_user_basic_auth)
277 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
278 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
279 user_id = user_pw = NULL;
280 fail_unless (res == 401);
285 GST_START_TEST (test_bad_password_basic_auth)
291 res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path);
292 GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
293 user_id = user_pw = NULL;
294 fail_unless (res == 401);
299 GST_START_TEST (test_good_user_digest_auth)
305 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
306 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
307 user_id = user_pw = NULL;
308 fail_unless (res == 0);
313 GST_START_TEST (test_bad_user_digest_auth)
319 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
320 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
321 user_id = user_pw = NULL;
322 fail_unless (res == 401);
327 GST_START_TEST (test_bad_password_digest_auth)
333 res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path);
334 GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
335 user_id = user_pw = NULL;
336 fail_unless (res == 401);
341 static gboolean icy_caps = FALSE;
344 got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
349 /* Caps can be anything if we don't except icy caps */
353 /* Otherwise they _must_ be "application/x-icy" */
354 fail_unless (GST_BUFFER_CAPS (buf) != NULL);
355 s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
356 fail_unless_equals_string (gst_structure_get_name (s), "application/x-icy");
359 GST_START_TEST (test_icy_stream)
361 GstElement *pipe, *src, *sink;
365 pipe = gst_pipeline_new (NULL);
367 src = gst_element_factory_make ("souphttpsrc", NULL);
368 fail_unless (src != NULL);
369 g_object_set (src, "iradio-mode", TRUE, NULL);
371 sink = gst_element_factory_make ("fakesink", NULL);
372 fail_unless (sink != NULL);
373 g_object_set (sink, "signal-handoffs", TRUE, NULL);
374 g_signal_connect (sink, "handoff", G_CALLBACK (got_buffer), NULL);
376 gst_bin_add (GST_BIN (pipe), src);
377 gst_bin_add (GST_BIN (pipe), sink);
378 fail_unless (gst_element_link (src, sink));
380 /* First try Virgin Radio Ogg stream, to see if there's connectivity and all
381 * (which is an attempt to work around the completely horrid error reporting
382 * and that we can't distinguish different types of failures here). */
384 g_object_set (src, "location", "http://ogg2.smgradio.com/vr32.ogg", NULL);
385 g_object_set (src, "num-buffers", 1, NULL);
387 gst_element_set_state (pipe, GST_STATE_PLAYING);
389 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
390 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
391 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
392 GST_INFO ("looks like there's no net connectivity or sgmradio.com is "
393 "down. In any case, let's just skip this test");
394 gst_message_unref (msg);
397 gst_message_unref (msg);
399 gst_element_set_state (pipe, GST_STATE_NULL);
401 /* Now, if the ogg stream works, the mp3 shoutcast stream should work as
402 * well (time will tell if that's true) */
404 /* Virgin Radio 32kbps mp3 shoutcast stream */
405 g_object_set (src, "location", "http://mp3-vr-32.smgradio.com:80/", NULL);
408 /* EOS after the first buffer */
409 g_object_set (src, "num-buffers", 1, NULL);
411 gst_element_set_state (pipe, GST_STATE_PLAYING);
412 msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
413 GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
415 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
416 GST_DEBUG ("success, we're done here");
417 gst_message_unref (msg);
424 gst_message_parse_error (msg, &err, NULL);
425 gst_message_unref (msg);
426 g_error ("Error with ICY mp3 shoutcast stream: %s", err->message);
433 gst_element_set_state (pipe, GST_STATE_NULL);
434 gst_object_unref (pipe);
440 souphttpsrc_suite (void)
444 TCase *tc_chain, *tc_internet;
448 #if !GLIB_CHECK_VERSION (2, 31, 0)
449 if (!g_thread_supported ())
450 g_thread_init (NULL);
453 s = suite_create ("souphttpsrc");
454 tc_chain = tcase_create ("general");
455 tc_internet = tcase_create ("internet");
457 suite_add_tcase (s, tc_chain);
458 run_server (&http_port, &https_port);
459 atexit (stop_server);
460 tcase_add_test (tc_chain, test_first_buffer_has_offset);
461 tcase_add_test (tc_chain, test_redirect_yes);
462 tcase_add_test (tc_chain, test_redirect_no);
463 tcase_add_test (tc_chain, test_not_found);
464 tcase_add_test (tc_chain, test_forbidden);
465 tcase_add_test (tc_chain, test_cookies);
466 tcase_add_test (tc_chain, test_good_user_basic_auth);
467 tcase_add_test (tc_chain, test_bad_user_basic_auth);
468 tcase_add_test (tc_chain, test_bad_password_basic_auth);
469 tcase_add_test (tc_chain, test_good_user_digest_auth);
470 tcase_add_test (tc_chain, test_bad_user_digest_auth);
471 tcase_add_test (tc_chain, test_bad_password_digest_auth);
472 if (soup_ssl_supported)
473 tcase_add_test (tc_chain, test_https);
475 suite_add_tcase (s, tc_internet);
476 tcase_set_timeout (tc_internet, 250);
477 tcase_add_test (tc_internet, test_icy_stream);
482 GST_CHECK_MAIN (souphttpsrc);
485 do_get (SoupMessage * msg, const char *path)
491 SoupKnownStatusCode status = SOUP_STATUS_OK;
493 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
494 GST_DEBUG ("request: \"%s\"", uri);
496 if (!strcmp (path, "/301"))
497 status = SOUP_STATUS_MOVED_PERMANENTLY;
498 else if (!strcmp (path, "/302"))
499 status = SOUP_STATUS_MOVED_TEMPORARILY;
500 else if (!strcmp (path, "/307"))
501 status = SOUP_STATUS_TEMPORARY_REDIRECT;
502 else if (!strcmp (path, "/403"))
503 status = SOUP_STATUS_FORBIDDEN;
504 else if (!strcmp (path, "/404"))
505 status = SOUP_STATUS_NOT_FOUND;
507 if (SOUP_STATUS_IS_REDIRECTION (status)) {
510 redir_uri = g_strdup_printf ("%s-redirected", uri);
511 soup_message_headers_append (msg->response_headers, "Location", redir_uri);
514 if (status != SOUP_STATUS_OK)
517 if (msg->method == SOUP_METHOD_GET) {
520 buf = g_malloc (buflen);
521 memset (buf, 0, buflen);
522 soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE,
524 } else { /* msg->method == SOUP_METHOD_HEAD */
528 /* We could just use the same code for both GET and
529 * HEAD. But we'll optimize and avoid the extra
532 length = g_strdup_printf ("%lu", (gulong) buflen);
533 soup_message_headers_append (msg->response_headers,
534 "Content-Length", length);
539 soup_message_set_status (msg, status);
544 print_header (const char *name, const char *value, gpointer data)
546 GST_DEBUG ("header: %s: %s", name, value);
550 server_callback (SoupServer * server, SoupMessage * msg,
551 const char *path, GHashTable * query,
552 SoupClientContext * context, gpointer data)
554 GST_DEBUG ("%s %s HTTP/1.%d", msg->method, path,
555 soup_message_get_http_version (msg));
556 soup_message_headers_foreach (msg->request_headers, print_header, NULL);
557 if (msg->request_body->length)
558 GST_DEBUG ("%s", msg->request_body->data);
560 if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
563 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
565 GST_DEBUG (" -> %d %s", msg->status_code, msg->reason_phrase);
568 static SoupServer *server; /* NULL */
569 static SoupServer *ssl_server; /* NULL */
572 run_server (guint * http_port, guint * https_port)
574 guint port = SOUP_ADDRESS_ANY_PORT;
575 guint ssl_port = SOUP_ADDRESS_ANY_PORT;
576 const char *ssl_cert_file = GST_TEST_FILES_PATH "/test-cert.pem";
577 const char *ssl_key_file = GST_TEST_FILES_PATH "/test-key.pem";
578 static int server_running = 0;
580 SoupAuthDomain *domain = NULL;
586 *http_port = *https_port = 0;
588 server = soup_server_new (SOUP_SERVER_PORT, port, NULL);
590 GST_DEBUG ("Unable to bind to server port %u", port);
593 *http_port = soup_server_get_port (server);
594 GST_INFO ("HTTP server listening on port %u", *http_port);
595 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
596 domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, realm,
597 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_cb,
598 SOUP_AUTH_DOMAIN_ADD_PATH, basic_auth_path, NULL);
599 soup_server_add_auth_domain (server, domain);
600 g_object_unref (domain);
601 domain = soup_auth_domain_digest_new (SOUP_AUTH_DOMAIN_REALM, realm,
602 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_cb,
603 SOUP_AUTH_DOMAIN_ADD_PATH, digest_auth_path, NULL);
604 soup_server_add_auth_domain (server, domain);
605 g_object_unref (domain);
606 soup_server_run_async (server);
608 if (ssl_cert_file && ssl_key_file) {
609 ssl_server = soup_server_new (SOUP_SERVER_PORT, ssl_port,
610 SOUP_SERVER_SSL_CERT_FILE, ssl_cert_file,
611 SOUP_SERVER_SSL_KEY_FILE, ssl_key_file, NULL);
614 GST_DEBUG ("Unable to bind to SSL server port %u", ssl_port);
617 *https_port = soup_server_get_port (ssl_server);
618 GST_INFO ("HTTPS server listening on port %u", *https_port);
619 soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL);
620 soup_server_run_async (ssl_server);
629 GST_INFO ("cleaning up");
632 g_object_unref (server);
636 g_object_unref (ssl_server);