From 02c23b0065317c88e0fdfba1d2b160cb7e9273f0 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 26 May 2014 23:38:19 +0200 Subject: [PATCH 1/9] Better thread sync, cleanup and libevent compability in ICY filescanner --- src/filescanner_icy.c | 164 +++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 40 deletions(-) diff --git a/src/filescanner_icy.c b/src/filescanner_icy.c index 1c12847d..1dc66bc7 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,13 +34,14 @@ #include #include #include +#include #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #endif #include -#ifdef HAVE_LIBEVENT2 +#if defined HAVE_LIBEVENT2 # include #else # include "evhttp/evhttp.h" @@ -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,19 +101,30 @@ resolve_address(char *hostname, char *s, size_t maxlen) default: strncpy(s, "Unknown AF", maxlen); + freeaddrinfo(result); return -1; } freeaddrinfo(result); return 0; } +#endif 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; + pthread_cond_signal(&ctx->cond); + pthread_mutex_unlock(&ctx->lck); + return; } @@ -122,29 +132,36 @@ scan_icy_request_cb(struct evhttp_request *req, void *arg) 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"); + +#ifdef HAVE_LIBEVENT2 + headers = evhttp_request_get_input_headers(req); +#else + headers = req->input_headers; +#endif + + 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; } @@ -153,11 +170,14 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) { struct evhttp_connection *evcon; struct evhttp_request *req; + struct evkeyvalq *headers; struct icy_ctx *ctx; + 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 +187,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 +208,15 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) if (ctx->port < 0) ctx->port = 80; +#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 +237,44 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) goto no_icy; } evhttp_connection_set_base(evcon, evbase_main); +#endif + 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; } + +#ifdef HAVE_LIBEVENT2 + evhttp_request_set_header_cb(req, scan_icy_header_cb); + headers = evhttp_request_get_output_headers(req); +#else 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"); + headers = req->output_headers; +#endif + evhttp_add_header(headers, "Host", ctx->hostname); + 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); /* 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 +286,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; } From 22d37f240b5e0e181117c426fbac510521ff4633 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 29 May 2014 11:50:56 +0200 Subject: [PATCH 2/9] ICY scanner modifications --- src/filescanner_icy.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/filescanner_icy.c b/src/filescanner_icy.c index 1dc66bc7..a6c6abb9 100644 --- a/src/filescanner_icy.c +++ b/src/filescanner_icy.c @@ -42,7 +42,7 @@ #include #if defined HAVE_LIBEVENT2 -# include +# include #else # include "evhttp/evhttp.h" #endif @@ -124,8 +124,6 @@ scan_icy_request_cb(struct evhttp_request *req, void *arg) status = ICY_DONE; pthread_cond_signal(&ctx->cond); pthread_mutex_unlock(&ctx->lck); - - return; } /* Will always return -1 to make evhttp close the connection - we only need the http headers */ @@ -172,6 +170,7 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) struct evhttp_request *req; struct evkeyvalq *headers; struct icy_ctx *ctx; + char s[PATH_MAX]; time_t start; time_t end; int ret; @@ -208,6 +207,12 @@ 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) @@ -257,7 +262,8 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) req->header_cb = scan_icy_header_cb; headers = req->output_headers; #endif - evhttp_add_header(headers, "Host", ctx->hostname); + 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 */ @@ -275,6 +281,7 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) /* Can't count on server support for ICY metadata, so * while waiting for a reply make a parallel call to scan_metadata_ffmpeg. + * TODO ffmpeg 2/libav 10 has ICY/Shoutcast support */ no_icy: ret = scan_metadata_ffmpeg(url, mfi); From 5b4ef31758fb3947ae22fc47a245dcf19973611c Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 29 May 2014 23:22:00 +0200 Subject: [PATCH 3/9] Migrate all evhttp to non-deprecated libevent2 - well except a troublemaker in httpd_daap.c (req->flags &= ~EVHTTP_PROXY_REQUEST) --- src/Makefile.am | 2 +- src/dmap_common.h | 2 +- src/evhttp/evhttp_compat.c | 42 ++++++++++++++++++ src/evhttp/evhttp_compat.h | 27 ++++++++++++ src/filescanner_icy.c | 13 +----- src/filescanner_itunes.c | 2 +- src/httpd.c | 88 +++++++++++++++++++++++++------------- src/httpd.h | 4 +- src/httpd_daap.c | 58 +++++++++++++++++-------- src/httpd_daap.h | 4 +- src/httpd_dacp.c | 39 +++++++++++------ src/httpd_dacp.h | 4 +- src/httpd_rsp.c | 17 +++++--- src/httpd_rsp.h | 4 +- src/remote_pairing.c | 25 ++++++----- src/transcode.h | 2 +- 16 files changed, 234 insertions(+), 99 deletions(-) create mode 100644 src/evhttp/evhttp_compat.c create mode 100644 src/evhttp/evhttp_compat.h diff --git a/src/Makefile.am b/src/Makefile.am index a8af5493..50c3ec18 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,7 +34,7 @@ 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 +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 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/filescanner_icy.c b/src/filescanner_icy.c index a6c6abb9..2f67a756 100644 --- a/src/filescanner_icy.c +++ b/src/filescanner_icy.c @@ -44,7 +44,7 @@ #if defined HAVE_LIBEVENT2 # include #else -# include "evhttp/evhttp.h" +# include "evhttp/evhttp_compat.h" #endif #include @@ -138,12 +138,7 @@ scan_icy_header_cb(struct evhttp_request *req, void *arg) DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Headers received\n"); -#ifdef HAVE_LIBEVENT2 headers = evhttp_request_get_input_headers(req); -#else - headers = req->input_headers; -#endif - if ( (ptr = evhttp_find_header(headers, "icy-name")) ) { ctx->icy_name = strdup(ptr); @@ -255,13 +250,9 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) goto no_icy; } -#ifdef HAVE_LIBEVENT2 evhttp_request_set_header_cb(req, scan_icy_header_cb); + headers = evhttp_request_get_output_headers(req); -#else - req->header_cb = scan_icy_header_cb; - headers = req->output_headers; -#endif snprintf(s, PATH_MAX, "%s:%d", ctx->hostname, ctx->port); evhttp_add_header(headers, "Host", s); evhttp_add_header(headers, "Icy-MetaData", "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..0c0c0448 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 * @@ -127,8 +126,12 @@ static pthread_t tid_httpd; 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); @@ -312,6 +315,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 +329,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 +394,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 +414,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 +479,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 +502,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 +513,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 +527,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 +550,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 +569,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 +590,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 +622,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 +635,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 +718,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 +749,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 +765,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 +775,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 +802,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 +934,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 +950,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 +1036,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 +1045,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 +1054,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 +1120,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 +1128,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 +1212,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..7d8e2fda 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) { 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 From c740e6e3b08830e360154b551355acae428876fc Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 30 May 2014 23:39:03 +0200 Subject: [PATCH 4/9] Make compatible with libevent 2.0 by crippling streaming and ICY metadata (see issue #30) --- configure.ac | 19 +- src/Makefile.am | 14 +- src/evrtsp/rtsp-libevent20.c | 1808 ++++++++++++++++++++++++++++++++++ src/filescanner_icy.c | 10 +- src/httpd.c | 37 + 5 files changed, 1879 insertions(+), 9 deletions(-) create mode 100644 src/evrtsp/rtsp-libevent20.c 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 50c3ec18..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 +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/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_icy.c b/src/filescanner_icy.c index 2f67a756..08f6d6d0 100644 --- a/src/filescanner_icy.c +++ b/src/filescanner_icy.c @@ -110,6 +110,7 @@ resolve_address(char *hostname, char *s, size_t maxlen) } #endif +#ifndef HAVE_LIBEVENT2_OLD static void scan_icy_request_cb(struct evhttp_request *req, void *arg) { @@ -157,15 +158,18 @@ scan_icy_header_cb(struct evhttp_request *req, void *arg) return -1; } +#endif int scan_metadata_icy(char *url, struct media_file_info *mfi) { + struct icy_ctx *ctx; struct evhttp_connection *evcon; +#ifndef HAVE_LIBEVENT2_OLD struct evhttp_request *req; struct evkeyvalq *headers; - struct icy_ctx *ctx; char s[PATH_MAX]; +#endif time_t start; time_t end; int ret; @@ -239,6 +243,9 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) 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)\n", ctx->hostname); +#else evhttp_connection_set_timeout(evcon, ICY_TIMEOUT); /* Set up request */ @@ -269,6 +276,7 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) status = ICY_DONE; goto no_icy; } +#endif /* Can't count on server support for ICY metadata, so * while waiting for a reply make a parallel call to scan_metadata_ffmpeg. diff --git a/src/httpd.c b/src/httpd.c index 0c0c0448..ed339749 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -122,6 +122,9 @@ 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) @@ -146,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); } @@ -180,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) { @@ -227,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; @@ -283,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; From b566c41a367d5997d433fe12ca262a739d189909 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 1 Jun 2014 23:58:44 +0200 Subject: [PATCH 5/9] Setting log level to info gave a lot of meaningless db.c messages --- src/db.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) 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)); From 116289f5b7f7500dbb1419e92f67ef24aad31ab3 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 2 Jun 2014 00:00:52 +0200 Subject: [PATCH 6/9] Libav 10 (and ffmpeg) now has native support for ICY metadata --- src/filescanner.c | 4 ++ src/filescanner_ffmpeg.c | 83 +++++++++++++++++++++++++++++++++++++++- src/filescanner_icy.c | 2 +- 3 files changed, 87 insertions(+), 2 deletions(-) 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..c4bb086b 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,82 @@ 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) < 3)) + icy_token = strtok(NULL, "\r\n"); + + 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 +407,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 +539,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 08f6d6d0..7a2fa88e 100644 --- a/src/filescanner_icy.c +++ b/src/filescanner_icy.c @@ -244,7 +244,7 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) #endif #ifdef HAVE_LIBEVENT2_OLD - DPRINTF(E_LOG, L_SCAN, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4)\n", ctx->hostname); + 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); From ada7ccca8ffb0eae8e5b62ea0defd9c4558c09cd Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 2 Jun 2014 21:08:24 +0200 Subject: [PATCH 7/9] Change log level of .remote message to ease pairing troubleshooting --- src/remote_pairing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote_pairing.c b/src/remote_pairing.c index 7d8e2fda..e658ea3e 100644 --- a/src/remote_pairing.c +++ b/src/remote_pairing.c @@ -886,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); From 6a8f93b7df5eff8f7e1f1943a25235196df957b2 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 2 Jun 2014 21:18:26 +0200 Subject: [PATCH 8/9] INSTALL update to reflect compability with libevent 2.0 --- INSTALL | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 From 844a9b43c91e94f65363ffcd8043165c65e0b4c4 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 2 Jun 2014 21:51:50 +0200 Subject: [PATCH 9/9] Fixup new libav 10 icy extraction --- src/filescanner_ffmpeg.c | 7 +++++-- src/filescanner_icy.c | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index c4bb086b..f38f7297 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -336,8 +336,11 @@ extract_metadata_icy(struct media_file_info *mfi, AVFormatContext *ctx) while (icy_token != NULL) { ptr = strchr(icy_token, ':'); - if (!ptr || (strlen(ptr) < 3)) - icy_token = strtok(NULL, "\r\n"); + if (!ptr || (strlen(ptr) < 4)) + { + icy_token = strtok(NULL, "\r\n"); + continue; + } ptr++; if (ptr[0] == ' ') diff --git a/src/filescanner_icy.c b/src/filescanner_icy.c index 7a2fa88e..23507963 100644 --- a/src/filescanner_icy.c +++ b/src/filescanner_icy.c @@ -280,7 +280,6 @@ scan_metadata_icy(char *url, struct media_file_info *mfi) /* Can't count on server support for ICY metadata, so * while waiting for a reply make a parallel call to scan_metadata_ffmpeg. - * TODO ffmpeg 2/libav 10 has ICY/Shoutcast support */ no_icy: ret = scan_metadata_ffmpeg(url, mfi);