1 /* GStreamer
2 * Copyright (C) <2005> Edgard Lima <edgard.lima@gmail.com>
3 * Copyright (C) <2006> Rosfran Borges <rosfran.borges@indt.org.br>
4 * Copyright (C) <2006> Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>
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
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include "gstneonhttpsrc.h"
22 #include <stdlib.h>
23 #include <string.h>
24 #ifdef _HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif /* _HAVE_UNISTD_H */
27
28 #include <ne_redirect.h>
29
30 #define STATUS_IS_REDIRECTION(status) ((status) >= 300 && (status) < 400)
31
32 GST_DEBUG_CATEGORY_STATIC (neonhttpsrc_debug);
33 #define GST_CAT_DEFAULT neonhttpsrc_debug
34
35 #define MAX_READ_SIZE (4 * 1024)
36
37 /* max number of HTTP redirects, when iterating over a sequence of HTTP 3xx status code */
38 #define MAX_HTTP_REDIRECTS_NUMBER 5
39
40 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
41 GST_PAD_SRC,
42 GST_PAD_ALWAYS,
43 GST_STATIC_CAPS_ANY);
44
45 #define HTTP_SOCKET_ERROR -2
46 #define HTTP_REQUEST_WRONG_PROXY -1
47 #define HTTP_DEFAULT_PORT 80
48 #define HTTPS_DEFAULT_PORT 443
49 #define HTTP_DEFAULT_HOST "localhost"
50
51 /* default properties */
52 #define DEFAULT_LOCATION "http://" HTTP_DEFAULT_HOST ":" G_STRINGIFY(HTTP_DEFAULT_PORT)
53 #define DEFAULT_PROXY ""
54 #define DEFAULT_USER_AGENT "GStreamer neonhttpsrc"
55 #define DEFAULT_AUTOMATIC_REDIRECT TRUE
56 #define DEFAULT_ACCEPT_SELF_SIGNED FALSE
57 #define DEFAULT_NEON_HTTP_DEBUG FALSE
58 #define DEFAULT_CONNECT_TIMEOUT 0
59 #define DEFAULT_READ_TIMEOUT 0
60 #define DEFAULT_IRADIO_MODE TRUE
61
62 enum
63 {
64 PROP_0,
65 PROP_LOCATION,
66 PROP_PROXY,
67 PROP_USER_AGENT,
68 PROP_COOKIES,
69 PROP_AUTOMATIC_REDIRECT,
70 PROP_ACCEPT_SELF_SIGNED,
71 PROP_CONNECT_TIMEOUT,
72 PROP_READ_TIMEOUT,
73 #ifndef GST_DISABLE_GST_DEBUG
74 PROP_NEON_HTTP_DEBUG,
75 #endif
76 PROP_IRADIO_MODE
77 };
78
79 static void gst_neonhttp_src_uri_handler_init (gpointer g_iface,
80 gpointer iface_data);
81 static void gst_neonhttp_src_dispose (GObject * gobject);
82 static void gst_neonhttp_src_set_property (GObject * object, guint prop_id,
83 const GValue * value, GParamSpec * pspec);
84 static void gst_neonhttp_src_get_property (GObject * object, guint prop_id,
85 GValue * value, GParamSpec * pspec);
86
87 static GstFlowReturn gst_neonhttp_src_fill (GstPushSrc * psrc,
88 GstBuffer * outbuf);
89 static gboolean gst_neonhttp_src_start (GstBaseSrc * bsrc);
90 static gboolean gst_neonhttp_src_stop (GstBaseSrc * bsrc);
91 static gboolean gst_neonhttp_src_get_size (GstBaseSrc * bsrc, guint64 * size);
92 static gboolean gst_neonhttp_src_is_seekable (GstBaseSrc * bsrc);
93 static gboolean gst_neonhttp_src_do_seek (GstBaseSrc * bsrc,
94 GstSegment * segment);
95 static gboolean gst_neonhttp_src_query (GstBaseSrc * bsrc, GstQuery * query);
96
97 static gboolean gst_neonhttp_src_set_proxy (GstNeonhttpSrc * src,
98 const gchar * uri);
99 static gboolean gst_neonhttp_src_set_location (GstNeonhttpSrc * src,
100 const gchar * uri, GError ** err);
101 static gint gst_neonhttp_src_send_request_and_redirect (GstNeonhttpSrc * src,
102 ne_session ** ses, ne_request ** req, gint64 offset, gboolean do_redir);
103 static gint gst_neonhttp_src_request_dispatch (GstNeonhttpSrc * src,
104 GstBuffer * outbuf);
105 static void gst_neonhttp_src_close_session (GstNeonhttpSrc * src);
106 static gchar *gst_neonhttp_src_unicodify (const gchar * str);
107 static void oom_callback (void);
108
109 #define parent_class gst_neonhttp_src_parent_class
110 G_DEFINE_TYPE_WITH_CODE (GstNeonhttpSrc, gst_neonhttp_src, GST_TYPE_PUSH_SRC,
111 G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
112 gst_neonhttp_src_uri_handler_init);
113 GST_DEBUG_CATEGORY_INIT (neonhttpsrc_debug, "neonhttpsrc", 0,
114 "NEON HTTP src");
115 );
116 GST_ELEMENT_REGISTER_DEFINE (neonhttpsrc, "neonhttpsrc", GST_RANK_NONE,
117 GST_TYPE_NEONHTTP_SRC);
118
119 static void
gst_neonhttp_src_class_init(GstNeonhttpSrcClass * klass)120 gst_neonhttp_src_class_init (GstNeonhttpSrcClass * klass)
121 {
122 GObjectClass *gobject_class;
123 GstElementClass *element_class;
124 GstBaseSrcClass *gstbasesrc_class;
125 GstPushSrcClass *gstpushsrc_class;
126
127 gobject_class = (GObjectClass *) klass;
128 element_class = (GstElementClass *) klass;
129 gstbasesrc_class = (GstBaseSrcClass *) klass;
130 gstpushsrc_class = (GstPushSrcClass *) klass;
131
132 gobject_class->set_property = gst_neonhttp_src_set_property;
133 gobject_class->get_property = gst_neonhttp_src_get_property;
134 gobject_class->dispose = gst_neonhttp_src_dispose;
135
136 g_object_class_install_property
137 (gobject_class, PROP_LOCATION,
138 g_param_spec_string ("location", "Location",
139 "Location to read from", "",
140 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
141
142 g_object_class_install_property
143 (gobject_class, PROP_PROXY,
144 g_param_spec_string ("proxy", "Proxy",
145 "Proxy server to use, in the form HOSTNAME:PORT. "
146 "Defaults to the http_proxy environment variable",
147 "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
148
149 g_object_class_install_property
150 (gobject_class, PROP_USER_AGENT,
151 g_param_spec_string ("user-agent", "User-Agent",
152 "Value of the User-Agent HTTP request header field",
153 "GStreamer neonhttpsrc", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154
155 g_object_class_install_property (gobject_class, PROP_COOKIES,
156 g_param_spec_boxed ("cookies", "Cookies", "HTTP request cookies",
157 G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
158
159 g_object_class_install_property
160 (gobject_class, PROP_AUTOMATIC_REDIRECT,
161 g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
162 "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
163 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
164
165 g_object_class_install_property
166 (gobject_class, PROP_ACCEPT_SELF_SIGNED,
167 g_param_spec_boolean ("accept-self-signed", "accept-self-signed",
168 "Accept self-signed SSL/TLS certificates",
169 DEFAULT_ACCEPT_SELF_SIGNED,
170 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
171
172 g_object_class_install_property (gobject_class, PROP_CONNECT_TIMEOUT,
173 g_param_spec_uint ("connect-timeout", "connect-timeout",
174 "Value in seconds to timeout a blocking connection (0 = default).", 0,
175 3600, DEFAULT_CONNECT_TIMEOUT,
176 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
177
178 g_object_class_install_property (gobject_class, PROP_READ_TIMEOUT,
179 g_param_spec_uint ("read-timeout", "read-timeout",
180 "Value in seconds to timeout a blocking read (0 = default).", 0,
181 3600, DEFAULT_READ_TIMEOUT,
182 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
183
184 #ifndef GST_DISABLE_GST_DEBUG
185 g_object_class_install_property
186 (gobject_class, PROP_NEON_HTTP_DEBUG,
187 g_param_spec_boolean ("neon-http-debug", "neon-http-debug",
188 "Enable Neon HTTP debug messages",
189 DEFAULT_NEON_HTTP_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
190 #endif
191
192 g_object_class_install_property (gobject_class, PROP_IRADIO_MODE,
193 g_param_spec_boolean ("iradio-mode", "iradio-mode",
194 "Enable internet radio mode (ask server to send shoutcast/icecast "
195 "metadata interleaved with the actual stream data)",
196 DEFAULT_IRADIO_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
197
198 gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_neonhttp_src_start);
199 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_neonhttp_src_stop);
200 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_neonhttp_src_get_size);
201 gstbasesrc_class->is_seekable =
202 GST_DEBUG_FUNCPTR (gst_neonhttp_src_is_seekable);
203 gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_neonhttp_src_do_seek);
204 gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_neonhttp_src_query);
205
206 gstpushsrc_class->fill = GST_DEBUG_FUNCPTR (gst_neonhttp_src_fill);
207
208 GST_DEBUG_CATEGORY_INIT (neonhttpsrc_debug, "neonhttpsrc", 0,
209 "NEON HTTP Client Source");
210
211 gst_element_class_add_static_pad_template (element_class, &srctemplate);
212
213 gst_element_class_set_static_metadata (element_class, "HTTP client source",
214 "Source/Network",
215 "Receive data as a client over the network via HTTP using NEON",
216 "Edgard Lima <edgard.lima@gmail.com>, "
217 "Rosfran Borges <rosfran.borges@indt.org.br>, "
218 "Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>");
219 }
220
221 static void
gst_neonhttp_src_init(GstNeonhttpSrc * src)222 gst_neonhttp_src_init (GstNeonhttpSrc * src)
223 {
224 const gchar *str;
225
226 src->neon_http_debug = DEFAULT_NEON_HTTP_DEBUG;
227 src->user_agent = g_strdup (DEFAULT_USER_AGENT);
228 src->automatic_redirect = DEFAULT_AUTOMATIC_REDIRECT;
229 src->accept_self_signed = DEFAULT_ACCEPT_SELF_SIGNED;
230 src->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
231 src->read_timeout = DEFAULT_READ_TIMEOUT;
232 src->iradio_mode = DEFAULT_IRADIO_MODE;
233
234 src->cookies = NULL;
235 src->session = NULL;
236 src->request = NULL;
237 memset (&src->uri, 0, sizeof (src->uri));
238 memset (&src->proxy, 0, sizeof (src->proxy));
239 src->content_size = -1;
240 src->seekable = TRUE;
241
242 gst_neonhttp_src_set_location (src, DEFAULT_LOCATION, NULL);
243
244 /* configure proxy */
245 str = g_getenv ("http_proxy");
246 if (str && !gst_neonhttp_src_set_proxy (src, str)) {
247 GST_WARNING_OBJECT (src,
248 "The proxy set on http_proxy env var ('%s') cannot be parsed.", str);
249 }
250 }
251
252 static void
gst_neonhttp_src_dispose(GObject * gobject)253 gst_neonhttp_src_dispose (GObject * gobject)
254 {
255 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (gobject);
256
257 ne_uri_free (&src->uri);
258 ne_uri_free (&src->proxy);
259
260 g_free (src->user_agent);
261
262 if (src->cookies) {
263 g_strfreev (src->cookies);
264 src->cookies = NULL;
265 }
266
267 if (src->request) {
268 ne_request_destroy (src->request);
269 src->request = NULL;
270 }
271
272 if (src->session) {
273 ne_close_connection (src->session);
274 ne_session_destroy (src->session);
275 src->session = NULL;
276 }
277
278 if (src->location) {
279 ne_free (src->location);
280 }
281 if (src->query_string) {
282 ne_free (src->query_string);
283 }
284
285 G_OBJECT_CLASS (parent_class)->dispose (gobject);
286 }
287
288 static void
gst_neonhttp_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)289 gst_neonhttp_src_set_property (GObject * object, guint prop_id,
290 const GValue * value, GParamSpec * pspec)
291 {
292 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (object);
293
294 switch (prop_id) {
295 case PROP_PROXY:
296 {
297 const gchar *proxy;
298
299 proxy = g_value_get_string (value);
300
301 if (proxy == NULL) {
302 GST_WARNING ("proxy property cannot be NULL");
303 goto done;
304 }
305 if (!gst_neonhttp_src_set_proxy (src, proxy)) {
306 GST_WARNING ("badly formatted proxy");
307 goto done;
308 }
309 break;
310 }
311 case PROP_LOCATION:
312 {
313 const gchar *location;
314
315 location = g_value_get_string (value);
316
317 if (location == NULL) {
318 GST_WARNING ("location property cannot be NULL");
319 goto done;
320 }
321 if (!gst_neonhttp_src_set_location (src, location, NULL)) {
322 GST_WARNING ("badly formatted location");
323 goto done;
324 }
325 break;
326 }
327 case PROP_USER_AGENT:
328 g_free (src->user_agent);
329 src->user_agent = g_value_dup_string (value);
330 break;
331 case PROP_COOKIES:
332 if (src->cookies)
333 g_strfreev (src->cookies);
334 src->cookies = (gchar **) g_value_dup_boxed (value);
335 break;
336 case PROP_AUTOMATIC_REDIRECT:
337 src->automatic_redirect = g_value_get_boolean (value);
338 break;
339 case PROP_ACCEPT_SELF_SIGNED:
340 src->accept_self_signed = g_value_get_boolean (value);
341 break;
342 case PROP_CONNECT_TIMEOUT:
343 src->connect_timeout = g_value_get_uint (value);
344 break;
345 case PROP_READ_TIMEOUT:
346 src->read_timeout = g_value_get_uint (value);
347 break;
348 #ifndef GST_DISABLE_GST_DEBUG
349 case PROP_NEON_HTTP_DEBUG:
350 src->neon_http_debug = g_value_get_boolean (value);
351 break;
352 #endif
353 case PROP_IRADIO_MODE:
354 src->iradio_mode = g_value_get_boolean (value);
355 break;
356 default:
357 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
358 break;
359 }
360 done:
361 return;
362 }
363
364 static void
gst_neonhttp_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)365 gst_neonhttp_src_get_property (GObject * object, guint prop_id,
366 GValue * value, GParamSpec * pspec)
367 {
368 GstNeonhttpSrc *neonhttpsrc = GST_NEONHTTP_SRC (object);
369
370 switch (prop_id) {
371 case PROP_PROXY:
372 {
373 gchar *str;
374
375 if (neonhttpsrc->proxy.host) {
376 str = ne_uri_unparse (&neonhttpsrc->proxy);
377 if (!str)
378 break;
379 g_value_set_string (value, str);
380 ne_free (str);
381 } else {
382 g_value_set_static_string (value, "");
383 }
384 break;
385 }
386 case PROP_LOCATION:
387 {
388 gchar *str;
389
390 if (neonhttpsrc->uri.host) {
391 str = ne_uri_unparse (&neonhttpsrc->uri);
392 if (!str)
393 break;
394 g_value_set_string (value, str);
395 ne_free (str);
396 } else {
397 g_value_set_static_string (value, "");
398 }
399 break;
400 }
401 case PROP_USER_AGENT:
402 g_value_set_string (value, neonhttpsrc->user_agent);
403 break;
404 case PROP_COOKIES:
405 g_value_set_boxed (value, neonhttpsrc->cookies);
406 break;
407 case PROP_AUTOMATIC_REDIRECT:
408 g_value_set_boolean (value, neonhttpsrc->automatic_redirect);
409 break;
410 case PROP_ACCEPT_SELF_SIGNED:
411 g_value_set_boolean (value, neonhttpsrc->accept_self_signed);
412 break;
413 case PROP_CONNECT_TIMEOUT:
414 g_value_set_uint (value, neonhttpsrc->connect_timeout);
415 break;
416 case PROP_READ_TIMEOUT:
417 g_value_set_uint (value, neonhttpsrc->read_timeout);
418 break;
419 #ifndef GST_DISABLE_GST_DEBUG
420 case PROP_NEON_HTTP_DEBUG:
421 g_value_set_boolean (value, neonhttpsrc->neon_http_debug);
422 break;
423 #endif
424 case PROP_IRADIO_MODE:
425 g_value_set_boolean (value, neonhttpsrc->iradio_mode);
426 break;
427 default:
428 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429 break;
430 }
431 }
432
433 /* NEON CALLBACK */
434 static void
oom_callback(void)435 oom_callback (void)
436 {
437 GST_ERROR ("memory exception in neon");
438 }
439
440 static GstFlowReturn
gst_neonhttp_src_fill(GstPushSrc * psrc,GstBuffer * outbuf)441 gst_neonhttp_src_fill (GstPushSrc * psrc, GstBuffer * outbuf)
442 {
443 GstNeonhttpSrc *src;
444 gint read;
445
446 src = GST_NEONHTTP_SRC (psrc);
447
448 /* The caller should know the number of bytes and not read beyond EOS. */
449 if (G_UNLIKELY (src->eos))
450 goto eos;
451
452 read = gst_neonhttp_src_request_dispatch (src, outbuf);
453 if (G_UNLIKELY (read < 0))
454 goto read_error;
455
456 GST_LOG_OBJECT (src, "returning %" G_GSIZE_FORMAT " bytes, "
457 "offset %" G_GUINT64_FORMAT, gst_buffer_get_size (outbuf),
458 GST_BUFFER_OFFSET (outbuf));
459
460 return GST_FLOW_OK;
461
462 /* ERRORS */
463 eos:
464 {
465 GST_DEBUG_OBJECT (src, "EOS reached");
466 return GST_FLOW_EOS;
467 }
468 read_error:
469 {
470 GST_ELEMENT_ERROR (src, RESOURCE, READ,
471 (NULL), ("Could not read any bytes (%i, %s)", read,
472 ne_get_error (src->session)));
473 return GST_FLOW_ERROR;
474 }
475 }
476
477 /* create a socket for connecting to remote server */
478 static gboolean
gst_neonhttp_src_start(GstBaseSrc * bsrc)479 gst_neonhttp_src_start (GstBaseSrc * bsrc)
480 {
481 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
482 const gchar *content_length;
483 gint res;
484
485 #ifndef GST_DISABLE_GST_DEBUG
486 if (src->neon_http_debug)
487 ne_debug_init (stderr, NE_DBG_HTTP);
488 #endif
489
490 ne_oom_callback (oom_callback);
491
492 res = ne_sock_init ();
493 if (res != 0)
494 goto init_failed;
495
496 res = gst_neonhttp_src_send_request_and_redirect (src,
497 &src->session, &src->request, 0, src->automatic_redirect);
498
499 if (res != NE_OK || !src->session) {
500 if (res == HTTP_SOCKET_ERROR) {
501 goto socket_error;
502 } else if (res == HTTP_REQUEST_WRONG_PROXY) {
503 goto wrong_proxy;
504 } else {
505 goto begin_req_failed;
506 }
507 }
508
509 content_length = ne_get_response_header (src->request, "Content-Length");
510
511 if (content_length)
512 src->content_size = g_ascii_strtoull (content_length, NULL, 10);
513 else
514 src->content_size = -1;
515
516 if (TRUE) {
517 /* Icecast stuff */
518 const gchar *str_value;
519 GstTagList *tags;
520 gchar *iradio_name;
521 gchar *iradio_url;
522 gchar *iradio_genre;
523 gint icy_metaint;
524
525 tags = gst_tag_list_new_empty ();
526
527 str_value = ne_get_response_header (src->request, "icy-metaint");
528 if (str_value) {
529 if (sscanf (str_value, "%d", &icy_metaint) == 1) {
530 GstCaps *icy_caps;
531
532 icy_caps = gst_caps_new_simple ("application/x-icy",
533 "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
534 gst_base_src_set_caps (GST_BASE_SRC (src), icy_caps);
535 }
536 }
537
538 /* FIXME: send tags with name, genre, url */
539 str_value = ne_get_response_header (src->request, "icy-name");
540 if (str_value) {
541 iradio_name = gst_neonhttp_src_unicodify (str_value);
542 if (iradio_name) {
543 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
544 iradio_name, NULL);
545 g_free (iradio_name);
546 }
547 }
548 str_value = ne_get_response_header (src->request, "icy-genre");
549 if (str_value) {
550 iradio_genre = gst_neonhttp_src_unicodify (str_value);
551 if (iradio_genre) {
552 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
553 iradio_genre, NULL);
554 g_free (iradio_genre);
555 }
556 }
557 str_value = ne_get_response_header (src->request, "icy-url");
558 if (str_value) {
559 iradio_url = gst_neonhttp_src_unicodify (str_value);
560 if (iradio_url) {
561 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
562 iradio_url, NULL);
563 g_free (iradio_url);
564 }
565 }
566 if (!gst_tag_list_is_empty (tags)) {
567 GST_DEBUG_OBJECT (src, "pushing tag list %" GST_PTR_FORMAT, tags);
568 gst_pad_push_event (GST_BASE_SRC_PAD (src), gst_event_new_tag (tags));
569 } else {
570 gst_tag_list_unref (tags);
571 }
572 }
573
574 return TRUE;
575
576 /* ERRORS */
577 init_failed:
578 {
579 GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL),
580 ("ne_sock_init() failed: %d", res));
581 return FALSE;
582 }
583 socket_error:
584 {
585 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
586 ("HTTP Request failed when opening socket: %d", res));
587 return FALSE;
588 }
589 wrong_proxy:
590 {
591 GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL),
592 ("Proxy Server URI is invalid - make sure that either both proxy host "
593 "and port are specified or neither."));
594 return FALSE;
595 }
596 begin_req_failed:
597 {
598 GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
599 ("Could not begin request: %d", res));
600 return FALSE;
601 }
602 }
603
604 /* close the socket and associated resources
605 * used both to recover from errors and go to NULL state */
606 static gboolean
gst_neonhttp_src_stop(GstBaseSrc * bsrc)607 gst_neonhttp_src_stop (GstBaseSrc * bsrc)
608 {
609 GstNeonhttpSrc *src;
610
611 src = GST_NEONHTTP_SRC (bsrc);
612
613 src->eos = FALSE;
614 src->content_size = -1;
615 src->read_position = 0;
616 src->seekable = TRUE;
617
618 gst_neonhttp_src_close_session (src);
619
620 #ifndef GST_DISABLE_GST_DEBUG
621 ne_debug_init (NULL, 0);
622 #endif
623 ne_oom_callback (NULL);
624 ne_sock_exit ();
625
626 return TRUE;
627 }
628
629 static gboolean
gst_neonhttp_src_get_size(GstBaseSrc * bsrc,guint64 * size)630 gst_neonhttp_src_get_size (GstBaseSrc * bsrc, guint64 * size)
631 {
632 GstNeonhttpSrc *src;
633
634 src = GST_NEONHTTP_SRC (bsrc);
635
636 if (src->content_size == -1)
637 return FALSE;
638
639 *size = src->content_size;
640
641 return TRUE;
642 }
643
644 static gboolean
gst_neonhttp_src_is_seekable(GstBaseSrc * bsrc)645 gst_neonhttp_src_is_seekable (GstBaseSrc * bsrc)
646 {
647 return TRUE;
648 }
649
650 static gboolean
gst_neonhttp_src_do_seek(GstBaseSrc * bsrc,GstSegment * segment)651 gst_neonhttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
652 {
653 GstNeonhttpSrc *src;
654 gint res;
655 ne_session *session = NULL;
656 ne_request *request = NULL;
657
658 src = GST_NEONHTTP_SRC (bsrc);
659
660 if (!src->seekable)
661 return FALSE;
662
663 if (src->read_position == segment->start)
664 return TRUE;
665
666 res = gst_neonhttp_src_send_request_and_redirect (src,
667 &session, &request, segment->start, src->automatic_redirect);
668
669 /* if we are able to seek, replace the session */
670 if (res == NE_OK && session) {
671 gst_neonhttp_src_close_session (src);
672 src->session = session;
673 src->request = request;
674 src->read_position = segment->start;
675 return TRUE;
676 }
677
678 return FALSE;
679 }
680
681 static gboolean
gst_neonhttp_src_query(GstBaseSrc * bsrc,GstQuery * query)682 gst_neonhttp_src_query (GstBaseSrc * bsrc, GstQuery * query)
683 {
684 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
685 gboolean ret;
686
687 switch (GST_QUERY_TYPE (query)) {
688 case GST_QUERY_URI:
689 gst_query_set_uri (query, src->location);
690 ret = TRUE;
691 break;
692 default:
693 ret = FALSE;
694 break;
695 }
696
697 if (!ret)
698 ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
699
700 switch (GST_QUERY_TYPE (query)) {
701 case GST_QUERY_SCHEDULING:{
702 GstSchedulingFlags flags;
703 gint minsize, maxsize, align;
704
705 gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
706 flags |= GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
707 gst_query_set_scheduling (query, flags, minsize, maxsize, align);
708 break;
709 }
710 default:
711 break;
712 }
713
714 return ret;
715 }
716
717 static gboolean
gst_neonhttp_src_set_location(GstNeonhttpSrc * src,const gchar * uri,GError ** err)718 gst_neonhttp_src_set_location (GstNeonhttpSrc * src, const gchar * uri,
719 GError ** err)
720 {
721 ne_uri_free (&src->uri);
722 if (src->location) {
723 ne_free (src->location);
724 src->location = NULL;
725 }
726 if (src->query_string) {
727 ne_free (src->query_string);
728 src->query_string = NULL;
729 }
730
731 if (ne_uri_parse (uri, &src->uri) != 0)
732 goto parse_error;
733
734 if (src->uri.scheme == NULL)
735 src->uri.scheme = g_strdup ("http");
736
737 if (src->uri.host == NULL)
738 src->uri.host = g_strdup (DEFAULT_LOCATION);
739
740 if (src->uri.port == 0) {
741 if (!strcmp (src->uri.scheme, "https"))
742 src->uri.port = HTTPS_DEFAULT_PORT;
743 else
744 src->uri.port = HTTP_DEFAULT_PORT;
745 }
746
747 if (!src->uri.path)
748 src->uri.path = g_strdup ("");
749
750 src->query_string = g_strjoin ("?", src->uri.path, src->uri.query, NULL);
751
752 src->location = ne_uri_unparse (&src->uri);
753
754 return TRUE;
755
756 /* ERRORS */
757 parse_error:
758 {
759 if (src->location) {
760 ne_free (src->location);
761 src->location = NULL;
762 }
763 if (src->query_string) {
764 ne_free (src->query_string);
765 src->query_string = NULL;
766 }
767 ne_uri_free (&src->uri);
768 return FALSE;
769 }
770 }
771
772 static gboolean
gst_neonhttp_src_set_proxy(GstNeonhttpSrc * src,const char * uri)773 gst_neonhttp_src_set_proxy (GstNeonhttpSrc * src, const char *uri)
774 {
775 ne_uri_free (&src->proxy);
776
777 if (ne_uri_parse (uri, &src->proxy) != 0)
778 goto error;
779
780 if (src->proxy.scheme)
781 GST_WARNING ("The proxy schema shouldn't be defined (schema is '%s')",
782 src->proxy.scheme);
783
784 if (src->proxy.host && !src->proxy.port)
785 goto error;
786
787 if (!src->proxy.path || src->proxy.userinfo)
788 goto error;
789 return TRUE;
790
791 /* ERRORS */
792 error:
793 {
794 ne_uri_free (&src->proxy);
795 return FALSE;
796 }
797 }
798
799 static int
ssl_verify_callback(void * data,int failures,const ne_ssl_certificate * cert)800 ssl_verify_callback (void *data, int failures, const ne_ssl_certificate * cert)
801 {
802 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (data);
803
804 if ((failures & NE_SSL_UNTRUSTED) &&
805 src->accept_self_signed && !ne_ssl_cert_signedby (cert)) {
806 GST_ELEMENT_INFO (src, RESOURCE, READ,
807 (NULL), ("Accepting self-signed server certificate"));
808
809 failures &= ~NE_SSL_UNTRUSTED;
810 }
811
812 if (failures & NE_SSL_NOTYETVALID)
813 GST_ELEMENT_ERROR (src, RESOURCE, READ,
814 (NULL), ("Server certificate not valid yet"));
815 if (failures & NE_SSL_EXPIRED)
816 GST_ELEMENT_ERROR (src, RESOURCE, READ,
817 (NULL), ("Server certificate has expired"));
818 if (failures & NE_SSL_IDMISMATCH)
819 GST_ELEMENT_ERROR (src, RESOURCE, READ,
820 (NULL), ("Server certificate doesn't match hostname"));
821 if (failures & NE_SSL_UNTRUSTED)
822 GST_ELEMENT_ERROR (src, RESOURCE, READ,
823 (NULL), ("Server certificate signer not trusted"));
824
825 GST_DEBUG_OBJECT (src, "failures: %d", failures);
826
827 return failures;
828 }
829
830 /* Try to send the HTTP request to the Icecast server, and if possible deals with
831 * all the probable redirections (HTTP status code == 3xx)
832 */
833 static gint
gst_neonhttp_src_send_request_and_redirect(GstNeonhttpSrc * src,ne_session ** ses,ne_request ** req,gint64 offset,gboolean do_redir)834 gst_neonhttp_src_send_request_and_redirect (GstNeonhttpSrc * src,
835 ne_session ** ses, ne_request ** req, gint64 offset, gboolean do_redir)
836 {
837 ne_session *session = NULL;
838 ne_request *request = NULL;
839 gchar **c;
840 gint res;
841 gint http_status = 0;
842 guint request_count = 0;
843
844 do {
845 if (src->proxy.host && src->proxy.port) {
846 session =
847 ne_session_create (src->uri.scheme, src->uri.host, src->uri.port);
848 ne_session_proxy (session, src->proxy.host, src->proxy.port);
849 } else if (src->proxy.host || src->proxy.port) {
850 /* both proxy host and port must be specified or none */
851 return HTTP_REQUEST_WRONG_PROXY;
852 } else {
853 session =
854 ne_session_create (src->uri.scheme, src->uri.host, src->uri.port);
855 }
856
857 if (src->connect_timeout > 0) {
858 ne_set_connect_timeout (session, src->connect_timeout);
859 }
860
861 if (src->read_timeout > 0) {
862 ne_set_read_timeout (session, src->read_timeout);
863 }
864
865 ne_set_session_flag (session, NE_SESSFLAG_ICYPROTO, 1);
866 ne_ssl_set_verify (session, ssl_verify_callback, src);
867
868 request = ne_request_create (session, "GET", src->query_string);
869
870 if (src->user_agent) {
871 ne_add_request_header (request, "User-Agent", src->user_agent);
872 }
873
874 for (c = src->cookies; c != NULL && *c != NULL; ++c) {
875 GST_INFO ("Adding header Cookie : %s", *c);
876 ne_add_request_header (request, "Cookies", *c);
877 }
878
879 if (src->iradio_mode)
880 ne_add_request_header (request, "icy-metadata", "1");
881
882 if (offset > 0) {
883 ne_print_request_header (request, "Range",
884 "bytes=%" G_GINT64_FORMAT "-", offset);
885 }
886
887 res = ne_begin_request (request);
888
889 if (res == NE_OK) {
890 /* When the HTTP status code is 3xx, it is not the SHOUTcast streaming content yet;
891 * Reload the HTTP request with a new URI value */
892 http_status = ne_get_status (request)->code;
893 if (STATUS_IS_REDIRECTION (http_status) && do_redir) {
894 const gchar *redir;
895
896 /* the new URI value to go when redirecting can be found on the 'Location' HTTP header */
897 redir = ne_get_response_header (request, "Location");
898 if (redir != NULL) {
899 ne_uri_free (&src->uri);
900 gst_neonhttp_src_set_location (src, redir, NULL);
901 GST_LOG_OBJECT (src, "Got HTTP Status Code %d", http_status);
902 GST_LOG_OBJECT (src, "Using 'Location' header [%s]", src->uri.host);
903 }
904 }
905 }
906
907 if ((res != NE_OK) ||
908 (offset == 0 && http_status != 200) ||
909 (offset > 0 && http_status != 206 &&
910 !STATUS_IS_REDIRECTION (http_status))) {
911 ne_request_destroy (request);
912 request = NULL;
913 ne_close_connection (session);
914 ne_session_destroy (session);
915 session = NULL;
916 if (offset > 0 && http_status != 206 &&
917 !STATUS_IS_REDIRECTION (http_status)) {
918 src->seekable = FALSE;
919 }
920 }
921
922 /* if - NE_OK */
923 if (STATUS_IS_REDIRECTION (http_status) && do_redir) {
924 ++request_count;
925 GST_LOG_OBJECT (src, "redirect request_count is now %d", request_count);
926 if (request_count < MAX_HTTP_REDIRECTS_NUMBER && do_redir) {
927 GST_INFO_OBJECT (src, "Redirecting to %s", src->uri.host);
928 } else {
929 GST_WARNING_OBJECT (src, "Will not redirect, try again with a "
930 "different URI or redirect location %s", src->uri.host);
931 }
932 /* FIXME: when not redirecting automatically, shouldn't we post a
933 * redirect element message on the bus? */
934 }
935 /* do the redirect, go back to send another HTTP request now using the 'Location' */
936 } while (do_redir && (request_count < MAX_HTTP_REDIRECTS_NUMBER)
937 && STATUS_IS_REDIRECTION (http_status));
938
939 if (session) {
940 *ses = session;
941 *req = request;
942 }
943
944 return res;
945 }
946
947 static gint
gst_neonhttp_src_request_dispatch(GstNeonhttpSrc * src,GstBuffer * outbuf)948 gst_neonhttp_src_request_dispatch (GstNeonhttpSrc * src, GstBuffer * outbuf)
949 {
950 GstMapInfo map = GST_MAP_INFO_INIT;
951 gint ret;
952 gint read = 0;
953 gint sizetoread;
954
955 /* Loop sending the request:
956 * Retry whilst authentication fails and we supply it. */
957
958 ssize_t len = 0;
959
960 if (!gst_buffer_map (outbuf, &map, GST_MAP_WRITE))
961 return -1;
962
963 sizetoread = map.size;
964
965 while (sizetoread > 0) {
966 len = ne_read_response_block (src->request, (gchar *) map.data + read,
967 sizetoread);
968 if (len > 0) {
969 read += len;
970 sizetoread -= len;
971 } else {
972 break;
973 }
974
975 }
976
977 gst_buffer_set_size (outbuf, read);
978 GST_BUFFER_OFFSET (outbuf) = src->read_position;
979
980 if (len < 0) {
981 read = -2;
982 goto done;
983 } else if (len == 0) {
984 ret = ne_end_request (src->request);
985 if (ret != NE_RETRY) {
986 if (ret == NE_OK) {
987 src->eos = TRUE;
988 } else {
989 read = -3;
990 }
991 }
992 goto done;
993 }
994
995 if (read > 0)
996 src->read_position += read;
997
998 done:
999
1000 gst_buffer_unmap (outbuf, &map);
1001
1002 return read;
1003 }
1004
1005 static void
gst_neonhttp_src_close_session(GstNeonhttpSrc * src)1006 gst_neonhttp_src_close_session (GstNeonhttpSrc * src)
1007 {
1008 if (src->request) {
1009 ne_request_destroy (src->request);
1010 src->request = NULL;
1011 }
1012
1013 if (src->session) {
1014 ne_close_connection (src->session);
1015 ne_session_destroy (src->session);
1016 src->session = NULL;
1017 }
1018 }
1019
1020 /* The following two charset mangling functions were copied from gnomevfssrc.
1021 * Preserve them under the unverified assumption that they do something vaguely
1022 * worthwhile.
1023 */
1024 static gchar *
unicodify(const gchar * str,gint len,...)1025 unicodify (const gchar * str, gint len, ...)
1026 {
1027 gchar *ret = NULL, *cset;
1028 va_list args;
1029 gsize bytes_read, bytes_written;
1030
1031 if (g_utf8_validate (str, len, NULL))
1032 return g_strndup (str, len >= 0 ? len : strlen (str));
1033
1034 va_start (args, len);
1035 while ((cset = va_arg (args, gchar *)) != NULL) {
1036 if (!strcmp (cset, "locale"))
1037 ret = g_locale_to_utf8 (str, len, &bytes_read, &bytes_written, NULL);
1038 else
1039 ret = g_convert (str, len, "UTF-8", cset,
1040 &bytes_read, &bytes_written, NULL);
1041 if (ret)
1042 break;
1043 }
1044 va_end (args);
1045
1046 return ret;
1047 }
1048
1049 static gchar *
gst_neonhttp_src_unicodify(const gchar * str)1050 gst_neonhttp_src_unicodify (const gchar * str)
1051 {
1052 return unicodify (str, -1, "locale", "ISO-8859-1", NULL);
1053 }
1054
1055 /* GstURIHandler Interface */
1056 static guint
gst_neonhttp_src_uri_get_type(GType type)1057 gst_neonhttp_src_uri_get_type (GType type)
1058 {
1059 return GST_URI_SRC;
1060 }
1061
1062 static const gchar *const *
gst_neonhttp_src_uri_get_protocols(GType type)1063 gst_neonhttp_src_uri_get_protocols (GType type)
1064 {
1065 static const gchar *protocols[] = { "http", "https", NULL };
1066
1067 return protocols;
1068 }
1069
1070 static gchar *
gst_neonhttp_src_uri_get_uri(GstURIHandler * handler)1071 gst_neonhttp_src_uri_get_uri (GstURIHandler * handler)
1072 {
1073 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);
1074
1075 /* FIXME: make thread-safe */
1076 return g_strdup (src->location);
1077 }
1078
1079 static gboolean
gst_neonhttp_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)1080 gst_neonhttp_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
1081 GError ** error)
1082 {
1083 GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);
1084
1085 return gst_neonhttp_src_set_location (src, uri, error);
1086 }
1087
1088 static void
gst_neonhttp_src_uri_handler_init(gpointer g_iface,gpointer iface_data)1089 gst_neonhttp_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1090 {
1091 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1092
1093 iface->get_type = gst_neonhttp_src_uri_get_type;
1094 iface->get_protocols = gst_neonhttp_src_uri_get_protocols;
1095 iface->get_uri = gst_neonhttp_src_uri_get_uri;
1096 iface->set_uri = gst_neonhttp_src_uri_set_uri;
1097 }
1098
1099 /* entry point to initialize the plug-in
1100 * initialize the plug-in itself
1101 * register the element factories and pad templates
1102 * register the features
1103 */
1104 static gboolean
plugin_init(GstPlugin * plugin)1105 plugin_init (GstPlugin * plugin)
1106 {
1107 return GST_ELEMENT_REGISTER (neonhttpsrc, plugin);
1108 }
1109
1110 /* this is the structure that gst-register looks for
1111 * so keep the name plugin_desc, or you cannot get your plug-in registered */
1112 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1113 GST_VERSION_MINOR,
1114 neonhttpsrc,
1115 "lib neon http client src",
1116 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1117