99468b85ed
Basically, I don't trust myself nor Max to not have bugs in our code when switching over to unsigned types, so I've added more assertions which will hopefully trip and force us to fix these bugs before somebody can exploit them :) Some cleanups for parameter parsing using strtol and error reporting to the user. Also, fix some completely garbled indentation in inputStream_http.c git-svn-id: https://svn.musicpd.org/mpd/trunk@7209 09075e82-0dd4-0310-85a5-a0d7c8717e4f
912 lines
21 KiB
C
912 lines
21 KiB
C
/* 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 "utils.h"
|
|
#include "log.h"
|
|
#include "conf.h"
|
|
#include "os_compat.h"
|
|
|
|
#define HTTP_CONN_STATE_CLOSED 0
|
|
#define HTTP_CONN_STATE_INIT 1
|
|
#define HTTP_CONN_STATE_HELLO 2
|
|
#define HTTP_CONN_STATE_OPEN 3
|
|
#define HTTP_CONN_STATE_REOPEN 4
|
|
|
|
#define HTTP_BUFFER_SIZE_DEFAULT 131072
|
|
#define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2)
|
|
|
|
#define HTTP_REDIRECT_MAX 10
|
|
|
|
#define HTTP_MAX_TRIES 100
|
|
|
|
static char *proxyHost;
|
|
static char *proxyPort;
|
|
static char *proxyUser;
|
|
static char *proxyPassword;
|
|
static size_t bufferSize = HTTP_BUFFER_SIZE_DEFAULT;
|
|
static size_t prebufferSize = HTTP_PREBUFFER_SIZE_DEFAULT;
|
|
|
|
typedef struct _InputStreemHTTPData {
|
|
char *host;
|
|
char *path;
|
|
char *port;
|
|
int sock;
|
|
int connState;
|
|
char *buffer;
|
|
size_t buflen;
|
|
int timesRedirected;
|
|
size_t icyMetaint;
|
|
int prebuffer;
|
|
size_t icyOffset;
|
|
char *proxyAuth;
|
|
char *httpAuth;
|
|
/* Number of times mpd tried to get data */
|
|
int tries;
|
|
} InputStreamHTTPData;
|
|
|
|
void inputStream_initHttp(void)
|
|
{
|
|
ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST);
|
|
char *test;
|
|
if (param) {
|
|
proxyHost = 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);
|
|
}
|
|
proxyPort = param->value;
|
|
|
|
param = getConfigParam(CONF_HTTP_PROXY_USER);
|
|
|
|
if (param) {
|
|
proxyUser = 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);
|
|
}
|
|
|
|
proxyPassword = 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);
|
|
}
|
|
|
|
bufferSize = tmp * 1024;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
prebufferSize = tmp * 1024;
|
|
}
|
|
|
|
if (prebufferSize > bufferSize)
|
|
prebufferSize = bufferSize;
|
|
assert(bufferSize > 0 && "http bufferSize too small");
|
|
assert(prebufferSize > 0 && "http prebufferSize too small");
|
|
}
|
|
|
|
/* 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 *authString(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 proxyAuthString(x, y) authString(PROXY_AUTH_HEADER, x, y)
|
|
#define httpAuthString(x, y) authString(HTTP_AUTH_HEADER, x, y)
|
|
|
|
static InputStreamHTTPData *newInputStreamHTTPData(void)
|
|
{
|
|
InputStreamHTTPData *ret = xmalloc(sizeof(InputStreamHTTPData));
|
|
|
|
if (proxyHost) {
|
|
ret->proxyAuth = proxyAuthString(proxyUser, proxyPassword);
|
|
} else
|
|
ret->proxyAuth = NULL;
|
|
|
|
ret->httpAuth = NULL;
|
|
ret->host = NULL;
|
|
ret->path = NULL;
|
|
ret->port = NULL;
|
|
ret->connState = HTTP_CONN_STATE_CLOSED;
|
|
ret->timesRedirected = 0;
|
|
ret->icyMetaint = 0;
|
|
ret->prebuffer = 0;
|
|
ret->icyOffset = 0;
|
|
ret->buffer = xmalloc(bufferSize);
|
|
ret->tries = 0;
|
|
return ret;
|
|
}
|
|
|
|
static void freeInputStreamHTTPData(InputStreamHTTPData * data)
|
|
{
|
|
if (data->host)
|
|
free(data->host);
|
|
if (data->path)
|
|
free(data->path);
|
|
if (data->port)
|
|
free(data->port);
|
|
if (data->proxyAuth)
|
|
free(data->proxyAuth);
|
|
if (data->httpAuth)
|
|
free(data->httpAuth);
|
|
|
|
free(data->buffer);
|
|
|
|
free(data);
|
|
}
|
|
|
|
static int parseUrl(InputStreamHTTPData * data, char *url)
|
|
{
|
|
char *temp;
|
|
char *colon;
|
|
char *slash;
|
|
char *at;
|
|
int len;
|
|
|
|
if (strncmp("http://", url, strlen("http://")) != 0)
|
|
return -1;
|
|
|
|
temp = url + strlen("http://");
|
|
|
|
colon = strchr(temp, ':');
|
|
at = strchr(temp, '@');
|
|
|
|
if (data->httpAuth) {
|
|
free(data->httpAuth);
|
|
data->httpAuth = NULL;
|
|
}
|
|
|
|
if (at) {
|
|
char *user;
|
|
char *passwd;
|
|
|
|
if (colon && colon < at) {
|
|
user = xmalloc(colon - temp + 1);
|
|
memcpy(user, temp, colon - temp);
|
|
user[colon - temp] = '\0';
|
|
|
|
passwd = xmalloc(at - colon);
|
|
memcpy(passwd, colon + 1, at - colon - 1);
|
|
passwd[at - colon - 1] = '\0';
|
|
} else {
|
|
user = xmalloc(at - temp + 1);
|
|
memcpy(user, temp, at - temp);
|
|
user[at - temp] = '\0';
|
|
|
|
passwd = xstrdup("");
|
|
}
|
|
|
|
data->httpAuth = httpAuthString(user, passwd);
|
|
|
|
free(user);
|
|
free(passwd);
|
|
|
|
temp = at + 1;
|
|
colon = strchr(temp, ':');
|
|
}
|
|
|
|
slash = strchr(temp, '/');
|
|
|
|
if (slash && colon && slash <= colon)
|
|
return -1;
|
|
|
|
/* fetch the host portion */
|
|
if (colon)
|
|
len = colon - temp + 1;
|
|
else if (slash)
|
|
len = slash - temp + 1;
|
|
else
|
|
len = strlen(temp) + 1;
|
|
|
|
if (len <= 1)
|
|
return -1;
|
|
|
|
data->host = xmalloc(len);
|
|
memcpy(data->host, temp, len - 1);
|
|
data->host[len - 1] = '\0';
|
|
/* 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");
|
|
}
|
|
|
|
/* fetch the path */
|
|
if (proxyHost)
|
|
data->path = xstrdup(url);
|
|
else
|
|
data->path = xstrdup(slash ? slash : "/");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int initHTTPConnection(InputStream * inStream)
|
|
{
|
|
char *connHost;
|
|
char *connPort;
|
|
struct addrinfo *ans = NULL;
|
|
struct addrinfo *ap = NULL;
|
|
struct addrinfo hints;
|
|
int error, flags;
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
/**
|
|
* Setup hints
|
|
*/
|
|
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 (proxyHost) {
|
|
connHost = proxyHost;
|
|
connPort = proxyPort;
|
|
} else {
|
|
connHost = data->host;
|
|
connPort = data->port;
|
|
}
|
|
|
|
error = getaddrinfo(connHost, connPort, &hints, &ans);
|
|
if (error) {
|
|
DEBUG(__FILE__ ": Error getting address info: %s\n",
|
|
gai_strerror(error));
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* loop through possible addresses */
|
|
for (ap = ans; ap != NULL; ap = ap->ai_next) {
|
|
if ((data->sock = socket(ap->ai_family, ap->ai_socktype,
|
|
ap->ai_protocol)) < 0) {
|
|
DEBUG(__FILE__ ": unable to connect: %s\n",
|
|
strerror(errno));
|
|
freeaddrinfo(ans);
|
|
return -1;
|
|
}
|
|
|
|
flags = fcntl(data->sock, F_GETFL, 0);
|
|
fcntl(data->sock, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
if (connect(data->sock, ap->ai_addr, ap->ai_addrlen) >= 0
|
|
|| errno == EINPROGRESS) {
|
|
data->connState = HTTP_CONN_STATE_INIT;
|
|
data->buflen = 0;
|
|
freeaddrinfo(ans);
|
|
return 0; /* success */
|
|
}
|
|
|
|
/* failed, get the next one */
|
|
|
|
DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno));
|
|
close(data->sock);
|
|
}
|
|
|
|
freeaddrinfo(ans);
|
|
return -1; /* failed */
|
|
}
|
|
|
|
static int finishHTTPInit(InputStream * inStream)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
struct timeval tv;
|
|
fd_set writeSet;
|
|
fd_set errorSet;
|
|
int error;
|
|
socklen_t error_len = sizeof(int);
|
|
int ret;
|
|
size_t length;
|
|
ssize_t nbytes;
|
|
char request[2048];
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
|
|
FD_ZERO(&writeSet);
|
|
FD_ZERO(&errorSet);
|
|
FD_SET(data->sock, &writeSet);
|
|
FD_SET(data->sock, &errorSet);
|
|
|
|
ret = select(data->sock + 1, NULL, &writeSet, &errorSet, &tv);
|
|
|
|
if (ret == 0 || (ret < 0 && errno == EINTR))
|
|
return 0;
|
|
|
|
if (ret < 0) {
|
|
DEBUG(__FILE__ ": problem select'ing: %s\n", strerror(errno));
|
|
goto close_err;
|
|
}
|
|
|
|
getsockopt(data->sock, SOL_SOCKET, SO_ERROR, &error, &error_len);
|
|
if (error)
|
|
goto close_err;
|
|
|
|
/* deal with ICY metadata later, for now its fucking up stuff! */
|
|
length = (size_t)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,
|
|
inStream->offset,
|
|
data->proxyAuth ? data->proxyAuth :
|
|
(data->httpAuth ? data->httpAuth : ""));
|
|
|
|
if (length >= sizeof(request))
|
|
goto close_err;
|
|
nbytes = write(data->sock, request, length);
|
|
if (nbytes < 0 || (size_t)nbytes != length)
|
|
goto close_err;
|
|
|
|
data->connState = HTTP_CONN_STATE_HELLO;
|
|
return 0;
|
|
|
|
close_err:
|
|
close(data->sock);
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
return -1;
|
|
}
|
|
|
|
static int getHTTPHello(InputStream * inStream)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
fd_set readSet;
|
|
struct timeval tv;
|
|
int ret;
|
|
char *needle;
|
|
char *cur = data->buffer;
|
|
int rc;
|
|
long readed;
|
|
|
|
FD_ZERO(&readSet);
|
|
FD_SET(data->sock, &readSet);
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
|
|
ret = select(data->sock + 1, &readSet, NULL, NULL, &tv);
|
|
|
|
if (ret == 0 || (ret < 0 && errno == EINTR))
|
|
return 0;
|
|
|
|
if (ret < 0) {
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
close(data->sock);
|
|
data->buflen = 0;
|
|
return -1;
|
|
}
|
|
|
|
if (data->buflen >= bufferSize - 1) {
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
close(data->sock);
|
|
return -1;
|
|
}
|
|
|
|
readed = recv(data->sock, data->buffer + data->buflen,
|
|
bufferSize - 1 - data->buflen, 0);
|
|
|
|
if (readed < 0 && (errno == EAGAIN || errno == EINTR))
|
|
return 0;
|
|
|
|
if (readed <= 0) {
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
close(data->sock);
|
|
data->buflen = 0;
|
|
return -1;
|
|
}
|
|
|
|
data->buffer[data->buflen + readed] = '\0';
|
|
data->buflen += readed;
|
|
|
|
needle = strstr(data->buffer, "\r\n\r\n");
|
|
|
|
if (!needle)
|
|
return 0;
|
|
|
|
if (0 == strncmp(cur, "HTTP/1.0 ", 9)) {
|
|
inStream->seekable = 0;
|
|
rc = atoi(cur + 9);
|
|
} else if (0 == strncmp(cur, "HTTP/1.1 ", 9)) {
|
|
inStream->seekable = 1;
|
|
rc = atoi(cur + 9);
|
|
} else if (0 == strncmp(cur, "ICY 200 OK", 10)) {
|
|
inStream->seekable = 0;
|
|
rc = 200;
|
|
} else if (0 == strncmp(cur, "ICY 400 Server Full", 19))
|
|
rc = 400;
|
|
else if (0 == strncmp(cur, "ICY 404", 7))
|
|
rc = 404;
|
|
else {
|
|
close(data->sock);
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
return -1;
|
|
}
|
|
|
|
switch (rc) {
|
|
case 200:
|
|
case 206:
|
|
break;
|
|
case 301:
|
|
case 302:
|
|
cur = strstr(cur, "Location: ");
|
|
if (cur) {
|
|
char *url;
|
|
int curlen = 0;
|
|
cur += strlen("Location: ");
|
|
while (*(cur + curlen) != '\0'
|
|
&& *(cur + curlen) != '\r') {
|
|
curlen++;
|
|
}
|
|
url = xmalloc(curlen + 1);
|
|
memcpy(url, cur, curlen);
|
|
url[curlen] = '\0';
|
|
ret = parseUrl(data, url);
|
|
free(url);
|
|
if (ret == 0 && data->timesRedirected <
|
|
HTTP_REDIRECT_MAX) {
|
|
data->timesRedirected++;
|
|
close(data->sock);
|
|
data->connState = HTTP_CONN_STATE_REOPEN;
|
|
data->buflen = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
case 400:
|
|
case 401:
|
|
case 403:
|
|
case 404:
|
|
default:
|
|
close(data->sock);
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
data->buflen = 0;
|
|
return -1;
|
|
}
|
|
|
|
cur = strstr(data->buffer, "\r\n");
|
|
while (cur && cur != needle) {
|
|
if (0 == strncasecmp(cur, "\r\nContent-Length: ", 18)) {
|
|
if (!inStream->size)
|
|
inStream->size = atol(cur + 18);
|
|
} else if (0 == strncasecmp(cur, "\r\nicy-metaint:", 14)) {
|
|
data->icyMetaint = strtoul(cur + 14, NULL, 0);
|
|
} else if (0 == strncasecmp(cur, "\r\nicy-name:", 11) ||
|
|
0 == strncasecmp(cur, "\r\nice-name:", 11)) {
|
|
int incr = 11;
|
|
char *temp = strstr(cur + incr, "\r\n");
|
|
if (!temp)
|
|
break;
|
|
*temp = '\0';
|
|
if (inStream->metaName)
|
|
free(inStream->metaName);
|
|
while (*(incr + cur) == ' ')
|
|
incr++;
|
|
inStream->metaName = xstrdup(cur + incr);
|
|
*temp = '\r';
|
|
DEBUG("inputStream_http: metaName: %s\n",
|
|
inStream->metaName);
|
|
} else if (0 == strncasecmp(cur, "\r\nx-audiocast-name:", 19)) {
|
|
int incr = 19;
|
|
char *temp = strstr(cur + incr, "\r\n");
|
|
if (!temp)
|
|
break;
|
|
*temp = '\0';
|
|
if (inStream->metaName)
|
|
free(inStream->metaName);
|
|
while (*(incr + cur) == ' ')
|
|
incr++;
|
|
inStream->metaName = xstrdup(cur + incr);
|
|
*temp = '\r';
|
|
DEBUG("inputStream_http: metaName: %s\n",
|
|
inStream->metaName);
|
|
} else if (0 == strncasecmp(cur, "\r\nContent-Type:", 15)) {
|
|
int incr = 15;
|
|
char *temp = strstr(cur + incr, "\r\n");
|
|
if (!temp)
|
|
break;
|
|
*temp = '\0';
|
|
if (inStream->mime)
|
|
free(inStream->mime);
|
|
while (*(incr + cur) == ' ')
|
|
incr++;
|
|
inStream->mime = xstrdup(cur + incr);
|
|
*temp = '\r';
|
|
}
|
|
|
|
cur = strstr(cur + 2, "\r\n");
|
|
}
|
|
|
|
if (inStream->size <= 0)
|
|
inStream->seekable = 0;
|
|
|
|
needle += 4; /* 4 == strlen("\r\n\r\n") */
|
|
data->buflen -= (needle - data->buffer);
|
|
memmove(data->buffer, needle, data->buflen);
|
|
|
|
data->connState = HTTP_CONN_STATE_OPEN;
|
|
|
|
data->prebuffer = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int inputStream_httpOpen(InputStream * inStream, char *url)
|
|
{
|
|
InputStreamHTTPData *data = newInputStreamHTTPData();
|
|
|
|
inStream->data = data;
|
|
if (parseUrl(data, url) < 0) {
|
|
freeInputStreamHTTPData(data);
|
|
return -1;
|
|
}
|
|
|
|
if (initHTTPConnection(inStream) < 0) {
|
|
freeInputStreamHTTPData(data);
|
|
return -1;
|
|
}
|
|
|
|
inStream->seekFunc = inputStream_httpSeek;
|
|
inStream->closeFunc = inputStream_httpClose;
|
|
inStream->readFunc = inputStream_httpRead;
|
|
inStream->atEOFFunc = inputStream_httpAtEOF;
|
|
inStream->bufferFunc = inputStream_httpBuffer;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int inputStream_httpSeek(InputStream * inStream, long offset, int whence)
|
|
{
|
|
InputStreamHTTPData *data;
|
|
if (!inStream->seekable)
|
|
return -1;
|
|
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
inStream->offset = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
inStream->offset += offset;
|
|
break;
|
|
case SEEK_END:
|
|
inStream->offset = inStream->size + offset;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
data = (InputStreamHTTPData *)inStream->data;
|
|
close(data->sock);
|
|
data->connState = HTTP_CONN_STATE_REOPEN;
|
|
data->buflen = 0;
|
|
|
|
inputStream_httpBuffer(inStream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void parseIcyMetadata(InputStream * inStream, char *metadata, int size)
|
|
{
|
|
char *r;
|
|
char *s;
|
|
char *temp = xmalloc(size + 1);
|
|
memcpy(temp, metadata, size);
|
|
temp[size] = '\0';
|
|
s = strtok_r(temp, ";", &r);
|
|
while (s) {
|
|
if (0 == strncmp(s, "StreamTitle=", 12)) {
|
|
int cur = 12;
|
|
if (inStream->metaTitle)
|
|
free(inStream->metaTitle);
|
|
if (*(s + cur) == '\'')
|
|
cur++;
|
|
if (s[strlen(s) - 1] == '\'') {
|
|
s[strlen(s) - 1] = '\0';
|
|
}
|
|
inStream->metaTitle = xstrdup(s + cur);
|
|
DEBUG("inputStream_http: metaTitle: %s\n",
|
|
inStream->metaTitle);
|
|
}
|
|
s = strtok_r(NULL, ";", &r);
|
|
}
|
|
free(temp);
|
|
}
|
|
|
|
size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size,
|
|
size_t nmemb)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
size_t tosend = 0;
|
|
size_t inlen = size * nmemb;
|
|
size_t maxToSend = data->buflen;
|
|
|
|
inputStream_httpBuffer(inStream);
|
|
|
|
switch (data->connState) {
|
|
case HTTP_CONN_STATE_OPEN:
|
|
if (data->prebuffer || data->buflen < data->icyMetaint)
|
|
return 0;
|
|
|
|
break;
|
|
case HTTP_CONN_STATE_CLOSED:
|
|
if (data->buflen)
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (data->icyMetaint > 0) {
|
|
if (data->icyOffset >= data->icyMetaint) {
|
|
size_t metalen = *(data->buffer);
|
|
/* maybe we're in some strange universe where a byte
|
|
* can hold more than 255 ... */
|
|
assert(metalen <= 255 && "metalen greater than 255");
|
|
metalen <<= 4;
|
|
if (metalen + 1 > data->buflen) {
|
|
/* damn that's some fucking big metadata! */
|
|
if (bufferSize < metalen + 1) {
|
|
data->connState =
|
|
HTTP_CONN_STATE_CLOSED;
|
|
close(data->sock);
|
|
data->buflen = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
if (metalen > 0) {
|
|
parseIcyMetadata(inStream, data->buffer + 1,
|
|
metalen);
|
|
}
|
|
data->buflen -= metalen + 1;
|
|
memmove(data->buffer, data->buffer + metalen + 1,
|
|
data->buflen);
|
|
data->icyOffset = 0;
|
|
}
|
|
assert(data->icyOffset <= data->icyMetaint &&
|
|
"icyOffset bigger than icyMetaint!");
|
|
maxToSend = data->icyMetaint - data->icyOffset;
|
|
maxToSend = maxToSend > data->buflen ? data->buflen : maxToSend;
|
|
}
|
|
|
|
if (data->buflen > 0) {
|
|
tosend = inlen > maxToSend ? maxToSend : inlen;
|
|
tosend = (tosend / size) * size;
|
|
|
|
memcpy(ptr, data->buffer, tosend);
|
|
data->buflen -= tosend;
|
|
data->icyOffset += tosend;
|
|
memmove(data->buffer, data->buffer + tosend, data->buflen);
|
|
|
|
inStream->offset += tosend;
|
|
}
|
|
|
|
return tosend / size;
|
|
}
|
|
|
|
int inputStream_httpClose(InputStream * inStream)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
|
|
switch (data->connState) {
|
|
case HTTP_CONN_STATE_CLOSED:
|
|
break;
|
|
default:
|
|
close(data->sock);
|
|
}
|
|
|
|
freeInputStreamHTTPData(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int inputStream_httpAtEOF(InputStream * inStream)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
switch (data->connState) {
|
|
case HTTP_CONN_STATE_CLOSED:
|
|
if (data->buflen == 0)
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int inputStream_httpBuffer(InputStream * inStream)
|
|
{
|
|
InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
|
|
ssize_t readed = 0;
|
|
if (data->connState == HTTP_CONN_STATE_REOPEN) {
|
|
if (initHTTPConnection(inStream) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (data->connState == HTTP_CONN_STATE_INIT) {
|
|
if (finishHTTPInit(inStream) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (data->connState == HTTP_CONN_STATE_HELLO) {
|
|
if (getHTTPHello(inStream) < 0)
|
|
return -1;
|
|
}
|
|
|
|
switch (data->connState) {
|
|
case HTTP_CONN_STATE_OPEN:
|
|
case HTTP_CONN_STATE_CLOSED:
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if (data->buflen == 0 || data->buflen < data->icyMetaint) {
|
|
data->prebuffer = 1;
|
|
} else if (data->buflen > prebufferSize)
|
|
data->prebuffer = 0;
|
|
|
|
if (data->connState == HTTP_CONN_STATE_OPEN &&
|
|
data->buflen < bufferSize - 1) {
|
|
readed = read(data->sock, data->buffer + data->buflen,
|
|
bufferSize - 1 - data->buflen);
|
|
/*
|
|
* If the connection is currently unavailable, or
|
|
* interrupted (EINTR)
|
|
* Don't give an error, so it's retried later.
|
|
* Max times in a row to retry this is HTTP_MAX_TRIES
|
|
*/
|
|
if (readed < 0 &&
|
|
(errno == EAGAIN || errno == EINTR) &&
|
|
data->tries < HTTP_MAX_TRIES) {
|
|
data->tries++;
|
|
DEBUG(__FILE__": Resource unavailable, trying %i "
|
|
"times again\n", HTTP_MAX_TRIES - data->tries);
|
|
readed = 0;
|
|
} else if (readed <= 0) {
|
|
while (close(data->sock) && errno == EINTR);
|
|
data->connState = HTTP_CONN_STATE_CLOSED;
|
|
readed = 0;
|
|
} else /* readed > 0, reset */
|
|
data->tries = 0;
|
|
|
|
data->buflen += readed;
|
|
}
|
|
|
|
if (data->buflen > prebufferSize)
|
|
data->prebuffer = 0;
|
|
|
|
return (readed ? 1 : 0);
|
|
}
|