aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL4
-rw-r--r--configure.ac18
-rw-r--r--src/Makefile.am10
-rw-r--r--src/inputStream.c18
-rw-r--r--src/inputStream_http.c1049
-rw-r--r--src/inputStream_http_auth.h102
-rw-r--r--src/input_curl.c493
-rw-r--r--src/input_curl.h (renamed from src/inputStream_http.h)22
-rw-r--r--src/ls.c2
9 files changed, 547 insertions, 1171 deletions
diff --git a/INSTALL b/INSTALL
index e0bc9e55..403f8d34 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 37057a80..d79240ff 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,6 +103,17 @@ AC_ARG_ENABLE(un,
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 7e2386dc..12e690cf 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 1c5e027e..b293cb1c 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 <stdlib.h>
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 bbde1182..00000000
--- 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 6aed1f36..00000000
--- 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 <string.h>
-
-/* 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 00000000..3c32d711
--- /dev/null
+++ b/src/input_curl.c
@@ -0,0 +1,493 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2008 Max Kellermann <max@duempel.org>
+ * 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 <assert.h>
+#include <sys/select.h>
+#include <string.h>
+#include <errno.h>
+
+#include <curl/curl.h>
+#include <glib.h>
+
+/**
+ * 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
index 01d70c1e..9ced70f7 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 <max@duempel.org>
* 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 <stdbool.h>
-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 b7095221..a456db04 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
};