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.
5  *
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.
10  *
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.
15  *
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., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25 
26 #include <stdlib.h>
27 
28 #include <glib.h>
29 #include <glib/gprintf.h>
30 
31 #define SOUP_VERSION_MIN_REQUIRED (SOUP_VERSION_2_40)
32 #include <libsoup/soup.h>
33 #include <gst/check/gstcheck.h>
34 
35 #if !defined(SOUP_MINOR_VERSION) || SOUP_MINOR_VERSION < 44
36 #define SoupStatus SoupKnownStatusCode
37 #endif
38 
39 
40 gboolean redirect = TRUE;
41 
42 static const char **cookies = NULL;
43 
44 /* Variables for authentication tests */
45 static const char *user_id = NULL;
46 static const char *user_pw = NULL;
47 static const char *good_user = "good_user";
48 static const char *bad_user = "bad_user";
49 static const char *good_pw = "good_pw";
50 static const char *bad_pw = "bad_pw";
51 static const char *realm = "SOUPHTTPSRC_REALM";
52 static const char *basic_auth_path = "/basic_auth";
53 static const char *digest_auth_path = "/digest_auth";
54 
55 static const char *ssl_cert_file = GST_TEST_FILES_PATH "/test-cert.pem";
56 static const char *ssl_key_file = GST_TEST_FILES_PATH "/test-key.pem";
57 
58 static guint get_port_from_server (SoupServer * server);
59 static SoupServer *run_server (gboolean use_https);
60 
61 static void
handoff_cb(GstElement * fakesink,GstBuffer * buf,GstPad * pad,GstBuffer ** p_outbuf)62 handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
63     GstBuffer ** p_outbuf)
64 {
65   GST_LOG ("handoff, buf = %p", buf);
66   if (*p_outbuf == NULL)
67     *p_outbuf = gst_buffer_ref (buf);
68 }
69 
70 static gboolean
basic_auth_cb(SoupAuthDomain * domain,SoupMessage * msg,const char * username,const char * password,gpointer user_data)71 basic_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
72     const char *username, const char *password, gpointer user_data)
73 {
74   /* There is only one good login for testing */
75   return (strcmp (username, good_user) == 0)
76       && (strcmp (password, good_pw) == 0);
77 }
78 
79 
80 static char *
digest_auth_cb(SoupAuthDomain * domain,SoupMessage * msg,const char * username,gpointer user_data)81 digest_auth_cb (SoupAuthDomain * domain, SoupMessage * msg,
82     const char *username, gpointer user_data)
83 {
84   /* There is only one good login for testing */
85   if (strcmp (username, good_user) == 0)
86     return soup_auth_domain_digest_encode_password (good_user, realm, good_pw);
87   return NULL;
88 }
89 
90 static gboolean
run_test(gboolean use_https,const gchar * path,gint expected)91 run_test (gboolean use_https, const gchar * path, gint expected)
92 {
93   GstStateChangeReturn ret;
94   GstElement *pipe, *src, *sink;
95   GstBuffer *buf = NULL;
96   GstMessage *msg;
97   gchar *url;
98   gboolean res = FALSE;
99   SoupServer *server;
100   guint port;
101 
102   server = run_server (use_https);
103   if (server == NULL) {
104     g_print ("Failed to start up %s server", use_https ? "HTTPS" : "HTTP");
105     /* skip this test */
106     return TRUE;
107   }
108 
109   pipe = gst_pipeline_new (NULL);
110 
111   src = gst_element_factory_make ("souphttpsrc", NULL);
112   fail_unless (src != NULL);
113 
114   sink = gst_element_factory_make ("fakesink", NULL);
115   fail_unless (sink != NULL);
116 
117   gst_bin_add (GST_BIN (pipe), src);
118   gst_bin_add (GST_BIN (pipe), sink);
119   fail_unless (gst_element_link (src, sink));
120 
121   port = get_port_from_server (server);
122   url = g_strdup_printf ("%s://127.0.0.1:%u%s",
123       use_https ? "https" : "http", port, path);
124   fail_unless (url != NULL);
125   g_object_set (src, "location", url, NULL);
126   g_free (url);
127 
128   if (use_https) {
129     GTlsDatabase *tlsdb;
130     GError *error = NULL;
131     gchar *path;
132 
133     /* GTlsFileDatabase needs an absolute path. Using a relative one
134      * causes a warning from GLib-Net followed by a segfault in GnuTLS */
135     if (g_path_is_absolute (ssl_cert_file)) {
136       path = g_strdup (ssl_cert_file);
137     } else {
138       gchar *cwd = g_get_current_dir ();
139       path = g_build_filename (cwd, ssl_cert_file, NULL);
140       g_free (cwd);
141     }
142 
143     tlsdb = g_tls_file_database_new (path, &error);
144     fail_unless (tlsdb, "Failed to load certificate: %s", error->message);
145 
146     g_object_set (src, "tls-database", tlsdb, NULL);
147 
148     g_object_unref (tlsdb);
149     g_free (path);
150   }
151 
152   g_object_set (src, "automatic-redirect", redirect, NULL);
153   if (cookies != NULL)
154     g_object_set (src, "cookies", cookies, NULL);
155   g_object_set (sink, "signal-handoffs", TRUE, NULL);
156   g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
157 
158   if (user_id != NULL)
159     g_object_set (src, "user-id", user_id, NULL);
160   if (user_pw != NULL)
161     g_object_set (src, "user-pw", user_pw, NULL);
162 
163   ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
164   if (ret != GST_STATE_CHANGE_ASYNC) {
165     GST_DEBUG ("failed to start up soup http src, ret = %d", ret);
166     goto done;
167   }
168 
169   gst_element_set_state (pipe, GST_STATE_PLAYING);
170   msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
171       GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
172   if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
173     gchar *debug = NULL;
174     GError *err = NULL;
175     gint rc = -1;
176 
177     gst_message_parse_error (msg, &err, &debug);
178     GST_INFO ("error: %s", err->message);
179     if (g_str_has_suffix (err->message, "Not Found"))
180       rc = 404;
181     else if (g_str_has_suffix (err->message, "Forbidden"))
182       rc = 403;
183     else if (g_str_has_suffix (err->message, "Unauthorized"))
184       rc = 401;
185     else if (g_str_has_suffix (err->message, "Found"))
186       rc = 302;
187     GST_INFO ("debug: %s", debug);
188     /* should not've gotten any output in case of a 40x error. Wait a bit
189      * to give the streaming thread a chance to push out a buffer and trigger
190      * our callback before shutting down the pipeline */
191     g_usleep (G_USEC_PER_SEC / 2);
192     fail_unless (buf == NULL);
193     g_error_free (err);
194     g_free (debug);
195     gst_message_unref (msg);
196     GST_DEBUG ("Got HTTP error %u, expected %u", rc, expected);
197     res = (rc == expected);
198     goto done;
199   }
200   gst_message_unref (msg);
201 
202   /* don't wait for more than 10 seconds */
203   ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
204   GST_LOG ("ret = %u", ret);
205 
206   if (buf == NULL) {
207     /* we want to test the buffer offset, nothing else; if there's a failure
208      * it might be for lots of reasons (no network connection, whatever), we're
209      * not interested in those */
210     GST_DEBUG ("didn't manage to get data within 10 seconds, skipping test");
211     res = TRUE;
212     goto done;
213   }
214 
215   GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
216 
217   /* first buffer should have a 0 offset */
218   fail_unless (GST_BUFFER_OFFSET (buf) == 0);
219   gst_buffer_unref (buf);
220   res = (expected == 0);
221 
222 done:
223 
224   gst_element_set_state (pipe, GST_STATE_NULL);
225   gst_object_unref (pipe);
226   gst_object_unref (server);
227   return res;
228 }
229 
GST_START_TEST(test_first_buffer_has_offset)230 GST_START_TEST (test_first_buffer_has_offset)
231 {
232   fail_unless (run_test (FALSE, "/", 0));
233 }
234 
235 GST_END_TEST;
236 
GST_START_TEST(test_not_found)237 GST_START_TEST (test_not_found)
238 {
239   fail_unless (run_test (FALSE, "/404", 404));
240   fail_unless (run_test (FALSE, "/404-with-data", 404));
241 }
242 
243 GST_END_TEST;
244 
GST_START_TEST(test_forbidden)245 GST_START_TEST (test_forbidden)
246 {
247   fail_unless (run_test (FALSE, "/403", 403));
248 }
249 
250 GST_END_TEST;
251 
GST_START_TEST(test_redirect_no)252 GST_START_TEST (test_redirect_no)
253 {
254   redirect = FALSE;
255   fail_unless (run_test (FALSE, "/302", 302));
256 }
257 
258 GST_END_TEST;
259 
GST_START_TEST(test_redirect_yes)260 GST_START_TEST (test_redirect_yes)
261 {
262   redirect = TRUE;
263   fail_unless (run_test (FALSE, "/302", 0));
264 }
265 
266 GST_END_TEST;
267 
GST_START_TEST(test_https)268 GST_START_TEST (test_https)
269 {
270   fail_unless (run_test (TRUE, "/", 0));
271 }
272 
273 GST_END_TEST;
274 
GST_START_TEST(test_cookies)275 GST_START_TEST (test_cookies)
276 {
277   static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL };
278   gboolean res;
279 
280   cookies = biscotti;
281   res = run_test (FALSE, "/", 0);
282   cookies = NULL;
283   fail_unless (res);
284 }
285 
286 GST_END_TEST;
287 
GST_START_TEST(test_good_user_basic_auth)288 GST_START_TEST (test_good_user_basic_auth)
289 {
290   gboolean res;
291 
292   user_id = good_user;
293   user_pw = good_pw;
294   res = run_test (FALSE, basic_auth_path, 0);
295   GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
296   user_id = user_pw = NULL;
297   fail_unless (res);
298 }
299 
300 GST_END_TEST;
301 
GST_START_TEST(test_bad_user_basic_auth)302 GST_START_TEST (test_bad_user_basic_auth)
303 {
304   gboolean res;
305 
306   user_id = bad_user;
307   user_pw = good_pw;
308   res = run_test (FALSE, basic_auth_path, 401);
309   GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
310   user_id = user_pw = NULL;
311   fail_unless (res);
312 }
313 
314 GST_END_TEST;
315 
GST_START_TEST(test_bad_password_basic_auth)316 GST_START_TEST (test_bad_password_basic_auth)
317 {
318   gboolean res;
319 
320   user_id = good_user;
321   user_pw = bad_pw;
322   res = run_test (FALSE, basic_auth_path, 401);
323   GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res);
324   user_id = user_pw = NULL;
325   fail_unless (res);
326 }
327 
328 GST_END_TEST;
329 
GST_START_TEST(test_good_user_digest_auth)330 GST_START_TEST (test_good_user_digest_auth)
331 {
332   gboolean res;
333 
334   user_id = good_user;
335   user_pw = good_pw;
336   res = run_test (FALSE, digest_auth_path, 0);
337   GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
338   user_id = user_pw = NULL;
339   fail_unless (res);
340 }
341 
342 GST_END_TEST;
343 
GST_START_TEST(test_bad_user_digest_auth)344 GST_START_TEST (test_bad_user_digest_auth)
345 {
346   gboolean res;
347 
348   user_id = bad_user;
349   user_pw = good_pw;
350   res = run_test (FALSE, digest_auth_path, 401);
351   GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
352   user_id = user_pw = NULL;
353   fail_unless (res);
354 }
355 
356 GST_END_TEST;
357 
GST_START_TEST(test_bad_password_digest_auth)358 GST_START_TEST (test_bad_password_digest_auth)
359 {
360   gboolean res;
361 
362   user_id = good_user;
363   user_pw = bad_pw;
364   res = run_test (FALSE, digest_auth_path, 401);
365   GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res);
366   user_id = user_pw = NULL;
367   fail_unless (res);
368 }
369 
370 GST_END_TEST;
371 
372 static gboolean icy_caps = FALSE;
373 
374 static void
got_buffer(GstElement * fakesink,GstBuffer * buf,GstPad * pad,gpointer user_data)375 got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
376     gpointer user_data)
377 {
378   GstStructure *s;
379   GstCaps *caps;
380 
381   /* Caps can be anything if we don't except icy caps */
382   if (!icy_caps)
383     return;
384 
385   /* Otherwise they _must_ be "application/x-icy" */
386   caps = gst_pad_get_current_caps (pad);
387   fail_unless (caps != NULL);
388   s = gst_caps_get_structure (caps, 0);
389   fail_unless_equals_string (gst_structure_get_name (s), "application/x-icy");
390   gst_caps_unref (caps);
391 }
392 
GST_START_TEST(test_icy_stream)393 GST_START_TEST (test_icy_stream)
394 {
395   GstElement *pipe, *src, *sink;
396 
397   GstMessage *msg;
398 
399   pipe = gst_pipeline_new (NULL);
400 
401   src = gst_element_factory_make ("souphttpsrc", NULL);
402   fail_unless (src != NULL);
403 
404   sink = gst_element_factory_make ("fakesink", NULL);
405   fail_unless (sink != NULL);
406   g_object_set (sink, "signal-handoffs", TRUE, NULL);
407   g_signal_connect (sink, "handoff", G_CALLBACK (got_buffer), NULL);
408 
409   gst_bin_add (GST_BIN (pipe), src);
410   gst_bin_add (GST_BIN (pipe), sink);
411   fail_unless (gst_element_link (src, sink));
412 
413   /* Radionomy Hot40Music shoutcast stream */
414   g_object_set (src, "location",
415       "http://streaming.radionomy.com:80/Hot40Music", NULL);
416 
417   /* EOS after the first buffer */
418   g_object_set (src, "num-buffers", 1, NULL);
419   icy_caps = TRUE;
420   gst_element_set_state (pipe, GST_STATE_PLAYING);
421   msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
422       GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
423 
424   switch (GST_MESSAGE_TYPE (msg)) {
425     case GST_MESSAGE_EOS:
426       GST_DEBUG ("success, we're done here");
427       gst_message_unref (msg);
428       break;
429     case GST_MESSAGE_ERROR:{
430       GError *err = NULL;
431 
432       gst_message_parse_error (msg, &err, NULL);
433       GST_INFO ("Error with ICY mp3 shoutcast stream: %s", err->message);
434       gst_message_unref (msg);
435       g_clear_error (&err);
436       break;
437     }
438     default:
439       break;
440   }
441 
442   icy_caps = FALSE;
443 
444   gst_element_set_state (pipe, GST_STATE_NULL);
445   gst_object_unref (pipe);
446 }
447 
448 GST_END_TEST;
449 
450 static Suite *
souphttpsrc_suite(void)451 souphttpsrc_suite (void)
452 {
453   TCase *tc_chain, *tc_internet;
454   Suite *s;
455 
456   /* we don't support exceptions from the proxy, so just unset the environment
457    * variable - in case it's set in the test environment it would otherwise
458    * prevent us from connecting to localhost (like jenkins.qa.ubuntu.com) */
459   g_unsetenv ("http_proxy");
460 
461   s = suite_create ("souphttpsrc");
462   tc_chain = tcase_create ("general");
463   tc_internet = tcase_create ("internet");
464 
465   suite_add_tcase (s, tc_chain);
466   tcase_add_test (tc_chain, test_first_buffer_has_offset);
467   tcase_add_test (tc_chain, test_redirect_yes);
468   tcase_add_test (tc_chain, test_redirect_no);
469   tcase_add_test (tc_chain, test_not_found);
470   tcase_add_test (tc_chain, test_forbidden);
471   tcase_add_test (tc_chain, test_cookies);
472   tcase_add_test (tc_chain, test_good_user_basic_auth);
473   tcase_add_test (tc_chain, test_bad_user_basic_auth);
474   tcase_add_test (tc_chain, test_bad_password_basic_auth);
475   tcase_add_test (tc_chain, test_good_user_digest_auth);
476   tcase_add_test (tc_chain, test_bad_user_digest_auth);
477   tcase_add_test (tc_chain, test_bad_password_digest_auth);
478   tcase_add_test (tc_chain, test_https);
479 
480   suite_add_tcase (s, tc_internet);
481   tcase_set_timeout (tc_internet, 250);
482   tcase_add_test (tc_internet, test_icy_stream);
483 
484   return s;
485 }
486 
487 GST_CHECK_MAIN (souphttpsrc);
488 
489 static void
do_get(SoupMessage * msg,const char * path)490 do_get (SoupMessage * msg, const char *path)
491 {
492   gboolean send_error_doc = FALSE;
493   char *uri;
494 
495   int buflen = 4096;
496 
497   SoupStatus status = SOUP_STATUS_OK;
498 
499   uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
500   GST_DEBUG ("request: \"%s\"", uri);
501 
502   if (!strcmp (path, "/301"))
503     status = SOUP_STATUS_MOVED_PERMANENTLY;
504   else if (!strcmp (path, "/302"))
505     status = SOUP_STATUS_MOVED_TEMPORARILY;
506   else if (!strcmp (path, "/307"))
507     status = SOUP_STATUS_TEMPORARY_REDIRECT;
508   else if (!strcmp (path, "/403"))
509     status = SOUP_STATUS_FORBIDDEN;
510   else if (!strcmp (path, "/404"))
511     status = SOUP_STATUS_NOT_FOUND;
512   else if (!strcmp (path, "/404-with-data")) {
513     status = SOUP_STATUS_NOT_FOUND;
514     send_error_doc = TRUE;
515   }
516 
517   if (SOUP_STATUS_IS_REDIRECTION (status)) {
518     char *redir_uri;
519 
520     redir_uri = g_strdup_printf ("%s-redirected", uri);
521     soup_message_headers_append (msg->response_headers, "Location", redir_uri);
522     g_free (redir_uri);
523   }
524   if (status != (SoupStatus) SOUP_STATUS_OK && !send_error_doc)
525     goto leave;
526 
527   if (msg->method == SOUP_METHOD_GET) {
528     char *buf;
529 
530     buf = g_malloc (buflen);
531     memset (buf, 0, buflen);
532     soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE,
533         buf, buflen);
534   } else {                      /* msg->method == SOUP_METHOD_HEAD */
535 
536     char *length;
537 
538     /* We could just use the same code for both GET and
539      * HEAD. But we'll optimize and avoid the extra
540      * malloc.
541      */
542     length = g_strdup_printf ("%lu", (gulong) buflen);
543     soup_message_headers_append (msg->response_headers,
544         "Content-Length", length);
545     g_free (length);
546   }
547 
548 leave:
549   soup_message_set_status (msg, status);
550   g_free (uri);
551 }
552 
553 static void
print_header(const char * name,const char * value,gpointer data)554 print_header (const char *name, const char *value, gpointer data)
555 {
556   GST_DEBUG ("header: %s: %s", name, value);
557 }
558 
559 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)560 server_callback (SoupServer * server, SoupMessage * msg,
561     const char *path, GHashTable * query,
562     SoupClientContext * context, gpointer data)
563 {
564   GST_DEBUG ("%s %s HTTP/1.%d", msg->method, path,
565       soup_message_get_http_version (msg));
566   soup_message_headers_foreach (msg->request_headers, print_header, NULL);
567   if (msg->request_body->length)
568     GST_DEBUG ("%s", msg->request_body->data);
569 
570   if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
571     do_get (msg, path);
572   else
573     soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
574 
575   GST_DEBUG ("  -> %d %s", msg->status_code, msg->reason_phrase);
576 }
577 
578 static guint
get_port_from_server(SoupServer * server)579 get_port_from_server (SoupServer * server)
580 {
581   GSList *uris;
582   guint port;
583 
584   uris = soup_server_get_uris (server);
585   g_assert (g_slist_length (uris) == 1);
586   port = soup_uri_get_port (uris->data);
587   g_slist_free_full (uris, (GDestroyNotify) soup_uri_free);
588 
589   return port;
590 }
591 
592 static SoupServer *
run_server(gboolean use_https)593 run_server (gboolean use_https)
594 {
595   SoupServer *server = soup_server_new (NULL, NULL);
596   SoupServerListenOptions listen_flags = 0;
597   guint port;
598 
599 
600   if (use_https) {
601     GTlsBackend *backend = g_tls_backend_get_default ();
602     GError *err = NULL;
603 
604     if (backend == NULL || !g_tls_backend_supports_tls (backend)) {
605       GST_INFO ("No TLS support");
606       g_object_unref (server);
607       return NULL;
608     }
609 
610     if (!soup_server_set_ssl_cert_file (server, ssl_cert_file, ssl_key_file,
611             &err)) {
612       GST_INFO ("Failed to load certificate: %s", err->message);
613       g_object_unref (server);
614       g_error_free (err);
615       return NULL;
616     }
617 
618     listen_flags |= SOUP_SERVER_LISTEN_HTTPS;
619   }
620 
621   soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
622 
623   {
624     SoupAuthDomain *domain;
625 
626     domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, realm,
627         SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_cb,
628         SOUP_AUTH_DOMAIN_ADD_PATH, basic_auth_path, NULL);
629     soup_server_add_auth_domain (server, domain);
630     g_object_unref (domain);
631 
632     domain = soup_auth_domain_digest_new (SOUP_AUTH_DOMAIN_REALM, realm,
633         SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_cb,
634         SOUP_AUTH_DOMAIN_ADD_PATH, digest_auth_path, NULL);
635     soup_server_add_auth_domain (server, domain);
636     g_object_unref (domain);
637   }
638 
639   {
640     GSocketAddress *address;
641     GError *err = NULL;
642 
643     address =
644         g_inet_socket_address_new_from_string ("0.0.0.0",
645         SOUP_ADDRESS_ANY_PORT);
646     soup_server_listen (server, address, listen_flags, &err);
647     g_object_unref (address);
648 
649     if (err) {
650       GST_ERROR ("Failed to start %s server: %s",
651           use_https ? "HTTPS" : "HTTP", err->message);
652       g_object_unref (server);
653       g_error_free (err);
654       return NULL;
655     }
656   }
657 
658   port = get_port_from_server (server);
659   GST_DEBUG ("%s server listening on port %u", use_https ? "HTTPS" : "HTTP",
660       port);
661 
662   /* check if we can connect to our local http server */
663   {
664     GSocketConnection *conn;
665     GSocketClient *client;
666 
667     client = g_socket_client_new ();
668     g_socket_client_set_timeout (client, 2);
669     conn =
670         g_socket_client_connect_to_host (client, "127.0.0.1", port, NULL, NULL);
671     if (conn == NULL) {
672       GST_INFO ("Couldn't connect to 127.0.0.1:%u", port);
673       g_object_unref (client);
674       g_object_unref (server);
675       return NULL;
676     }
677 
678     g_object_unref (conn);
679     g_object_unref (client);
680   }
681 
682   return server;
683 }
684