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

















                                                                            
                
                 
                  
                     
















                                 
                                                                       

                               


                                  
                                                       
 
                                     
                   
                   




                            
                      
                      
                        
                      


                                                                  
 
                    


                                                             
                             
                                                                              
                                                    
                                           
                                         
 
                                                             
                            
                                                 
 
                                                                         
                                     
                                                                  
                                                                

                                                   
                                                     
                 
                                                                 
                            
                                                          
                                                                              
                                           

                                                                               
                                   

                                                                               
                                   


                                                                        
                                   

                                                      
                    
                                                             
                                                       
                                                                            
                                                                                



                                           
                                                   


                                                         
                    
                                                                
                                                          
                                                                            

                                                                     


                                           
         
 
                                           
 


                                                  
                               
                            
                                                      
                        






                                                       

                                                                         
                                      
                                      


                                                              
                                             




                                          


                   

                                                                 
                    
                     
 
                               
                                                      


                               

                                 
                                                          
                            





                            




                                                                   

                                                                       
 
                        
                                                                           
                                      
 
                             
                         
                         
                         

                                                
                           
                           
                                         
 
                   
 










                                                               
 
                           
                   
 





                                                          
 
                                                            
 
                                       
 
                                  
                               
                             

                                      
 

                             
 
                                                        
                                                         

                                                    
                                                                  

                                                      
                                                      
                                               







                                                              
                                          
         
                                  
 
                                             
 





                                       
 
                          
 
                                 
                                          





                                                      
                                                   
                                       
                                                           
                
                                          
 





                                                         
 
                                                     




                                    
                                                                           

                      








                                        
                                     
                


                                      
                                                              

                                                                    
                          
 
                                             



                                                                        

                                          
 
                                                               

                                                                         

                                                               
                                                     
                 
 
                                              
 
                                                                             
                                  
 
                          
                                    
 





































                                                                              
                                                                         


                                                                    
                                           















                                                                               
 








































































































                                                                           
                 






























































                                                                            
                                                     



                                                           
 
                            
 
                                                                  
                               
                 
 

                                                             
 
                              
 


                                              
 


                                               
 



                                                      
 

                 
                                                                         

                                                                   
                                          
 
                                                              




                                                         
 
                                                              

                  
                                                                              
 

                                      

                                     
                                                          
                                     




                                                          
                         

                                                                  




                                            
                                                                           
 

                                                                           
                                      
 
                                         
 


                                                                       
                      







                                                          
                                                      

                                                         
                                                                            


                                                                   


                                                         

                                                                            
                         

                                                                         
                                            
                                                               
                                                                                
 

                                                               
 







                                                                           
 
                             
 

                                                                           
 




                                    
 
                                      
 
                 
 








                                                                           
 


                                                                           
 


                                                        
 


                                                      
 


                                                       
 





                                    
 
                                                                   
                                    














                                                                        
                                       
         
 
                                         
 
                                
 
/* the Music Player Daemon (MPD)
 * (c)2003-2006 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 <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.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

static char *proxyHost = NULL;
static char *proxyPort = NULL;
static char *proxyUser = NULL;
static char *proxyPassword = NULL;
static int bufferSize = HTTP_BUFFER_SIZE_DEFAULT;
static int 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;
	int icyMetaint;
	int prebuffer;
	int icyOffset;
	char *proxyAuth;
	char *httpAuth;
} 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) {
			ERROR("%s specified but not %s", CONF_HTTP_PROXY_HOST,
			      CONF_HTTP_PROXY_PORT);
			exit(EXIT_FAILURE);
		}
		proxyPort = param->value;

		param = getConfigParam(CONF_HTTP_PROXY_USER);

		if (param) {
			proxyUser = param->value;

			param = getConfigParam(CONF_HTTP_PROXY_PASSWORD);

			if (!param) {
				ERROR("%s specified but not %s\n",
				      CONF_HTTP_PROXY_USER,
				      CONF_HTTP_PROXY_PASSWORD);
				exit(EXIT_FAILURE);
			}

			proxyPassword = param->value;
		}

		param = getConfigParam(CONF_HTTP_PROXY_PASSWORD);

		if (param) {
			ERROR("%s specified but not %s\n",
			      CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_USER);
			exit(EXIT_FAILURE);
		}
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_PORT))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_PORT, CONF_HTTP_PROXY_HOST, param->line);
		exit(EXIT_FAILURE);
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_USER))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_USER, CONF_HTTP_PROXY_HOST, param->line);
		exit(EXIT_FAILURE);
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_PASSWORD))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_HOST,
		      param->line);
		exit(EXIT_FAILURE);
	}

	param = getConfigParam(CONF_HTTP_BUFFER_SIZE);

	if (param) {
		bufferSize = strtol(param->value, &test, 10);

		if (bufferSize <= 0 || *test != '\0') {
			ERROR("\"%s\" specified for %s at line %i is not a "
			      "positive integer\n",
			      param->value, CONF_HTTP_BUFFER_SIZE, param->line);
			exit(EXIT_FAILURE);
		}

		bufferSize *= 1024;

		if (prebufferSize > bufferSize)
			prebufferSize = bufferSize;
	}

	param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE);

	if (param) {
		prebufferSize = strtol(param->value, &test, 10);

		if (prebufferSize <= 0 || *test != '\0') {
			ERROR("\"%s\" specified for %s at line %i is not a "
			      "positive integer\n",
			      param->value, CONF_HTTP_PREBUFFER_SIZE,
			      param->line);
			exit(EXIT_FAILURE);
		}

		prebufferSize *= 1024;
	}

	if (prebufferSize > bufferSize)
		prebufferSize = bufferSize;
}

/* 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 = calloc(BASE64_LENGTH(len) + 1, 1);
	unsigned char *p = (unsigned char *)ret;

	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(char *header, char *user, char *password)
{
	char *ret = NULL;
	int templen;
	char *temp;
	char *temp64;

	if (!user || !password)
		return NULL;

	templen = strlen(user) + strlen(password) + 2;
	temp = malloc(templen);
	strcpy(temp, user);
	strcat(temp, ":");
	strcat(temp, password);
	temp64 = base64Dup(temp);
	free(temp);

	ret = malloc(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 = malloc(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 = malloc(bufferSize);

	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 = malloc(colon - temp + 1);
			memcpy(user, temp, colon - temp);
			user[colon - temp] = '\0';

			passwd = malloc(at - colon);
			memcpy(passwd, colon + 1, at - colon - 1);
			passwd[at - colon - 1] = '\0';
		} else {
			user = malloc(at - temp + 1);
			memcpy(user, temp, at - temp);
			user[at - temp] = '\0';

			passwd = strdup("");
		}

		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 = malloc(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 = malloc(len + 1);
		memcpy(data->port, colon + 1, len);
		data->port[len] = '\0';
		DEBUG(__FILE__ ": Port: %s\n", data->port);
	} else {
		data->port = strdup("80");
	}

	/* fetch the path */
	if (proxyHost)
		data->path = strdup(url);
	else
		data->path = strdup(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;
	char request[2049];

	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));
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	getsockopt(data->sock, SOL_SOCKET, SO_ERROR, &error, &error_len);
	if (error) {
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	memset(request, 0, 2049);
	/* deal with ICY metadata later, for now its fucking up stuff! */
	snprintf(request, 2048, "GET %s HTTP/1.0\r\n" "Host: %s\r\n"
		 /*"Connection: close\r\n" */
		 "User-Agent: %s/%s\r\n"
		 /*"Range: bytes=%ld-\r\n" */
		 "%s"	/* authorization */
		 "Icy-Metadata:1\r\n"
		 "\r\n", data->path, data->host, PACKAGE_NAME, PACKAGE_VERSION,
		 /*inStream->offset, */
		 data->proxyAuth ? data->proxyAuth :
		 (data->httpAuth ? data->httpAuth : "")
	    );

	ret = write(data->sock, request, strlen(request));
	if (ret != strlen(request)) {
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	data->connState = HTTP_CONN_STATE_HELLO;

	return 0;
}

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 = malloc(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 == strncmp(cur, "\r\nContent-Length: ", 18)) {
			if (!inStream->size)
				inStream->size = atol(cur + 18);
		} else if (0 == strncmp(cur, "\r\nicy-metaint:", 14)) {
			data->icyMetaint = atoi(cur + 14);
		} else if (0 == strncmp(cur, "\r\nicy-name:", 11) ||
			   0 == strncmp(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 = strdup(cur + incr);
			*temp = '\r';
			DEBUG("inputStream_http: metaName: %s\n",
			      inStream->metaName);
		} else if (0 == strncmp(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 = strdup(cur + incr);
			*temp = '\r';
			DEBUG("inputStream_http: metaName: %s\n",
			      inStream->metaName);
		} else if (0 == strncmp(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 = strdup(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);
	/*fwrite(data->buffer, 1, data->buflen, stdout); */
	memmove(data->buffer, needle, data->buflen);

	data->connState = HTTP_CONN_STATE_OPEN;

	data->prebuffer = 1;

	/*mark as unseekable till we actually implement seeking */
	inStream->seekable = 0;

	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)
{
	/* hack to reopen an HTTP stream if we're trying to seek to
	 * the beginning */
	if ((whence == SEEK_SET) && (offset == 0)) {
		InputStreamHTTPData *data;

		data = (InputStreamHTTPData *) inStream->data;
		close(data->sock);
		data->connState = HTTP_CONN_STATE_REOPEN;
		data->buflen = 0;
		inStream->offset = 0;
		return 0;
	}

	/* otherwise, we don't know how to seek in HTTP yet */
	return -1;
}

static void parseIcyMetadata(InputStream * inStream, char *metadata, int size)
{
	char *r;
	char *s;
	char *temp = malloc(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 = strdup(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;
	long tosend = 0;
	long inlen = size * nmemb;
	long 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) {
			int metalen = *(data->buffer);
			metalen <<= 4;
			if (metalen < 0)
				metalen = 0;
			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;
		}
		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);
		/*fwrite(ptr,1,readed,stdout); */
		data->buflen -= tosend;
		data->icyOffset += tosend;
		/*fwrite(data->buffer,1,readed,stdout); */
		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,
			      (size_t) (bufferSize - 1 - data->buflen));

		if (readed < 0 && (errno == EAGAIN || errno == EINTR)) {
			readed = 0;
		} else if (readed <= 0) {
			close(data->sock);
			data->connState = HTTP_CONN_STATE_CLOSED;
			readed = 0;
		}
		/*fwrite(data->buffer+data->buflen,1,readed,stdout); */
		data->buflen += readed;
	}

	if (data->buflen > prebufferSize)
		data->prebuffer = 0;

	return (readed ? 1 : 0);
}