From 3609de8685cfdfbf2d01fb8bbff02a78d9c07d06 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 26 Oct 2008 19:32:43 +0100 Subject: [PATCH] http: use libcurl MPD's HTTP client code has always been broken, no matter how effort was put into fixing it. Replace it with libcurl, which is known to be quite stable. This adds a fat library dependency, but only for people who need streaming. --- INSTALL | 4 + configure.ac | 18 + src/Makefile.am | 10 +- src/inputStream.c | 18 +- src/inputStream_http.c | 1049 ---------------------- src/inputStream_http_auth.h | 102 --- src/input_curl.c | 493 ++++++++++ src/{inputStream_http.h => input_curl.h} | 22 +- src/ls.c | 2 + 9 files changed, 547 insertions(+), 1171 deletions(-) delete mode 100644 src/inputStream_http.c delete mode 100644 src/inputStream_http_auth.h create mode 100644 src/input_curl.c rename src/{inputStream_http.h => input_curl.h} (59%) diff --git a/INSTALL b/INSTALL index e0bc9e551..403f8d349 100644 --- a/INSTALL +++ b/INSTALL @@ -70,6 +70,10 @@ For Zeroconf support. libsamplerate - http://www.mega-nerd.com/SRC/ For advanced samplerate conversions. +libcurl - http://curl.haxx.se/ +For playing HTTP streams. + + Download -------- diff --git a/configure.ac b/configure.ac index 37057a802..d79240ff9 100644 --- a/configure.ac +++ b/configure.ac @@ -102,6 +102,17 @@ AC_ARG_ENABLE(un, [enable_un=yes]) +dnl +dnl input options +dnl + +AC_ARG_ENABLE(curl, + AS_HELP_STRING([--disable-curl], + [enable support obtaining song data via HTTP (default: enable)]), + [enable_curl=$enableval], + [enable_curl=yes]) + + dnl dnl audio output plugins dnl @@ -316,6 +327,13 @@ case $host in enable_osx=yes ;; esac +if test x$enable_curl = xyes; then + PKG_CHECK_MODULES(CURL, [libcurl], + AC_DEFINE(HAVE_CURL, 1, [Define when libcurl is used for HTTP streaming]), + enable_curl=no) +fi +AM_CONDITIONAL(HAVE_CURL, test x$enable_curl = xyes) + if test x$enable_shout_ogg = xyes || x$enable_shout_mp3 = xyes; then enable_shout=yes PKG_CHECK_MODULES([SHOUT], [shout], diff --git a/src/Makefile.am b/src/Makefile.am index 7e2386dc8..12e690cf7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,8 +49,6 @@ mpd_headers = \ decoder/_ogg_common.h \ inputStream.h \ inputStream_file.h \ - inputStream_http.h \ - inputStream_http_auth.h \ client.h \ list.h \ dlist.h \ @@ -132,7 +130,6 @@ mpd_SOURCES = \ decoder_list.c \ inputStream.c \ inputStream_file.c \ - inputStream_http.c \ client.c \ ioops.c \ list.c \ @@ -246,8 +243,14 @@ mpd_SOURCES += zeroconf.c endif +if HAVE_CURL +mpd_SOURCES += input_curl.c +endif + + mpd_CFLAGS = $(MPD_CFLAGS) mpd_CPPFLAGS = \ + $(CURL_CFLAGS) \ $(AO_CFLAGS) $(ALSA_CFLAGS) \ $(SHOUT_CFLAGS) \ $(OGGVORBIS_CFLAGS) $(VORBISENC_CFLAGS) \ @@ -258,6 +261,7 @@ mpd_CPPFLAGS = \ $(FFMPEG_CFLAGS) \ $(GLIB_CFLAGS) mpd_LDADD = $(MPD_LIBS) \ + $(CURL_LIBS) \ $(AO_LIBS) $(ALSA_LIBS) \ $(SHOUT_LIBS) \ $(OGGVORBIS_LIBS) $(VORBISENC_LIBS) $(FLAC_LIBS) \ diff --git a/src/inputStream.c b/src/inputStream.c index 1c5e027e9..b293cb1cd 100644 --- a/src/inputStream.c +++ b/src/inputStream.c @@ -17,20 +17,29 @@ */ #include "inputStream.h" +#include "config.h" #include "inputStream_file.h" -#include "inputStream_http.h" + +#ifdef HAVE_CURL +#include "input_curl.h" +#endif #include void initInputStream(void) { inputStream_initFile(); - inputStream_initHttp(); +#ifdef HAVE_CURL + input_curl_global_init(); +#endif } void input_stream_global_finish(void) { +#ifdef HAVE_CURL + input_curl_global_finish(); +#endif } int openInputStream(struct input_stream *inStream, char *url) @@ -46,8 +55,11 @@ int openInputStream(struct input_stream *inStream, char *url) if (inputStream_fileOpen(inStream, url) == 0) return 0; - if (inputStream_httpOpen(inStream, url) == 0) + +#ifdef HAVE_CURL + if (input_curl_open(inStream, url)) return 0; +#endif return -1; } diff --git a/src/inputStream_http.c b/src/inputStream_http.c deleted file mode 100644 index bbde11825..000000000 --- a/src/inputStream_http.c +++ /dev/null @@ -1,1049 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) - * This project's homepage is: http://www.musicpd.org - * - * 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 "inputStream_http.h" -#include "inputStream_http_auth.h" - -#include "utils.h" -#include "log.h" -#include "conf.h" -#include "os_compat.h" -#include "ringbuf.h" -#include "condition.h" - -enum conn_state { /* only written by io thread, read by both */ - CONN_STATE_NEW, /* just (re)initialized */ - CONN_STATE_REDIRECT, /* redirect */ - CONN_STATE_CONNECTED, /* connected to the socket */ - CONN_STATE_REQUESTED, /* sent HTTP request */ - CONN_STATE_RESP_HEAD, /* reading HTTP response header */ - CONN_STATE_PREBUFFER, /* prebuffering data stream */ - CONN_STATE_BUFFER, /* buffering data stream */ - CONN_STATE_BUFFER_FULL, /* reading actual data stream */ - CONN_STATE_CLOSED /* it's over, time to die */ -}; - -/* used by all HTTP header matching */ -#define match(s) !strncasecmp(cur, s, (offset = sizeof(s) - 1)) - -#define assert_state(st) assert(data->state == st) -#define assert_state2(s1,s2) assert((data->state == s1) || (data->state == s2)) - -enum conn_action { /* only written by control thread, read by both */ - CONN_ACTION_NONE, - CONN_ACTION_CLOSE, - CONN_ACTION_DOSEEK -}; - -#define HTTP_BUFFER_SIZE_DEFAULT 131072 -#define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2) -#define HTTP_REDIRECT_MAX 10 - -static char *proxy_host; -static char *proxy_port; -static char *proxy_user; -static char *proxy_password; -static size_t buffer_size = HTTP_BUFFER_SIZE_DEFAULT; -static size_t prebuffer_size = HTTP_PREBUFFER_SIZE_DEFAULT; - -struct http_data { - int fd; - enum conn_state state; - - /* { we may have a non-multithreaded HTTP discipline in the future */ - enum conn_action action; - int pipe_fds[2]; - - pthread_t io_thread; - struct ringbuf *rb; - - struct condition full_cond; - struct condition empty_cond; - struct condition action_cond; - /* } */ - - int nr_redirect; - size_t icy_metaint; - size_t icy_offset; - char *host; - char *path; - char *port; - char *proxy_auth; - char *http_auth; -}; - -static int awaken_buffer_task(struct http_data *data); - -static void init_http_data(struct http_data *data) -{ - data->fd = -1; - data->action = CONN_ACTION_NONE; - data->state = CONN_STATE_NEW; - init_async_pipe(data->pipe_fds); - - data->proxy_auth = proxy_host ? - proxy_auth_string(proxy_user, proxy_password) : - NULL; - data->http_auth = NULL; - data->host = NULL; - data->path = NULL; - data->port = NULL; - data->nr_redirect = 0; - data->icy_metaint = 0; - data->icy_offset = 0; - data->rb = ringbuf_create(buffer_size); - - cond_init(&data->action_cond); - cond_init(&data->full_cond); - cond_init(&data->empty_cond); -} - -static struct http_data *new_http_data(void) -{ - struct http_data *ret = xmalloc(sizeof(struct http_data)); - init_http_data(ret); - return ret; -} - -static void free_http_data(struct http_data * data) -{ - if (data->host) free(data->host); - if (data->path) free(data->path); - if (data->port) free(data->port); - if (data->proxy_auth) free(data->proxy_auth); - if (data->http_auth) free(data->http_auth); - - cond_destroy(&data->action_cond); - cond_destroy(&data->full_cond); - cond_destroy(&data->empty_cond); - - xclose(data->pipe_fds[0]); - xclose(data->pipe_fds[1]); - ringbuf_free(data->rb); - free(data); -} - -static int parse_url(struct http_data * data, char *url) -{ - char *colon; - char *slash; - char *at; - int len; - char *cur = url; - size_t offset; - - if (!match("http://")) - return -1; - - cur = url + offset; - colon = strchr(cur, ':'); - at = strchr(cur, '@'); - - if (data->http_auth) { - free(data->http_auth); - data->http_auth = NULL; - } - - if (at) { - char *user; - char *passwd; - - if (colon && colon < at) { - user = xmalloc(colon - cur + 1); - memcpy(user, cur, colon - cur); - user[colon - cur] = '\0'; - - passwd = xmalloc(at - colon); - memcpy(passwd, colon + 1, at - colon - 1); - passwd[at - colon - 1] = '\0'; - } else { - user = xmalloc(at - cur + 1); - memcpy(user, cur, at - cur); - user[at - cur] = '\0'; - - passwd = xstrdup(""); - } - - data->http_auth = http_auth_string(user, passwd); - - free(user); - free(passwd); - - cur = at + 1; - colon = strchr(cur, ':'); - } - - slash = strchr(cur, '/'); - - if (slash && colon && slash <= colon) - return -1; - - /* fetch the host portion */ - if (colon) - len = colon - cur + 1; - else if (slash) - len = slash - cur + 1; - else - len = strlen(cur) + 1; - - if (len <= 1) - return -1; - - if (data->host) - free(data->host); - data->host = xmalloc(len); - memcpy(data->host, cur, len - 1); - data->host[len - 1] = '\0'; - if (data->port) - free(data->port); - /* fetch the port */ - if (colon && (!slash || slash != colon + 1)) { - len = strlen(colon) - 1; - if (slash) - len -= strlen(slash); - data->port = xmalloc(len + 1); - memcpy(data->port, colon + 1, len); - data->port[len] = '\0'; - DEBUG(__FILE__ ": Port: %s\n", data->port); - } else { - data->port = xstrdup("80"); - } - - if (data->path) - free(data->path); - /* fetch the path */ - data->path = proxy_host ? xstrdup(url) : xstrdup(slash ? slash : "/"); - - return 0; -} - -/* triggers an action and waits for completion */ -static int trigger_action(struct http_data *data, - enum conn_action action, - int nonblocking) -{ - int ret = -1; - - assert(!pthread_equal(data->io_thread, pthread_self())); - cond_enter(&data->action_cond); - if (data->action != CONN_ACTION_NONE) - goto out; - data->action = action; - if (awaken_buffer_task(data)) { - /* DEBUG("wokeup from cond_wait to trigger action\n"); */ - } else if (xwrite(data->pipe_fds[1], "", 1) != 1) { - ERROR(__FILE__ ": pipe full, couldn't trigger action\n"); - data->action = CONN_ACTION_NONE; - goto out; - } - if (nonblocking) - cond_timedwait(&data->action_cond, 1); - else - cond_wait(&data->action_cond); - ret = 0; -out: - cond_leave(&data->action_cond); - return ret; -} - -static int take_action(struct http_data *data) -{ - assert(pthread_equal(data->io_thread, pthread_self())); - - cond_enter(&data->action_cond); - switch (data->action) { - case CONN_ACTION_NONE: - cond_leave(&data->action_cond); - return 0; - case CONN_ACTION_DOSEEK: - data->state = CONN_STATE_NEW; - break; - case CONN_ACTION_CLOSE: - data->state = CONN_STATE_CLOSED; - } - xclose(data->fd); - data->fd = -1; - data->action = CONN_ACTION_NONE; - cond_signal_sync(&data->action_cond); - cond_leave(&data->action_cond); - return 1; -} - -static int err_close(struct http_data *data) -{ - assert(pthread_equal(data->io_thread, pthread_self())); - xclose(data->fd); - data->state = CONN_STATE_CLOSED; - return -1; -} - -/* returns -1 on error, 0 on success (and sets dest) */ -static int my_getaddrinfo(struct addrinfo **dest, - const char *host, const char *port) -{ - struct addrinfo hints; - int error; - - hints.ai_flags = 0; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_addrlen = 0; - hints.ai_addr = NULL; - hints.ai_canonname = NULL; - hints.ai_next = NULL; - - if ((error = getaddrinfo(host, port, &hints, dest))) { - DEBUG(__FILE__ ": Error getting address info for %s:%s: %s\n", - host, port, gai_strerror(error)); - return -1; - } - return 0; -} - -/* returns the fd we connected to, or -1 on error */ -static int my_connect_addrs(struct addrinfo *ans) -{ - int fd; - struct addrinfo *ap; - - /* loop through possible addresses */ - for (ap = ans; ap != NULL; ap = ap->ai_next) { - fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); - if (fd < 0) { - DEBUG(__FILE__ ": unable to get socket: %s\n", - strerror(errno)); - continue; - } - - set_nonblocking(fd); - if (connect(fd, ap->ai_addr, ap->ai_addrlen) >= 0 - || errno == EINPROGRESS) - return fd; /* success */ - DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno)); - xclose(fd); /* failed, get the next one */ - } - return -1; -} - -static int init_connection(struct http_data *data) -{ - struct addrinfo *ans = NULL; - - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state2(CONN_STATE_NEW, CONN_STATE_REDIRECT); - - if ((proxy_host ? my_getaddrinfo(&ans, proxy_host, proxy_port) : - my_getaddrinfo(&ans, data->host, data->port)) < 0) - return -1; - - assert(data->fd < 0); - data->fd = my_connect_addrs(ans); - freeaddrinfo(ans); - - if (data->fd < 0) - return -1; /* failed */ - data->state = CONN_STATE_CONNECTED; - return 0; -} - -#define my_nfds(d) ((d->fd > d->pipe_fds[0] ? d->fd : d->pipe_fds[0]) + 1) - -static int pipe_notified(struct http_data * data, fd_set *rfds) -{ - char buf; - int fd = data->pipe_fds[0]; - - assert(pthread_equal(data->io_thread, pthread_self())); - return FD_ISSET(fd, rfds) && (xread(fd, &buf, 1) == 1); -} - -enum await_result { - AWAIT_READY, - AWAIT_ACTION_PENDING, - AWAIT_ERROR -}; - -static enum await_result socket_error_or_ready(int fd) -{ - int ret; - int error = 0; - socklen_t error_len = sizeof(int); - - ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len); - return (ret < 0 || error) ? AWAIT_ERROR : AWAIT_READY; -} - -static enum await_result await_sendable(struct http_data *data) -{ - fd_set rfds, wfds; - - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state(CONN_STATE_CONNECTED); - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_SET(data->pipe_fds[0], &rfds); - FD_SET(data->fd, &wfds); - - if (select(my_nfds(data), &rfds, &wfds, NULL, NULL) <= 0) - return AWAIT_ERROR; - if (pipe_notified(data, &rfds)) return AWAIT_ACTION_PENDING; - return socket_error_or_ready(data->fd); -} - -static enum await_result await_recvable(struct http_data *data) -{ - fd_set rfds; - - assert(pthread_equal(data->io_thread, pthread_self())); - - FD_ZERO(&rfds); - FD_SET(data->pipe_fds[0], &rfds); - FD_SET(data->fd, &rfds); - - if (select(my_nfds(data), &rfds, NULL, NULL, NULL) <= 0) - return AWAIT_ERROR; - if (pipe_notified(data, &rfds)) return AWAIT_ACTION_PENDING; - return socket_error_or_ready(data->fd); -} - -static void await_buffer_space(struct http_data *data) -{ - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state(CONN_STATE_BUFFER_FULL); - cond_wait(&data->full_cond); - if (ringbuf_write_space(data->rb) > 0) - data->state = CONN_STATE_BUFFER; - /* else spurious wakeup or action triggered ... */ -} - -static void feed_starved(struct http_data *data) -{ - assert(pthread_equal(data->io_thread, pthread_self())); - cond_signal_async(&data->empty_cond); -} - -static int starved_wait(struct http_data *data, const long sec) -{ - assert(!pthread_equal(data->io_thread, pthread_self())); - return cond_timedwait(&data->empty_cond, sec); -} - -static int awaken_buffer_task(struct http_data *data) -{ - assert(!pthread_equal(data->io_thread, pthread_self())); - - return ! cond_signal_async(&data->full_cond); -} - -static ssize_t buffer_data(InputStream *is) -{ - struct iovec vec[2]; - ssize_t r; - struct http_data *data = (struct http_data *)is->data; - - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state2(CONN_STATE_BUFFER, CONN_STATE_PREBUFFER); - - if (!ringbuf_get_write_vector(data->rb, vec)) { - data->state = CONN_STATE_BUFFER_FULL; - return 0; - } - r = readv(data->fd, vec, vec[1].iov_len ? 2 : 1); - if (r > 0) { - size_t buflen; - - ringbuf_write_advance(data->rb, r); - buflen = ringbuf_read_space(data->rb); - if (buflen == 0 || buflen < data->icy_metaint) - data->state = CONN_STATE_PREBUFFER; - else if (buflen >= prebuffer_size) - data->state = CONN_STATE_BUFFER; - if (data->state == CONN_STATE_BUFFER) - feed_starved(data); - return r; - } else if (r < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - is->error = errno; - } - err_close(data); - return r; -} - -/* - * This requires the socket to be writable beforehand (determined via - * select(2)). This does NOT retry or continue if we can't write the - * HTTP header in one shot. One reason for this is laziness, I don't - * want to have to store the header when recalling this function, but - * the other reason is practical, too: if we can't send a small HTTP - * request without blocking, the connection is pathetic anyways and we - * should just stop - * - * Returns -1 on error, 0 on success - */ -static int send_request(InputStream * is) -{ - struct http_data *data = (struct http_data *) is->data; - int length; - ssize_t nbytes; - char request[2048]; /* todo(?): write item-at-a-time and cork */ - - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state(CONN_STATE_CONNECTED); - length = snprintf(request, sizeof(request), - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Connection: close\r\n" - "User-Agent: " PACKAGE_NAME "/" PACKAGE_VERSION "\r\n" - "Range: bytes=%ld-\r\n" - "%s" /* authorization */ - "Icy-Metadata:1\r\n" - "\r\n", - data->path, - data->host, - is->offset, - data->proxy_auth ? data->proxy_auth : - (data->http_auth ? data->http_auth : "")); - if (length < 0 || length >= (int)sizeof(request)) - return err_close(data); - nbytes = write(data->fd, request, (size_t)length); - if (nbytes < 0 || nbytes != (ssize_t)length) - return err_close(data); - data->state = CONN_STATE_REQUESTED; - return 0; -} - -/* handles parsing of the first line of the HTTP response */ -static int parse_response_code(InputStream * is, const char *response) -{ - size_t offset; - const char *cur = response; - - is->seekable = 0; - if (match("HTTP/1.0 ")) { - return atoi(cur + offset); - } else if (match("HTTP/1.1 ")) { - is->seekable = 1; - return atoi(cur + offset); - } else if (match("ICY 200 OK")) { - return 200; - } else if (match("ICY 400 Server Full")) { - return 400; - } else if (match("ICY 404")) - return 404; - return 0; -} - -static int leading_space(int c) -{ - return (c == ' ' || c == '\t'); -} - -static int parse_header_dup(char **dst, char *cur) -{ - char *eol; - size_t len; - - if (!(eol = strstr(cur, "\r\n"))) - return -1; - *eol = '\0'; - while (leading_space(*cur)) - cur++; - len = strlen(cur) + 1; - *dst = xrealloc(*dst, len); - memcpy(*dst, cur, len); - *eol = '\r'; - return 0; -} - -static int parse_redirect(InputStream * is, char *response, const char *needle) -{ - char *url = NULL; - char *cur = strstr(response, "\r\n"); - size_t offset; - struct http_data *data = (struct http_data *) is->data; - int ret; - - while (cur && cur != needle) { - assert(cur < needle); - if (match("\r\nLocation:")) - goto found; - cur = strstr(cur + 2, "\r\n"); - } - return -1; -found: - if (parse_header_dup(&url, cur + offset) < 0) - return -1; - ret = parse_url(data, url); - free(url); - if (!ret && data->nr_redirect < HTTP_REDIRECT_MAX) { - data->nr_redirect++; - xclose(data->fd); - data->fd = -1; - data->state = CONN_STATE_REDIRECT; - is->ready = 1; - return 0; /* success */ - } - return -1; -} - -static int parse_headers(InputStream * is, char *response, const char *needle) -{ - struct http_data *data = (struct http_data *) is->data; - char *cur = strstr(response, "\r\n"); - size_t offset; - long tmp; - - data->icy_metaint = 0; - data->icy_offset = 0; - if (is->mime) { - free(is->mime); - is->mime = NULL; - } - if (is->metaName) { - free(is->metaName); - is->metaName = NULL; - } - is->size = 0; - - while (cur && cur != needle) { - assert(cur < needle); - if (match("\r\nContent-Length:")) { - if ((tmp = atol(cur + offset)) >= 0) - is->size = tmp; - } else if (match("\r\nicy-metaint:")) { - if ((tmp = atol(cur + offset)) >= 0) - data->icy_metaint = tmp; - } else if (match("\r\nicy-name:") || - match("\r\nice-name:") || - match("\r\nx-audiocast-name:")) { - if (parse_header_dup(&is->metaName, cur + offset) < 0) - return -1; - DEBUG(__FILE__": metaName: %s\n", is->metaName); - } else if (match("\r\nContent-Type:")) { - if (parse_header_dup(&is->mime, cur + offset) < 0) - return -1; - } - cur = strstr(cur + 2, "\r\n"); - } - return 0; -} - -/* Returns -1 on error, 0 on success */ -static int recv_response(InputStream * is) -{ - struct http_data *data = (struct http_data *) is->data; - char *needle; - char response[2048]; - const size_t response_max = sizeof(response) - 1; - ssize_t r; - ssize_t peeked; - - assert(pthread_equal(data->io_thread, pthread_self())); - assert_state2(CONN_STATE_RESP_HEAD, CONN_STATE_REQUESTED); - do { - r = recv(data->fd, response, response_max, MSG_PEEK); - } while (r < 0 && errno == EINTR); - if (r <= 0) - return err_close(data); /* EOF */ - response[r] = '\0'; - if (!(needle = strstr(response, "\r\n\r\n"))) { - if ((size_t)r == response_max) - return err_close(data); - /* response too small, try again */ - data->state = CONN_STATE_RESP_HEAD; - return -1; - } - - switch (parse_response_code(is, response)) { - case 200: /* OK */ - case 206: /* Partial Content */ - break; - case 301: /* Moved Permanently */ - case 302: /* Moved Temporarily */ - if (parse_redirect(is, response, needle) == 0) - return 0; /* success, reconnect */ - default: - return err_close(data); - } - - parse_headers(is, response, needle); - if (is->size <= 0) - is->seekable = 0; - needle += sizeof("\r\n\r\n") - 1; - peeked = needle - response; - assert(peeked <= r); - do { - r = recv(data->fd, response, peeked, 0); - } while (r < 0 && errno == EINTR); - assert(r == peeked && "r != peeked"); - - ringbuf_writer_reset(data->rb); - data->state = CONN_STATE_PREBUFFER; - is->ready = 1; - - return 0; -} - -static void * http_io_task(void *arg) -{ - InputStream *is = (InputStream *) arg; - struct http_data *data = (struct http_data *) is->data; - - cond_enter(&data->full_cond); - while (1) { - take_action(data); - switch (data->state) { - case CONN_STATE_NEW: - case CONN_STATE_REDIRECT: - init_connection(data); - break; - case CONN_STATE_CONNECTED: - switch (await_sendable(data)) { - case AWAIT_READY: send_request(is); break; - case AWAIT_ACTION_PENDING: break; - case AWAIT_ERROR: goto err; - } - break; - case CONN_STATE_REQUESTED: - case CONN_STATE_RESP_HEAD: - switch (await_recvable(data)) { - case AWAIT_READY: recv_response(is); break; - case AWAIT_ACTION_PENDING: break; - case AWAIT_ERROR: goto err; - } - break; - case CONN_STATE_PREBUFFER: - case CONN_STATE_BUFFER: - switch (await_recvable(data)) { - case AWAIT_READY: buffer_data(is); break; - case AWAIT_ACTION_PENDING: break; - case AWAIT_ERROR: goto err; - } - break; - case CONN_STATE_BUFFER_FULL: - await_buffer_space(data); - break; - case CONN_STATE_CLOSED: goto closed; - } - } -err: - err_close(data); -closed: - assert_state(CONN_STATE_CLOSED); - cond_leave(&data->full_cond); - return NULL; -} - -int inputStream_httpBuffer(mpd_unused InputStream *is) -{ - return 0; -} - -int inputStream_httpOpen(InputStream * is, char *url) -{ - struct http_data *data = new_http_data(); - pthread_attr_t attr; - - is->seekable = 0; - is->data = data; - if (parse_url(data, url) < 0) { - free_http_data(data); - return -1; - } - - is->seekFunc = inputStream_httpSeek; - is->closeFunc = inputStream_httpClose; - is->readFunc = inputStream_httpRead; - is->atEOFFunc = inputStream_httpAtEOF; - is->bufferFunc = inputStream_httpBuffer; - - pthread_attr_init(&attr); - if (pthread_create(&data->io_thread, &attr, http_io_task, is)) - FATAL("failed to spawn http_io_task: %s", strerror(errno)); - - cond_enter(&data->empty_cond); /* httpClose will leave this */ - return 0; -} - -int inputStream_httpSeek(InputStream * is, long offset, int whence) -{ - struct http_data *data = (struct http_data *)is->data; - long old_offset = is->offset; - long diff; - - if (!is->seekable) { - is->error = ESPIPE; - return -1; - } - assert(is->size > 0); - - switch (whence) { - case SEEK_SET: - is->offset = offset; - break; - case SEEK_CUR: - is->offset += offset; - break; - case SEEK_END: - is->offset = is->size + offset; - break; - default: - is->error = EINVAL; - return -1; - } - - diff = is->offset - old_offset; - if (!diff) - return 0; /* nothing to seek */ - if (diff > 0) { /* seek forward if we've already buffered it */ - long avail = (long)ringbuf_read_space(data->rb); - if (avail >= diff) { - ringbuf_read_advance(data->rb, diff); - return 0; - } - } - trigger_action(data, CONN_ACTION_DOSEEK, 0); - return 0; -} - -static void parse_icy_metadata(InputStream * is, char *metadata, size_t size) -{ - char *r = NULL; - char *cur; - size_t offset; - - assert(size); - metadata[size] = '\0'; - cur = strtok_r(metadata, ";", &r); - while (cur) { - if (match("StreamTitle=")) { - if (is->metaTitle) - free(is->metaTitle); - if (cur[offset] == '\'') - offset++; - if (r[-2] == '\'') - r[-2] = '\0'; - is->metaTitle = xstrdup(cur + offset); - DEBUG(__FILE__ ": metaTitle: %s\n", is->metaTitle); - return; - } - cur = strtok_r(NULL, ";", &r); - } -} - -static size_t read_with_metadata(InputStream *is, unsigned char *ptr, - ssize_t len) -{ - struct http_data *data = (struct http_data *) is->data; - size_t readed = 0; - size_t r; - size_t to_read; - assert(data->icy_metaint > 0); - - while (len > 0) { - if (ringbuf_read_space(data->rb) < data->icy_metaint) - break; - if (data->icy_offset >= data->icy_metaint) { - unsigned char metabuf[(UCHAR_MAX << 4) + 1]; - size_t metalen; - r = ringbuf_read(data->rb, metabuf, 1); - assert(r == 1 && "failed to read"); - awaken_buffer_task(data); - metalen = *(metabuf); - metalen <<= 4; - if (metalen) { - r = ringbuf_read(data->rb, metabuf, metalen); - assert(r == metalen && "short metadata read"); - parse_icy_metadata(is, (char*)metabuf, metalen); - } - data->icy_offset = 0; - } - to_read = len; - if (to_read > (data->icy_metaint - data->icy_offset)) - to_read = data->icy_metaint - data->icy_offset; - if (!(r = ringbuf_read(data->rb, ptr, to_read))) - break; - awaken_buffer_task(data); - len -= r; - ptr += r; - readed += r; - data->icy_offset += r; - } - return readed; -} - -size_t inputStream_httpRead(InputStream * is, void *_ptr, size_t size) -{ - struct http_data *data = (struct http_data *) is->data; - size_t len = size; - size_t r; - unsigned char *ptr = _ptr, *ptr0 = _ptr; - long tries = len / 128; /* try harder for bigger reads */ - -retry: - switch (data->state) { - case CONN_STATE_NEW: - case CONN_STATE_REDIRECT: - case CONN_STATE_CONNECTED: - case CONN_STATE_REQUESTED: - case CONN_STATE_RESP_HEAD: - case CONN_STATE_PREBUFFER: - if ((starved_wait(data, 1) == 0) || (tries-- > 0)) - goto retry; /* success */ - return 0; - case CONN_STATE_BUFFER: - case CONN_STATE_BUFFER_FULL: - break; - case CONN_STATE_CLOSED: - if (!ringbuf_read_space(data->rb)) - return 0; - } - - while (1) { - if (data->icy_metaint > 0) - r = read_with_metadata(is, ptr, len); - else /* easy, no metadata to worry about */ - r = ringbuf_read(data->rb, ptr, len); - assert(r <= len); - if (r) { - awaken_buffer_task(data); - is->offset += r; - ptr += r; - len -= r; - } - if (!len || (--tries < 0) || - (data->state == CONN_STATE_CLOSED && - !ringbuf_read_space(data->rb))) - break; - starved_wait(data, 1); - } - return (ptr - ptr0) / size; -} - -int inputStream_httpClose(InputStream * is) -{ - struct http_data *data = (struct http_data *) is->data; - - /* - * The cancellation routines in pthreads suck (and - * are probably unportable) and using signal handlers - * between threads is _definitely_ unportable. - */ - while (data->state != CONN_STATE_CLOSED) - trigger_action(data, CONN_ACTION_CLOSE, 1); - pthread_join(data->io_thread, NULL); - cond_leave(&data->empty_cond); - free_http_data(data); - return 0; -} - -int inputStream_httpAtEOF(InputStream * is) -{ - struct http_data *data = (struct http_data *) is->data; - if (data->state == CONN_STATE_CLOSED && !ringbuf_read_space(data->rb)) - return 1; - return 0; -} - -void inputStream_initHttp(void) -{ - ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST); - char *test; - if (param) { - proxy_host = param->value; - - param = getConfigParam(CONF_HTTP_PROXY_PORT); - - if (!param) { - FATAL("%s specified but not %s\n", CONF_HTTP_PROXY_HOST, - CONF_HTTP_PROXY_PORT); - } - proxy_port = param->value; - - param = getConfigParam(CONF_HTTP_PROXY_USER); - - if (param) { - proxy_user = param->value; - - param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); - - if (!param) { - FATAL("%s specified but not %s\n", - CONF_HTTP_PROXY_USER, - CONF_HTTP_PROXY_PASSWORD); - } - - proxy_password = param->value; - } else { - param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); - - if (param) { - FATAL("%s specified but not %s\n", - CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_USER); - } - } - } else if ((param = getConfigParam(CONF_HTTP_PROXY_PORT))) { - FATAL("%s specified but not %s, line %i\n", - CONF_HTTP_PROXY_PORT, CONF_HTTP_PROXY_HOST, param->line); - } else if ((param = getConfigParam(CONF_HTTP_PROXY_USER))) { - FATAL("%s specified but not %s, line %i\n", - CONF_HTTP_PROXY_USER, CONF_HTTP_PROXY_HOST, param->line); - } else if ((param = getConfigParam(CONF_HTTP_PROXY_PASSWORD))) { - FATAL("%s specified but not %s, line %i\n", - CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_HOST, - param->line); - } - - param = getConfigParam(CONF_HTTP_BUFFER_SIZE); - - if (param) { - long tmp = strtol(param->value, &test, 10); - if (*test != '\0' || tmp <= 0) { - FATAL("\"%s\" specified for %s at line %i is not a " - "positive integer\n", - param->value, CONF_HTTP_BUFFER_SIZE, param->line); - } - - buffer_size = tmp * 1024; - } - if (buffer_size < 4096) - FATAL(CONF_HTTP_BUFFER_SIZE" must be >= 4KB\n"); - - param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE); - - if (param) { - long tmp = strtol(param->value, &test, 10); - if (*test != '\0' || tmp <= 0) { - FATAL("\"%s\" specified for %s at line %i is not a " - "positive integer\n", - param->value, CONF_HTTP_PREBUFFER_SIZE, - param->line); - } - - prebuffer_size = tmp * 1024; - } - - if (prebuffer_size > buffer_size) - prebuffer_size = buffer_size; - assert(buffer_size > 0 && "http buffer_size too small"); - assert(prebuffer_size > 0 && "http prebuffer_size too small"); -} - diff --git a/src/inputStream_http_auth.h b/src/inputStream_http_auth.h deleted file mode 100644 index 6aed1f36e..000000000 --- a/src/inputStream_http_auth.h +++ /dev/null @@ -1,102 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) - * This project's homepage is: http://www.musicpd.org - * - * 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 - */ - -/* This file is only included by inputStream_http.c */ -#ifndef INPUT_STREAM_HTTP_AUTH_H -#define INPUT_STREAM_HTTP_AUTH_H - -#include "utils.h" - -#include - -/* base64 code taken from xmms */ - -#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3)) - -static char *base64dup(char *s) -{ - int i; - int len = strlen(s); - char *ret = xcalloc(BASE64_LENGTH(len) + 1, 1); - unsigned char *p = (unsigned char *)ret; - - static const char tbl[64] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /* Transform the 3x8 bits to 4x6 bits, as required by base64. */ - for (i = 0; i < len; i += 3) { - *p++ = tbl[s[0] >> 2]; - *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)]; - *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)]; - *p++ = tbl[s[2] & 0x3f]; - s += 3; - } - /* Pad the result if necessary... */ - if (i == len + 1) - *(p - 1) = '='; - else if (i == len + 2) - *(p - 1) = *(p - 2) = '='; - /* ...and zero-terminate it. */ - *p = '\0'; - - return ret; -} - -static char *auth_string(const char *header, - const char *user, const char *password) -{ - char *ret = NULL; - int templen; - char *temp; - char *temp64; - - if (!user || !password) - return NULL; - - templen = strlen(user) + strlen(password) + 2; - temp = xmalloc(templen); - strcpy(temp, user); - strcat(temp, ":"); - strcat(temp, password); - temp64 = base64dup(temp); - free(temp); - - ret = xmalloc(strlen(temp64) + strlen(header) + 3); - strcpy(ret, header); - strcat(ret, temp64); - strcat(ret, "\r\n"); - free(temp64); - - return ret; -} - -#define PROXY_AUTH_HEADER "Proxy-Authorization: Basic " -#define HTTP_AUTH_HEADER "Authorization: Basic " - -#define proxy_auth_string(x, y) auth_string(PROXY_AUTH_HEADER, x, y) -#define http_auth_string(x, y) auth_string(HTTP_AUTH_HEADER, x, y) - -#endif /* INPUT_STREAM_HTTP_AUTH_H */ diff --git a/src/input_curl.c b/src/input_curl.c new file mode 100644 index 000000000..3c32d7118 --- /dev/null +++ b/src/input_curl.c @@ -0,0 +1,493 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2008 Max Kellermann + * This project's homepage is: http://www.musicpd.org + * + * 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 "input_curl.h" +#include "inputStream.h" +#include "gcc.h" +#include "dlist.h" + +#include +#include +#include +#include + +#include +#include + +/** + * Buffers created by input_curl_writefunction(). + */ +struct buffer { + struct list_head siblings; + + /** size of the payload */ + size_t size; + + /** how much has been consumed yet? */ + size_t consumed; + + /** the payload */ + unsigned char data[sizeof(long)]; +}; + +struct input_curl { + /* some buffers which were passed to libcurl, which we have + too free */ + char *url, *range; + struct curl_slist *request_headers; + + /** the curl handles */ + CURL *easy; + CURLM *multi; + + /** list of buffers, where input_curl_writefunction() appends + to, and input_curl_read() reads from them */ + struct list_head buffers; + + /** has something been added to the buffers list? */ + bool buffered; + + /** did libcurl tell us the we're at the end of the response body? */ + bool eof; +}; + +/** libcurl should accept "ICY 200 OK" */ +static struct curl_slist *http_200_aliases; + +void input_curl_global_init(void) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) + g_warning("curl_global_init() failed: %s\n", + curl_easy_strerror(code)); + + http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); +} + +void input_curl_global_finish(void) +{ + curl_slist_free_all(http_200_aliases); + + curl_global_cleanup(); +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + if (c->easy != NULL) { + curl_multi_remove_handle(c->multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + } + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; + + while (!list_empty(&c->buffers)) { + struct buffer *buffer = (struct buffer *)c->buffers.next; + list_del(&buffer->siblings); + + g_free(buffer); + } +} + +/** + * Frees this stream (but not the input_stream struct itself). + */ +static void +input_curl_free(struct input_stream *is) +{ + struct input_curl *c = is->data; + + input_curl_easy_free(c); + + if (c->multi != NULL) + curl_multi_cleanup(c->multi); + + g_free(c->url); + g_free(c); +} + +/** + * Wait for the libcurl socket. + * + * @return -1 on error, 0 if no data is available yet, 1 if data is + * available + */ +static int +input_curl_select(struct input_curl *c) +{ + fd_set rfds, wfds, efds; + int max_fd, ret; + CURLMcode mcode; + /* XXX hard coded timeout value.. */ + struct timeval timeout = { + .tv_sec = 1, + .tv_usec = 0, + }; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); + if (mcode != CURLM_OK) { + g_warning("curl_multi_fdset() failed: %s\n", + curl_multi_strerror(mcode)); + return -1; + } + + assert(max_fd >= 0); + + ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); + if (ret < 0) + g_warning("select() failed: %s\n", strerror(errno)); + + return ret; +} + +static size_t +read_from_buffer(struct buffer *buffer, void *dest, size_t length) +{ + assert(buffer->size > 0); + assert(buffer->consumed < buffer->size); + + if (length > buffer->size - buffer->consumed) + length = buffer->size - buffer->consumed; + + memcpy(dest, buffer->data + buffer->consumed, length); + + buffer->consumed += length; + if (buffer->consumed == buffer->size) { + list_del(&buffer->siblings); + g_free(buffer); + } + + return length; +} + +static size_t +input_curl_read(struct input_stream *is, void *ptr, size_t size) +{ + struct input_curl *c = is->data; + CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; + size_t nbytes = 0; + char *dest = ptr; + + /* fill the buffer */ + + while (!c->eof && list_empty(&c->buffers)) { + int running_handles; + + if (mcode != CURLM_CALL_MULTI_PERFORM) { + /* if we're still here, there is no input yet + - wait for input */ + int ret = input_curl_select(c); + if (ret <= 0) + /* no data yet or error */ + return 0; + } + + mcode = curl_multi_perform(c->multi, &running_handles); + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + g_warning("curl_multi_perform() failed: %s\n", + curl_multi_strerror(mcode)); + c->eof = true; + return 0; + } + + c->eof = running_handles == 0; + } + + /* send buffer contents */ + + while (size > 0 && !list_empty(&c->buffers)) { + struct buffer *buffer = (struct buffer *)c->buffers.next; + size_t copy = read_from_buffer(buffer, dest + nbytes, size); + + nbytes += copy; + size -= copy; + } + + is->offset += (off_t)nbytes; + return nbytes; +} + +static int +input_curl_close(struct input_stream *is) +{ + input_curl_free(is); + return 0; +} + +static int +input_curl_eof(mpd_unused struct input_stream *is) +{ + struct input_curl *c = is->data; + + return c->eof && list_empty(&c->buffers); +} + +static int +input_curl_buffer(mpd_unused struct input_stream *is) +{ + struct input_curl *c = is->data; + CURLMcode mcode; + int running_handles; + + c->buffered = false; + + do { + mcode = curl_multi_perform(c->multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM && list_empty(&c->buffers)); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + g_warning("curl_multi_perform() failed: %s\n", + curl_multi_strerror(mcode)); + c->eof = true; + return -1; + } + + c->eof = running_handles == 0; + + return c->buffered; +} + +/** called by curl when new data is available */ +static size_t +input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_stream *is = stream; + const char *header = ptr, *end, *colon; + char name[64]; + + size *= nmemb; + end = header + size; + + colon = memchr(header, ':', size); + if (colon == NULL || (size_t)(colon - header) >= sizeof(name)) + return size; + + memcpy(name, header, colon - header); + name[colon - header] = 0; + + if (strcasecmp(name, "accept-ranges") == 0) + is->seekable = true; + else if (strcasecmp(name, "content-length") == 0) { + char value[64]; + + header = colon + 1; + while (header < end && header[0] == ' ') + ++header; + + if ((size_t)(end - header) >= sizeof(value)) + return size; + + memcpy(value, header, end - header); + value[end - header] = 0; + + is->size = is->offset + g_ascii_strtoull(value, NULL, 10); + } + + return size; +} + +/** called by curl when new data is available */ +static size_t +input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct input_stream *is = stream; + struct input_curl *c = is->data; + struct buffer *buffer; + + size *= nmemb; + if (size == 0) + return 0; + + buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); + buffer->size = size; + buffer->consumed = 0; + memcpy(buffer->data, ptr, size); + list_add_tail(&buffer->siblings, &c->buffers); + + c->buffered = true; + is->ready = true; + + return size; +} + +static bool +input_curl_easy_init(struct input_stream *is) +{ + struct input_curl *c = is->data; + CURLcode code; + CURLMcode mcode; + + c->eof = false; + + c->easy = curl_easy_init(); + if (c->easy == NULL) { + g_warning("curl_easy_init() failed\n"); + return false; + } + + mcode = curl_multi_add_handle(c->multi, c->easy); + if (mcode != CURLM_OK) + return false; + + curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, + input_curl_headerfunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); + curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, + input_curl_writefunction); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is); + curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); + + code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); + if (code != CURLE_OK) + return false; + + c->request_headers = NULL; + c->request_headers = curl_slist_append(c->request_headers, + "Icy-Metadata: 1"); + curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); + + return true; +} + +static bool +input_curl_send_request(struct input_curl *c) +{ + CURLMcode mcode; + int running_handles; + + do { + mcode = curl_multi_perform(c->multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + c->eof = running_handles == 0; + + if (mcode != CURLM_OK) { + g_warning("curl_multi_perform() failed: %s\n", + curl_multi_strerror(mcode)); + return false; + } + + return true; +} + +static int +input_curl_seek(struct input_stream *is, mpd_unused long offset, + mpd_unused int whence) +{ + struct input_curl *c = is->data; + bool ret; + + if (!is->seekable) + return -1; + + /* calculate the absolute offset */ + + switch (whence) { + case SEEK_SET: + is->offset = (off_t)offset; + break; + + case SEEK_CUR: + is->offset += (off_t)offset; + break; + + case SEEK_END: + is->offset = (off_t)is->size + (off_t)offset; + break; + + default: + return -1; + } + + if (is->offset < 0) + return -1; + + /* close the old connection and open a new one */ + + input_curl_easy_free(c); + + ret = input_curl_easy_init(is); + if (!ret) + return -1; + + /* send the "Range" header */ + + if (is->offset > 0) { + c->range = g_strdup_printf("%ld-", is->offset); /* XXX 64 bit safety */ + curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); + } + + ret = input_curl_send_request(c); + if (!ret) + return -1; + + return 0; +} + +bool input_curl_open(struct input_stream *is, char *url) +{ + struct input_curl *c; + bool ret; + + c = g_new0(struct input_curl, 1); + c->url = g_strdup(url); + INIT_LIST_HEAD(&c->buffers); + + is->data = c; + + c->multi = curl_multi_init(); + if (c->multi == NULL) { + g_warning("curl_multi_init() failed\n"); + + input_curl_free(is); + return false; + } + + ret = input_curl_easy_init(is); + if (!ret) { + input_curl_free(is); + return false; + } + + ret = input_curl_send_request(c); + if (!ret) { + input_curl_free(is); + return false; + } + + is->seekFunc = input_curl_seek; + is->closeFunc = input_curl_close; + is->readFunc = input_curl_read; + is->atEOFFunc = input_curl_eof; + is->bufferFunc = input_curl_buffer; + + return true; +} diff --git a/src/inputStream_http.h b/src/input_curl.h similarity index 59% rename from src/inputStream_http.h rename to src/input_curl.h index 01d70c1eb..9ced70f7f 100644 --- a/src/inputStream_http.h +++ b/src/input_curl.h @@ -1,5 +1,5 @@ /* the Music Player Daemon (MPD) - * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann * This project's homepage is: http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -16,23 +16,17 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef INPUT_STREAM_HTTP_H -#define INPUT_STREAM_HTTP_H +#ifndef MPD_INPUT_CURL_H +#define MPD_INPUT_CURL_H -#include "inputStream.h" +#include -void inputStream_initHttp(void); +struct input_stream; -int inputStream_httpOpen(InputStream * inStream, char *filename); +void input_curl_global_init(void); -int inputStream_httpSeek(InputStream * inStream, long offset, int whence); +void input_curl_global_finish(void); -size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size); - -int inputStream_httpClose(InputStream * inStream); - -int inputStream_httpAtEOF(InputStream * inStream); - -int inputStream_httpBuffer(InputStream * inStream); +bool input_curl_open(struct input_stream *is, char *url); #endif diff --git a/src/ls.c b/src/ls.c index b70952214..a456db040 100644 --- a/src/ls.c +++ b/src/ls.c @@ -27,7 +27,9 @@ #include "os_compat.h" static const char *remoteUrlPrefixes[] = { +#ifdef HAVE_CURL "http://", +#endif NULL };