aboutsummaryrefslogblamecommitdiffstats
path: root/src/inputStream_http.c
blob: 35aa9ea0ce78b7b3d44bd19614f19e3f56fb8c41 (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() {
	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() {
        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);
			strncpy(user, temp, colon-temp);
			user[colon-temp] = '\0';

			passwd = malloc(at-colon);
			strncpy(passwd, colon+1, at-colon-1);
			passwd[at-colon-1] = '\0';
		}
		else {
			user = malloc(at-temp+1);
			strncpy(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);
        strncpy(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);
                strncpy(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);
}