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