2004-05-18 04:46:36 +02:00
|
|
|
/* the Music Player Daemon (MPD)
|
2007-04-05 05:22:33 +02:00
|
|
|
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
|
2004-05-18 04:46:36 +02:00
|
|
|
* 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"
|
2008-06-30 04:42:45 +02:00
|
|
|
#include "inputStream_http_auth.h"
|
2004-05-18 04:46:36 +02:00
|
|
|
|
|
|
|
#include "utils.h"
|
2004-05-25 01:45:11 +02:00
|
|
|
#include "log.h"
|
2004-06-20 19:07:13 +02:00
|
|
|
#include "conf.h"
|
2008-01-03 08:29:49 +01:00
|
|
|
#include "os_compat.h"
|
2008-06-30 04:43:13 +02:00
|
|
|
#include "ringbuf.h"
|
2008-06-30 04:43:22 +02:00
|
|
|
#include "condition.h"
|
2008-06-30 04:43:13 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
};
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2004-11-02 19:53:38 +01:00
|
|
|
#define HTTP_BUFFER_SIZE_DEFAULT 131072
|
|
|
|
#define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2)
|
2004-05-18 04:46:36 +02:00
|
|
|
#define HTTP_REDIRECT_MAX 10
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2004-10-28 07:14:55 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data {
|
|
|
|
int fd;
|
|
|
|
enum conn_state state;
|
2004-11-02 19:53:38 +01:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
/* { we may have a non-multithreaded HTTP discipline in the future */
|
|
|
|
enum conn_action action;
|
|
|
|
int pipe_fds[2];
|
2004-11-02 19:53:38 +01:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
pthread_t io_thread;
|
|
|
|
struct ringbuf *rb;
|
2004-11-02 19:53:38 +01:00
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
struct condition full_cond;
|
|
|
|
struct condition empty_cond;
|
|
|
|
struct condition action_cond;
|
2008-06-30 04:43:13 +02:00
|
|
|
/* } */
|
2004-11-02 19:53:38 +01:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
int nr_redirect;
|
|
|
|
size_t icy_metaint;
|
|
|
|
size_t icy_offset;
|
|
|
|
char *host;
|
|
|
|
char *path;
|
|
|
|
char *port;
|
|
|
|
char *proxy_auth;
|
|
|
|
char *http_auth;
|
|
|
|
};
|
2004-11-02 19:53:38 +01:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static int awaken_buffer_task(struct http_data *data);
|
2005-04-17 20:51:03 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_init(&data->action_cond);
|
|
|
|
cond_init(&data->full_cond);
|
|
|
|
cond_init(&data->empty_cond);
|
2004-06-20 19:07:13 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static struct http_data *new_http_data(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data *ret = xmalloc(sizeof(struct http_data));
|
|
|
|
init_http_data(ret);
|
2006-07-20 18:02:40 +02:00
|
|
|
return ret;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static void free_http_data(struct http_data * data)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_destroy(&data->action_cond);
|
|
|
|
cond_destroy(&data->full_cond);
|
|
|
|
cond_destroy(&data->empty_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
|
|
|
|
xclose(data->pipe_fds[0]);
|
|
|
|
xclose(data->pipe_fds[1]);
|
|
|
|
ringbuf_free(data->rb);
|
2006-07-20 18:02:40 +02:00
|
|
|
free(data);
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static int parse_url(struct http_data * data, char *url)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
|
|
|
char *colon;
|
|
|
|
char *slash;
|
|
|
|
char *at;
|
|
|
|
int len;
|
2008-06-30 04:43:13 +02:00
|
|
|
char *cur = url;
|
|
|
|
size_t offset;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
if (!match("http://"))
|
2006-07-20 18:02:40 +02:00
|
|
|
return -1;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
cur = url + offset;
|
|
|
|
colon = strchr(cur, ':');
|
|
|
|
at = strchr(cur, '@');
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
if (data->http_auth) {
|
|
|
|
free(data->http_auth);
|
|
|
|
data->http_auth = NULL;
|
2004-07-17 15:41:31 +02:00
|
|
|
}
|
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (at) {
|
|
|
|
char *user;
|
|
|
|
char *passwd;
|
2004-07-17 15:41:31 +02:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (colon && colon < at) {
|
2008-06-30 04:43:13 +02:00
|
|
|
user = xmalloc(colon - cur + 1);
|
|
|
|
memcpy(user, cur, colon - cur);
|
|
|
|
user[colon - cur] = '\0';
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2006-08-26 08:25:57 +02:00
|
|
|
passwd = xmalloc(at - colon);
|
2006-07-30 12:31:41 +02:00
|
|
|
memcpy(passwd, colon + 1, at - colon - 1);
|
2006-07-20 18:02:40 +02:00
|
|
|
passwd[at - colon - 1] = '\0';
|
|
|
|
} else {
|
2008-06-30 04:43:13 +02:00
|
|
|
user = xmalloc(at - cur + 1);
|
|
|
|
memcpy(user, cur, at - cur);
|
|
|
|
user[at - cur] = '\0';
|
2004-07-17 15:41:31 +02:00
|
|
|
|
2006-08-26 08:25:57 +02:00
|
|
|
passwd = xstrdup("");
|
2004-07-17 15:41:31 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
data->http_auth = http_auth_string(user, passwd);
|
2004-07-17 15:41:31 +02:00
|
|
|
|
|
|
|
free(user);
|
|
|
|
free(passwd);
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
cur = at + 1;
|
|
|
|
colon = strchr(cur, ':');
|
2004-07-17 15:41:31 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
slash = strchr(cur, '/');
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (slash && colon && slash <= colon)
|
|
|
|
return -1;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
/* fetch the host portion */
|
|
|
|
if (colon)
|
2008-06-30 04:43:13 +02:00
|
|
|
len = colon - cur + 1;
|
2006-07-20 18:02:40 +02:00
|
|
|
else if (slash)
|
2008-06-30 04:43:13 +02:00
|
|
|
len = slash - cur + 1;
|
2006-07-20 18:02:40 +02:00
|
|
|
else
|
2008-06-30 04:43:13 +02:00
|
|
|
len = strlen(cur) + 1;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (len <= 1)
|
|
|
|
return -1;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:08 +02:00
|
|
|
if (data->host)
|
|
|
|
free(data->host);
|
2006-08-26 08:25:57 +02:00
|
|
|
data->host = xmalloc(len);
|
2008-06-30 04:43:13 +02:00
|
|
|
memcpy(data->host, cur, len - 1);
|
2006-07-20 18:02:40 +02:00
|
|
|
data->host[len - 1] = '\0';
|
2008-06-30 04:43:08 +02:00
|
|
|
if (data->port)
|
|
|
|
free(data->port);
|
2006-07-20 18:02:40 +02:00
|
|
|
/* fetch the port */
|
|
|
|
if (colon && (!slash || slash != colon + 1)) {
|
|
|
|
len = strlen(colon) - 1;
|
|
|
|
if (slash)
|
|
|
|
len -= strlen(slash);
|
2006-08-26 08:25:57 +02:00
|
|
|
data->port = xmalloc(len + 1);
|
2006-07-30 12:31:41 +02:00
|
|
|
memcpy(data->port, colon + 1, len);
|
2006-07-20 18:02:40 +02:00
|
|
|
data->port[len] = '\0';
|
2006-05-21 03:36:49 +02:00
|
|
|
DEBUG(__FILE__ ": Port: %s\n", data->port);
|
2006-07-20 18:02:40 +02:00
|
|
|
} else {
|
2006-08-26 08:25:57 +02:00
|
|
|
data->port = xstrdup("80");
|
2006-05-14 13:40:34 +02:00
|
|
|
}
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:08 +02:00
|
|
|
if (data->path)
|
|
|
|
free(data->path);
|
2006-07-20 18:02:40 +02:00
|
|
|
/* fetch the path */
|
2008-06-30 04:43:13 +02:00
|
|
|
data->path = proxy_host ? xstrdup(url) : xstrdup(slash ? slash : "/");
|
2006-07-20 18:02:40 +02:00
|
|
|
|
|
|
|
return 0;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
/* 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()));
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_enter(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2008-06-30 04:43:22 +02:00
|
|
|
if (nonblocking)
|
|
|
|
cond_timedwait(&data->action_cond, 1);
|
|
|
|
else
|
|
|
|
cond_wait(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
ret = 0;
|
|
|
|
out:
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_leave(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int take_action(struct http_data *data)
|
|
|
|
{
|
|
|
|
assert(pthread_equal(data->io_thread, pthread_self()));
|
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_enter(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
switch (data->action) {
|
|
|
|
case CONN_ACTION_NONE:
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_leave(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_signal_sync(&data->action_cond);
|
|
|
|
cond_leave(&data->action_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-06-30 04:42:49 +02:00
|
|
|
/* returns -1 on error, 0 on success (and sets dest) */
|
|
|
|
static int my_getaddrinfo(struct addrinfo **dest,
|
|
|
|
const char *host, const char *port)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2006-05-14 13:40:34 +02:00
|
|
|
struct addrinfo hints;
|
2008-03-26 11:39:03 +01:00
|
|
|
int error;
|
2008-06-30 04:42:49 +02:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
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;
|
|
|
|
|
2008-06-30 04:42:49 +02:00
|
|
|
if ((error = getaddrinfo(host, port, &hints, dest))) {
|
|
|
|
DEBUG(__FILE__ ": Error getting address info for %s:%s: %s\n",
|
|
|
|
host, port, gai_strerror(error));
|
2006-05-14 13:40:34 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2008-06-30 04:42:49 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2006-05-14 13:40:34 +02:00
|
|
|
/* loop through possible addresses */
|
2006-07-20 18:02:40 +02:00
|
|
|
for (ap = ans; ap != NULL; ap = ap->ai_next) {
|
2008-06-30 04:42:49 +02:00
|
|
|
fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol);
|
|
|
|
if (fd < 0) {
|
|
|
|
DEBUG(__FILE__ ": unable to get socket: %s\n",
|
2006-07-20 18:02:40 +02:00
|
|
|
strerror(errno));
|
2008-06-30 04:42:49 +02:00
|
|
|
continue;
|
2006-05-14 13:40:34 +02:00
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:42:49 +02:00
|
|
|
set_nonblocking(fd);
|
|
|
|
if (connect(fd, ap->ai_addr, ap->ai_addrlen) >= 0
|
|
|
|
|| errno == EINPROGRESS)
|
|
|
|
return fd; /* success */
|
2004-06-20 22:15:05 +02:00
|
|
|
DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno));
|
2008-06-30 04:42:49 +02:00
|
|
|
xclose(fd); /* failed, get the next one */
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:42:49 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static int init_connection(struct http_data *data)
|
2008-06-30 04:42:49 +02:00
|
|
|
{
|
|
|
|
struct addrinfo *ans = NULL;
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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)
|
2008-06-30 04:42:49 +02:00
|
|
|
return -1;
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
assert(data->fd < 0);
|
|
|
|
data->fd = my_connect_addrs(ans);
|
2006-05-14 13:40:34 +02:00
|
|
|
freeaddrinfo(ans);
|
2008-06-30 04:42:49 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
if (data->fd < 0)
|
2008-06-30 04:42:49 +02:00
|
|
|
return -1; /* failed */
|
2008-06-30 04:43:13 +02:00
|
|
|
data->state = CONN_STATE_CONNECTED;
|
2008-06-30 04:42:49 +02:00
|
|
|
return 0;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
#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)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
|
|
|
int ret;
|
2008-06-30 04:43:13 +02:00
|
|
|
int error = 0;
|
|
|
|
socklen_t error_len = sizeof(int);
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len);
|
|
|
|
return (ret < 0 || error) ? AWAIT_ERROR : AWAIT_READY;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static enum await_result await_sendable(struct http_data *data)
|
|
|
|
{
|
|
|
|
fd_set rfds, wfds;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
assert(pthread_equal(data->io_thread, pthread_self()));
|
|
|
|
assert_state(CONN_STATE_CONNECTED);
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_wait(&data->full_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
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()));
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_signal_async(&data->empty_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int starved_wait(struct http_data *data, const long sec)
|
|
|
|
{
|
|
|
|
assert(!pthread_equal(data->io_thread, pthread_self()));
|
2008-06-30 04:43:22 +02:00
|
|
|
return cond_timedwait(&data->empty_cond, sec);
|
2008-06-30 04:43:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int awaken_buffer_task(struct http_data *data)
|
|
|
|
{
|
|
|
|
assert(!pthread_equal(data->io_thread, pthread_self()));
|
2008-06-30 04:43:22 +02:00
|
|
|
|
|
|
|
return ! cond_signal_async(&data->full_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
/*
|
|
|
|
* 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 */
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
assert(pthread_equal(data->io_thread, pthread_self()));
|
|
|
|
assert_state(CONN_STATE_CONNECTED);
|
|
|
|
length = snprintf(request, sizeof(request),
|
2007-06-09 15:34:53 +02:00
|
|
|
"GET %s HTTP/1.1\r\n"
|
|
|
|
"Host: %s\r\n"
|
2007-09-08 03:06:29 +02:00
|
|
|
"Connection: close\r\n"
|
2007-06-09 15:43:40 +02:00
|
|
|
"User-Agent: " PACKAGE_NAME "/" PACKAGE_VERSION "\r\n"
|
2007-06-09 15:34:53 +02:00
|
|
|
"Range: bytes=%ld-\r\n"
|
|
|
|
"%s" /* authorization */
|
|
|
|
"Icy-Metadata:1\r\n"
|
|
|
|
"\r\n",
|
|
|
|
data->path,
|
|
|
|
data->host,
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
return 0;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
/* handles parsing of the first line of the HTTP response */
|
|
|
|
static int parse_response_code(InputStream * is, const char *response)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static int leading_space(int c)
|
|
|
|
{
|
|
|
|
return (c == ' ' || c == '\t');
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static int parse_header_dup(char **dst, char *cur)
|
|
|
|
{
|
|
|
|
char *eol;
|
|
|
|
size_t len;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
while (cur && cur != needle) {
|
|
|
|
assert(cur < needle);
|
|
|
|
if (match("\r\nLocation:"))
|
|
|
|
goto found;
|
|
|
|
cur = strstr(cur + 2, "\r\n");
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
return -1;
|
|
|
|
found:
|
|
|
|
if (parse_header_dup(&url, cur + offset) < 0)
|
2006-07-20 18:02:40 +02:00
|
|
|
return -1;
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
return 0; /* success */
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
if (is->metaName) {
|
|
|
|
free(is->metaName);
|
|
|
|
is->metaName = NULL;
|
|
|
|
}
|
|
|
|
is->size = 0;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
/* 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;
|
2006-07-20 18:02:40 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
switch (parse_response_code(is, response)) {
|
|
|
|
case 200: /* OK */
|
|
|
|
case 206: /* Partial Content */
|
2006-07-20 18:02:40 +02:00
|
|
|
break;
|
2008-06-30 04:43:13 +02:00
|
|
|
case 301: /* Moved Permanently */
|
|
|
|
case 302: /* Moved Temporarily */
|
|
|
|
if (parse_redirect(is, response, needle) == 0)
|
|
|
|
return 0; /* success, reconnect */
|
2006-07-20 18:02:40 +02:00
|
|
|
default:
|
2008-06-30 04:43:13 +02:00
|
|
|
return err_close(data);
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static void * http_io_task(void *arg)
|
|
|
|
{
|
|
|
|
InputStream *is = (InputStream *) arg;
|
|
|
|
struct http_data *data = (struct http_data *) is->data;
|
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_enter(&data->full_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_leave(&data->full_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2004-05-18 21:32:05 +02:00
|
|
|
|
2008-08-26 08:27:02 +02:00
|
|
|
int inputStream_httpBuffer(mpd_unused InputStream *is)
|
2008-06-30 04:43:13 +02:00
|
|
|
{
|
2006-07-20 18:02:40 +02:00
|
|
|
return 0;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
int inputStream_httpOpen(InputStream * is, char *url)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data *data = new_http_data();
|
|
|
|
pthread_attr_t attr;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
is->seekable = 0;
|
|
|
|
is->data = data;
|
|
|
|
if (parse_url(data, url) < 0) {
|
|
|
|
free_http_data(data);
|
2006-07-20 18:02:40 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
is->seekFunc = inputStream_httpSeek;
|
|
|
|
is->closeFunc = inputStream_httpClose;
|
|
|
|
is->readFunc = inputStream_httpRead;
|
|
|
|
is->atEOFFunc = inputStream_httpAtEOF;
|
|
|
|
is->bufferFunc = inputStream_httpBuffer;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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));
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_enter(&data->empty_cond); /* httpClose will leave this */
|
2004-05-18 04:46:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
int inputStream_httpSeek(InputStream * is, long offset, int whence)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data *data = (struct http_data *)is->data;
|
|
|
|
long old_offset = is->offset;
|
|
|
|
long diff;
|
|
|
|
|
|
|
|
if (!is->seekable) {
|
|
|
|
is->error = ESPIPE;
|
2006-12-23 20:57:28 +01:00
|
|
|
return -1;
|
2008-06-30 04:43:13 +02:00
|
|
|
}
|
|
|
|
assert(is->size > 0);
|
2006-03-16 07:52:46 +01:00
|
|
|
|
2006-12-23 20:57:28 +01:00
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET:
|
2008-06-30 04:43:13 +02:00
|
|
|
is->offset = offset;
|
2006-12-23 20:57:28 +01:00
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
2008-06-30 04:43:13 +02:00
|
|
|
is->offset += offset;
|
2006-12-23 20:57:28 +01:00
|
|
|
break;
|
|
|
|
case SEEK_END:
|
2008-06-30 04:43:13 +02:00
|
|
|
is->offset = is->size + offset;
|
2006-12-23 20:57:28 +01:00
|
|
|
break;
|
|
|
|
default:
|
2008-06-30 04:43:13 +02:00
|
|
|
is->error = EINVAL;
|
2006-12-23 20:57:28 +01:00
|
|
|
return -1;
|
2006-03-16 07:52:46 +01:00
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
diff = is->offset - old_offset;
|
2008-06-30 04:43:36 +02:00
|
|
|
if (!diff)
|
|
|
|
return 0; /* nothing to seek */
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2006-12-23 20:57:28 +01:00
|
|
|
return 0;
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
static void parse_icy_metadata(InputStream * is, char *metadata, size_t size)
|
2004-06-07 07:00:56 +02:00
|
|
|
{
|
2008-04-12 06:20:45 +02:00
|
|
|
char *r = NULL;
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2004-06-07 07:00:56 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
cur = strtok_r(NULL, ";", &r);
|
2004-06-07 07:00:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-25 15:49:06 +02:00
|
|
|
static size_t read_with_metadata(InputStream *is, unsigned char *ptr,
|
|
|
|
ssize_t len)
|
2004-05-18 04:46:36 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
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)
|
2006-07-20 18:02:40 +02:00
|
|
|
break;
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2004-06-07 07:00:56 +02:00
|
|
|
metalen <<= 4;
|
2008-06-30 04:43:13 +02:00
|
|
|
if (metalen) {
|
|
|
|
r = ringbuf_read(data->rb, metabuf, metalen);
|
|
|
|
assert(r == metalen && "short metadata read");
|
|
|
|
parse_icy_metadata(is, (char*)metabuf, metalen);
|
2004-06-07 07:00:56 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
data->icy_offset = 0;
|
2004-06-07 07:00:56 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
return readed;
|
2004-05-18 11:54:45 +02:00
|
|
|
}
|
|
|
|
|
2008-08-25 15:49:06 +02:00
|
|
|
size_t inputStream_httpRead(InputStream * is, void *_ptr, size_t size,
|
2008-06-30 04:43:13 +02:00
|
|
|
size_t nmemb)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data *data = (struct http_data *) is->data;
|
|
|
|
size_t len = size * nmemb;
|
|
|
|
size_t r;
|
2008-08-25 15:49:06 +02:00
|
|
|
unsigned char *ptr = _ptr, *ptr0 = _ptr;
|
2008-06-30 04:43:13 +02:00
|
|
|
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:
|
2006-07-20 18:02:40 +02:00
|
|
|
break;
|
2008-06-30 04:43:13 +02:00
|
|
|
case CONN_STATE_CLOSED:
|
|
|
|
if (!ringbuf_read_space(data->rb))
|
|
|
|
return 0;
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2004-05-18 11:54:45 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
|
|
|
}
|
2004-05-18 11:54:45 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2008-06-30 04:43:22 +02:00
|
|
|
cond_leave(&data->empty_cond);
|
2008-06-30 04:43:13 +02:00
|
|
|
free_http_data(data);
|
2006-07-20 18:02:40 +02:00
|
|
|
return 0;
|
2004-05-18 11:54:45 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
int inputStream_httpAtEOF(InputStream * is)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
struct http_data *data = (struct http_data *) is->data;
|
|
|
|
if (data->state == CONN_STATE_CLOSED && !ringbuf_read_space(data->rb))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
2004-05-18 11:54:45 +02:00
|
|
|
}
|
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
void inputStream_initHttp(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-06-30 04:43:13 +02:00
|
|
|
ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST);
|
|
|
|
char *test;
|
|
|
|
if (param) {
|
|
|
|
proxy_host = param->value;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
param = getConfigParam(CONF_HTTP_PROXY_PORT);
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
if (!param) {
|
|
|
|
FATAL("%s specified but not %s\n", CONF_HTTP_PROXY_HOST,
|
|
|
|
CONF_HTTP_PROXY_PORT);
|
|
|
|
}
|
|
|
|
proxy_port = param->value;
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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);
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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;
|
2006-07-20 18:02:40 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
if (buffer_size < 4096)
|
|
|
|
FATAL(CONF_HTTP_BUFFER_SIZE" must be >= 4KB\n");
|
2004-05-18 04:46:36 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE);
|
2004-05-18 21:32:05 +02:00
|
|
|
|
2008-06-30 04:43:13 +02:00
|
|
|
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");
|
2004-05-18 04:46:36 +02:00
|
|
|
}
|
2008-06-30 04:43:13 +02:00
|
|
|
|