mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-08 13:18:52 +00:00
Original commit message from CVS: Patch by: Lutz Mueller <lutz topfrose de> * ext/neon/gstneonhttpsrc.c: (gst_neonhttp_src_class_init), (gst_neonhttp_src_init), (gst_neonhttp_src_set_property), (gst_neonhttp_src_set_uri), (gst_neonhttp_src_set_proxy), (gst_neonhttp_src_send_request_and_redirect), (gst_neonhttp_src_uri_set_uri): * ext/neon/gstneonhttpsrc.h: Simplify _set_uri() and _set_proxy() and remove the unused ishttp member (#388050). * tests/check/elements/neonhttpsrc.c: (GST_START_TEST): Fix bogus URI to something that actually exists, otherwise we just bypass the test (and also to something that doesn't redirect, since neonhttpsrc doesn't seem to handle this very gracefully yet)
1046 lines
27 KiB
C
1046 lines
27 KiB
C
/* GStreamer
|
|
* Copyright (C) <2005> Edgard Lima <edgard.lima@indt.org.br>
|
|
* Copyright (C) <2006> Rosfran Borges <rosfran.borges@indt.org.br>
|
|
* Copyright (C) <2006> Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstneonhttpsrc.h"
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <ne_redirect.h>
|
|
|
|
#ifndef NE_FREE
|
|
#define NEON_026_OR_LATER 1
|
|
#endif
|
|
|
|
#define HTTP_DEFAULT_HOST "localhost"
|
|
#define HTTP_DEFAULT_PORT 80
|
|
#define HTTPS_DEFAULT_PORT 443
|
|
#define HTTP_DEFAULT_URI "http://localhost:80/"
|
|
#define NEON_HTTP_DEBUG_DEFAULT FALSE
|
|
#define HTTP_SOCKET_ERROR -2
|
|
#define HTTP_REQUEST_WRONG_PROXY -1
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (neonhttpsrc_debug);
|
|
#define GST_CAT_DEFAULT neonhttpsrc_debug
|
|
|
|
#define MAX_READ_SIZE (4 * 1024)
|
|
|
|
/* max number of HTTP redirects, when iterating over a sequence of HTTP 302 status code */
|
|
#define MAX_HTTP_REDIRECTS_NUMBER 5
|
|
|
|
static const GstElementDetails gst_neonhttp_src_details =
|
|
GST_ELEMENT_DETAILS ("HTTP client source",
|
|
"Source/Network",
|
|
"Receive data as a client over the network via HTTP using NEON",
|
|
"Edgard Lima <edgard.lima@indt.org.br>, "
|
|
"Rosfran Borges <rosfran.borges@indt.org.br>, "
|
|
"Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>");
|
|
|
|
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_LOCATION,
|
|
PROP_URI,
|
|
PROP_PROXY,
|
|
PROP_USER_AGENT,
|
|
PROP_IRADIO_MODE,
|
|
PROP_IRADIO_NAME,
|
|
PROP_IRADIO_GENRE,
|
|
PROP_IRADIO_URL,
|
|
PROP_NEON_HTTP_REDIRECT,
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
PROP_NEON_HTTP_DBG
|
|
#endif
|
|
};
|
|
|
|
static void gst_neonhttp_src_uri_handler_init (gpointer g_iface,
|
|
gpointer iface_data);
|
|
static void gst_neonhttp_src_dispose (GObject * gobject);
|
|
static void gst_neonhttp_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_neonhttp_src_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static GstFlowReturn gst_neonhttp_src_create (GstPushSrc * psrc,
|
|
GstBuffer ** outbuf);
|
|
static gboolean gst_neonhttp_src_start (GstBaseSrc * bsrc);
|
|
static gboolean gst_neonhttp_src_stop (GstBaseSrc * bsrc);
|
|
static gboolean gst_neonhttp_src_get_size (GstBaseSrc * bsrc, guint64 * size);
|
|
static gboolean gst_neonhttp_src_is_seekable (GstBaseSrc * bsrc);
|
|
static gboolean gst_neonhttp_src_do_seek (GstBaseSrc * bsrc,
|
|
GstSegment * segment);
|
|
|
|
static gboolean gst_neonhttp_src_set_proxy (GstNeonhttpSrc * src,
|
|
const gchar * uri);
|
|
static gboolean gst_neonhttp_src_set_uri (GstNeonhttpSrc * src,
|
|
const gchar * uri);
|
|
static gint gst_neonhttp_src_send_request_and_redirect (GstNeonhttpSrc * src,
|
|
ne_session ** ses, ne_request ** req, gint64 offset, gboolean do_redir);
|
|
static gint gst_neonhttp_src_request_dispatch (GstNeonhttpSrc * src,
|
|
GstBuffer * outbuf);
|
|
static void gst_neonhttp_src_close_session (GstNeonhttpSrc * src);
|
|
static gchar *gst_neonhttp_src_unicodify (const gchar * str);
|
|
static void oom_callback (void);
|
|
|
|
static void
|
|
_urihandler_init (GType type)
|
|
{
|
|
static const GInterfaceInfo urihandler_info = {
|
|
gst_neonhttp_src_uri_handler_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (neonhttpsrc_debug, "neonhttpsrc", 0,
|
|
"NEON HTTP src");
|
|
}
|
|
|
|
GST_BOILERPLATE_FULL (GstNeonhttpSrc, gst_neonhttp_src, GstPushSrc,
|
|
GST_TYPE_PUSH_SRC, _urihandler_init);
|
|
|
|
static void
|
|
gst_neonhttp_src_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&srctemplate));
|
|
|
|
gst_element_class_set_details (element_class, &gst_neonhttp_src_details);
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_class_init (GstNeonhttpSrcClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstBaseSrcClass *gstbasesrc_class;
|
|
GstPushSrcClass *gstpushsrc_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstbasesrc_class = (GstBaseSrcClass *) klass;
|
|
gstpushsrc_class = (GstPushSrcClass *) klass;
|
|
|
|
gobject_class->set_property = gst_neonhttp_src_set_property;
|
|
gobject_class->get_property = gst_neonhttp_src_get_property;
|
|
gobject_class->dispose = gst_neonhttp_src_dispose;
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_LOCATION,
|
|
g_param_spec_string ("location", "Location",
|
|
"The location. In the form:"
|
|
"\n\t\t\thttp://a.com/file.txt - default port '80' "
|
|
"\n\t\t\thttp://a.com:80/file.txt "
|
|
"\n\t\t\ta.com/file.txt - defualt scheme 'HTTP' "
|
|
"\n\t\t\thttps://a.com/file.txt - default port '443' "
|
|
"\n\t\t\thttp:///file.txt - default host '" HTTP_DEFAULT_HOST "'",
|
|
"", G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_URI,
|
|
g_param_spec_string ("uri", "Uri",
|
|
"The location in form of a URI (deprecated; use location)",
|
|
"", G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PROXY,
|
|
g_param_spec_string ("proxy", "Proxy",
|
|
"The proxy. In the form myproxy.mycompany.com:8080. "
|
|
"\n\t\t\tIf nothing is passed g_getenv(\"http_proxy\") will be used "
|
|
"\n\t\t\tIf that http_proxy enviroment var isn't define no proxy is used",
|
|
"", G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_USER_AGENT,
|
|
g_param_spec_string ("user-agent", "User-Agent",
|
|
"The User-Agent used for connection.",
|
|
"neonhttpsrc", G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_IRADIO_MODE,
|
|
g_param_spec_boolean ("iradio-mode", "iradio-mode",
|
|
"Enable internet radio mode (extraction of shoutcast/icecast metadata)",
|
|
FALSE, G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_IRADIO_NAME,
|
|
g_param_spec_string ("iradio-name",
|
|
"iradio-name", "Name of the stream", NULL, G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_IRADIO_GENRE,
|
|
g_param_spec_string ("iradio-genre",
|
|
"iradio-genre", "Genre of the stream", NULL, G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_IRADIO_URL,
|
|
g_param_spec_string ("iradio-url",
|
|
"iradio-url",
|
|
"Homepage URL for radio stream", NULL, G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_NEON_HTTP_REDIRECT,
|
|
g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
|
|
"Enable Neon HTTP Redirects (HTTP Status Code 302)",
|
|
FALSE, G_PARAM_READWRITE));
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_NEON_HTTP_DBG,
|
|
g_param_spec_boolean ("neon-http-debug", "neon-http-debug",
|
|
"Enable Neon HTTP debug messages",
|
|
NEON_HTTP_DEBUG_DEFAULT, G_PARAM_READWRITE));
|
|
#endif
|
|
|
|
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_neonhttp_src_start);
|
|
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_neonhttp_src_stop);
|
|
gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_neonhttp_src_get_size);
|
|
gstbasesrc_class->is_seekable =
|
|
GST_DEBUG_FUNCPTR (gst_neonhttp_src_is_seekable);
|
|
gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_neonhttp_src_do_seek);
|
|
|
|
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_neonhttp_src_create);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (neonhttpsrc_debug, "neonhttpsrc", 0,
|
|
"NEON HTTP Client Source");
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_init (GstNeonhttpSrc * src, GstNeonhttpSrcClass * g_class)
|
|
{
|
|
const gchar *str = g_getenv ("http_proxy");
|
|
|
|
src->neon_http_msgs_dbg = NEON_HTTP_DEBUG_DEFAULT;
|
|
src->session = NULL;
|
|
src->request = NULL;
|
|
memset (&src->uri, 0, sizeof (src->uri));
|
|
memset (&src->proxy, 0, sizeof (src->proxy));
|
|
src->content_size = -1;
|
|
src->uristr = NULL;
|
|
|
|
gst_neonhttp_src_set_uri (src, HTTP_DEFAULT_URI);
|
|
if (str && !gst_neonhttp_src_set_proxy (src, str)) {
|
|
GST_WARNING_OBJECT (src,
|
|
"The proxy set on http_proxy env var ('%s') cannot be parsed.", str);
|
|
}
|
|
|
|
src->user_agent = g_strdup ("neonhttpsrc");
|
|
|
|
src->iradio_mode = FALSE;
|
|
src->iradio_name = NULL;
|
|
src->iradio_genre = NULL;
|
|
src->iradio_url = NULL;
|
|
src->icy_caps = NULL;
|
|
src->icy_metaint = 0;
|
|
src->user_agent = g_strdup ("neonhttpsrc");
|
|
src->seekable = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_dispose (GObject * gobject)
|
|
{
|
|
GstNeonhttpSrc *src = GST_NEONHTTP_SRC (gobject);
|
|
|
|
ne_uri_free (&src->uri);
|
|
ne_uri_free (&src->proxy);
|
|
|
|
g_free (src->user_agent);
|
|
g_free (src->iradio_name);
|
|
g_free (src->iradio_genre);
|
|
g_free (src->iradio_url);
|
|
|
|
if (src->icy_caps) {
|
|
gst_caps_unref (src->icy_caps);
|
|
src->icy_caps = NULL;
|
|
}
|
|
|
|
if (src->request) {
|
|
ne_request_destroy (src->request);
|
|
src->request = NULL;
|
|
}
|
|
|
|
if (src->session) {
|
|
ne_close_connection (src->session);
|
|
ne_session_destroy (src->session);
|
|
src->session = NULL;
|
|
}
|
|
|
|
if (src->uristr) {
|
|
ne_free (src->uristr);
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstNeonhttpSrc *src = GST_NEONHTTP_SRC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PROXY:
|
|
{
|
|
if (!g_value_get_string (value)) {
|
|
GST_WARNING ("proxy property cannot be NULL");
|
|
goto done;
|
|
}
|
|
if (!gst_neonhttp_src_set_proxy (src, g_value_get_string (value))) {
|
|
GST_WARNING ("badly formated proxy");
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_URI:
|
|
case PROP_LOCATION:
|
|
{
|
|
if (!g_value_get_string (value)) {
|
|
GST_WARNING ("location property cannot be NULL");
|
|
goto done;
|
|
}
|
|
if (!gst_neonhttp_src_set_uri (src, g_value_get_string (value))) {
|
|
GST_WARNING ("badly formated location");
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_USER_AGENT:
|
|
{
|
|
if (src->user_agent) {
|
|
g_free (src->user_agent);
|
|
src->user_agent = NULL;
|
|
}
|
|
if (g_value_get_string (value)) {
|
|
src->user_agent = g_strdup (g_value_get_string (value));
|
|
}
|
|
break;
|
|
}
|
|
case PROP_IRADIO_MODE:
|
|
{
|
|
src->iradio_mode = g_value_get_boolean (value);
|
|
break;
|
|
}
|
|
case PROP_NEON_HTTP_REDIRECT:
|
|
{
|
|
src->neon_http_redirect = g_value_get_boolean (value);
|
|
break;
|
|
}
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
case PROP_NEON_HTTP_DBG:
|
|
{
|
|
src->neon_http_msgs_dbg = g_value_get_boolean (value);
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstNeonhttpSrc *neonhttpsrc = GST_NEONHTTP_SRC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PROXY:
|
|
{
|
|
gchar *str;
|
|
|
|
if (neonhttpsrc->proxy.host) {
|
|
str = ne_uri_unparse (&neonhttpsrc->proxy);
|
|
if (!str)
|
|
break;
|
|
g_value_set_string (value, str);
|
|
ne_free (str);
|
|
} else {
|
|
g_value_set_string (value, "");
|
|
}
|
|
break;
|
|
}
|
|
case PROP_URI:
|
|
case PROP_LOCATION:
|
|
{
|
|
gchar *str;
|
|
|
|
if (neonhttpsrc->uri.host) {
|
|
str = ne_uri_unparse (&neonhttpsrc->uri);
|
|
if (!str)
|
|
break;
|
|
g_value_set_string (value, str);
|
|
ne_free (str);
|
|
} else {
|
|
g_value_set_string (value, "");
|
|
}
|
|
break;
|
|
}
|
|
case PROP_USER_AGENT:
|
|
{
|
|
g_value_set_string (value, neonhttpsrc->user_agent);
|
|
break;
|
|
}
|
|
case PROP_IRADIO_MODE:
|
|
g_value_set_boolean (value, neonhttpsrc->iradio_mode);
|
|
break;
|
|
case PROP_IRADIO_NAME:
|
|
g_value_set_string (value, neonhttpsrc->iradio_name);
|
|
break;
|
|
case PROP_IRADIO_GENRE:
|
|
g_value_set_string (value, neonhttpsrc->iradio_genre);
|
|
break;
|
|
case PROP_IRADIO_URL:
|
|
g_value_set_string (value, neonhttpsrc->iradio_url);
|
|
break;
|
|
case PROP_NEON_HTTP_REDIRECT:
|
|
g_value_set_boolean (value, neonhttpsrc->neon_http_redirect);
|
|
break;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
case PROP_NEON_HTTP_DBG:
|
|
g_value_set_boolean (value, neonhttpsrc->neon_http_msgs_dbg);
|
|
break;
|
|
#endif
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_neonhttp_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
|
|
{
|
|
GstNeonhttpSrc *src;
|
|
GstBaseSrc *basesrc;
|
|
GstFlowReturn ret;
|
|
gint read;
|
|
|
|
src = GST_NEONHTTP_SRC (psrc);
|
|
basesrc = GST_BASE_SRC_CAST (psrc);
|
|
|
|
/* The caller should know the number of bytes and not read beyond EOS. */
|
|
if (G_UNLIKELY (src->eos))
|
|
goto eos;
|
|
|
|
/* Create the buffer. */
|
|
ret = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc),
|
|
basesrc->segment.last_stop, basesrc->blocksize,
|
|
src->icy_caps ? src->icy_caps :
|
|
GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)), outbuf);
|
|
|
|
if (G_UNLIKELY (ret != GST_FLOW_OK))
|
|
goto done;
|
|
|
|
read = gst_neonhttp_src_request_dispatch (src, *outbuf);
|
|
if (G_UNLIKELY (read < 0))
|
|
goto read_error;
|
|
|
|
GST_LOG_OBJECT (src, "returning %u bytes", GST_BUFFER_SIZE (*outbuf));
|
|
|
|
done:
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
eos:
|
|
{
|
|
GST_DEBUG_OBJECT (src, "EOS reached");
|
|
return GST_FLOW_UNEXPECTED;
|
|
}
|
|
read_error:
|
|
{
|
|
GST_ELEMENT_ERROR (src, RESOURCE, READ,
|
|
(NULL), ("Could not read any bytes (%i, %s)", read,
|
|
ne_get_error (src->session)));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
/* create a socket for connecting to remote server */
|
|
static gboolean
|
|
gst_neonhttp_src_start (GstBaseSrc * bsrc)
|
|
{
|
|
GstNeonhttpSrc *src = GST_NEONHTTP_SRC (bsrc);
|
|
const gchar *content_length;
|
|
gint res;
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg)
|
|
ne_debug_init (stderr, NE_DBG_HTTP);
|
|
#endif
|
|
|
|
ne_oom_callback (oom_callback);
|
|
|
|
if (ne_sock_init () != 0)
|
|
return FALSE;
|
|
|
|
res = gst_neonhttp_src_send_request_and_redirect (src,
|
|
&src->session, &src->request, 0, src->neon_http_redirect);
|
|
|
|
if (res != NE_OK || !src->session) {
|
|
if (res == HTTP_SOCKET_ERROR) {
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg) {
|
|
GST_ERROR_OBJECT (src, "HTTP Request failed when opening socket!");
|
|
}
|
|
#endif
|
|
goto init_failed;
|
|
} else if (res == HTTP_REQUEST_WRONG_PROXY) {
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg) {
|
|
GST_ERROR_OBJECT (src,
|
|
"Proxy Server URI is invalid to the HTTP Request!");
|
|
}
|
|
#endif
|
|
goto wrong_proxy;
|
|
} else {
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg) {
|
|
GST_ERROR_OBJECT (src, "HTTP Request failed, error unrecognized!");
|
|
}
|
|
#endif
|
|
goto begin_req_failed;
|
|
}
|
|
}
|
|
|
|
content_length = ne_get_response_header (src->request, "Content-Length");
|
|
|
|
if (content_length)
|
|
src->content_size = g_ascii_strtoull (content_length, NULL, 10);
|
|
else
|
|
src->content_size = -1;
|
|
|
|
if (src->iradio_mode) {
|
|
/* Icecast stuff */
|
|
const gchar *str_value;
|
|
gint gint_value;
|
|
|
|
str_value = ne_get_response_header (src->request, "icy-metaint");
|
|
if (str_value) {
|
|
if (sscanf (str_value, "%d", &gint_value) == 1) {
|
|
if (src->icy_caps) {
|
|
gst_caps_unref (src->icy_caps);
|
|
src->icy_caps = NULL;
|
|
}
|
|
src->icy_metaint = gint_value;
|
|
src->icy_caps = gst_caps_new_simple ("application/x-icy",
|
|
"metadata-interval", G_TYPE_INT, src->icy_metaint, NULL);
|
|
}
|
|
}
|
|
|
|
str_value = ne_get_response_header (src->request, "icy-name");
|
|
if (str_value) {
|
|
if (src->iradio_name) {
|
|
g_free (src->iradio_name);
|
|
src->iradio_name = NULL;
|
|
}
|
|
src->iradio_name = gst_neonhttp_src_unicodify (str_value);
|
|
}
|
|
str_value = ne_get_response_header (src->request, "icy-genre");
|
|
if (str_value) {
|
|
if (src->iradio_genre) {
|
|
g_free (src->iradio_genre);
|
|
src->iradio_genre = NULL;
|
|
}
|
|
src->iradio_genre = gst_neonhttp_src_unicodify (str_value);
|
|
}
|
|
str_value = ne_get_response_header (src->request, "icy-url");
|
|
if (str_value) {
|
|
if (src->iradio_url) {
|
|
g_free (src->iradio_url);
|
|
src->iradio_url = NULL;
|
|
}
|
|
src->iradio_url = gst_neonhttp_src_unicodify (str_value);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
init_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
|
|
(NULL), ("Could not initialize neon library (%i)", res));
|
|
return FALSE;
|
|
}
|
|
wrong_proxy:
|
|
{
|
|
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
|
|
(NULL), ("Both proxy host and port must be specified or none"));
|
|
return FALSE;
|
|
}
|
|
begin_req_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
|
|
(NULL), ("Could not begin request (%i)", res));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* close the socket and associated resources
|
|
* used both to recover from errors and go to NULL state */
|
|
static gboolean
|
|
gst_neonhttp_src_stop (GstBaseSrc * bsrc)
|
|
{
|
|
GstNeonhttpSrc *src;
|
|
|
|
src = GST_NEONHTTP_SRC (bsrc);
|
|
|
|
if (src->iradio_name) {
|
|
g_free (src->iradio_name);
|
|
src->iradio_name = NULL;
|
|
}
|
|
|
|
if (src->iradio_genre) {
|
|
g_free (src->iradio_genre);
|
|
src->iradio_genre = NULL;
|
|
}
|
|
|
|
if (src->iradio_url) {
|
|
g_free (src->iradio_url);
|
|
src->iradio_url = NULL;
|
|
}
|
|
|
|
if (src->icy_caps) {
|
|
gst_caps_unref (src->icy_caps);
|
|
src->icy_caps = NULL;
|
|
}
|
|
|
|
src->eos = FALSE;
|
|
src->content_size = -1;
|
|
src->read_position = 0;
|
|
src->seekable = TRUE;
|
|
|
|
gst_neonhttp_src_close_session (src);
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
ne_debug_init (NULL, 0);
|
|
#endif
|
|
ne_oom_callback (NULL);
|
|
ne_sock_exit ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_get_size (GstBaseSrc * bsrc, guint64 * size)
|
|
{
|
|
GstNeonhttpSrc *src;
|
|
|
|
src = GST_NEONHTTP_SRC (bsrc);
|
|
|
|
if (src->content_size == -1)
|
|
return FALSE;
|
|
|
|
*size = src->content_size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_is_seekable (GstBaseSrc * bsrc)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
|
|
{
|
|
GstNeonhttpSrc *src;
|
|
gint res;
|
|
ne_session *session = NULL;
|
|
ne_request *request = NULL;
|
|
|
|
src = GST_NEONHTTP_SRC (bsrc);
|
|
|
|
if (!src->seekable)
|
|
return FALSE;
|
|
|
|
if (src->read_position == segment->start)
|
|
return TRUE;
|
|
|
|
res = gst_neonhttp_src_send_request_and_redirect (src,
|
|
&session, &request, segment->start, src->neon_http_redirect);
|
|
|
|
/* if we are able to seek, replace the session */
|
|
if (res == NE_OK && session) {
|
|
gst_neonhttp_src_close_session (src);
|
|
src->session = session;
|
|
src->request = request;
|
|
src->read_position = segment->start;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_set_uri (GstNeonhttpSrc * src, const gchar * uri)
|
|
{
|
|
ne_uri_free (&src->uri);
|
|
if (src->uristr) {
|
|
ne_free (src->uristr);
|
|
src->uristr = NULL;
|
|
}
|
|
|
|
if (ne_uri_parse (uri, &src->uri) != 0)
|
|
goto parse_error;
|
|
|
|
if (src->uri.scheme == NULL)
|
|
src->uri.scheme = g_strdup ("http");
|
|
|
|
if (src->uri.host == NULL)
|
|
src->uri.host = g_strdup (HTTP_DEFAULT_HOST);
|
|
|
|
if (src->uri.port == 0) {
|
|
if (!strcmp (src->uri.scheme, "https"))
|
|
src->uri.port = HTTPS_DEFAULT_PORT;
|
|
else
|
|
src->uri.port = HTTP_DEFAULT_PORT;
|
|
}
|
|
|
|
if (!src->uri.path)
|
|
src->uri.path = g_strdup ("");
|
|
|
|
src->uristr = ne_uri_unparse (&src->uri);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
parse_error:
|
|
{
|
|
if (src->uristr) {
|
|
ne_free (src->uristr);
|
|
src->uristr = NULL;
|
|
}
|
|
ne_uri_free (&src->uri);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_set_proxy (GstNeonhttpSrc * src, const char *uri)
|
|
{
|
|
ne_uri_free (&src->proxy);
|
|
|
|
if (ne_uri_parse (uri, &src->proxy) != 0)
|
|
goto error;
|
|
|
|
if (src->proxy.scheme)
|
|
GST_WARNING ("The proxy schema shouldn't be defined (schema is '%s')",
|
|
src->proxy.scheme);
|
|
|
|
if (src->proxy.host && !src->proxy.port)
|
|
goto error;
|
|
|
|
#ifdef NEON_026_OR_LATER
|
|
if (!src->proxy.path || src->proxy.userinfo)
|
|
goto error;
|
|
#else
|
|
if (!src->proxy.path || src->proxy.authinfo)
|
|
goto error;
|
|
#endif
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
error:
|
|
{
|
|
ne_uri_free (&src->proxy);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to send the HTTP request to the Icecast server, and if possible deals with
|
|
* all the probable redirections (HTTP status code == 302)
|
|
*/
|
|
static gint
|
|
gst_neonhttp_src_send_request_and_redirect (GstNeonhttpSrc * src,
|
|
ne_session ** ses, ne_request ** req, gint64 offset, gboolean do_redir)
|
|
{
|
|
ne_session *session = NULL;
|
|
ne_request *request = NULL;
|
|
gint res;
|
|
gint http_status = 0;
|
|
guint request_count = 0;
|
|
|
|
do {
|
|
if (src->proxy.host && src->proxy.port) {
|
|
session =
|
|
ne_session_create (src->uri.scheme, src->uri.host, src->uri.port);
|
|
ne_session_proxy (session, src->proxy.host, src->proxy.port);
|
|
} else if (src->proxy.host || src->proxy.port) {
|
|
/* both proxy host and port must be specified or none */
|
|
return HTTP_REQUEST_WRONG_PROXY;
|
|
} else {
|
|
session =
|
|
ne_session_create (src->uri.scheme, src->uri.host, src->uri.port);
|
|
}
|
|
|
|
request = ne_request_create (session, "GET", src->uri.path);
|
|
|
|
if (src->user_agent) {
|
|
ne_add_request_header (request, "User-Agent", src->user_agent);
|
|
}
|
|
|
|
if (src->iradio_mode) {
|
|
ne_add_request_header (request, "icy-metadata", "1");
|
|
}
|
|
|
|
if (offset > 0) {
|
|
ne_print_request_header (request, "Range",
|
|
"bytes=%" G_GINT64_FORMAT "-", offset);
|
|
}
|
|
|
|
res = ne_begin_request (request);
|
|
|
|
if (res == NE_OK) {
|
|
/* When the HTTP status code is 302, it is not the SHOUTcast streaming content yet;
|
|
* Reload the HTTP request with a new URI value */
|
|
http_status = ne_get_status (request)->code;
|
|
if (http_status == 302 && do_redir) {
|
|
const gchar *redir;
|
|
|
|
/* the new URI value to go when redirecting can be found on the 'Location' HTTP header */
|
|
redir = ne_get_response_header (request, "Location");
|
|
if (redir != NULL) {
|
|
ne_uri_free (&src->uri);
|
|
gst_neonhttp_src_set_uri (src, redir);
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg)
|
|
GST_LOG_OBJECT (src,
|
|
"--> Got HTTP Status Code %d; Using 'Location' header [%s]",
|
|
http_status, src->uri.host);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((res != NE_OK) ||
|
|
(offset == 0 && http_status != 200) ||
|
|
(offset > 0 && http_status != 206 && http_status != 302)) {
|
|
ne_request_destroy (request);
|
|
request = NULL;
|
|
ne_close_connection (session);
|
|
ne_session_destroy (session);
|
|
session = NULL;
|
|
if (offset > 0 && http_status != 206 && http_status != 302) {
|
|
src->seekable = FALSE;
|
|
}
|
|
}
|
|
|
|
/* if - NE_OK */
|
|
if (http_status == 302 && do_redir) {
|
|
++request_count;
|
|
GST_WARNING_OBJECT (src, "%s %s.",
|
|
(request_count < MAX_HTTP_REDIRECTS_NUMBER)
|
|
&& do_redir ? "Redirecting to" :
|
|
"WILL NOT redirect, try it again with a different URI; an alternative is",
|
|
src->uri.host);
|
|
/* FIXME: when not redirecting automatically, shouldn't we post a
|
|
* redirect element message on the bus? */
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (src->neon_http_msgs_dbg)
|
|
GST_LOG_OBJECT (src, "--> request_count = %d", request_count);
|
|
#endif
|
|
}
|
|
/* do the redirect, go back to send another HTTP request now using the 'Location' */
|
|
} while (do_redir && (request_count < MAX_HTTP_REDIRECTS_NUMBER)
|
|
&& http_status == 302);
|
|
|
|
if (session) {
|
|
*ses = session;
|
|
*req = request;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gint
|
|
gst_neonhttp_src_request_dispatch (GstNeonhttpSrc * src, GstBuffer * outbuf)
|
|
{
|
|
gint ret;
|
|
gint read = 0;
|
|
gint sizetoread = GST_BUFFER_SIZE (outbuf);
|
|
|
|
/* Loop sending the request:
|
|
* Retry whilst authentication fails and we supply it. */
|
|
|
|
ssize_t len = 0;
|
|
|
|
while (sizetoread > 0) {
|
|
len = ne_read_response_block (src->request,
|
|
(gchar *) GST_BUFFER_DATA (outbuf) + read, sizetoread);
|
|
if (len > 0) {
|
|
read += len;
|
|
sizetoread -= len;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
GST_BUFFER_SIZE (outbuf) = read;
|
|
|
|
if (len < 0) {
|
|
read = -2;
|
|
goto done;
|
|
} else if (len == 0) {
|
|
ret = ne_end_request (src->request);
|
|
if (ret != NE_RETRY) {
|
|
if (ret == NE_OK) {
|
|
src->eos = TRUE;
|
|
} else {
|
|
read = -3;
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
if (read > 0)
|
|
src->read_position += read;
|
|
|
|
done:
|
|
return read;
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_close_session (GstNeonhttpSrc * src)
|
|
{
|
|
if (src->request) {
|
|
ne_request_destroy (src->request);
|
|
src->request = NULL;
|
|
}
|
|
|
|
if (src->session) {
|
|
ne_close_connection (src->session);
|
|
ne_session_destroy (src->session);
|
|
src->session = NULL;
|
|
}
|
|
}
|
|
|
|
/* The following two charset mangling functions were copied from gnomevfssrc.
|
|
* Preserve them under the unverified assumption that they do something vaguely
|
|
* worthwhile.
|
|
*/
|
|
static gchar *
|
|
unicodify (const gchar * str, gint len, ...)
|
|
{
|
|
gchar *ret = NULL, *cset;
|
|
va_list args;
|
|
gsize bytes_read, bytes_written;
|
|
|
|
if (g_utf8_validate (str, len, NULL))
|
|
return g_strndup (str, len >= 0 ? len : strlen (str));
|
|
|
|
va_start (args, len);
|
|
while ((cset = va_arg (args, gchar *)) != NULL) {
|
|
if (!strcmp (cset, "locale"))
|
|
ret = g_locale_to_utf8 (str, len, &bytes_read, &bytes_written, NULL);
|
|
else
|
|
ret = g_convert (str, len, "UTF-8", cset,
|
|
&bytes_read, &bytes_written, NULL);
|
|
if (ret)
|
|
break;
|
|
}
|
|
va_end (args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gchar *
|
|
gst_neonhttp_src_unicodify (const gchar * str)
|
|
{
|
|
return unicodify (str, -1, "locale", "ISO-8859-1", NULL);
|
|
}
|
|
|
|
/* GstURIHandler Interface */
|
|
static guint
|
|
gst_neonhttp_src_uri_get_type (void)
|
|
{
|
|
return GST_URI_SRC;
|
|
}
|
|
|
|
static gchar **
|
|
gst_neonhttp_src_uri_get_protocols (void)
|
|
{
|
|
static gchar *protocols[] = { "http", "https", NULL };
|
|
return protocols;
|
|
}
|
|
|
|
static const gchar *
|
|
gst_neonhttp_src_uri_get_uri (GstURIHandler * handler)
|
|
{
|
|
GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);
|
|
|
|
return src->uristr;
|
|
}
|
|
|
|
static gboolean
|
|
gst_neonhttp_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
|
|
{
|
|
GstNeonhttpSrc *src = GST_NEONHTTP_SRC (handler);
|
|
|
|
return gst_neonhttp_src_set_uri (src, uri);
|
|
}
|
|
|
|
static void
|
|
gst_neonhttp_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
|
|
{
|
|
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
|
|
|
|
iface->get_type = gst_neonhttp_src_uri_get_type;
|
|
iface->get_protocols = gst_neonhttp_src_uri_get_protocols;
|
|
iface->get_uri = gst_neonhttp_src_uri_get_uri;
|
|
iface->set_uri = gst_neonhttp_src_uri_set_uri;
|
|
}
|
|
|
|
/* NEON CALLBACK */
|
|
static void
|
|
oom_callback ()
|
|
{
|
|
GST_ERROR ("memory exeception in neon");
|
|
}
|
|
|
|
/* entry point to initialize the plug-in
|
|
* initialize the plug-in itself
|
|
* register the element factories and pad templates
|
|
* register the features
|
|
*/
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "neonhttpsrc", GST_RANK_NONE,
|
|
GST_TYPE_NEONHTTP_SRC);
|
|
}
|
|
|
|
/* this is the structure that gst-register looks for
|
|
* so keep the name plugin_desc, or you cannot get your plug-in registered */
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
"neon",
|
|
"lib neon http client src",
|
|
plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")
|