diff --git a/INSTALL b/INSTALL
index 695cd683..f53b6ba7 100644
--- a/INSTALL
+++ b/INSTALL
@@ -24,14 +24,16 @@ sudo apt-get install \
antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
libavcodec-dev libavformat-dev libswscale-dev libavutil-dev libasound2-dev \
libmxml-dev libgcrypt11-dev libavahi-client-dev libavl-dev zlib1g-dev \
- libevent1-dev
+ libevent-dev
Depending on the version of libav/ffmpeg in your distribution you may also need
libavresample-dev.
-Note that libevent1-dev is not in the Debian repository, but you can get the
-packages in the Ubuntu repository. You can also use libevent 2, but it must be
-version 2.1.4 or newer.
+Note that while forked-daapd will work with versions of libevent between 2.0.0
+and 2.1.3, it is recommended to use either libevent 1 or 2.1.4+. Otherwise you
+will not have support for Shoutcast metadata and simultaneous streaming to
+multiple clients. Libevent 1 is not in the Debian repository, but you can get
+the packages in the Ubuntu repository.
Then run the following:
@@ -86,7 +88,7 @@ Libraries:
from
- libconfuse
from
- - libevent 1.4 or 2.1.4+
+ - libevent 1.4+ (best with version 1.4 or 2.1.4+)
from
- libavl 0.3.5
from
diff --git a/configure.ac b/configure.ac
index d2a90a64..5c5e3342 100644
--- a/configure.ac
+++ b/configure.ac
@@ -160,19 +160,28 @@ fi
PKG_CHECK_MODULES(MINIXML, [ mxml ])
-PKG_CHECK_MODULES(LIBEVENT, [ libevent >= 2.1.4 ],
- AC_DEFINE(HAVE_LIBEVENT2, 1, [Define to 1 if you have libevent >= 2.1.4]),
+PKG_CHECK_MODULES(LIBEVENT, [ libevent >= 2 ],
+ AC_DEFINE(HAVE_LIBEVENT2, 1, [Define to 1 if you have libevent 2]),
try_libevent1=true;
)
+AM_CONDITIONAL(COND_LIBEVENT1, false)
+AM_CONDITIONAL(COND_LIBEVENT20, false)
+AM_CONDITIONAL(COND_LIBEVENT21, false)
+
if test x$try_libevent1 = xtrue; then
AC_CHECK_HEADER(event.h, , AC_MSG_ERROR([event.h not found]))
AC_CHECK_LIB([event_core], [event_init], [LIBEVENT_LIBS="-levent_core"], AC_MSG_ERROR([libevent not found]))
- AC_CHECK_LIB([event_core], [event_new], AC_MSG_ERROR([found libevent 2 but version should be at least 2.1.4]))
AC_SUBST(LIBEVENT_LIBS)
- AM_CONDITIONAL(COND_LIBEVENT2, false)
+ AM_CONDITIONAL(COND_LIBEVENT1, true)
else
- AM_CONDITIONAL(COND_LIBEVENT2, true)
+ PKG_CHECK_EXISTS([ libevent >= 2.1 ],
+ AM_CONDITIONAL(COND_LIBEVENT21, true),
+ AM_CONDITIONAL(COND_LIBEVENT20, true)
+ )
+ PKG_CHECK_EXISTS([ libevent >= 2.1.4 ], ,
+ AC_DEFINE(HAVE_LIBEVENT2_OLD, 1, [Define to 1 if you have libevent 2 (<2.1.4)])
+ )
fi
AC_CHECK_HEADER(avl.h, , AC_MSG_ERROR([avl.h not found]))
diff --git a/src/Makefile.am b/src/Makefile.am
index a8af5493..e5cc7bdf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,13 +31,21 @@ else
FFURL_SRC=ffmpeg_url_evbuffer.c ffmpeg_url_evbuffer.h
endif
-if COND_LIBEVENT2
-RTSP_SRC=evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h
-else
-EVHTTP_SRC=evhttp/http.c evhttp/evhttp.h evhttp/http-internal.h evhttp/log.h
+if COND_LIBEVENT1
+EVHTTP_SRC=evhttp/http.c evhttp/evhttp.h evhttp/evhttp_compat.c evhttp/evhttp_compat.h evhttp/http-internal.h evhttp/log.h
RTSP_SRC=evrtsp/rtsp-libevent1.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h
endif
+if COND_LIBEVENT20
+EVHTTP_SRC=
+RTSP_SRC=evrtsp/rtsp-libevent20.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h
+endif
+
+if COND_LIBEVENT21
+EVHTTP_SRC=
+RTSP_SRC=evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h
+endif
+
GPERF_FILES = \
daap_query.gperf \
rsp_query.gperf \
diff --git a/src/db.c b/src/db.c
index fc102f6a..1c03fef7 100644
--- a/src/db.c
+++ b/src/db.c
@@ -1580,7 +1580,7 @@ db_query_fetch_file(struct query_params *qp, struct db_media_file_info *dbmfi)
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
- DPRINTF(E_INFO, L_DB, "End of query results\n");
+ DPRINTF(E_DBG, L_DB, "End of query results\n");
dbmfi->id = NULL;
return 0;
}
@@ -1636,7 +1636,7 @@ db_query_fetch_pl(struct query_params *qp, struct db_playlist_info *dbpli)
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
- DPRINTF(E_INFO, L_DB, "End of query results\n");
+ DPRINTF(E_DBG, L_DB, "End of query results\n");
dbpli->id = NULL;
return 0;
}
@@ -1716,7 +1716,7 @@ db_query_fetch_group(struct query_params *qp, struct db_group_info *dbgri)
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
- DPRINTF(E_INFO, L_DB, "End of query results\n");
+ DPRINTF(E_DBG, L_DB, "End of query results\n");
return 1;
}
else if (ret != SQLITE_ROW)
@@ -1765,7 +1765,7 @@ db_query_fetch_string(struct query_params *qp, char **string)
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
- DPRINTF(E_INFO, L_DB, "End of query results\n");
+ DPRINTF(E_DBG, L_DB, "End of query results\n");
*string = NULL;
return 0;
}
@@ -1802,7 +1802,7 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
ret = db_blocking_step(qp->stmt);
if (ret == SQLITE_DONE)
{
- DPRINTF(E_INFO, L_DB, "End of query results\n");
+ DPRINTF(E_DBG, L_DB, "End of query results\n");
*string = NULL;
return 0;
}
@@ -1997,7 +1997,7 @@ db_file_path_byid(int id)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -2046,7 +2046,7 @@ db_file_id_byquery(char *query)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -2219,7 +2219,7 @@ db_file_stamp_bypath(char *path, time_t *stamp, int *id)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -2283,7 +2283,7 @@ db_file_fetch_byquery(char *query)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -2815,7 +2815,7 @@ db_pl_id_bypath(char *path, int *id)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -2878,7 +2878,7 @@ db_pl_fetch_byquery(char *query)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
@@ -3432,7 +3432,7 @@ db_group_type_byid(int id)
if (ret != SQLITE_ROW)
{
if (ret == SQLITE_DONE)
- DPRINTF(E_INFO, L_DB, "No results\n");
+ DPRINTF(E_DBG, L_DB, "No results\n");
else
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
diff --git a/src/dmap_common.h b/src/dmap_common.h
index bc9aba23..181b2d8d 100644
--- a/src/dmap_common.h
+++ b/src/dmap_common.h
@@ -4,7 +4,7 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
# include "evhttp/evhttp.h"
#endif
diff --git a/src/evhttp/evhttp_compat.c b/src/evhttp/evhttp_compat.c
new file mode 100644
index 00000000..81263723
--- /dev/null
+++ b/src/evhttp/evhttp_compat.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009-2010 Julien BLACHE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include
+#include "evhttp_compat.h"
+
+struct evhttp_connection *
+evhttp_connection_base_new(struct event_base *base, void *ignore, const char *address, unsigned short port)
+{
+ struct evhttp_connection *evcon;
+
+ if (!base || !address || !port)
+ return NULL;
+
+ evcon = evhttp_connection_new(address, port);
+ if (evcon)
+ evhttp_connection_set_base(evcon, base);
+
+ return evcon;
+}
+
+void
+evhttp_request_set_header_cb(struct evhttp_request *req, int (*cb)(struct evhttp_request *, void *))
+{
+ req->header_cb = cb;
+}
+
diff --git a/src/evhttp/evhttp_compat.h b/src/evhttp/evhttp_compat.h
new file mode 100644
index 00000000..657bdd2d
--- /dev/null
+++ b/src/evhttp/evhttp_compat.h
@@ -0,0 +1,27 @@
+#include "evhttp.h"
+
+/* This file should only be included if using libevent 1
+ *
+ * The following adds libevent 2 evhttp functions to libevent 1, so we avoid
+ * the need of having many HAVE_LIBEVENT2 conditions inside the code
+ */
+
+#define evhttp_request_get_response_code(x) x->response_code
+
+#define evhttp_request_get_input_headers(x) x->input_headers
+#define evhttp_request_get_output_headers(x) x->output_headers
+
+#define evhttp_request_get_input_buffer(x) x->input_buffer
+#define evhttp_request_get_output_buffer(x) x->output_buffer
+
+#define evhttp_request_get_host(x) x->remote_host
+
+#define evhttp_request_get_uri evhttp_request_uri
+
+struct evhttp_connection *
+evhttp_connection_base_new(struct event_base *base, void *ignore, const char *address, unsigned short port);
+
+void
+evhttp_request_set_header_cb(struct evhttp_request *req, int (*cb)(struct evhttp_request *, void *));
+
+
diff --git a/src/evrtsp/rtsp-libevent20.c b/src/evrtsp/rtsp-libevent20.c
new file mode 100644
index 00000000..ab724781
--- /dev/null
+++ b/src/evrtsp/rtsp-libevent20.c
@@ -0,0 +1,1808 @@
+/*
+ * Copyright (C) 2010 Julien BLACHE
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright (c) 2002-2006 Niels Provos
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include
+
+#ifdef _EVENT_HAVE_SYS_PARAM_H
+#include
+#endif
+#ifdef _EVENT_HAVE_SYS_TYPES_H
+#include
+#endif
+
+#ifdef _EVENT_HAVE_SYS_TIME_H
+#include
+#endif
+#ifdef _EVENT_HAVE_SYS_IOCCOM_H
+#include
+#endif
+
+#ifndef WIN32
+#include
+#include
+#include
+#include
+#endif
+
+#include
+
+#ifndef WIN32
+#include
+#include
+#include
+#endif
+
+#ifdef WIN32
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef WIN32
+#include
+#endif
+#include
+#include
+#ifdef _EVENT_HAVE_UNISTD_H
+#include
+#endif
+#ifdef _EVENT_HAVE_FCNTL_H
+#include
+#endif
+
+#undef timeout_pending
+#undef timeout_initialized
+
+#include "evrtsp.h"
+/* #define USE_DEBUG */
+#include "log.h"
+#include "rtsp-internal.h"
+
+#ifdef WIN32
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+#define strdup _strdup
+#endif
+
+#ifndef _EVENT_HAVE_GETNAMEINFO
+#define NI_MAXSERV 32
+#define NI_MAXHOST 1025
+
+#define NI_NUMERICHOST 1
+#define NI_NUMERICSERV 2
+
+static int
+fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host,
+ size_t hostlen, char *serv, size_t servlen, int flags)
+{
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+ int ret;
+
+ if (serv != NULL) {
+ char tmpserv[16];
+ evutil_snprintf(tmpserv, sizeof(tmpserv),
+ "%d", ntohs(sin->sin_port));
+ ret = evutil_snprintf(serv, servlen, "%s", tmpserv);
+ if ((ret < 0) || (ret >= servlen))
+ return (-1);
+ }
+
+ if (host != NULL) {
+ if (flags & NI_NUMERICHOST) {
+ ret = evutil_snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr));
+ if ((ret < 0) || (ret >= hostlen))
+ return (-1);
+ else
+ return (0);
+ } else {
+ struct hostent *hp;
+ hp = gethostbyaddr((char *)&sin->sin_addr,
+ sizeof(struct in_addr), AF_INET);
+ if (hp == NULL)
+ return (-2);
+
+ ret = evutil_snprintf(host, hostlen, "%s", hp->h_name);
+ if ((ret < 0) || (ret >= hostlen))
+ return (-1);
+ else
+ return (0);
+ }
+ }
+ return (0);
+}
+
+#endif
+
+#ifndef _EVENT_HAVE_GETADDRINFO
+struct addrinfo {
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ size_t ai_addrlen;
+ struct sockaddr *ai_addr;
+ struct addrinfo *ai_next;
+};
+static int
+fake_getaddrinfo(const char *hostname, struct addrinfo *ai)
+{
+ struct hostent *he = NULL;
+ struct sockaddr_in *sa;
+ if (hostname) {
+ he = gethostbyname(hostname);
+ if (!he)
+ return (-1);
+ }
+ ai->ai_family = he ? he->h_addrtype : AF_INET;
+ ai->ai_socktype = SOCK_STREAM;
+ ai->ai_protocol = 0;
+ ai->ai_addrlen = sizeof(struct sockaddr_in);
+ if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen)))
+ return (-1);
+ sa = (struct sockaddr_in*)ai->ai_addr;
+ memset(sa, 0, ai->ai_addrlen);
+ if (he) {
+ sa->sin_family = he->h_addrtype;
+ memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length);
+ } else {
+ sa->sin_family = AF_INET;
+ sa->sin_addr.s_addr = INADDR_ANY;
+ }
+ ai->ai_next = NULL;
+ return (0);
+}
+static void
+fake_freeaddrinfo(struct addrinfo *ai)
+{
+ free(ai->ai_addr);
+}
+#endif
+
+#ifndef MIN
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#endif
+
+/* wrapper for setting the base from the rtsp server */
+#define EVRTSP_BASE_SET(x, y) do { \
+ if ((x)->base != NULL) event_base_set((x)->base, y); \
+} while (0)
+
+extern int debug;
+
+static int socket_connect(int fd, const char *address, unsigned short port);
+static int bind_socket_ai(int family, struct addrinfo *, int reuse);
+static int bind_socket(int family, const char *, u_short, int reuse);
+static void name_from_addr(struct sockaddr *, socklen_t, char **, char **);
+static void evrtsp_connection_start_detectclose(
+ struct evrtsp_connection *evcon);
+static void evrtsp_connection_stop_detectclose(
+ struct evrtsp_connection *evcon);
+static void evrtsp_request_dispatch(struct evrtsp_connection* evcon);
+static void evrtsp_read_firstline(struct evrtsp_connection *evcon,
+ struct evrtsp_request *req);
+static void evrtsp_read_header(struct evrtsp_connection *evcon,
+ struct evrtsp_request *req);
+static int evrtsp_add_header_internal(struct evkeyvalq *headers,
+ const char *key, const char *value);
+
+void evrtsp_read(int, short, void *);
+void evrtsp_write(int, short, void *);
+
+#ifndef _EVENT_HAVE_STRSEP
+/* strsep replacement for platforms that lack it. Only works if
+ * del is one character long. */
+static char *
+strsep(char **s, const char *del)
+{
+ char *d, *tok;
+ assert(strlen(del) == 1);
+ if (!s || !*s)
+ return NULL;
+ tok = *s;
+ d = strstr(tok, del);
+ if (d) {
+ *d = '\0';
+ *s = d + 1;
+ } else
+ *s = NULL;
+ return tok;
+}
+#endif
+
+const char *
+evrtsp_method(enum evrtsp_cmd_type type)
+{
+ const char *method;
+
+ switch (type) {
+ case EVRTSP_REQ_ANNOUNCE:
+ method = "ANNOUNCE";
+ break;
+
+ case EVRTSP_REQ_OPTIONS:
+ method = "OPTIONS";
+ break;
+
+ case EVRTSP_REQ_SETUP:
+ method = "SETUP";
+ break;
+
+ case EVRTSP_REQ_RECORD:
+ method = "RECORD";
+ break;
+
+ case EVRTSP_REQ_PAUSE:
+ method = "PAUSE";
+ break;
+
+ case EVRTSP_REQ_GET_PARAMETER:
+ method = "GET_PARAMETER";
+ break;
+
+ case EVRTSP_REQ_SET_PARAMETER:
+ method = "SET_PARAMETER";
+ break;
+
+ case EVRTSP_REQ_FLUSH:
+ method = "FLUSH";
+ break;
+
+ case EVRTSP_REQ_TEARDOWN:
+ method = "TEARDOWN";
+ break;
+
+ default:
+ method = NULL;
+ break;
+ }
+
+ return (method);
+}
+
+static void
+evrtsp_add_event(struct event *ev, int timeout, int default_timeout)
+{
+ if (timeout != 0) {
+ struct timeval tv;
+
+ evutil_timerclear(&tv);
+ tv.tv_sec = timeout != -1 ? timeout : default_timeout;
+ event_add(ev, &tv);
+ } else {
+ event_add(ev, NULL);
+ }
+}
+
+void
+evrtsp_write_buffer(struct evrtsp_connection *evcon,
+ void (*cb)(struct evrtsp_connection *, void *), void *arg)
+{
+ event_debug(("%s: preparing to write buffer\n", __func__));
+
+ /* Set call back */
+ evcon->cb = cb;
+ evcon->cb_arg = arg;
+
+ /* check if the event is already pending */
+ if (event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL))
+ event_del(&evcon->ev);
+
+ event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_write, evcon);
+ evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_WRITE_TIMEOUT);
+}
+
+static int
+evrtsp_connected(struct evrtsp_connection *evcon)
+{
+ switch (evcon->state) {
+ case EVCON_DISCONNECTED:
+ case EVCON_CONNECTING:
+ return (0);
+ case EVCON_IDLE:
+ case EVCON_READING_FIRSTLINE:
+ case EVCON_READING_HEADERS:
+ case EVCON_READING_BODY:
+ case EVCON_READING_TRAILER:
+ case EVCON_WRITING:
+ default:
+ return (1);
+ }
+}
+
+/*
+ * Create the headers needed for an RTSP request
+ */
+static void
+evrtsp_make_header_request(struct evrtsp_connection *evcon,
+ struct evrtsp_request *req)
+{
+ const char *method;
+
+ /* Generate request line */
+ method = evrtsp_method(req->type);
+ evbuffer_add_printf(evcon->output_buffer, "%s %s RTSP/%d.%d\r\n",
+ method, req->uri, req->major, req->minor);
+
+ /* Content-Length is mandatory, absent means 0 */
+ if ((evbuffer_get_length(req->output_buffer) > 0)
+ && (evrtsp_find_header(req->output_headers, "Content-Length") == NULL))
+ {
+ char size[12];
+ evutil_snprintf(size, sizeof(size), "%ld",
+ (long)evbuffer_get_length(req->output_buffer));
+ evrtsp_add_header(req->output_headers, "Content-Length", size);
+ }
+}
+
+void
+evrtsp_make_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+ struct evkeyval *header;
+
+ evrtsp_make_header_request(evcon, req);
+
+ TAILQ_FOREACH(header, req->output_headers, next) {
+ evbuffer_add_printf(evcon->output_buffer, "%s: %s\r\n",
+ header->key, header->value);
+ }
+ evbuffer_add(evcon->output_buffer, "\r\n", 2);
+
+ if (evbuffer_get_length(req->output_buffer) > 0) {
+ evbuffer_add_buffer(evcon->output_buffer, req->output_buffer);
+ }
+}
+
+/* Separated host, port and file from URI */
+
+int /* FIXME: needed? */
+evrtsp_hostportfile(char *url, char **phost, u_short *pport, char **pfile)
+{
+ /* XXX not threadsafe. */
+ static char host[1024];
+ static char file[1024];
+ char *p;
+ const char *p2;
+ int len;
+ int ret;
+ u_short port;
+
+ len = strlen(RTSP_PREFIX);
+ if (strncasecmp(url, RTSP_PREFIX, len))
+ return (-1);
+
+ url += len;
+
+ /* We might overrun */
+ ret = evutil_snprintf(host, sizeof(host), "%s", url);
+ if ((ret < 0) || (ret >= sizeof(host)))
+ return (-1);
+
+ p = strchr(host, '/');
+ if (p != NULL) {
+ *p = '\0';
+ p2 = p + 1;
+ } else
+ p2 = NULL;
+
+ if (pfile != NULL) {
+ /* Generate request file */
+ if (p2 == NULL)
+ p2 = "";
+ evutil_snprintf(file, sizeof(file), "/%s", p2);
+ }
+
+ p = strchr(host, ':');
+ if (p != NULL) {
+ *p = '\0';
+ port = atoi(p + 1);
+
+ if (port == 0)
+ return (-1);
+ } else
+ return -1;
+
+ if (phost != NULL)
+ *phost = host;
+ if (pport != NULL)
+ *pport = port;
+ if (pfile != NULL)
+ *pfile = file;
+
+ return (0);
+}
+
+void
+evrtsp_connection_fail(struct evrtsp_connection *evcon,
+ enum evrtsp_connection_error error)
+{
+ struct evrtsp_request* req = TAILQ_FIRST(&evcon->requests);
+ void (*cb)(struct evrtsp_request *, void *);
+ void *cb_arg;
+ assert(req != NULL);
+
+ /* save the callback for later; the cb might free our object */
+ cb = req->cb;
+ cb_arg = req->cb_arg;
+
+ TAILQ_REMOVE(&evcon->requests, req, next);
+ evrtsp_request_free(req);
+
+ /* xxx: maybe we should fail all requests??? */
+
+ /* reset the connection */
+ evrtsp_connection_reset(evcon);
+
+ /* We are trying the next request that was queued on us */
+ if (TAILQ_FIRST(&evcon->requests) != NULL)
+ evrtsp_connection_connect(evcon);
+
+ /* inform the user */
+ if (cb != NULL)
+ (*cb)(NULL, cb_arg);
+}
+
+void
+evrtsp_write(int fd, short what, void *arg)
+{
+ struct evrtsp_connection *evcon = arg;
+ int n;
+
+ if (what == EV_TIMEOUT) {
+ evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
+ return;
+ }
+
+ n = evbuffer_write(evcon->output_buffer, fd);
+ if (n == -1) {
+ event_debug(("%s: evbuffer_write", __func__));
+ evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+ return;
+ }
+
+ if (n == 0) {
+ event_debug(("%s: write nothing", __func__));
+ evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+ return;
+ }
+
+ if (evbuffer_get_length(evcon->output_buffer) != 0) {
+ evrtsp_add_event(&evcon->ev,
+ evcon->timeout, RTSP_WRITE_TIMEOUT);
+ return;
+ }
+
+ /* Activate our call back */
+ if (evcon->cb != NULL)
+ (*evcon->cb)(evcon, evcon->cb_arg);
+}
+
+/**
+ * Advance the connection state.
+ * - If this is an outgoing connection, we've just processed the response;
+ * idle or close the connection.
+ */
+static void
+evrtsp_connection_done(struct evrtsp_connection *evcon)
+{
+ struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+
+ /* idle or close the connection */
+ TAILQ_REMOVE(&evcon->requests, req, next);
+ req->evcon = NULL;
+
+ evcon->state = EVCON_IDLE;
+
+ if (TAILQ_FIRST(&evcon->requests) != NULL) {
+ /*
+ * We have more requests; reset the connection
+ * and deal with the next request.
+ */
+ if (!evrtsp_connected(evcon))
+ evrtsp_connection_connect(evcon);
+ else
+ evrtsp_request_dispatch(evcon);
+ } else {
+ /*
+ * The connection is going to be persistent, but we
+ * need to detect if the other side closes it.
+ */
+ evrtsp_connection_start_detectclose(evcon);
+ }
+
+ /* notify the user of the request */
+ (*req->cb)(req, req->cb_arg);
+
+ evrtsp_request_free(req);
+}
+
+static void /* FIXME: needed? */
+evrtsp_read_trailer(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+ struct evbuffer *buf = evcon->input_buffer;
+
+ switch (evrtsp_parse_headers(req, buf)) {
+ case DATA_CORRUPTED:
+ evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+ break;
+ case ALL_DATA_READ:
+ event_del(&evcon->ev);
+ evrtsp_connection_done(evcon);
+ break;
+ case MORE_DATA_EXPECTED:
+ default:
+ evrtsp_add_event(&evcon->ev, evcon->timeout,
+ RTSP_READ_TIMEOUT);
+ break;
+ }
+}
+
+static void
+evrtsp_read_body(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+ struct evbuffer *buf = evcon->input_buffer;
+
+ if (req->ntoread < 0) {
+ /* Read until connection close. */
+ evbuffer_add_buffer(req->input_buffer, buf);
+ } else if (evbuffer_get_length(buf) >= req->ntoread) {
+ /* Completed content length */
+ evbuffer_add(req->input_buffer, evbuffer_pullup(buf,-1),
+ (size_t)req->ntoread);
+ evbuffer_drain(buf, (size_t)req->ntoread);
+ req->ntoread = 0;
+ evrtsp_connection_done(evcon);
+ return;
+ }
+ /* Read more! */
+ event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon);
+ evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT);
+}
+
+/*
+ * Reads data into a buffer structure until no more data
+ * can be read on the file descriptor or we have read all
+ * the data that we wanted to read.
+ * Execute callback when done.
+ */
+
+void
+evrtsp_read(int fd, short what, void *arg)
+{
+ struct evrtsp_connection *evcon = arg;
+ struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+ struct evbuffer *buf = evcon->input_buffer;
+ int n;
+
+ if (what == EV_TIMEOUT) {
+ evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
+ return;
+ }
+ n = evbuffer_read(buf, fd, -1);
+ event_debug(("%s: got %d on %d\n", __func__, n, fd));
+
+ if (n == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ event_debug(("%s: evbuffer_read", __func__));
+ evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
+ } else {
+ evrtsp_add_event(&evcon->ev, evcon->timeout,
+ RTSP_READ_TIMEOUT);
+ }
+ return;
+ } else if (n == 0) {
+ /* Connection closed */
+ evcon->state = EVCON_DISCONNECTED;
+ evrtsp_connection_done(evcon);
+ return;
+ }
+
+ switch (evcon->state) {
+ case EVCON_READING_FIRSTLINE:
+ evrtsp_read_firstline(evcon, req);
+ break;
+ case EVCON_READING_HEADERS:
+ evrtsp_read_header(evcon, req);
+ break;
+ case EVCON_READING_BODY:
+ evrtsp_read_body(evcon, req);
+ break;
+ case EVCON_READING_TRAILER:
+ evrtsp_read_trailer(evcon, req);
+ break;
+ case EVCON_DISCONNECTED:
+ case EVCON_CONNECTING:
+ case EVCON_IDLE:
+ case EVCON_WRITING:
+ default:
+ event_errx(1, "%s: illegal connection state %d",
+ __func__, evcon->state);
+ }
+}
+
+static void
+evrtsp_write_connectioncb(struct evrtsp_connection *evcon, void *arg)
+{
+ /* This is after writing the request to the server */
+ struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+ assert(req != NULL);
+
+ assert(evcon->state == EVCON_WRITING);
+
+ /* We are done writing our header and are now expecting the response */
+ req->kind = EVRTSP_RESPONSE;
+
+ evrtsp_start_read(evcon);
+}
+
+
+
+/*
+ * Clean up a connection object
+ */
+
+void
+evrtsp_connection_free(struct evrtsp_connection *evcon)
+{
+ struct evrtsp_request *req;
+
+ /* notify interested parties that this connection is going down */
+ if (evcon->fd != -1) {
+ if (evrtsp_connected(evcon) && evcon->closecb != NULL)
+ (*evcon->closecb)(evcon, evcon->closecb_arg);
+ }
+
+ /* remove all requests that might be queued on this connection */
+ while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) {
+ TAILQ_REMOVE(&evcon->requests, req, next);
+ evrtsp_request_free(req);
+ }
+
+ if (event_initialized(&evcon->close_ev))
+ event_del(&evcon->close_ev);
+
+ if (event_initialized(&evcon->ev))
+ event_del(&evcon->ev);
+
+ if (evcon->fd != -1)
+ EVUTIL_CLOSESOCKET(evcon->fd);
+
+ if (evcon->bind_address != NULL)
+ free(evcon->bind_address);
+
+ if (evcon->address != NULL)
+ free(evcon->address);
+
+ if (evcon->input_buffer != NULL)
+ evbuffer_free(evcon->input_buffer);
+
+ if (evcon->output_buffer != NULL)
+ evbuffer_free(evcon->output_buffer);
+
+ free(evcon);
+}
+
+static void
+evrtsp_request_dispatch(struct evrtsp_connection* evcon)
+{
+ struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+
+ /* this should not usually happy but it's possible */
+ if (req == NULL)
+ return;
+
+ /* delete possible close detection events */
+ evrtsp_connection_stop_detectclose(evcon);
+
+ /* we assume that the connection is connected already */
+ assert(evcon->state == EVCON_IDLE);
+
+ evcon->state = EVCON_WRITING;
+
+ /* Create the header from the store arguments */
+ evrtsp_make_header(evcon, req);
+
+ evrtsp_write_buffer(evcon, evrtsp_write_connectioncb, NULL);
+}
+
+/* Reset our connection state */
+void
+evrtsp_connection_reset(struct evrtsp_connection *evcon)
+{
+ if (event_initialized(&evcon->ev))
+ event_del(&evcon->ev);
+
+ if (evcon->fd != -1) {
+ /* inform interested parties about connection close */
+ if (evrtsp_connected(evcon) && evcon->closecb != NULL)
+ (*evcon->closecb)(evcon, evcon->closecb_arg);
+
+ EVUTIL_CLOSESOCKET(evcon->fd);
+ evcon->fd = -1;
+ }
+ evcon->state = EVCON_DISCONNECTED;
+
+ evbuffer_drain(evcon->input_buffer,
+ evbuffer_get_length(evcon->input_buffer));
+ evbuffer_drain(evcon->output_buffer,
+ evbuffer_get_length(evcon->output_buffer));
+}
+
+static void
+evrtsp_detect_close_cb(int fd, short what, void *arg)
+{
+ struct evrtsp_connection *evcon = arg;
+
+ evrtsp_connection_reset(evcon);
+}
+
+static void
+evrtsp_connection_start_detectclose(struct evrtsp_connection *evcon)
+{
+ evcon->flags |= EVRTSP_CON_CLOSEDETECT;
+
+ if (event_initialized(&evcon->close_ev))
+ event_del(&evcon->close_ev);
+ event_assign(&evcon->close_ev, evcon->base, evcon->fd, EV_READ,
+ evrtsp_detect_close_cb, evcon);
+ event_add(&evcon->close_ev, NULL);
+}
+
+static void
+evrtsp_connection_stop_detectclose(struct evrtsp_connection *evcon)
+{
+ evcon->flags &= ~EVRTSP_CON_CLOSEDETECT;
+ event_del(&evcon->close_ev);
+}
+
+/*
+ * Call back for asynchronous connection attempt.
+ */
+
+static void
+evrtsp_connectioncb(int fd, short what, void *arg)
+{
+ struct evrtsp_connection *evcon = arg;
+ int error;
+ socklen_t errsz = sizeof(error);
+
+ if (what == EV_TIMEOUT) {
+ event_debug(("%s: connection timeout for \"%s:%d\" on %d",
+ __func__, evcon->address, evcon->port, evcon->fd));
+ goto cleanup;
+ }
+
+ /* Check if the connection completed */
+ if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+ &errsz) == -1) {
+ event_debug(("%s: getsockopt for \"%s:%d\" on %d",
+ __func__, evcon->address, evcon->port, evcon->fd));
+ goto cleanup;
+ }
+
+ if (error) {
+ event_debug(("%s: connect failed for \"%s:%d\" on %d: %s",
+ __func__, evcon->address, evcon->port, evcon->fd,
+ strerror(error)));
+ goto cleanup;
+ }
+
+ /* We are connected to the server now */
+ event_debug(("%s: connected to \"%s:%d\" on %d\n",
+ __func__, evcon->address, evcon->port, evcon->fd));
+
+ evcon->state = EVCON_IDLE;
+
+ /* try to start requests that have queued up on this connection */
+ evrtsp_request_dispatch(evcon);
+ return;
+
+ cleanup:
+ evrtsp_connection_reset(evcon);
+
+ /* for now, we just signal all requests by executing their callbacks */
+ while (TAILQ_FIRST(&evcon->requests) != NULL) {
+ struct evrtsp_request *request = TAILQ_FIRST(&evcon->requests);
+ TAILQ_REMOVE(&evcon->requests, request, next);
+ request->evcon = NULL;
+
+ /* we might want to set an error here */
+ request->cb(request, request->cb_arg);
+ evrtsp_request_free(request);
+ }
+}
+
+/*
+ * Check if we got a valid response code.
+ */
+
+static int
+evrtsp_valid_response_code(int code)
+{
+ if (code == 0)
+ return (0);
+
+ return (1);
+}
+
+/* Parses the status line of an RTSP server */
+
+static int
+evrtsp_parse_response_line(struct evrtsp_request *req, char *line)
+{
+ char *protocol;
+ char *number;
+ const char *readable = "";
+
+ protocol = strsep(&line, " ");
+ if (line == NULL)
+ return (-1);
+ number = strsep(&line, " ");
+ if (line != NULL)
+ readable = line;
+
+ if (strcmp(protocol, "RTSP/1.0") == 0) {
+ req->major = 1;
+ req->minor = 0;
+ } else if (strcmp(protocol, "RTSP/1.1") == 0) {
+ req->major = 1;
+ req->minor = 1;
+ } else {
+ event_debug(("%s: bad protocol \"%s\"",
+ __func__, protocol));
+ return (-1);
+ }
+
+ req->response_code = atoi(number);
+ if (!evrtsp_valid_response_code(req->response_code)) {
+ event_debug(("%s: bad response code \"%s\"",
+ __func__, number));
+ return (-1);
+ }
+
+ if ((req->response_code_line = strdup(readable)) == NULL)
+ event_err(1, "%s: strdup", __func__);
+
+ return (0);
+}
+
+const char *
+evrtsp_find_header(const struct evkeyvalq *headers, const char *key)
+{
+ struct evkeyval *header;
+
+ TAILQ_FOREACH(header, headers, next) {
+ if (strcasecmp(header->key, key) == 0)
+ return (header->value);
+ }
+
+ return (NULL);
+}
+
+void
+evrtsp_clear_headers(struct evkeyvalq *headers)
+{
+ struct evkeyval *header;
+
+ for (header = TAILQ_FIRST(headers);
+ header != NULL;
+ header = TAILQ_FIRST(headers)) {
+ TAILQ_REMOVE(headers, header, next);
+ free(header->key);
+ free(header->value);
+ free(header);
+ }
+}
+
+/*
+ * Returns 0, if the header was successfully removed.
+ * Returns -1, if the header could not be found.
+ */
+
+int
+evrtsp_remove_header(struct evkeyvalq *headers, const char *key)
+{
+ struct evkeyval *header;
+
+ TAILQ_FOREACH(header, headers, next) {
+ if (strcasecmp(header->key, key) == 0)
+ break;
+ }
+
+ if (header == NULL)
+ return (-1);
+
+ /* Free and remove the header that we found */
+ TAILQ_REMOVE(headers, header, next);
+ free(header->key);
+ free(header->value);
+ free(header);
+
+ return (0);
+}
+
+static int
+evrtsp_header_is_valid_value(const char *value)
+{
+ const char *p = value;
+
+ while ((p = strpbrk(p, "\r\n")) != NULL) {
+ /* we really expect only one new line */
+ p += strspn(p, "\r\n");
+ /* we expect a space or tab for continuation */
+ if (*p != ' ' && *p != '\t')
+ return (0);
+ }
+ return (1);
+}
+
+int
+evrtsp_add_header(struct evkeyvalq *headers,
+ const char *key, const char *value)
+{
+ event_debug(("%s: key: %s val: %s\n", __func__, key, value));
+
+ if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) {
+ /* drop illegal headers */
+ event_debug(("%s: dropping illegal header key\n", __func__));
+ return (-1);
+ }
+
+ if (!evrtsp_header_is_valid_value(value)) {
+ event_debug(("%s: dropping illegal header value\n", __func__));
+ return (-1);
+ }
+
+ return (evrtsp_add_header_internal(headers, key, value));
+}
+
+static int
+evrtsp_add_header_internal(struct evkeyvalq *headers,
+ const char *key, const char *value)
+{
+ struct evkeyval *header = calloc(1, sizeof(struct evkeyval));
+
+ if (header == NULL) {
+ event_warn("%s: calloc", __func__);
+ return (-1);
+ }
+ if ((header->key = strdup(key)) == NULL) {
+ free(header);
+ event_warn("%s: strdup", __func__);
+ return (-1);
+ }
+ if ((header->value = strdup(value)) == NULL) {
+ free(header->key);
+ free(header);
+ event_warn("%s: strdup", __func__);
+ return (-1);
+ }
+
+ TAILQ_INSERT_TAIL(headers, header, next);
+
+ return (0);
+}
+
+/*
+ * Parses header lines from a request or a response into the specified
+ * request object given an event buffer.
+ *
+ * Returns
+ * DATA_CORRUPTED on error
+ * MORE_DATA_EXPECTED when we need to read more headers
+ * ALL_DATA_READ when all headers have been read.
+ */
+
+enum message_read_status
+evrtsp_parse_firstline(struct evrtsp_request *req, struct evbuffer *buffer)
+{
+ char *line;
+ enum message_read_status status = ALL_DATA_READ;
+
+ line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY);
+ if (line == NULL)
+ return (MORE_DATA_EXPECTED);
+
+ switch (req->kind) {
+ case EVRTSP_RESPONSE:
+ if (evrtsp_parse_response_line(req, line) == -1)
+ status = DATA_CORRUPTED;
+ break;
+ default:
+ status = DATA_CORRUPTED;
+ }
+
+ free(line);
+ return (status);
+}
+
+static int
+evrtsp_append_to_last_header(struct evkeyvalq *headers, const char *line)
+{
+ struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq);
+ char *newval;
+ size_t old_len, line_len;
+
+ if (header == NULL)
+ return (-1);
+
+ old_len = strlen(header->value);
+ line_len = strlen(line);
+
+ newval = realloc(header->value, old_len + line_len + 1);
+ if (newval == NULL)
+ return (-1);
+
+ memcpy(newval + old_len, line, line_len + 1);
+ header->value = newval;
+
+ return (0);
+}
+
+enum message_read_status
+evrtsp_parse_headers(struct evrtsp_request *req, struct evbuffer *buffer)
+{
+ char *line;
+ enum message_read_status status = MORE_DATA_EXPECTED;
+
+ struct evkeyvalq *headers = req->input_headers;
+ while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF))
+ != NULL) {
+ char *skey, *svalue;
+
+ if (*line == '\0') { /* Last header - Done */
+ status = ALL_DATA_READ;
+ free(line);
+ break;
+ }
+
+ /* Check if this is a continuation line */
+ if (*line == ' ' || *line == '\t') {
+ if (evrtsp_append_to_last_header(headers, line) == -1)
+ goto error;
+ free(line);
+ continue;
+ }
+
+ /* Processing of header lines */
+ svalue = line;
+ skey = strsep(&svalue, ":");
+ if (svalue == NULL)
+ goto error;
+
+ svalue += strspn(svalue, " ");
+
+ if (evrtsp_add_header(headers, skey, svalue) == -1)
+ goto error;
+
+ free(line);
+ }
+
+ return (status);
+
+ error:
+ free(line);
+ return (DATA_CORRUPTED);
+}
+
+static int
+evrtsp_get_body_length(struct evrtsp_request *req)
+{
+ struct evkeyvalq *headers = req->input_headers;
+ const char *content_length;
+
+ content_length = evrtsp_find_header(headers, "Content-Length");
+
+ if (content_length == NULL) {
+ /* If there is no Content-Length: header, a value of 0 is assumed, per spec. */
+ req->ntoread = 0;
+ } else {
+ char *endp;
+ ev_int64_t ntoread = evutil_strtoll(content_length, &endp, 10);
+ if (*content_length == '\0' || *endp != '\0' || ntoread < 0) {
+ event_debug(("%s: illegal content length: %s",
+ __func__, content_length));
+ return (-1);
+ }
+ req->ntoread = ntoread;
+ }
+
+ event_debug(("%s: bytes to read: %lld (in buffer %ld)\n",
+ __func__, req->ntoread,
+ evbuffer_get_length(req->evcon->input_buffer)));
+
+ return (0);
+}
+
+static void
+evrtsp_get_body(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+ evcon->state = EVCON_READING_BODY;
+ if (evrtsp_get_body_length(req) == -1) {
+ evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+ return;
+ }
+
+ evrtsp_read_body(evcon, req);
+}
+
+static void
+evrtsp_read_firstline(struct evrtsp_connection *evcon,
+ struct evrtsp_request *req)
+{
+ enum message_read_status res;
+
+ res = evrtsp_parse_firstline(req, evcon->input_buffer);
+ if (res == DATA_CORRUPTED) {
+ /* Error while reading, terminate */
+ event_debug(("%s: bad header lines on %d\n",
+ __func__, evcon->fd));
+ evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+ return;
+ } else if (res == MORE_DATA_EXPECTED) {
+ /* Need more header lines */
+ evrtsp_add_event(&evcon->ev,
+ evcon->timeout, RTSP_READ_TIMEOUT);
+ return;
+ }
+
+ evcon->state = EVCON_READING_HEADERS;
+ evrtsp_read_header(evcon, req);
+}
+
+static void
+evrtsp_read_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
+{
+ enum message_read_status res;
+ int fd = evcon->fd;
+
+ res = evrtsp_parse_headers(req, evcon->input_buffer);
+ if (res == DATA_CORRUPTED) {
+ /* Error while reading, terminate */
+ event_debug(("%s: bad header lines on %d\n", __func__, fd));
+ evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+ return;
+ } else if (res == MORE_DATA_EXPECTED) {
+ /* Need more header lines */
+ evrtsp_add_event(&evcon->ev,
+ evcon->timeout, RTSP_READ_TIMEOUT);
+ return;
+ }
+
+ /* Done reading headers, do the real work */
+ switch (req->kind) {
+ case EVRTSP_RESPONSE:
+ event_debug(("%s: start of read body on %d\n",
+ __func__, fd));
+ evrtsp_get_body(evcon, req);
+ break;
+
+ default:
+ event_warnx("%s: bad header on %d", __func__, fd);
+ evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+ break;
+ }
+}
+
+/*
+ * Creates a TCP connection to the specified port and executes a callback
+ * when finished. Failure or sucess is indicate by the passed connection
+ * object.
+ *
+ * Although this interface accepts a hostname, it is intended to take
+ * only numeric hostnames so that non-blocking DNS resolution can
+ * happen elsewhere.
+ */
+
+struct evrtsp_connection *
+evrtsp_connection_new(const char *address, unsigned short port)
+{
+ struct evrtsp_connection *evcon = NULL;
+ char *intf;
+ char *addr;
+ unsigned char scratch[16];
+ int family;
+
+ if ((addr = strdup(address)) == NULL) {
+ event_warn("%s: strdup failed", __func__);
+ goto error;
+ }
+
+ intf = strchr(addr, '%');
+ if (intf)
+ *intf = '\0';
+
+ if (inet_pton(AF_INET6, addr, scratch) == 1)
+ family = AF_INET6;
+ else if (inet_pton(AF_INET, addr, scratch) == 1)
+ family = AF_INET;
+ else {
+ free(addr);
+ event_warn("%s: address is neither IPv6 nor IPv4", __func__);
+ return NULL;
+ }
+
+ if (intf)
+ *intf = '%';
+
+ event_debug(("Attempting connection to %s:%d\n", address, port));
+
+ if ((evcon = calloc(1, sizeof(struct evrtsp_connection))) == NULL) {
+ free(addr);
+ event_warn("%s: calloc failed", __func__);
+ goto error;
+ }
+
+ evcon->fd = -1;
+ evcon->port = port;
+
+ evcon->timeout = -1;
+
+ evcon->cseq = 1;
+
+ evcon->family = family;
+ evcon->address = addr;
+
+ if ((evcon->input_buffer = evbuffer_new()) == NULL) {
+ event_warn("%s: evbuffer_new failed", __func__);
+ goto error;
+ }
+
+ if ((evcon->output_buffer = evbuffer_new()) == NULL) {
+ event_warn("%s: evbuffer_new failed", __func__);
+ goto error;
+ }
+
+ evcon->state = EVCON_DISCONNECTED;
+ TAILQ_INIT(&evcon->requests);
+
+ return (evcon);
+
+ error:
+ if (evcon != NULL)
+ evrtsp_connection_free(evcon);
+ return (NULL);
+}
+
+void evrtsp_connection_set_base(struct evrtsp_connection *evcon,
+ struct event_base *base)
+{
+ assert(evcon->base == NULL);
+ assert(evcon->state == EVCON_DISCONNECTED);
+ evcon->base = base;
+}
+
+void
+evrtsp_connection_set_timeout(struct evrtsp_connection *evcon,
+ int timeout_in_secs)
+{
+ evcon->timeout = timeout_in_secs;
+}
+
+void
+evrtsp_connection_set_closecb(struct evrtsp_connection *evcon,
+ void (*cb)(struct evrtsp_connection *, void *), void *cbarg)
+{
+ evcon->closecb = cb;
+ evcon->closecb_arg = cbarg;
+}
+
+void
+evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
+ char **address, u_short *port)
+{
+ union {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } addr;
+ socklen_t slen;
+ int ret;
+
+ *address = NULL;
+ *port = 0;
+
+ if (!evrtsp_connected(evcon))
+ return;
+
+ slen = sizeof(struct sockaddr_storage);
+ ret = getsockname(evcon->fd, &addr.sa, &slen);
+ if (ret < 0)
+ return;
+
+ name_from_addr(&addr.sa, slen, address, NULL);
+
+ if (!*address)
+ return;
+
+ switch (addr.ss.ss_family)
+ {
+ case AF_INET:
+ *port = ntohs(addr.sin.sin_port);
+ break;
+
+#ifdef AF_INET6
+ case AF_INET6:
+ *port = ntohs(addr.sin6.sin6_port);
+ break;
+#endif
+
+ default:
+ free(*address);
+ address = NULL;
+
+ event_err(1, "%s: unhandled address family\n", __func__);
+ return;
+ }
+}
+
+void
+evrtsp_connection_get_peer(struct evrtsp_connection *evcon,
+ char **address, u_short *port)
+{
+ *address = evcon->address;
+ *port = evcon->port;
+}
+
+int
+evrtsp_connection_connect(struct evrtsp_connection *evcon)
+{
+ if (evcon->state == EVCON_CONNECTING)
+ return (0);
+
+ evrtsp_connection_reset(evcon);
+
+ evcon->fd = bind_socket(evcon->family,
+ evcon->bind_address, evcon->bind_port, 0 /*reuse*/);
+ if (evcon->fd == -1) {
+ event_debug(("%s: failed to bind to \"%s\"",
+ __func__, evcon->bind_address));
+ return (-1);
+ }
+
+ if (socket_connect(evcon->fd, evcon->address, evcon->port) == -1) {
+ EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1;
+ return (-1);
+ }
+
+ /* Set up a callback for successful connection setup */
+ event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_connectioncb, evcon);
+ evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_CONNECT_TIMEOUT);
+
+ evcon->state = EVCON_CONNECTING;
+
+ return (0);
+}
+
+/*
+ * Starts an RTSP request on the provided evrtsp_connection object.
+ * If the connection object is not connected to the server already,
+ * this will start the connection.
+ */
+
+int
+evrtsp_make_request(struct evrtsp_connection *evcon,
+ struct evrtsp_request *req,
+ enum evrtsp_cmd_type type, const char *uri)
+{
+ /* We are making a request */
+ req->kind = EVRTSP_REQUEST;
+ req->type = type;
+ if (req->uri != NULL)
+ free(req->uri);
+ if ((req->uri = strdup(uri)) == NULL)
+ event_err(1, "%s: strdup", __func__);
+
+ /* Set the protocol version if it is not supplied */
+ if (!req->major && !req->minor) {
+ req->major = 1;
+ req->minor = 0;
+ }
+
+ assert(req->evcon == NULL);
+ req->evcon = evcon;
+ assert(!(req->flags & EVRTSP_REQ_OWN_CONNECTION));
+
+ TAILQ_INSERT_TAIL(&evcon->requests, req, next);
+
+ /* If the connection object is not connected; make it so */
+ if (!evrtsp_connected(evcon))
+ return (evrtsp_connection_connect(evcon));
+
+ /*
+ * If it's connected already and we are the first in the queue,
+ * then we can dispatch this request immediately. Otherwise, it
+ * will be dispatched once the pending requests are completed.
+ */
+ if (TAILQ_FIRST(&evcon->requests) == req)
+ evrtsp_request_dispatch(evcon);
+
+ return (0);
+}
+
+/*
+ * Reads data from file descriptor into request structure
+ * Request structure needs to be set up correctly.
+ */
+
+void
+evrtsp_start_read(struct evrtsp_connection *evcon)
+{
+ /* Set up an event to read the headers */
+ if (event_initialized(&evcon->ev))
+ event_del(&evcon->ev);
+ event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon);
+ evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT);
+ evcon->state = EVCON_READING_FIRSTLINE;
+}
+
+static void
+evrtsp_send_done(struct evrtsp_connection *evcon, void *arg)
+{
+ struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests);
+ TAILQ_REMOVE(&evcon->requests, req, next);
+
+ /* delete possible close detection events */
+ evrtsp_connection_stop_detectclose(evcon);
+
+ assert(req->flags & EVRTSP_REQ_OWN_CONNECTION);
+ evrtsp_request_free(req);
+}
+
+/* Requires that headers and response code are already set up */
+
+static inline void
+evrtsp_send(struct evrtsp_request *req, struct evbuffer *databuf)
+{
+ struct evrtsp_connection *evcon = req->evcon;
+
+ if (evcon == NULL) {
+ evrtsp_request_free(req);
+ return;
+ }
+
+ assert(TAILQ_FIRST(&evcon->requests) == req);
+
+ /* xxx: not sure if we really should expose the data buffer this way */
+ if (databuf != NULL)
+ evbuffer_add_buffer(req->output_buffer, databuf);
+
+ /* Adds headers to the response */
+ evrtsp_make_header(evcon, req);
+
+ evrtsp_write_buffer(evcon, evrtsp_send_done, NULL);
+}
+
+/*
+ * Request related functions
+ */
+
+struct evrtsp_request *
+evrtsp_request_new(void (*cb)(struct evrtsp_request *, void *), void *arg)
+{
+ struct evrtsp_request *req = NULL;
+
+ /* Allocate request structure */
+ if ((req = calloc(1, sizeof(struct evrtsp_request))) == NULL) {
+ event_warn("%s: calloc", __func__);
+ goto error;
+ }
+
+ req->kind = EVRTSP_RESPONSE;
+ req->input_headers = calloc(1, sizeof(struct evkeyvalq));
+ if (req->input_headers == NULL) {
+ event_warn("%s: calloc", __func__);
+ goto error;
+ }
+ TAILQ_INIT(req->input_headers);
+
+ req->output_headers = calloc(1, sizeof(struct evkeyvalq));
+ if (req->output_headers == NULL) {
+ event_warn("%s: calloc", __func__);
+ goto error;
+ }
+ TAILQ_INIT(req->output_headers);
+
+ if ((req->input_buffer = evbuffer_new()) == NULL) {
+ event_warn("%s: evbuffer_new", __func__);
+ goto error;
+ }
+
+ if ((req->output_buffer = evbuffer_new()) == NULL) {
+ event_warn("%s: evbuffer_new", __func__);
+ goto error;
+ }
+
+ req->cb = cb;
+ req->cb_arg = arg;
+
+ return (req);
+
+ error:
+ if (req != NULL)
+ evrtsp_request_free(req);
+ return (NULL);
+}
+
+void
+evrtsp_request_free(struct evrtsp_request *req)
+{
+ if (req->uri != NULL)
+ free(req->uri);
+ if (req->response_code_line != NULL)
+ free(req->response_code_line);
+
+ evrtsp_clear_headers(req->input_headers);
+ free(req->input_headers);
+
+ evrtsp_clear_headers(req->output_headers);
+ free(req->output_headers);
+
+ if (req->input_buffer != NULL)
+ evbuffer_free(req->input_buffer);
+
+ if (req->output_buffer != NULL)
+ evbuffer_free(req->output_buffer);
+
+ free(req);
+}
+
+/*
+ * Allows for inspection of the request URI
+ */
+
+const char *
+evrtsp_request_uri(struct evrtsp_request *req) {
+ if (req->uri == NULL)
+ event_debug(("%s: request %p has no uri\n", __func__, req));
+ return (req->uri);
+}
+
+/*
+ * Network helper functions that we do not want to export to the rest of
+ * the world.
+ */
+#if 0 /* Unused */
+static struct addrinfo *
+addr_from_name(char *address)
+{
+#ifdef _EVENT_HAVE_GETADDRINFO
+ struct addrinfo ai, *aitop;
+ int ai_result;
+
+ memset(&ai, 0, sizeof(ai));
+ ai.ai_family = AF_INET;
+ ai.ai_socktype = SOCK_RAW;
+ ai.ai_flags = 0;
+ if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) {
+ if ( ai_result == EAI_SYSTEM )
+ event_warn("getaddrinfo");
+ else
+ event_warnx("getaddrinfo: %s", gai_strerror(ai_result));
+ }
+
+ return (aitop);
+#else
+ assert(0);
+ return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */
+#endif
+}
+#endif
+
+static void
+name_from_addr(struct sockaddr *sa, socklen_t salen,
+ char **phost, char **pport)
+{
+ char ntop[NI_MAXHOST];
+ char strport[NI_MAXSERV];
+ int ni_result;
+
+#ifdef _EVENT_HAVE_GETNAMEINFO
+ ni_result = getnameinfo(sa, salen,
+ ntop, sizeof(ntop), strport, sizeof(strport),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+
+ if (ni_result != 0) {
+ if (ni_result == EAI_SYSTEM)
+ event_err(1, "getnameinfo failed");
+ else
+ event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result));
+ return;
+ }
+#else
+ ni_result = fake_getnameinfo(sa, salen,
+ ntop, sizeof(ntop), strport, sizeof(strport),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ if (ni_result != 0)
+ return;
+#endif
+ if (phost)
+ *phost = strdup(ntop);
+ if (pport)
+ *pport = strdup(strport);
+}
+
+/* Create a non-blocking socket and bind it */
+/* todo: rename this function */
+static int
+bind_socket_ai(int family, struct addrinfo *ai, int reuse)
+{
+ int fd, on = 1, r;
+ int serrno;
+
+ if (ai)
+ family = ai->ai_family;
+
+ /* Create listen socket */
+ fd = socket(family, SOCK_STREAM, 0);
+ if (fd == -1) {
+ event_warn("socket");
+ return (-1);
+ }
+
+ if (evutil_make_socket_nonblocking(fd) < 0)
+ goto out;
+
+#ifndef WIN32
+ if (fcntl(fd, F_SETFD, 1) == -1) {
+ event_warn("fcntl(F_SETFD)");
+ goto out;
+ }
+#endif
+
+ if (family == AF_INET6)
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+
+ setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
+ if (reuse) {
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ (void *)&on, sizeof(on));
+ }
+
+ if (ai != NULL) {
+ r = bind(fd, ai->ai_addr, ai->ai_addrlen);
+ if (r == -1)
+ goto out;
+ }
+
+ return (fd);
+
+ out:
+ serrno = EVUTIL_SOCKET_ERROR();
+ EVUTIL_CLOSESOCKET(fd);
+ EVUTIL_SET_SOCKET_ERROR(serrno);
+ return (-1);
+}
+
+static struct addrinfo *
+make_addrinfo(const char *address, u_short port)
+{
+ struct addrinfo *aitop = NULL;
+
+#ifdef _EVENT_HAVE_GETADDRINFO
+ struct addrinfo ai;
+ char strport[NI_MAXSERV];
+ int ai_result;
+
+ memset(&ai, 0, sizeof(ai));
+ ai.ai_family = AF_UNSPEC;
+ ai.ai_socktype = SOCK_STREAM;
+ ai.ai_flags = AI_PASSIVE; /* turn NULL host name into INADDR_ANY */
+ evutil_snprintf(strport, sizeof(strport), "%d", port);
+ if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) {
+ if ( ai_result == EAI_SYSTEM )
+ event_warn("getaddrinfo");
+ else
+ event_warnx("getaddrinfo: %s", gai_strerror(ai_result));
+ return (NULL);
+ }
+#else
+ static int cur;
+ static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */
+ if (++cur == 2) cur = 0; /* allow calling this function twice */
+
+ if (fake_getaddrinfo(address, &ai[cur]) < 0) {
+ event_warn("fake_getaddrinfo");
+ return (NULL);
+ }
+ aitop = &ai[cur];
+ ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port);
+#endif
+
+ return (aitop);
+}
+
+static int
+bind_socket(int family, const char *address, u_short port, int reuse)
+{
+ int fd;
+ struct addrinfo *aitop = NULL;
+
+ /* just create an unbound socket */
+ if (address == NULL && port == 0)
+ return bind_socket_ai(family, NULL, 0);
+
+ aitop = make_addrinfo(address, port);
+
+ if (aitop == NULL)
+ return (-1);
+
+ fd = bind_socket_ai(family, aitop, reuse);
+
+#ifdef _EVENT_HAVE_GETADDRINFO
+ freeaddrinfo(aitop);
+#else
+ fake_freeaddrinfo(aitop);
+#endif
+
+ return (fd);
+}
+
+static int
+socket_connect(int fd, const char *address, unsigned short port)
+{
+ struct addrinfo *ai = make_addrinfo(address, port);
+ int res = -1;
+
+ if (ai == NULL) {
+ event_debug(("%s: make_addrinfo: \"%s:%d\"",
+ __func__, address, port));
+ return (-1);
+ }
+
+ if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+#ifdef WIN32
+ int tmp_error = WSAGetLastError();
+ if (tmp_error != WSAEWOULDBLOCK && tmp_error != WSAEINVAL &&
+ tmp_error != WSAEINPROGRESS) {
+ goto out;
+ }
+#else
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+#endif
+ }
+
+ /* everything is fine */
+ res = 0;
+
+out:
+#ifdef _EVENT_HAVE_GETADDRINFO
+ freeaddrinfo(ai);
+#else
+ fake_freeaddrinfo(ai);
+#endif
+
+ return (res);
+}
diff --git a/src/filescanner.c b/src/filescanner.c
index 75069169..5a6e9248 100644
--- a/src/filescanner.c
+++ b/src/filescanner.c
@@ -525,7 +525,11 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
else if (type & F_SCAN_TYPE_URL)
{
mfi->data_kind = 1; /* url/stream */
+#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
+ ret = scan_metadata_ffmpeg(path, mfi);
+#else
ret = scan_metadata_icy(path, mfi);
+#endif
}
else if (type & F_SCAN_TYPE_SPOTIFY)
{
diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c
index a57340c5..f38f7297 100644
--- a/src/filescanner_ffmpeg.c
+++ b/src/filescanner_ffmpeg.c
@@ -32,6 +32,7 @@
#include
#include
+#include
#include "logger.h"
#include "filescanner.h"
@@ -313,10 +314,85 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
return mdcount;
}
+/* Extracts ICY metadata (requires libav 10: libavformat 55.13) */
+static void
+extract_metadata_icy(struct media_file_info *mfi, AVFormatContext *ctx)
+{
+ uint8_t *icy_meta;
+ char *icy_token;
+ char *icy_str;
+ char *ptr;
+
+ icy_meta = NULL;
+ // TODO Also get icy_metadata_packet to show current track
+ av_opt_get(ctx, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN, &icy_meta);
+
+ if (!icy_meta)
+ return;
+
+ icy_str = strdup((char *)icy_meta);
+ icy_token = strtok(icy_str, "\r\n");
+
+ while (icy_token != NULL)
+ {
+ ptr = strchr(icy_token, ':');
+ if (!ptr || (strlen(ptr) < 4))
+ {
+ icy_token = strtok(NULL, "\r\n");
+ continue;
+ }
+
+ ptr++;
+ if (ptr[0] == ' ')
+ ptr++;
+
+ if (strstr(icy_token, "icy-name"))
+ {
+ DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, name is '%s'\n", ptr);
+
+ if (mfi->title)
+ free(mfi->title);
+ if (mfi->artist)
+ free(mfi->artist);
+ if (mfi->album_artist)
+ free(mfi->album_artist);
+
+ mfi->title = strdup(ptr);
+ mfi->artist = strdup(ptr);
+ mfi->album_artist = strdup(ptr);
+ }
+
+ if (strstr(icy_token, "icy-description"))
+ {
+ DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, description is '%s'\n", ptr);
+
+ if (mfi->album)
+ free(mfi->album);
+
+ mfi->album = strdup(ptr);
+ }
+
+ if (strstr(icy_token, "icy-genre"))
+ {
+ DPRINTF(E_DBG, L_SCAN, "Libav/ffmpeg found ICY metadata, genre is '%s'\n", ptr);
+
+ if (mfi->genre)
+ free(mfi->genre);
+
+ mfi->genre = strdup(ptr);
+ }
+
+ icy_token = strtok(NULL, "\r\n");
+ }
+ av_free(icy_meta);
+ free(icy_str);
+}
+
int
scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
{
AVFormatContext *ctx;
+ AVDictionary *options;
const struct metadata_map *extra_md_map;
#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35)
enum AVCodecID codec_id;
@@ -334,9 +410,13 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
int ret;
ctx = NULL;
+ options = NULL;
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
- ret = avformat_open_input(&ctx, file, NULL, NULL);
+ if (mfi->data_kind == 1)
+ av_dict_set(&options, "icy", "1", 0);
+
+ ret = avformat_open_input(&ctx, file, NULL, &options);
#else
ret = av_open_input_file(&ctx, file, NULL, 0, NULL);
#endif
@@ -462,6 +542,10 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate);
+ /* Try to extract ICY metadata if url/stream */
+ if (mfi->data_kind == 1)
+ extract_metadata_icy(mfi, ctx);
+
/* Get some more information on the audio stream */
if (audio_stream)
{
diff --git a/src/filescanner_icy.c b/src/filescanner_icy.c
index 1c12847d..23507963 100644
--- a/src/filescanner_icy.c
+++ b/src/filescanner_icy.c
@@ -1,9 +1,6 @@
/*
* Copyright (C) 2009-2010 Julien BLACHE
*
- * Rewritten from mt-daapd code:
- * Copyright (C) 2003 Ron Pedde (ron@pedde.com)
- *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -37,16 +34,17 @@
#include
#include
#include
+#include
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#include
#endif
#include
-#ifdef HAVE_LIBEVENT2
-# include
+#if defined HAVE_LIBEVENT2
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
#include
@@ -71,15 +69,16 @@ struct icy_ctx
char hostname[PATH_MAX];
char path[PATH_MAX];
int port;
+
+ char *icy_name;
+ char *icy_description;
+ char *icy_genre;
+
+ pthread_mutex_t lck;
+ pthread_cond_t cond;
};
-static void
-free_icy(struct icy_ctx *ctx)
-{
- if (ctx)
- free(ctx);
-}
-
+#ifndef HAVE_LIBEVENT2
static int
resolve_address(char *hostname, char *s, size_t maxlen)
{
@@ -102,62 +101,81 @@ resolve_address(char *hostname, char *s, size_t maxlen)
default:
strncpy(s, "Unknown AF", maxlen);
+ freeaddrinfo(result);
return -1;
}
freeaddrinfo(result);
return 0;
}
+#endif
+#ifndef HAVE_LIBEVENT2_OLD
static void
scan_icy_request_cb(struct evhttp_request *req, void *arg)
{
- DPRINTF(E_DBG, L_SCAN, "ICY metadata request completed\n");
+ struct icy_ctx *ctx;
+
+ ctx = (struct icy_ctx *)arg;
+
+ pthread_mutex_lock(&ctx->lck);
+
+ DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Signal callback\n");
status = ICY_DONE;
- return;
+ pthread_cond_signal(&ctx->cond);
+ pthread_mutex_unlock(&ctx->lck);
}
/* Will always return -1 to make evhttp close the connection - we only need the http headers */
static int
scan_icy_header_cb(struct evhttp_request *req, void *arg)
{
- struct media_file_info *mfi;
+ struct icy_ctx *ctx;
+ struct evkeyvalq *headers;
const char *ptr;
- mfi = (struct media_file_info *)arg;
+ ctx = (struct icy_ctx *)arg;
- if ( (ptr = evhttp_find_header(req->input_headers, "icy-name")) )
+ DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Headers received\n");
+
+ headers = evhttp_request_get_input_headers(req);
+ if ( (ptr = evhttp_find_header(headers, "icy-name")) )
{
- mfi->title = strdup(ptr);
- mfi->artist = strdup(ptr);
- DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name (title/artist) is %s\n", mfi->title);
+ ctx->icy_name = strdup(ptr);
+ DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is %s\n", ctx->icy_name);
}
- if ( (ptr = evhttp_find_header(req->input_headers, "icy-description")) )
+ if ( (ptr = evhttp_find_header(headers, "icy-description")) )
{
- mfi->album = strdup(ptr);
- DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description (album) is %s\n", mfi->album);
+ ctx->icy_description = strdup(ptr);
+ DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is %s\n", ctx->icy_description);
}
- if ( (ptr = evhttp_find_header(req->input_headers, "icy-genre")) )
+ if ( (ptr = evhttp_find_header(headers, "icy-genre")) )
{
- mfi->genre = strdup(ptr);
- DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", mfi->genre);
+ ctx->icy_genre = strdup(ptr);
+ DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", ctx->icy_genre);
}
- status = ICY_DONE;
return -1;
}
+#endif
int
scan_metadata_icy(char *url, struct media_file_info *mfi)
{
- struct evhttp_connection *evcon;
- struct evhttp_request *req;
struct icy_ctx *ctx;
+ struct evhttp_connection *evcon;
+#ifndef HAVE_LIBEVENT2_OLD
+ struct evhttp_request *req;
+ struct evkeyvalq *headers;
+ char s[PATH_MAX];
+#endif
+ time_t start;
+ time_t end;
int ret;
- int i;
status = ICY_INIT;
+ start = time(NULL);
/* We can set this straight away */
mfi->url = strdup(url);
@@ -167,10 +185,13 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for ICY metadata context\n");
- goto no_icy;
+ return -1;
}
memset(ctx, 0, sizeof(struct icy_ctx));
+ pthread_mutex_init(&ctx->lck, NULL);
+ pthread_cond_init(&ctx->cond, NULL);
+
ctx->url = url;
/* TODO https */
@@ -185,6 +206,21 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
if (ctx->port < 0)
ctx->port = 80;
+ if (strlen(ctx->path) == 0)
+ {
+ ctx->path[0] = '/';
+ ctx->path[1] = '\0';
+ }
+
+#ifdef HAVE_LIBEVENT2
+ evcon = evhttp_connection_base_new(evbase_main, NULL, ctx->hostname, (unsigned short)ctx->port);
+ if (!evcon)
+ {
+ DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname);
+
+ goto no_icy;
+ }
+#else
/* Resolve IP address */
ret = resolve_address(ctx->hostname, ctx->address, sizeof(ctx->address));
if (ret < 0)
@@ -205,37 +241,45 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
goto no_icy;
}
evhttp_connection_set_base(evcon, evbase_main);
+#endif
+
+#ifdef HAVE_LIBEVENT2_OLD
+ DPRINTF(E_LOG, L_SCAN, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4 or libav 10)\n", ctx->hostname);
+#else
evhttp_connection_set_timeout(evcon, ICY_TIMEOUT);
/* Set up request */
- req = evhttp_request_new(scan_icy_request_cb, mfi);
+ req = evhttp_request_new(scan_icy_request_cb, ctx);
if (!req)
{
DPRINTF(E_LOG, L_SCAN, "Could not create request to %s\n", ctx->hostname);
- evhttp_connection_free(evcon);
goto no_icy;
}
- req->header_cb = scan_icy_header_cb;
- evhttp_add_header(req->output_headers, "Host", ctx->hostname);
- evhttp_add_header(req->output_headers, "Icy-MetaData", "1");
+
+ evhttp_request_set_header_cb(req, scan_icy_header_cb);
+
+ headers = evhttp_request_get_output_headers(req);
+ snprintf(s, PATH_MAX, "%s:%d", ctx->hostname, ctx->port);
+ evhttp_add_header(headers, "Host", s);
+ evhttp_add_header(headers, "Icy-MetaData", "1");
/* Make request */
+ DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", ctx->hostname);
+
status = ICY_WAITING;
ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, ctx->path);
if (ret < 0)
{
- DPRINTF(E_LOG, L_SCAN, "Could not make request to %s\n", ctx->hostname);
+ DPRINTF(E_LOG, L_SCAN, "Error making request to %s\n", ctx->hostname);
status = ICY_DONE;
- evhttp_connection_free(evcon);
goto no_icy;
}
- DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", url);
+#endif
/* Can't count on server support for ICY metadata, so
* while waiting for a reply make a parallel call to scan_metadata_ffmpeg.
- * This call will also determine final return value.
*/
no_icy:
ret = scan_metadata_ffmpeg(url, mfi);
@@ -247,13 +291,58 @@ scan_metadata_icy(char *url, struct media_file_info *mfi)
mfi->description = strdup("MPEG audio file");
}
- /* Wait till ICY request completes or we reach timeout */
- for (i = 0; (status == ICY_WAITING) && (i <= ICY_TIMEOUT); i++)
- sleep(1);
+ /* Wait for ICY request to complete or timeout */
+ pthread_mutex_lock(&ctx->lck);
- free_icy(ctx);
+ if (status == ICY_WAITING)
+ pthread_cond_wait(&ctx->cond, &ctx->lck);
- DPRINTF(E_DBG, L_SCAN, "scan_metadata_icy exiting with status %d after waiting %d sec\n", status, i);
+ pthread_mutex_unlock(&ctx->lck);
+
+ /* Copy result to mfi */
+ if (ctx->icy_name)
+ {
+ if (mfi->title)
+ free(mfi->title);
+ if (mfi->artist)
+ free(mfi->artist);
+ if (mfi->album_artist)
+ free(mfi->album_artist);
+
+ mfi->title = strdup(ctx->icy_name);
+ mfi->artist = strdup(ctx->icy_name);
+ mfi->album_artist = strdup(ctx->icy_name);
+
+ free(ctx->icy_name);
+ }
+
+ if (ctx->icy_description)
+ {
+ if (mfi->album)
+ free(mfi->album);
+
+ mfi->album = ctx->icy_description;
+ }
+
+ if (ctx->icy_genre)
+ {
+ if (mfi->genre)
+ free(mfi->genre);
+
+ mfi->genre = ctx->icy_genre;
+ }
+
+ /* Clean up */
+ if (evcon)
+ evhttp_connection_free(evcon);
+
+ pthread_cond_destroy(&ctx->cond);
+ pthread_mutex_destroy(&ctx->lck);
+ free(ctx);
+
+ end = time(NULL);
+
+ DPRINTF(E_DBG, L_SCAN, "ICY metadata scan of %s completed in %.f sec\n", url, difftime(end, start));
return 1;
}
diff --git a/src/filescanner_itunes.c b/src/filescanner_itunes.c
index 28262771..da577202 100644
--- a/src/filescanner_itunes.c
+++ b/src/filescanner_itunes.c
@@ -38,7 +38,7 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
# include "evhttp/evhttp.h"
#endif
diff --git a/src/httpd.c b/src/httpd.c
index 8cff6c1f..ed339749 100644
--- a/src/httpd.c
+++ b/src/httpd.c
@@ -53,7 +53,6 @@
#include "httpd_dacp.h"
#include "transcode.h"
-
/*
* HTTP client quirks by User-Agent, from mt-daapd
*
@@ -123,12 +122,19 @@ static struct event exitev;
static struct evhttp *evhttpd;
static pthread_t tid_httpd;
+#ifdef HAVE_LIBEVENT2_OLD
+struct stream_ctx *g_st;
+#endif
static void
stream_end(struct stream_ctx *st, int failed)
{
- if (st->req->evcon)
- evhttp_connection_set_closecb(st->req->evcon, NULL, NULL);
+ struct evhttp_connection *evcon;
+
+ evcon = evhttp_request_get_connection(st->req);
+
+ if (evcon)
+ evhttp_connection_set_closecb(evcon, NULL, NULL);
if (!failed)
evhttp_send_reply_end(st->req);
@@ -143,6 +149,11 @@ stream_end(struct stream_ctx *st, int failed)
close(st->fd);
}
+#ifdef HAVE_LIBEVENT2_OLD
+ if (g_st == st)
+ g_st = NULL;
+#endif
+
free(st);
}
@@ -177,6 +188,15 @@ stream_chunk_resched_cb(struct evhttp_connection *evcon, void *arg)
}
}
+#ifdef HAVE_LIBEVENT2_OLD
+static void
+stream_chunk_resched_cb_wrapper(struct bufferevent *bufev, void *arg)
+{
+ if (g_st)
+ stream_chunk_resched_cb(NULL, g_st);
+}
+#endif
+
static void
stream_chunk_xcode_cb(int fd, short event, void *arg)
{
@@ -224,7 +244,17 @@ stream_chunk_xcode_cb(int fd, short event, void *arg)
else
ret = xcoded;
+#ifdef HAVE_LIBEVENT2_OLD
+ evhttp_send_reply_chunk(st->req, st->evbuf);
+
+ struct evhttp_connection *evcon = evhttp_request_get_connection(st->req);
+ struct bufferevent *bufev = evhttp_connection_get_bufferevent(evcon);
+
+ g_st = st; // Can't pass st to callback so use global - limits libevent 2.0 to a single stream
+ bufev->writecb = stream_chunk_resched_cb_wrapper;
+#else
evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
+#endif
st->offset += ret;
@@ -280,7 +310,17 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
evbuffer_add(st->evbuf, st->buf, ret);
+#ifdef HAVE_LIBEVENT2_OLD
+ evhttp_send_reply_chunk(st->req, st->evbuf);
+
+ struct evhttp_connection *evcon = evhttp_request_get_connection(st->req);
+ struct bufferevent *bufev = evhttp_connection_get_bufferevent(evcon);
+
+ g_st = st; // Can't pass st to callback so use global - limits libevent 2.0 to a single stream
+ bufev->writecb = stream_chunk_resched_cb_wrapper;
+#else
evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
+#endif
st->offset += ret;
@@ -312,6 +352,9 @@ httpd_stream_file(struct evhttp_request *req, int id)
void (*stream_cb)(int fd, short event, void *arg);
struct stat sb;
struct timeval tv;
+ struct evhttp_connection *evcon;
+ struct evkeyvalq *input_headers;
+ struct evkeyvalq *output_headers;
const char *param;
const char *param_end;
char buf[64];
@@ -323,7 +366,10 @@ httpd_stream_file(struct evhttp_request *req, int id)
offset = 0;
end_offset = 0;
- param = evhttp_find_header(req->input_headers, "Range");
+
+ input_headers = evhttp_request_get_input_headers(req);
+
+ param = evhttp_find_header(input_headers, "Range");
if (param)
{
DPRINTF(E_DBG, L_HTTPD, "Found Range header: %s\n", param);
@@ -385,7 +431,9 @@ httpd_stream_file(struct evhttp_request *req, int id)
memset(st, 0, sizeof(struct stream_ctx));
st->fd = -1;
- transcode = transcode_needed(req->input_headers, mfi->codectype);
+ transcode = transcode_needed(input_headers, mfi->codectype);
+
+ output_headers = evhttp_request_get_output_headers(req);
if (transcode)
{
@@ -403,8 +451,8 @@ httpd_stream_file(struct evhttp_request *req, int id)
goto out_free_st;
}
- if (!evhttp_find_header(req->output_headers, "Content-Type"))
- evhttp_add_header(req->output_headers, "Content-Type", "audio/wav");
+ if (!evhttp_find_header(output_headers, "Content-Type"))
+ evhttp_add_header(output_headers, "Content-Type", "audio/wav");
}
else
{
@@ -468,21 +516,21 @@ httpd_stream_file(struct evhttp_request *req, int id)
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
else
{
- evhttp_remove_header(req->output_headers, "Content-Type");
- evhttp_add_header(req->output_headers, "Content-Type", buf);
+ evhttp_remove_header(output_headers, "Content-Type");
+ evhttp_add_header(output_headers, "Content-Type", buf);
}
}
/* If no Content-Type has been set and we're streaming audio, add a proper
* Content-Type for the file we're streaming. Remember DAAP streams audio
* with application/x-dmap-tagged as the Content-Type (ugh!).
*/
- else if (!evhttp_find_header(req->output_headers, "Content-Type") && mfi->type)
+ else if (!evhttp_find_header(output_headers, "Content-Type") && mfi->type)
{
ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
else
- evhttp_add_header(req->output_headers, "Content-Type", buf);
+ evhttp_add_header(output_headers, "Content-Type", buf);
}
}
@@ -491,7 +539,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
{
DPRINTF(E_LOG, L_HTTPD, "Could not allocate an evbuffer for streaming\n");
- evhttp_clear_headers(req->output_headers);
+ evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
@@ -502,7 +550,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
{
DPRINTF(E_LOG, L_HTTPD, "Could not expand evbuffer for streaming\n");
- evhttp_clear_headers(req->output_headers);
+ evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
@@ -516,7 +564,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
{
DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");
- evhttp_clear_headers(req->output_headers);
+ evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
@@ -539,7 +587,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
else
- evhttp_add_header(req->output_headers, "Content-Length", buf);
+ evhttp_add_header(output_headers, "Content-Length", buf);
}
evhttp_send_reply_start(req, HTTP_OK, "OK");
@@ -558,13 +606,13 @@ httpd_stream_file(struct evhttp_request *req, int id)
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Range too large for buffer, dropping\n");
else
- evhttp_add_header(req->output_headers, "Content-Range", buf);
+ evhttp_add_header(output_headers, "Content-Range", buf);
ret = snprintf(buf, sizeof(buf), "%" PRIi64, ((end_offset) ? end_offset + 1 : (int64_t)st->size) - offset);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
else
- evhttp_add_header(req->output_headers, "Content-Length", buf);
+ evhttp_add_header(output_headers, "Content-Length", buf);
evhttp_send_reply_start(req, 206, "Partial Content");
}
@@ -579,7 +627,9 @@ httpd_stream_file(struct evhttp_request *req, int id)
}
#endif
- evhttp_connection_set_closecb(req->evcon, stream_fail_cb, st);
+ evcon = evhttp_request_get_connection(req);
+
+ evhttp_connection_set_closecb(evcon, stream_fail_cb, st);
DPRINTF(E_INFO, L_HTTPD, "Kicking off streaming for %s\n", mfi->path);
@@ -609,6 +659,7 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
unsigned char outbuf[128 * 1024];
z_stream strm;
struct evbuffer *gzbuf;
+ struct evkeyvalq *headers;
const char *param;
int flush;
int zret;
@@ -621,7 +672,9 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
goto no_gzip;
}
- param = evhttp_find_header(req->input_headers, "Accept-Encoding");
+ headers = evhttp_request_get_input_headers(req);
+
+ param = evhttp_find_header(headers, "Accept-Encoding");
if (!param)
{
DPRINTF(E_DBG, L_HTTPD, "Not gzipping; no Accept-Encoding header\n");
@@ -702,7 +755,9 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
deflateEnd(&strm);
- evhttp_add_header(req->output_headers, "Content-Encoding", "gzip");
+ headers = evhttp_request_get_output_headers(req);
+
+ evhttp_add_header(headers, "Content-Encoding", "gzip");
evhttp_send_reply(req, code, reason, gzbuf);
evbuffer_free(gzbuf);
@@ -731,6 +786,7 @@ path_is_legal(char *path)
static void
redirect_to_index(struct evhttp_request *req, char *uri)
{
+ struct evkeyvalq *headers;
char buf[256];
int slashed;
int ret;
@@ -746,7 +802,9 @@ redirect_to_index(struct evhttp_request *req, char *uri)
return;
}
- evhttp_add_header(req->output_headers, "Location", buf);
+ headers = evhttp_request_get_output_headers(req);
+
+ evhttp_add_header(headers, "Location", buf);
evhttp_send_reply(req, HTTP_MOVETEMP, "Moved", NULL);
}
@@ -754,12 +812,14 @@ redirect_to_index(struct evhttp_request *req, char *uri)
static void
serve_file(struct evhttp_request *req, char *uri)
{
+ const char *host;
char *ext;
char path[PATH_MAX];
char *deref;
char *ctype;
char *passwd;
struct evbuffer *evbuf;
+ struct evkeyvalq *headers;
struct stat sb;
int fd;
int i;
@@ -779,8 +839,9 @@ serve_file(struct evhttp_request *req, char *uri)
}
else
{
- if ((strcmp(req->remote_host, "::1") != 0)
- && (strcmp(req->remote_host, "127.0.0.1") != 0))
+ host = evhttp_request_get_host(req);
+ if ((strcmp(host, "::1") != 0)
+ && (strcmp(host, "127.0.0.1") != 0))
{
DPRINTF(E_LOG, L_HTTPD, "Remote web interface request denied; no password set\n");
@@ -910,8 +971,9 @@ serve_file(struct evhttp_request *req, char *uri)
}
}
- evhttp_add_header(req->output_headers, "Content-Type", ctype);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "Content-Type", ctype);
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
evbuffer_free(evbuf);
@@ -925,7 +987,7 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
char *uri;
char *ptr;
- req_uri = evhttp_request_uri(req);
+ req_uri = evhttp_request_get_uri(req);
if (!req_uri)
{
redirect_to_index(req, "/");
@@ -1011,6 +1073,7 @@ exit_cb(int fd, short event, void *arg)
char *
httpd_fixup_uri(struct evhttp_request *req)
{
+ struct evkeyvalq *headers;
const char *ua;
const char *uri;
const char *u;
@@ -1019,7 +1082,7 @@ httpd_fixup_uri(struct evhttp_request *req)
char *f;
int len;
- uri = evhttp_request_uri(req);
+ uri = evhttp_request_get_uri(req);
if (!uri)
return NULL;
@@ -1028,7 +1091,8 @@ httpd_fixup_uri(struct evhttp_request *req)
if (!q)
return strdup(uri);
- ua = evhttp_find_header(req->input_headers, "User-Agent");
+ headers = evhttp_request_get_input_headers(req);
+ ua = evhttp_find_header(headers, "User-Agent");
if (!ua)
return strdup(uri);
@@ -1093,6 +1157,7 @@ int
httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *realm)
{
struct evbuffer *evbuf;
+ struct evkeyvalq *headers;
char *header;
const char *auth;
char *authuser;
@@ -1100,7 +1165,8 @@ httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *rea
int len;
int ret;
- auth = evhttp_find_header(req->input_headers, "Authorization");
+ headers = evhttp_request_get_input_headers(req);
+ auth = evhttp_find_header(headers, "Authorization");
if (!auth)
{
DPRINTF(E_DBG, L_HTTPD, "No Authorization header\n");
@@ -1183,7 +1249,8 @@ httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *rea
return -1;
}
- evhttp_add_header(req->output_headers, "WWW-Authenticate", header);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "WWW-Authenticate", header);
evbuffer_add(evbuf, http_reply_401, strlen(http_reply_401));
evhttp_send_reply(req, 401, "Unauthorized", evbuf);
diff --git a/src/httpd.h b/src/httpd.h
index 3258cedb..9fa90c2e 100644
--- a/src/httpd.h
+++ b/src/httpd.h
@@ -4,9 +4,9 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
void
diff --git a/src/httpd_daap.c b/src/httpd_daap.c
index 82a1fcc5..a4720311 100644
--- a/src/httpd_daap.c
+++ b/src/httpd_daap.c
@@ -51,6 +51,10 @@
#include "daap_query.h"
#include "dmap_common.h"
+#ifdef HAVE_LIBEVENT2
+# include
+#endif
+
/* httpd event base, from httpd.c */
extern struct event_base *evbase_httpd;
@@ -313,6 +317,7 @@ static void
update_refresh_cb(int fd, short event, void *arg)
{
struct daap_update_request *ur;
+ struct evhttp_connection *evcon;
struct evbuffer *evbuf;
int ret;
@@ -339,7 +344,8 @@ update_refresh_cb(int fd, short event, void *arg)
dmap_add_int(evbuf, "mstt", 200); /* 12 */
dmap_add_int(evbuf, "musr", current_rev); /* 12 */
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
+ evcon = evhttp_request_get_connection(ur->req);
+ evhttp_connection_set_closecb(evcon, NULL, NULL);
httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf);
@@ -350,14 +356,16 @@ update_refresh_cb(int fd, short event, void *arg)
static void
update_fail_cb(struct evhttp_connection *evcon, void *arg)
{
+ struct evhttp_connection *evc;
struct daap_update_request *ur;
ur = (struct daap_update_request *)arg;
DPRINTF(E_DBG, L_DAAP, "Update request: client closed connection\n");
- if (ur->req->evcon)
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
+ evc = evhttp_request_get_connection(ur->req);
+ if (evc)
+ evhttp_connection_set_closecb(evc, NULL, NULL);
update_remove(ur);
update_free(ur);
@@ -725,6 +733,7 @@ static void
daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
{
struct evbuffer *content;
+ struct evkeyvalq *headers;
cfg_t *lib;
char *name;
char *passwd;
@@ -748,7 +757,8 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char
mpro = 2 << 16 | 10;
apro = 3 << 16 | 12;
- clientver = evhttp_find_header(req->input_headers, "Client-DAAP-Version");
+ headers = evhttp_request_get_input_headers(req);
+ clientver = evhttp_find_header(headers, "Client-DAAP-Version");
if (clientver)
{
if (strcmp(clientver, "1.0") == 0)
@@ -859,6 +869,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
{
struct pairing_info pi;
struct daap_session *s;
+ struct evkeyvalq *headers;
const char *ua;
const char *param;
int request_session_id;
@@ -873,7 +884,8 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
return;
}
- ua = evhttp_find_header(req->input_headers, "User-Agent");
+ headers = evhttp_request_get_input_headers(req);
+ ua = evhttp_find_header(headers, "User-Agent");
if (ua && (strncmp(ua, "Remote", strlen("Remote")) == 0))
{
param = evhttp_find_header(query, "pairing-guid");
@@ -949,6 +961,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
struct timeval tv;
struct daap_session *s;
struct daap_update_request *ur;
+ struct evhttp_connection *evcon;
const char *param;
int reqd_rev;
int ret;
@@ -1035,7 +1048,9 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
/* If the connection fails before we have an update to push out
* to the client, we need to know.
*/
- evhttp_connection_set_closecb(req->evcon, update_fail_cb, ur);
+ evcon = evhttp_request_get_connection(req);
+ if (evcon)
+ evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
}
static void
@@ -1107,6 +1122,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
struct db_media_file_info dbmfi;
struct evbuffer *song;
struct evbuffer *songlist;
+ struct evkeyvalq *headers;
const struct dmap_field **meta;
struct sort_ctx *sctx;
const char *param;
@@ -1244,7 +1260,8 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
{
nsongs++;
- transcode = transcode_needed(req->input_headers, dbmfi.codectype);
+ headers = evhttp_request_get_input_headers(req);
+ transcode = transcode_needed(headers, dbmfi.codectype);
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers, transcode);
if (ret < 0)
@@ -2121,6 +2138,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
{
char clen[32];
struct daap_session *s;
+ struct evkeyvalq *headers;
const char *param;
char *ctype;
int id;
@@ -2191,10 +2209,11 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
goto no_artwork;
}
- evhttp_remove_header(req->output_headers, "Content-Type");
- evhttp_add_header(req->output_headers, "Content-Type", ctype);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_remove_header(headers, "Content-Type");
+ evhttp_add_header(headers, "Content-Type", ctype);
snprintf(clen, sizeof(clen), "%ld", (long)EVBUFFER_LENGTH(evbuf));
- evhttp_add_header(req->output_headers, "Content-Length", clen);
+ evhttp_add_header(headers, "Content-Length", clen);
/* No gzip compression for artwork */
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
@@ -2435,6 +2454,7 @@ daap_request(struct evhttp_request *req)
char *uri_parts[7];
struct evbuffer *evbuf;
struct evkeyvalq query;
+ struct evkeyvalq *headers;
const char *ua;
cfg_t *lib;
char *libname;
@@ -2532,7 +2552,8 @@ daap_request(struct evhttp_request *req)
* valid session-id that Remote can only obtain if its pairing-guid is in
* our database. So HTTP authentication is waived for Remote.
*/
- ua = evhttp_find_header(req->input_headers, "User-Agent");
+ headers = evhttp_request_get_input_headers(req);
+ ua = evhttp_find_header(headers, "User-Agent");
if ((ua) && (strncmp(ua, "Remote", strlen("Remote")) == 0))
passwd = NULL;
@@ -2587,13 +2608,14 @@ daap_request(struct evhttp_request *req)
evhttp_parse_query(full_uri, &query);
- evhttp_add_header(req->output_headers, "Accept-Ranges", "bytes");
- evhttp_add_header(req->output_headers, "DAAP-Server", "forked-daapd/" VERSION);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "Accept-Ranges", "bytes");
+ evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
/* Content-Type for all replies, even the actual audio streaming. Note that
* video streaming will override this Content-Type with a more appropriate
* video/ Content-Type as expected by clients like Front Row.
*/
- evhttp_add_header(req->output_headers, "Content-Type", "application/x-dmap-tagged");
+ evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
daap_handlers[handler].handler(req, evbuf, uri_parts, &query);
@@ -2679,6 +2701,7 @@ void
daap_deinit(void)
{
struct daap_update_request *ur;
+ struct evhttp_connection *evcon;
int i;
for (i = 0; daap_handlers[i].handler; i++)
@@ -2690,10 +2713,11 @@ daap_deinit(void)
{
update_requests = ur->next;
- if (ur->req->evcon)
+ evcon = evhttp_request_get_connection(ur->req);
+ if (evcon)
{
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
- evhttp_connection_free(ur->req->evcon);
+ evhttp_connection_set_closecb(evcon, NULL, NULL);
+ evhttp_connection_free(evcon);
}
update_free(ur);
diff --git a/src/httpd_daap.h b/src/httpd_daap.h
index 4633e87c..f04c3398 100644
--- a/src/httpd_daap.h
+++ b/src/httpd_daap.h
@@ -4,9 +4,9 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
int
diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c
index eba3959c..707f7123 100644
--- a/src/httpd_dacp.c
+++ b/src/httpd_dacp.c
@@ -47,7 +47,6 @@
#include "db.h"
#include "player.h"
-
/* httpd event base, from httpd.c */
extern struct event_base *evbase_httpd;
@@ -268,6 +267,7 @@ playstatusupdate_cb(int fd, short what, void *arg)
struct dacp_update_request *ur;
struct evbuffer *evbuf;
struct evbuffer *update;
+ struct evhttp_connection *evcon;
int ret;
#ifdef USE_EVENTFD
@@ -313,7 +313,9 @@ playstatusupdate_cb(int fd, short what, void *arg)
{
update_requests = ur->next;
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
+ evcon = evhttp_request_get_connection(ur->req);
+ if (evcon)
+ evhttp_connection_set_closecb(evcon, NULL, NULL);
evbuffer_add(evbuf, EVBUFFER_DATA(update), EVBUFFER_LENGTH(update));
@@ -358,13 +360,15 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg)
{
struct dacp_update_request *ur;
struct dacp_update_request *p;
+ struct evhttp_connection *evc;
ur = (struct dacp_update_request *)arg;
DPRINTF(E_DBG, L_DACP, "Update request: client closed connection\n");
- if (ur->req->evcon)
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
+ evc = evhttp_request_get_connection(ur->req);
+ if (evc)
+ evhttp_connection_set_closecb(evc, NULL, NULL);
if (ur == update_requests)
update_requests = ur->next;
@@ -1678,6 +1682,7 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf,
{
struct daap_session *s;
struct dacp_update_request *ur;
+ struct evhttp_connection *evcon;
const char *param;
int reqd_rev;
int ret;
@@ -1733,7 +1738,9 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf,
/* If the connection fails before we have an update to push out
* to the client, we need to know.
*/
- evhttp_connection_set_closecb(req->evcon, update_fail_cb, ur);
+ evcon = evhttp_request_get_connection(req);
+ if (evcon)
+ evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
}
static void
@@ -1741,6 +1748,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
{
char clen[32];
struct daap_session *s;
+ struct evkeyvalq *headers;
const char *param;
char *ctype;
uint32_t id;
@@ -1810,10 +1818,11 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
goto no_artwork;
}
- evhttp_remove_header(req->output_headers, "Content-Type");
- evhttp_add_header(req->output_headers, "Content-Type", ctype);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_remove_header(headers, "Content-Type");
+ evhttp_add_header(headers, "Content-Type", ctype);
snprintf(clen, sizeof(clen), "%ld", (long)EVBUFFER_LENGTH(evbuf));
- evhttp_add_header(req->output_headers, "Content-Length", clen);
+ evhttp_add_header(headers, "Content-Length", clen);
/* No gzip compression for artwork */
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
@@ -2216,6 +2225,7 @@ dacp_request(struct evhttp_request *req)
char *uri_parts[7];
struct evbuffer *evbuf;
struct evkeyvalq query;
+ struct evkeyvalq *headers;
int handler;
int ret;
int i;
@@ -2307,9 +2317,10 @@ dacp_request(struct evhttp_request *req)
evhttp_parse_query(full_uri, &query);
- evhttp_add_header(req->output_headers, "DAAP-Server", "forked-daapd/" VERSION);
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
/* Content-Type for all DACP replies; can be overriden as needed */
- evhttp_add_header(req->output_headers, "Content-Type", "application/x-dmap-tagged");
+ evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
dacp_handlers[handler].handler(req, evbuf, uri_parts, &query);
@@ -2401,6 +2412,7 @@ void
dacp_deinit(void)
{
struct dacp_update_request *ur;
+ struct evhttp_connection *evcon;
int i;
player_set_update_handler(NULL);
@@ -2412,10 +2424,11 @@ dacp_deinit(void)
{
update_requests = ur->next;
- if (ur->req->evcon)
+ evcon = evhttp_request_get_connection(ur->req);
+ if (evcon)
{
- evhttp_connection_set_closecb(ur->req->evcon, NULL, NULL);
- evhttp_connection_free(ur->req->evcon);
+ evhttp_connection_set_closecb(evcon, NULL, NULL);
+ evhttp_connection_free(evcon);
}
free(ur);
diff --git a/src/httpd_dacp.h b/src/httpd_dacp.h
index dcee87bc..8963e696 100644
--- a/src/httpd_dacp.h
+++ b/src/httpd_dacp.h
@@ -4,9 +4,9 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
int
diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c
index 30392d76..e16438e8 100644
--- a/src/httpd_rsp.c
+++ b/src/httpd_rsp.c
@@ -42,7 +42,6 @@
#include "httpd_rsp.h"
#include "rsp_query.h"
-
#define RSP_VERSION "1.0"
#define RSP_XML_ROOT "?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?"
@@ -219,6 +218,7 @@ static void
rsp_send_error(struct evhttp_request *req, char *errmsg)
{
struct evbuffer *evbuf;
+ struct evkeyvalq *headers;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *node;
@@ -254,8 +254,9 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
return;
}
- evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=utf-8");
- evhttp_add_header(req->output_headers, "Connection", "close");
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
+ evhttp_add_header(headers, "Connection", "close");
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
evbuffer_free(evbuf);
@@ -265,6 +266,7 @@ static void
rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
{
struct evbuffer *evbuf;
+ struct evkeyvalq *headers;
evbuf = mxml_to_evbuf(reply);
mxmlDelete(reply);
@@ -276,8 +278,9 @@ rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
return;
}
- evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=utf-8");
- evhttp_add_header(req->output_headers, "Connection", "close");
+ headers = evhttp_request_get_output_headers(req);
+ evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
+ evhttp_add_header(headers, "Connection", "close");
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
evbuffer_free(evbuf);
@@ -434,6 +437,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
{
struct query_params qp;
struct db_media_file_info dbmfi;
+ struct evkeyvalq *headers;
const char *param;
char **strval;
mxml_node_t *reply;
@@ -526,7 +530,8 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
/* Items block (all items) */
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
{
- transcode = transcode_needed(req->input_headers, dbmfi.codectype);
+ headers = evhttp_request_get_input_headers(req);
+ transcode = transcode_needed(headers, dbmfi.codectype);
/* Item block (one item) */
item = mxmlNewElement(items, "item");
diff --git a/src/httpd_rsp.h b/src/httpd_rsp.h
index 94629768..4ccfa0ab 100644
--- a/src/httpd_rsp.h
+++ b/src/httpd_rsp.h
@@ -4,9 +4,9 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
int
diff --git a/src/remote_pairing.c b/src/remote_pairing.c
index 1e00cc99..e658ea3e 100644
--- a/src/remote_pairing.c
+++ b/src/remote_pairing.c
@@ -48,9 +48,9 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
-# include "evhttp/evhttp.h"
+# include "evhttp/evhttp_compat.h"
#endif
#include
@@ -414,8 +414,10 @@ static void
pairing_request_cb(struct evhttp_request *req, void *arg)
{
struct remote_info *ri;
+ struct evbuffer *input_buffer;
uint8_t *response;
char guid[17];
+ int response_code;
int len;
int i;
int ret;
@@ -429,21 +431,24 @@ pairing_request_cb(struct evhttp_request *req, void *arg)
goto cleanup;
}
- if (req->response_code != HTTP_OK)
+ response_code = evhttp_request_get_response_code(req);
+ if (response_code != HTTP_OK)
{
- DPRINTF(E_LOG, L_REMOTE, "Pairing failed with Remote %s/%s, HTTP response code %d\n", ri->pi.remote_id, ri->pi.name, req->response_code);
+ DPRINTF(E_LOG, L_REMOTE, "Pairing failed with Remote %s/%s, HTTP response code %d\n", ri->pi.remote_id, ri->pi.name, response_code);
goto cleanup;
}
- if (EVBUFFER_LENGTH(req->input_buffer) < 8)
+ input_buffer = evhttp_request_get_input_buffer(req);
+
+ if (EVBUFFER_LENGTH(input_buffer) < 8)
{
DPRINTF(E_LOG, L_REMOTE, "Remote %s/%s: pairing response too short\n", ri->pi.remote_id, ri->pi.name);
goto cleanup;
}
- response = EVBUFFER_DATA(req->input_buffer);
+ response = EVBUFFER_DATA(input_buffer);
if ((response[0] != 'c') || (response[1] != 'm') || (response[2] != 'p') || (response[3] != 'a'))
{
@@ -453,10 +458,10 @@ pairing_request_cb(struct evhttp_request *req, void *arg)
}
len = (response[4] << 24) | (response[5] << 16) | (response[6] << 8) | (response[7]);
- if (EVBUFFER_LENGTH(req->input_buffer) < 8 + len)
+ if (EVBUFFER_LENGTH(input_buffer) < 8 + len)
{
DPRINTF(E_LOG, L_REMOTE, "Remote %s/%s: pairing response truncated (got %d expected %d)\n",
- ri->pi.remote_id, ri->pi.name, (int)EVBUFFER_LENGTH(req->input_buffer), len + 8);
+ ri->pi.remote_id, ri->pi.name, (int)EVBUFFER_LENGTH(input_buffer), len + 8);
goto cleanup;
}
@@ -536,7 +541,7 @@ send_pairing_request(struct remote_info *ri, char *req_uri, int family)
return -1;
}
- evcon = evhttp_connection_new(address, port);
+ evcon = evhttp_connection_base_new(evbase_main, NULL, address, port);
if (!evcon)
{
DPRINTF(E_LOG, L_REMOTE, "Could not create connection for pairing with %s\n", ri->pi.name);
@@ -544,8 +549,6 @@ send_pairing_request(struct remote_info *ri, char *req_uri, int family)
return -1;
}
- evhttp_connection_set_base(evcon, evbase_main);
-
req = evhttp_request_new(pairing_request_cb, ri);
if (!req)
{
@@ -883,7 +886,7 @@ remote_pairing_read_pin(char *path)
return;
}
- DPRINTF(E_DBG, L_REMOTE, "Adding Remote pin data: name '%s', pin '%s'\n", devname, pin);
+ DPRINTF(E_LOG, L_REMOTE, "Read Remote pairing data (name '%s', pin '%s') from %s\n", devname, pin, path);
pthread_mutex_lock(&remote_lck);
diff --git a/src/transcode.h b/src/transcode.h
index 57d8d7ec..46b38398 100644
--- a/src/transcode.h
+++ b/src/transcode.h
@@ -4,7 +4,7 @@
#include
#ifdef HAVE_LIBEVENT2
-# include
+# include
#else
# include "evhttp/evhttp.h"
#endif