• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer unit tests for the curlhttpsrc element
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Library General Public
5  * License as published by the Free Software Foundation; either
6  * version 2 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Library General Public License for more details.
12  *
13  * You should have received a copy of the GNU Library General Public
14  * License along with this library; if not, write to the
15  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
16  * Boston, MA 02110-1301, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 
23 #include <stdlib.h>
24 
25 #include <gio/gio.h>
26 #include <glib.h>
27 #include <glib/gprintf.h>
28 
29 #include <gst/check/gstcheck.h>
30 
31 gboolean redirect = TRUE;
32 
33 static const char **cookies = NULL;
34 
35 typedef struct _GioHttpServer
36 {
37   guint16 port;
38   char *root;
39   GSocketService *service;
40   guint64 delay;
41 } GioHttpServer;
42 
43 typedef struct _HttpHeader
44 {
45   gchar *header;
46   gchar *value;
47 } HttpHeader;
48 
49 typedef struct _HttpRequest
50 {
51   gchar *method;
52   gchar *version;
53   gchar *path;
54   gchar *query;
55   gint64 range_start;
56   gint64 range_stop;
57   GSList *headers;
58 } HttpRequest;
59 
60 static GioHttpServer *run_server (void);
61 static void stop_server (GioHttpServer * server);
62 static guint16 get_port_from_server (GioHttpServer * server);
63 
64 static const gchar *STATUS_OK = "200 OK";
65 static const gchar *STATUS_PARTIAL_CONTENT = "206 OK";
66 static const gchar *STATUS_MOVED_PERMANENTLY = "301 Moved Permanently";
67 static const gchar *STATUS_MOVED_TEMPORARILY = "302 Moved Temporarily";
68 static const gchar *STATUS_TEMPORARY_REDIRECT = "307 Temporary Redirect";
69 static const gchar *STATUS_FORBIDDEN = "403 Forbidden";
70 static const gchar *STATUS_NOT_FOUND = "404 Not Found";
71 
72 static const guint64 http_content_length = G_GUINT64_CONSTANT (1024);
73 
74 static void
do_get(GioHttpServer * server,const HttpRequest * req,GOutputStream * out)75 do_get (GioHttpServer * server, const HttpRequest * req, GOutputStream * out)
76 {
77   gboolean send_error_doc = FALSE;
78   const gchar *status = STATUS_OK;
79   const gchar *content_type = "application/octet-stream";
80   guint64 buflen;
81   GString *s;
82   gpointer *buf = NULL;
83   gsize written = 0;
84 
85   GST_DEBUG ("%s request: \"%s\"", req->method, req->path);
86 
87   if (!strcmp (req->path, "/301"))
88     status = STATUS_MOVED_PERMANENTLY;
89   else if (!strcmp (req->path, "/302"))
90     status = STATUS_MOVED_TEMPORARILY;
91   else if (!strcmp (req->path, "/307"))
92     status = STATUS_TEMPORARY_REDIRECT;
93   else if (!strcmp (req->path, "/403"))
94     status = STATUS_FORBIDDEN;
95   else if (!strcmp (req->path, "/404"))
96     status = STATUS_NOT_FOUND;
97   else if (!strcmp (req->path, "/404-with-data")) {
98     status = STATUS_NOT_FOUND;
99     send_error_doc = TRUE;
100   }
101   if (g_strcmp0 (req->method, "GET") == 0 &&
102       (req->range_start > 0 || req->range_stop >= 0)) {
103     status = STATUS_PARTIAL_CONTENT;
104   }
105   s = g_string_new ("HTTP/");
106   g_string_append_printf (s, "%s %s\r\n", req->version, status);
107 
108   if (g_str_has_prefix (status, "30")) {
109     g_string_append_printf (s, "Location: %s-redirected\r\n", req->path);
110   }
111 
112   if (g_strcmp0 (req->method, "GET") == 0
113       || g_strcmp0 (req->method, "HEAD") == 0) {
114     g_string_append_printf (s, "Accept-Ranges: bytes\r\n");
115   }
116   if (status == STATUS_OK || status == STATUS_PARTIAL_CONTENT || send_error_doc) {
117     g_string_append_printf (s, "Content-Type: %s\r\n", content_type);
118     buflen = http_content_length;
119     if (req->range_start > 0 && req->range_stop >= 0) {
120       buflen = 1 + MIN (req->range_stop, buflen - 1) - req->range_start;
121     } else if (req->range_start > 0) {
122       buflen = buflen - req->range_start;
123     } else if (req->range_stop >= 0) {
124       buflen = 1 + MIN (req->range_stop, buflen - 1);
125     }
126     if (buflen != http_content_length) {
127       g_string_append_printf (s, "Content-Range: bytes %" G_GINT64_FORMAT "-%"
128           G_GINT64_FORMAT "/%" G_GUINT64_FORMAT "\r\n",
129           req->range_start,
130           req->range_stop >= 0 ? req->range_stop : (http_content_length - 1),
131           http_content_length);
132     }
133     GST_TRACE ("buflen = %" G_GUINT64_FORMAT " range = %" G_GINT64_FORMAT
134         " -> %" G_GINT64_FORMAT, buflen, req->range_start, req->range_stop);
135     buf = g_malloc (buflen);
136     memset (buf, 0, buflen);
137     g_string_append_printf (s, "Content-Length: %" G_GUINT64_FORMAT "\r\n",
138         buflen);
139   }
140 
141   g_string_append (s, "\r\n");
142   GST_DEBUG ("Response headers: %u\n%s\n********", (guint) s->len, s->str);
143   g_output_stream_write_all (out, s->str, s->len, &written, NULL, NULL);
144   fail_if (written != s->len);
145   g_string_free (s, TRUE);
146   if (buf) {
147     g_output_stream_write_all (out, buf, buflen, &written, NULL, NULL);
148     fail_if (written != buflen);
149     g_free (buf);
150   }
151 }
152 
153 static void
send_error(GOutputStream * out,int error_code,const gchar * reason)154 send_error (GOutputStream * out, int error_code, const gchar * reason)
155 {
156   gchar *res;
157 
158   res = g_strdup_printf ("HTTP/1.0 %d %s\r\n\r\n"
159       "<html><head><title>%d %s</title></head>"
160       "<body>%s</body></html>", error_code, reason, error_code, reason, reason);
161   g_output_stream_write_all (out, res, strlen (res), NULL, NULL, NULL);
162   g_free (res);
163 }
164 
165 static HttpHeader *
http_header_new(const gchar * header,const gchar * value)166 http_header_new (const gchar * header, const gchar * value)
167 {
168   HttpHeader *ret;
169 
170   ret = g_slice_new (HttpHeader);
171   ret->header = g_strdup (header);
172   ret->value = g_strdup (value);
173   return ret;
174 }
175 
176 static void
http_header_free(HttpHeader * header)177 http_header_free (HttpHeader * header)
178 {
179   if (header) {
180     g_free (header->header);
181     g_free (header->value);
182     g_slice_free (HttpHeader, header);
183   }
184 }
185 
186 static HttpRequest *
http_request_new(const gchar * method,const gchar * version,const gchar * path,const gchar * query)187 http_request_new (const gchar * method, const gchar * version,
188     const gchar * path, const gchar * query)
189 {
190   HttpRequest *req;
191 
192   req = g_slice_new0 (HttpRequest);
193   req->method = g_strdup (method);
194   if (version)
195     req->version = g_strdup (version);
196   req->path = g_uri_unescape_string (path, NULL);
197   if (query)
198     req->query = g_strdup (query);
199   req->range_start = 0;
200   req->range_stop = -1;
201   return req;
202 }
203 
204 static void
http_request_free(HttpRequest * req)205 http_request_free (HttpRequest * req)
206 {
207   if (!req)
208     return;
209   g_free (req->method);
210   g_free (req->version);
211   g_free (req->path);
212   g_free (req->query);
213   if (req->headers)
214     g_slist_free_full (req->headers, (GDestroyNotify) http_header_free);
215   g_slice_free (HttpRequest, req);
216 }
217 
218 static gboolean
server_callback(GThreadedSocketService * service,GSocketConnection * connection,GSocketListener * listener,gpointer user_data)219 server_callback (GThreadedSocketService * service,
220     GSocketConnection * connection,
221     GSocketListener * listener, gpointer user_data)
222 {
223   GioHttpServer *server = (GioHttpServer *) user_data;
224   GOutputStream *out;
225   GInputStream *in;
226   GDataInputStream *data = NULL;
227   gchar *line = NULL, *escaped, *tmp;
228   HttpRequest *req = NULL;
229   gboolean done = FALSE;
230   gchar *version = NULL, *query;
231 
232   in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
233   out = g_io_stream_get_output_stream (G_IO_STREAM (connection));
234 
235   data = g_data_input_stream_new (in);
236 
237   g_data_input_stream_set_newline_type (data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
238 
239   line = g_data_input_stream_read_line (data, NULL, NULL, NULL);
240 
241   if (line == NULL) {
242     send_error (out, 400, "Invalid request");
243     goto out;
244   }
245 
246   tmp = strchr (line, ' ');
247   if (!tmp) {
248     send_error (out, 400, "Invalid request");
249     goto out;
250   }
251   *tmp = '\0';
252   escaped = tmp + 1;
253 
254   tmp = strchr (escaped, ' ');
255   if (tmp != NULL) {
256     *tmp = 0;
257     version = tmp + 6;          /* skip "HTTP/" from version field */
258   }
259 
260   query = strchr (escaped, '?');
261   if (query != NULL) {
262     *query = '\0';
263     query++;
264   }
265 
266   req = http_request_new (line, version, escaped, query);
267 
268   GST_TRACE ("%s %s HTTP/%s", req->method, req->path, req->version);
269 
270   while (!done) {
271     g_free (line);
272     line = g_data_input_stream_read_line (data, NULL, NULL, NULL);
273     if (!line) {
274       send_error (out, 400, "Invalid request");
275       goto out;
276     }
277     tmp = strchr (line, ':');
278     if (!tmp) {
279       /* reached end of HTTP request headers */
280       done = TRUE;
281       continue;
282     }
283     *tmp = '\0';
284     do {
285       ++tmp;
286     } while (*tmp == ' ');
287     GST_TRACE ("Request header: %s: %s", line, tmp);
288     req->headers = g_slist_append (req->headers, http_header_new (line, tmp));
289     if (g_ascii_strcasecmp (line, "range") == 0) {
290       gchar *start, *end;
291       start = strchr (tmp, '=');
292       if (!start) {
293         GST_ERROR ("Invalid range request: %s", tmp);
294         send_error (out, 400, "Invalid request");
295         goto out;
296       }
297       start++;
298       end = strchr (start, '-');
299       if (!end) {
300         GST_ERROR ("Invalid range request: %s", tmp);
301         send_error (out, 400, "Invalid request");
302         goto out;
303       }
304       *end = '\0';
305       end++;
306       if (*start != '\0') {
307         req->range_start = atoi (start);
308       }
309       if (*end != '\0') {
310         req->range_stop = atoi (end);
311       }
312       GST_DEBUG ("RANGE request %" G_GINT64_FORMAT " -> %" G_GINT64_FORMAT,
313           req->range_start, req->range_stop);
314     }
315   }
316   if (server->delay) {
317     g_usleep (server->delay);
318   }
319   do_get (server, req, out);
320 
321 out:
322   g_free (line);
323   http_request_free (req);
324   if (data)
325     g_object_unref (data);
326 
327   return TRUE;
328 }
329 
330 static guint16
get_port_from_server(GioHttpServer * server)331 get_port_from_server (GioHttpServer * server)
332 {
333   fail_if (server == NULL);
334   return server->port;
335 }
336 
337 static GioHttpServer *
run_server(void)338 run_server (void)
339 {
340   GioHttpServer *server;
341   GError *error = NULL;
342 
343   server = g_slice_new0 (GioHttpServer);
344   server->service = g_threaded_socket_service_new (10);
345   server->port =
346       g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (server->service),
347       NULL, &error);
348   fail_if (server->port == 0);
349   g_signal_connect (server->service, "run", G_CALLBACK (server_callback),
350       server);
351 
352   GST_DEBUG ("HTTP server listening on port %u", server->port);
353 
354   /* check if we can connect to our local http server */
355   {
356     GSocketConnection *conn;
357     GSocketClient *client;
358 
359     client = g_socket_client_new ();
360     g_socket_client_set_timeout (client, 2);
361     conn =
362         g_socket_client_connect_to_host (client, "127.0.0.1", server->port,
363         NULL, NULL);
364     if (conn == NULL) {
365       GST_INFO ("Couldn't connect to 127.0.0.1:%u", server->port);
366       g_object_unref (client);
367       g_slice_free (GioHttpServer, server);
368       return NULL;
369     }
370 
371     g_object_unref (conn);
372     g_object_unref (client);
373   }
374 
375   return server;
376 }
377 
378 static void
stop_server(GioHttpServer * server)379 stop_server (GioHttpServer * server)
380 {
381   fail_if (server == NULL);
382   GST_DEBUG ("Stopping server...");
383   g_socket_service_stop (server->service);
384   g_socket_listener_close (G_SOCKET_LISTENER (server->service));
385   g_object_unref (server->service);
386   g_slice_free (GioHttpServer, server);
387   GST_DEBUG ("Server stopped");
388 }
389 
390 static void
handoff_cb(GstElement * fakesink,GstBuffer * buf,GstPad * pad,GstBuffer ** p_outbuf)391 handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
392     GstBuffer ** p_outbuf)
393 {
394   GST_LOG ("handoff, buf = %p", buf);
395   if (*p_outbuf == NULL)
396     *p_outbuf = gst_buffer_ref (buf);
397 }
398 
399 static gboolean
run_test(const gchar * path,gint expected_status_code,gboolean has_body,gboolean has_error)400 run_test (const gchar * path, gint expected_status_code,
401     gboolean has_body, gboolean has_error)
402 {
403   GstStateChangeReturn ret;
404   GstElement *pipe, *src, *sink;
405   GstBuffer *buf = NULL;
406   GstMessage *msg;
407   gchar *url;
408   gboolean res = FALSE;
409   GioHttpServer *server;
410   guint port;
411   gboolean done = FALSE;
412 
413   server = run_server ();
414   fail_if (server == NULL, "Failed to start up HTTP server");
415 
416   pipe = gst_pipeline_new (NULL);
417   fail_unless (pipe != NULL);
418 
419   src = gst_element_factory_make ("curlhttpsrc", NULL);
420   fail_unless (src != NULL);
421 
422   sink = gst_element_factory_make ("fakesink", NULL);
423   fail_unless (sink != NULL);
424 
425   gst_bin_add (GST_BIN (pipe), src);
426   gst_bin_add (GST_BIN (pipe), sink);
427   fail_unless (gst_element_link (src, sink));
428 
429   port = get_port_from_server (server);
430   url = g_strdup_printf ("http://127.0.0.1:%u%s", port, path);
431   fail_unless (url != NULL);
432   g_object_set (src, "location", url, NULL);
433   g_free (url);
434 
435   g_object_set (src, "automatic-redirect", redirect, NULL);
436   if (cookies != NULL)
437     g_object_set (src, "cookies", cookies, NULL);
438   g_object_set (sink, "signal-handoffs", TRUE, NULL);
439   /*g_object_set (sink, "dump", TRUE, NULL); */
440   g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
441 
442   ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
443   if (ret != GST_STATE_CHANGE_ASYNC) {
444     GST_DEBUG ("failed to start up curl http src, ret = %d", ret);
445     goto done;
446   }
447 
448   gst_element_set_state (pipe, GST_STATE_PLAYING);
449   while (!done) {
450     msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
451         GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
452     GST_DEBUG ("Message: %" GST_PTR_FORMAT, msg);
453     switch (GST_MESSAGE_TYPE (msg)) {
454       case GST_MESSAGE_ERROR:{
455         gchar *debug = NULL;
456         GError *err = NULL;
457         gint rc = -1;
458         const GstStructure *details = NULL;
459 
460         gst_message_parse_error (msg, &err, &debug);
461         gst_message_parse_error_details (msg, &details);
462         GST_DEBUG ("debug object: %s", debug);
463         GST_DEBUG ("err->message: \"%s\"", err->message);
464         GST_DEBUG ("err->details: %" GST_PTR_FORMAT, details);
465         if (g_str_has_suffix (err->message, "Not Found"))
466           rc = 404;
467         else if (g_str_has_suffix (err->message, "Forbidden"))
468           rc = 403;
469         else if (g_str_has_suffix (err->message, "Unauthorized"))
470           rc = 401;
471         else if (g_str_has_suffix (err->message, "Found"))
472           rc = 302;
473         if (details) {
474           if (gst_structure_has_field_typed (details, "http-status-code",
475                   G_TYPE_UINT)) {
476             guint code = 0;
477             gst_structure_get_uint (details, "http-status-code", &code);
478             rc = code;
479           }
480         }
481         g_error_free (err);
482         g_free (debug);
483         fail_unless (has_error);
484         GST_DEBUG ("Got HTTP error %d, expected_status_code %d", rc,
485             expected_status_code);
486         res = (rc == expected_status_code);
487         done = TRUE;
488       }
489         break;
490       case GST_MESSAGE_EOS:
491         if (!has_error)
492           done = TRUE;
493         break;
494       default:
495         fail_if (TRUE, "Unexpected GstMessage");
496         break;
497     }
498     gst_message_unref (msg);
499   }
500 
501   /* don't wait for more than 10 seconds */
502   ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
503   GST_LOG ("ret = %u", ret);
504 
505   if (buf != NULL) {
506     fail_unless (has_body);
507     /* we want to test the buffer offset, nothing else; if there's a failure
508      * it might be for lots of reasons (no network connection, whatever), we're
509      * not interested in those */
510     GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
511 
512     /* first buffer should have a 0 offset */
513     fail_unless (GST_BUFFER_OFFSET (buf) == 0);
514     gst_buffer_unref (buf);
515   }
516   res = TRUE;
517 
518 done:
519 
520   gst_element_set_state (pipe, GST_STATE_NULL);
521   gst_object_unref (pipe);
522   stop_server (server);
523   return res;
524 }
525 
GST_START_TEST(test_first_buffer_has_offset)526 GST_START_TEST (test_first_buffer_has_offset)
527 {
528   fail_unless (run_test ("/", 200, TRUE, FALSE));
529 }
530 
531 GST_END_TEST;
532 
GST_START_TEST(test_not_found)533 GST_START_TEST (test_not_found)
534 {
535   fail_unless (run_test ("/404", 404, FALSE, TRUE));
536 }
537 
538 GST_END_TEST;
539 
GST_START_TEST(test_not_found_with_data)540 GST_START_TEST (test_not_found_with_data)
541 {
542   fail_unless (run_test ("/404-with-data", 404, TRUE, TRUE));
543 }
544 
545 GST_END_TEST;
546 
GST_START_TEST(test_forbidden)547 GST_START_TEST (test_forbidden)
548 {
549   fail_unless (run_test ("/403", 403, FALSE, TRUE));
550 }
551 
552 GST_END_TEST;
553 
GST_START_TEST(test_redirect_no)554 GST_START_TEST (test_redirect_no)
555 {
556   redirect = FALSE;
557   fail_unless (run_test ("/302", 302, FALSE, FALSE));
558 }
559 
560 GST_END_TEST;
561 
GST_START_TEST(test_redirect_yes)562 GST_START_TEST (test_redirect_yes)
563 {
564   redirect = TRUE;
565   fail_unless (run_test ("/302", 200, TRUE, FALSE));
566 }
567 
568 GST_END_TEST;
569 
GST_START_TEST(test_cookies)570 GST_START_TEST (test_cookies)
571 {
572   static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL };
573   gboolean res;
574 
575   cookies = biscotti;
576   res = run_test ("/", 200, TRUE, FALSE);
577   cookies = NULL;
578   fail_unless (res);
579 }
580 
581 GST_END_TEST;
582 
583 typedef struct _HttpSrcTestDownloader
584 {
585   GstElement *bin;
586   GstElement *src;
587   GstElement *sink;
588   GioHttpServer *server;
589   guint count;
590   gint64 start_position;
591   gint64 stop_position;
592 } HttpSrcTestDownloader;
593 
594 static gboolean
move_element_to_ready(gpointer user_data)595 move_element_to_ready (gpointer user_data)
596 {
597   HttpSrcTestDownloader *tp = (HttpSrcTestDownloader *) user_data;
598 
599   GST_TRACE_OBJECT (tp->bin, "Move bin to READY state");
600   gst_element_set_state (tp->bin, GST_STATE_READY);
601   return G_SOURCE_REMOVE;
602 }
603 
604 static GstPadProbeReturn
src_event_probe(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)605 src_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
606 {
607   HttpSrcTestDownloader *tp = (HttpSrcTestDownloader *) user_data;
608   GstEvent *event;
609 
610   event = gst_pad_probe_info_get_event (info);
611 
612   if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
613     GST_DEBUG_OBJECT (tp->bin, "finished last request");
614     g_idle_add (move_element_to_ready, tp);
615   }
616   return GST_PAD_PROBE_OK;
617 }
618 
619 static void
start_next_download(HttpSrcTestDownloader * tp)620 start_next_download (HttpSrcTestDownloader * tp)
621 {
622   gchar *url;
623 
624   url = g_strdup_printf ("http://127.0.0.1:%u/multi/%s-%u",
625       tp->server->port, GST_ELEMENT_NAME (tp->bin), tp->count);
626   fail_unless (url != NULL);
627   GST_DEBUG_OBJECT (tp->bin, "Start next request for: %s", url);
628   g_object_set (tp->src, "location", url, NULL);
629   g_free (url);
630   if (tp->start_position != 0 || tp->stop_position != -1) {
631     /* Send the seek event to the uri_handler, as the other pipeline elements
632      * can't handle it when READY. */
633     GST_DEBUG ("Range get %" G_GINT64_FORMAT " -> %" G_GINT64_FORMAT,
634         tp->start_position, tp->stop_position);
635     fail_if (!gst_element_send_event (tp->src, gst_event_new_seek (1.0,
636                 GST_FORMAT_BYTES, (GstSeekFlags) GST_SEEK_FLAG_FLUSH,
637                 GST_SEEK_TYPE_SET, tp->start_position, GST_SEEK_TYPE_SET,
638                 tp->stop_position + 1)),
639         "Source element can't handle range requests");
640   }
641   fail_unless (gst_element_sync_state_with_parent (tp->bin));
642 }
643 
644 static HttpSrcTestDownloader *
test_curl_http_src_downloader_new(const gchar * name,guint64 delay)645 test_curl_http_src_downloader_new (const gchar * name, guint64 delay)
646 {
647   HttpSrcTestDownloader *tp;
648   gchar *url;
649   GstPad *src_pad;
650 
651   tp = g_slice_new0 (HttpSrcTestDownloader);
652   tp->server = run_server ();
653   fail_if (tp->server == NULL, "Failed to start up HTTP server");
654   tp->server->delay = delay;
655   tp->start_position = 0;
656   tp->stop_position = -1;
657 
658   tp->src = gst_element_factory_make ("curlhttpsrc", NULL);
659   fail_unless (tp->src != NULL);
660 
661   url = g_strdup_printf ("http://127.0.0.1:%u/multi/%s-0", tp->server->port,
662       name);
663   fail_unless (url != NULL);
664   g_object_set (tp->src, "location", url, NULL);
665   g_free (url);
666 
667   src_pad = gst_element_get_static_pad (tp->src, "src");
668   fail_unless (src_pad != NULL);
669   gst_pad_add_probe (src_pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
670       src_event_probe, tp, NULL);
671   gst_object_unref (src_pad);
672 
673   tp->sink = gst_element_factory_make ("fakesink", NULL);
674   fail_unless (tp->sink != NULL);
675 
676   tp->bin = gst_bin_new (name);
677   fail_unless (tp->bin != NULL);
678 
679   gst_bin_add (GST_BIN (tp->bin), tp->src);
680   gst_bin_add (GST_BIN (tp->bin), tp->sink);
681   fail_unless (gst_element_link (tp->src, tp->sink));
682   gst_element_set_locked_state (GST_ELEMENT (tp->bin), TRUE);
683 
684   return tp;
685 }
686 
687 static void
test_curl_http_src_downloader_free(HttpSrcTestDownloader * downloader)688 test_curl_http_src_downloader_free (HttpSrcTestDownloader * downloader)
689 {
690   gst_element_set_state (downloader->bin, GST_STATE_NULL);
691   stop_server (downloader->server);
692   g_slice_free (HttpSrcTestDownloader, downloader);
693 }
694 
695 typedef struct _MultipleHttpRequestsContext
696 {
697   GMainLoop *loop;
698   GstElement *pipe;
699   HttpSrcTestDownloader *downloader1;
700   HttpSrcTestDownloader *downloader2;
701   gboolean failed;
702 } MultipleHttpRequestsContext;
703 
704 static gboolean
bus_message(GstBus * bus,GstMessage * msg,gpointer user_data)705 bus_message (GstBus * bus, GstMessage * msg, gpointer user_data)
706 {
707   MultipleHttpRequestsContext *context =
708       (MultipleHttpRequestsContext *) user_data;
709   gchar *debug;
710   GError *err;
711   GstState newstate;
712   GstState pending;
713   const GstStructure *details;
714 
715   GST_TRACE ("Message: %" GST_PTR_FORMAT, msg);
716   switch (GST_MESSAGE_TYPE (msg)) {
717     case GST_MESSAGE_STATE_CHANGED:
718       gst_message_parse_state_changed (msg, NULL, &newstate, &pending);
719       if (newstate == GST_STATE_PLAYING && pending == GST_STATE_VOID_PENDING &&
720           GST_MESSAGE_SRC (msg) == GST_OBJECT (context->pipe)) {
721         GST_DEBUG ("Test ready to start");
722         start_next_download (context->downloader1);
723         if (context->downloader2)
724           start_next_download (context->downloader2);
725       } else if (newstate == GST_STATE_READY
726           && pending == GST_STATE_VOID_PENDING) {
727         if (GST_MESSAGE_SRC (msg) == GST_OBJECT (context->downloader1->bin)) {
728           if (++context->downloader1->count < 20) {
729             start_next_download (context->downloader1);
730           }
731         } else if (context->downloader2 && GST_MESSAGE_SRC (msg) ==
732             GST_OBJECT (context->downloader2->bin)) {
733           if (++context->downloader2->count < 20) {
734             start_next_download (context->downloader2);
735           }
736         }
737         if (context->downloader1->count == 20 &&
738             (context->downloader2 == NULL
739                 || context->downloader2->count == 20)) {
740           g_main_loop_quit (context->loop);
741         }
742       }
743       break;
744     case GST_MESSAGE_ERROR:
745       debug = NULL;
746       err = NULL;
747       details = NULL;
748       gst_message_parse_error (msg, &err, &debug);
749       gst_message_parse_error_details (msg, &details);
750       GST_DEBUG ("err->debug: %s", debug);
751       GST_DEBUG ("err->message: \"%s\"", err->message);
752       GST_DEBUG ("err->details: %" GST_PTR_FORMAT, details);
753       g_error_free (err);
754       g_free (debug);
755       context->failed = TRUE;
756       g_main_loop_quit (context->loop);
757       break;
758     case GST_MESSAGE_EOS:
759       if (context->downloader1->count == 20 &&
760           (context->downloader2 == NULL || context->downloader2->count == 20)) {
761         g_main_loop_quit (context->loop);
762       }
763       break;
764     default:
765       break;
766   }
767   return TRUE;
768 }
769 
770 /* test_multiple_http_requests tries to reproduce the way in which
771  * GstAdaptiveDemux makes use of URI source elements. GstAdaptiveDemux
772  * creates a bin with the httpsrc element and a queue element and sets the
773  * locked state of that bin to TRUE, so that it does not follow the state
774  * transitions of its parent. It then moves this bin to the PLAYING state
775  * to start each download and back to READY when the download completes.
776  */
GST_START_TEST(test_multiple_http_requests)777 GST_START_TEST (test_multiple_http_requests)
778 {
779   GstStateChangeReturn ret;
780   MultipleHttpRequestsContext context;
781   guint watch_id;
782   GstBus *bus;
783 
784   context.loop = g_main_loop_new (NULL, FALSE);
785   context.downloader1 =
786       test_curl_http_src_downloader_new ("bin1", 5 * G_USEC_PER_SEC / 1000);
787   fail_unless (context.downloader1 != NULL);
788   context.downloader2 =
789       test_curl_http_src_downloader_new ("bin2", 7 * G_USEC_PER_SEC / 1000);
790   fail_unless (context.downloader2 != NULL);
791 
792   context.pipe = gst_pipeline_new (NULL);
793   fail_unless (context.pipe != NULL);
794 
795   gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader1->bin);
796   gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader2->bin);
797 
798   bus = gst_pipeline_get_bus (GST_PIPELINE (context.pipe));
799   watch_id = gst_bus_add_watch (bus, bus_message, &context);
800   gst_object_unref (bus);
801 
802   GST_DEBUG ("Start pipeline playing");
803   ret = gst_element_set_state (context.pipe, GST_STATE_PLAYING);
804   fail_unless (ret == GST_STATE_CHANGE_ASYNC
805       || ret == GST_STATE_CHANGE_SUCCESS);
806 
807   g_main_loop_run (context.loop);
808   g_source_remove (watch_id);
809   test_curl_http_src_downloader_free (context.downloader1);
810   test_curl_http_src_downloader_free (context.downloader2);
811   gst_element_set_state (context.pipe, GST_STATE_NULL);
812   gst_object_unref (context.pipe);
813   g_main_loop_unref (context.loop);
814 }
815 
816 GST_END_TEST;
817 
818 typedef struct _DataProbeResult
819 {
820   guint64 received;
821 } DataProbeResult;
822 
823 static GstPadProbeReturn
src_data_probe(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)824 src_data_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
825 {
826   DataProbeResult *dpr = (DataProbeResult *) user_data;
827   GstBuffer *buf;
828 
829   if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
830     buf = GST_PAD_PROBE_INFO_BUFFER (info);
831     dpr->received += gst_buffer_get_size (buf);
832   }
833 
834   return GST_PAD_PROBE_OK;
835 }
836 
GST_START_TEST(test_range_get)837 GST_START_TEST (test_range_get)
838 {
839   GstStateChangeReturn ret;
840   MultipleHttpRequestsContext context;
841   guint watch_id;
842   GstBus *bus;
843   GstPad *src_pad;
844   gulong probe_id;
845   DataProbeResult dpr;
846 
847   context.loop = g_main_loop_new (NULL, FALSE);
848   context.downloader1 =
849       test_curl_http_src_downloader_new ("bin1", 5 * G_USEC_PER_SEC / 1000);
850   fail_unless (context.downloader1 != NULL);
851   context.downloader1->start_position = 128;
852   context.downloader1->stop_position = 255;
853   src_pad = gst_element_get_static_pad (context.downloader1->src, "src");
854   fail_unless (src_pad != NULL);
855   dpr.received = 0;
856   probe_id = gst_pad_add_probe (src_pad, GST_PAD_PROBE_TYPE_BUFFER,
857       src_data_probe, &dpr, NULL);
858   fail_unless (probe_id > 0);
859   context.downloader2 = NULL;
860 
861   context.pipe = gst_pipeline_new (NULL);
862   fail_unless (context.pipe != NULL);
863 
864   gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader1->bin);
865 
866   bus = gst_pipeline_get_bus (GST_PIPELINE (context.pipe));
867   watch_id = gst_bus_add_watch (bus, bus_message, &context);
868   gst_object_unref (bus);
869 
870   GST_DEBUG ("Start pipeline playing");
871   ret = gst_element_set_state (context.pipe, GST_STATE_PLAYING);
872   fail_unless (ret == GST_STATE_CHANGE_ASYNC
873       || ret == GST_STATE_CHANGE_SUCCESS);
874 
875   g_main_loop_run (context.loop);
876   fail_unless_equals_uint64 (dpr.received,
877       1 + context.downloader1->stop_position -
878       context.downloader1->start_position);
879   g_source_remove (watch_id);
880   gst_pad_remove_probe (src_pad, probe_id);
881   gst_object_unref (src_pad);
882   test_curl_http_src_downloader_free (context.downloader1);
883   gst_element_set_state (context.pipe, GST_STATE_NULL);
884   gst_object_unref (context.pipe);
885   g_main_loop_unref (context.loop);
886 }
887 
888 GST_END_TEST;
889 
890 static Suite *
curlhttpsrc_suite(void)891 curlhttpsrc_suite (void)
892 {
893   TCase *tc_chain;
894   Suite *s;
895 
896   /* we don't support exceptions from the proxy, so just unset the environment
897    * variable - in case it's set in the test environment it would otherwise
898    * prevent us from connecting to localhost (like jenkins.qa.ubuntu.com) */
899   g_unsetenv ("http_proxy");
900 
901   s = suite_create ("curlhttpsrc");
902   tc_chain = tcase_create ("general");
903 
904   suite_add_tcase (s, tc_chain);
905   tcase_add_test (tc_chain, test_first_buffer_has_offset);
906   tcase_add_test (tc_chain, test_redirect_yes);
907   tcase_add_test (tc_chain, test_redirect_no);
908   tcase_add_test (tc_chain, test_not_found);
909   tcase_add_test (tc_chain, test_not_found_with_data);
910   tcase_add_test (tc_chain, test_forbidden);
911   tcase_add_test (tc_chain, test_cookies);
912   tcase_add_test (tc_chain, test_multiple_http_requests);
913   tcase_add_test (tc_chain, test_range_get);
914 
915   return s;
916 }
917 
918 GST_CHECK_MAIN (curlhttpsrc);
919