aboutsummaryrefslogblamecommitdiffstats
path: root/src/inputStream_http.c
blob: e633b7a660ebbe617c0309f1e02d26f8ff954368 (plain) (tree)
1
2
                                
                                                                   

















                                                                            
                
                 
                      





                                 
                                                                       

                               
                          


                           
                                                          
 
                                     
                   
                   



                            
                          
                      
                         
                        
                                                   
                      


                                                                  
                    


                                                             
                             
                                                                                
                                                    
                 
                                         
 
                                                             
                            
                                                 
 
                                                                         
                                     
                                                                  
                                                                
                         
                                                     
                                                                         
 
                                    
                                                                  
                                                                                      
                         
                 
                                                                    
                                                           
                                                                               
                                                                    
                                                           
                                                                               
                                                                        
                                                           
                                                                     
         

                                                      
                    
                                                           
                                                                            
                                                                                
                 
                                        


                                                         
                    
                                                           
                                                                            

                                                                     
                 
                                           
         
 
                                           
                                                                    
 


                                                  
                               
                            
                                                       
                                                
 
                                     






                                                       

                                                                         
                                      
                                      


                                                              
                                             




                                          


                   
                                                               
                         
                    
                     
 
                               
                                                      
                                

                               

                                 
                                                           
                            





                            




                                                                   
                                                        
                                                                        
 
                        
                                                                           
                                      
 
                             
                         
                         
                         

                                                
                           
                           
                                          
                       
                   
 










                                                               
 
                           
                   
 





                                                          
 
                                                            
 
                                       
 
                                  
                               
                             

                                      
 

                             
 
                                          
                                                         
                                                         
                                                  
                                                     
                                                                  
                                                      
                                                      
                                                      
                                               
 
                                             





                                                              
                                          
         
                                  
 
                                             
 





                                       
 
                          
 
                                  
                                          




                                                      
                                              
                                                   
                                       
                                                           
                
                                           
         
 
                            
                                          
            
                                                          
                 
 
                                                     



                                    
                  
                                                                           

                      








                                        
                                     
                


                                      
                                                              

                                                                    
 
                          
 
                                             



                                                                        

                                          
 
                                            

                                                                         

                                                               
                                                     
                 
 
                                              
 
                                                                             
                                  
 
                          
                                    
 







                                                                           
                       
                           














                                                                              
                               

                                                                         
                               
 
                                                                         
                                                           
                                              
                                                
                                                                               




                                                  

                                                                 

                                      
                                                    
                               
                                                
                 



                                                 
 


























































                                                                           
                                       



























                                                                
                                                  













                                                                         
                 








                                                         
                                                                        
                                                                
                                                                           
                                                                      
                                                                        







                                                                
                                                                 

                                                                 
                                                                                







                                                                
                                                                 

                                                                 
                                                                            







                                                                
                                                             







                                              
                                                     
                                                

                                                    
 
                            
 
                 
 

                                                             
 
                              


                                              
 


                                               
 



                                                      
 

                 
                                                                         
                                  
                                
 










                                                           
         
 
                                                     





                                                 
 
                                                                              
 
                
                                       

                                     
                                                          
                                     




                                                          
                         
                                                               
                                                                  




                                            
                                                                           
 
                                                                           

                                        
 
                                         
 


                                                                       
                      







                                                          
                                                         

                                                                             
                                      
                                                         
                                                                            


                                                                   


                                                         

                                                                            
                         

                                                                         
                                            
                                                             
                                                               
                                                                                
 

                                                               
 
                                                  
                                          


                                                                           
 
                             
 

                                                                           
 




                                    
 
                                      
 
                 
 








                                                                           
 


                                                                           


                                                        
 


                                                      
 


                                                       
 





                                    
 
                                                                   
                                    




                                                                      
                                                             











                                                                             
                                         
                                                                    
                                                                 
                                              
 
                                       
         
 
                                         
 
                                
 
/* 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;
	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;
		}

		set_nonblocking(data->sock);

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