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.
This commit is contained in:
Max Kellermann 2008-10-26 19:32:43 +01:00
parent 6b09e4daef
commit 3609de8685
9 changed files with 547 additions and 1171 deletions

View File

@ -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
--------

View File

@ -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],

View File

@ -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) \

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 */

493
src/input_curl.c Normal file
View File

@ -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;
}

View File

@ -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

View File

@ -27,7 +27,9 @@
#include "os_compat.h"
static const char *remoteUrlPrefixes[] = {
#ifdef HAVE_CURL
"http://",
#endif
NULL
};