#include "GlobHdr.h"
#include "IReader.h"
#include "IFileTypes.h"
#include "Header.h"
#include "Utils.h"
#include "Log.h"

#include "swis.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "netdb.h"
#include "inetlib.h"

#include "socklib.h"
#include "sys/filio.h"
#include "sys/errno.h"

typedef enum
{
	  EFromHttp_State_FatalError      = 1
	, EFromHttp_State_Resolving       = 2
	, EFromHttp_State_WaitConnection  = 3
	, EFromHttp_State_SendQuery       = 4
	, EFromHttp_State_ReadFirstHeader = 5
	, EFromHttp_State_ReadHeader      = 6
	, EFromHttp_State_LocationHeader  = 7
	, EFromHttp_State_ReadData        = 8
	, EFromHttp_State_Done            = 9
} EFromHttp_State;

typedef enum
{
	  EIcy_State_Data = 0
	, EIcy_State_Size = 1
	, EIcy_State_Meta = 2
} EIcy_State;

typedef enum
{
	  EUdp_State_FatalError     = 1
	, EUdp_State_WaitConnection = 2
	, EUdp_State_ReadData       = 3
} EUdp_State;

#define iMaxUrlLen 1024

#define ISocket_Flag_Redirected 0x00000001
#define ISocket_Flag_IcyMeta    0x00000002
#define ISocket_Flag_UdpMeta    0x00000004

typedef struct surl
{
	char*   url;
	char*   user;
	char*   password;
	char*   host;
	int     port;
	char*   path;
} surl;

typedef	struct
{
	surl                server;
	surl                proxy;
	EFromHttp_State     state;
	int                 handle;
	struct sockaddr_in  sAddr;
	int                 content_length;
	struct
	{
		int         interval;
		int         size;
		int         dataread;
		EIcy_State  state;

	}            icymeta;
	struct
	{
		int         handle;
		int         port;
		EUdp_State  state;
	}           udp;
	int         flags;
	char        work[1024];
	char        meta[4096];
} ISocket;

static const _kernel_oserror Err_SocketFailed =
{ErrNum_InputError, "Failed to create socket."};
static const _kernel_oserror Err_BindFailed =
{ErrNum_InputError, "Failed to bind socket to local host."};
static const _kernel_oserror Err_SocketInfoFailed =
{ErrNum_InputError, "Couldn't retrieve socket info."};
static const _kernel_oserror Err_Disconnected =
{ErrNum_InputError, "Disconnected from Server."};
static const _kernel_oserror Err_BadHttpHdr =
{ErrNum_InputError, "Bad Http header received."};

// HTTP headers
static const char hdr_http[] = "HTTP";
static const char hdr_icy[] = "ICY"; // non-std Shoutcast version
static const char hdr_contents[] = "Content-Type:";
static const char hdr_length[] = "Content-Length:";
static const char hdr_location[] = "Location:";


/*------------------------------------------------------------------------------
 * URL parsing
 */

static void surl_clear(IStream* s, surl* u)
{
	IStream_Free(s, (void**) &u->url);
	IStream_Free(s, (void**) &u->user);
	IStream_Free(s, (void**) &u->password);
	IStream_Free(s, (void**) &u->host);
	IStream_Free(s, (void**) &u->path);
	u->port = 0;
}

// http://[user[:password]@]host[:port][/path]

static const _kernel_oserror* surl_parse(IStream* s, surl* u, const char* ref_url, int def_port)
{
	const _kernel_oserror* e = NULL;
	char *h, *p, *f, *ptr;

	surl_clear(s, u);

	e = IStream_AllocString(s, &u->url, ref_url);
	if (e) return e;

	ptr = u->url;

	if (strnicmp("http://", ptr, 7))
		goto syntax_error;

	ptr += 7;
	// first / now indicates start of path (if existing)
	f = strchr(ptr, '/');
	if (f) *f = '\0';

	// is there an @ before that to indicate authentification info?
	h = strchr(ptr, '@');

	if (h != NULL)
	{
		// there is authentification info
		*h = '\0';

		// check for password presence
		p = strchr(ptr, ':');
		if (p != NULL) *p = '\0';

		// read user
		e = IStream_AllocString(s, &u->user, ptr);

		// read password if any
		if (p != NULL)
		{
			*p = ':';

			if (!e) e = IStream_AllocString(s, &u->password, p + 1);
		}
		*h = '@';
		// move after authentification info
		ptr = h + 1;
	}

	// check for the presence of port info before path
	p = strchr(ptr, ':');
	if (p != NULL) *p = '\0';

	// read host
	if (!e) e = IStream_AllocString(s, &u->host, ptr);

	// read port if any
	if (p != NULL)
	{
		*p = ':';
		u->port = atoi(p + 1);
	}

	if (!u->port)
		u->port = def_port;

	// read path if any, may be empty but not null
	if (f)
	{
		*f = '/';
		f++;
	}
	if (!e) e = IStream_AllocString(s, &u->path, f);

	return e;

syntax_error:
	return ErrorFromString(ErrNum_ParamsError, "URL '%s' syntax should be http://[<user>[:<password>]@]<host name>[:<port>][/<path>].", u->url);
}

static const _kernel_oserror* FromHttp_ConnectParams(IStream* s, const char* server_url, const char* proxy_url)
{
	ISocket* so = s->reader.data;
	const _kernel_oserror* e;

	// Reset input buffer
	s->inb->free = s->inb->start;

	so->icymeta.interval = 0;
	so->icymeta.dataread = 0;
	so->icymeta.state = EIcy_State_Data;
	so->udp.port = 0;
	so->content_length = -1;

	// Check server url
	e = surl_parse(s, &so->server, server_url, 80);
	if (e) return e;

	// proxy to handle?
	if (proxy_url)
	{
		// Check proxy url
		e = surl_parse(s, &so->proxy, proxy_url, 8080);
		if (e) return e;

		// Reset Icy behind a proxy because stupid returned ICY header gets eaten by proxy servers
		// Reset Udp behind a proxy because we can't monitor the server address
		so->flags &= ~(ISocket_Flag_IcyMeta | ISocket_Flag_UdpMeta);
	}

	so->state = EFromHttp_State_Resolving;
	s->state = EIStream_State_SourceInit;

	return NULL;
}

/*------------------------------------------------------------------------------
 * User authentification
 */

#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))

/**
 * Encodes the string S of length LENGTH to base64 format and place it
 * to STORE.  STORE will be 0-terminated, and must point to a writable
 * buffer of at least 1+BASE64_LENGTH(length) bytes.
 *
 * returns length of written string, not including zero-terminator or -1 if not enough place.
 */
static int base64_encode(char* store, const char* store_end, const char* s, int length)
{
	// Conversion table.
	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','+','/'
	};
	int i;
	char* p = store;

	if ((store_end - store) < (1 + BASE64_LENGTH(length)))
		return -1;

	// Transform the 3x8 bits to 4x6 bits, as required by base64.
	for (i = 0; i < length; 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 == length + 1)
		*(p - 1) = '=';
	else if (i == length + 2)
		*(p - 1) = *(p - 2) = '=';
	// ...and zero-terminate it.
	*p = '\0';

	return (p - store);
}

/**
 * Creates the authentication header contents for the `Basic' scheme.
 * This is done by encoding the string `USER:PASS' in base64.
 *
 * returns length of written string, not including zero-terminator or required length if not enough place.
 */
static int basic_authentication_encode(char* store, const char* store_end, const char* user, const char* passwd)
{
	char *t1;
	int len1 = strlen (user) + 1 + strlen (passwd);
	int len2 = BASE64_LENGTH(len1) + 1;

	// leave enough space to contain encoded value
	// and use the free area (really?) behind it as tmp storage
	// to build the string to encode
	if ((store_end - store) < (len1 + len2))
		return len1 + len2;

	t1 = store + len2;
	sprintf(t1, "%s:%s", user, passwd);

	return base64_encode(store, store_end, t1, len1);
}

/*------------------------------------------------------------------------------
 * Server metadata handling
 */

static const _kernel_oserror* FromHttp_UdpDisconnect(IStream* s)
{
	ISocket* so = s->reader.data;

	if (!so) return NULL;

	// Close socket
	if (so->udp.handle >= 0)
	{
		shutdown(so->udp.handle, 2);
		socketclose(so->udp.handle);
		so->udp.handle = -1;
	}

	return NULL;
}

static const _kernel_oserror* FromHttp_UdpConnect(IStream* s)
{
	ISocket*            so = s->reader.data;
	const _kernel_oserror*    e = NULL;
	int                 sAddrLen;
	struct sockaddr_in  sAddr;
	int                 on = 1;

	FromHttp_UdpDisconnect(s);

	so->udp.handle = socket(AF_INET, SOCK_DGRAM, 0);
	if (so->udp.handle < 0)
		return &Err_SocketFailed;

	// Make it non blocking
	if (!e && (socketioctl(so->udp.handle, FIONBIO, &on) < 0))
		e = &Err_SocketFailed;

	so->udp.port = 0;
	sAddrLen = sizeof(sAddr);
	memset(&sAddr, 0, sAddrLen);
	sAddr.sin_family = AF_INET;
	sAddr.sin_addr.s_addr = INADDR_ANY;
	sAddr.sin_port = htons((unsigned short) s->pGlobHdr->cfg.audiocastport);

	if (!e && (bind(so->udp.handle, (struct sockaddr *) &sAddr, sAddrLen) < 0))
		e = &Err_BindFailed;

	if (!e && (getsockname(so->udp.handle, (struct sockaddr *) &sAddr, &sAddrLen) >= 0))
	{
		so->udp.port = ntohs(sAddr.sin_port);
	}
	else if (!e) e = &Err_SocketInfoFailed;

	if (e)
	{
		FromHttp_UdpDisconnect(s);
		return e;
	}

	Log("Listening on local %s:%d", inet_ntoa(sAddr.sin_addr), ntohs(sAddr.sin_port));

	return NULL;
}

static const _kernel_oserror* FromHttp_UdpReceive(IStream* s)
{
	ISocket* so = s->reader.data;
	struct sockaddr_in from;
	int fromlen;
	int len;

	if (so->udp.handle < 0) return NULL;

	fromlen = sizeof(struct sockaddr_in);

	if ((len = recvfrom(so->udp.handle, so->meta, 1024, 0, (struct sockaddr *)&from, &fromlen)) < 0)
	{
		if (errno != EAGAIN)
			return ErrorFromString(ErrNum_InputError, "UDP socket recv error %d (%s).", errno, _inet_err());
		return NULL;
	}
	so->meta[len] = '\0';
	Log("Received X-audiocast: len %d, [%s]\n", len, so->meta);

	return NULL;
}

static const char* stristr(const char* ch1, const char* ch2)
{
	int c = tolower(*ch2);

	if (c == 0)
		return ch1;

	for (; *ch1; ch1++)
	{
		if (tolower(*ch1) == c)
		{
			for (int i = 1; ; i++)
			{
				if (ch2[i] == 0)
					return ch1;
				if (tolower(ch1[i]) != tolower(ch2[i]))
					break;
			}
		}
	}

	return NULL;
}

typedef struct
{
	unsigned int level;
	const char*	 text;
	unsigned int metaid;
} hdr_map;

static int Map_Header(IStream* s, const hdr_map* map, int count, char* p, const _kernel_oserror** pe)
{
	int i = 0;
	int len;
	*pe = NULL;

	for (i = 0; i < count; i++)
	{
		len = strlen(map[i].text);
		if (!strnicmp(p, map[i].text, len))
		{
			p += len;

			while (*p == ' ')
				p++;

			if (*p)
			{
				*pe = IStream_SetText(s, map[i].level, map[i].metaid, p, 0);
//				if (!*pe) Log("Text: %s", (char*) IStream_FindMetadata(s, map[i].metaid)->data);
			}
			return 1;
		}
	}

	return 0;
}

// Shoutcast meta-data

#define meta_icy_count 2
static const hdr_map meta_icy[meta_icy_count] =
{
	  {1, "StreamTitle='"      , EMeta_StreamTitle}      // station name
	, {0, "StreamUrl='"        , EMeta_StreamUrl}        // station home url
};

static const _kernel_oserror* Icy_ParseMeta(IStream* s)
{
	ISocket* so = s->reader.data;
	const _kernel_oserror* e;
	char* start = so->meta;
	char* end = so->meta + so->icymeta.dataread;
	char* sep;

	// ensure is 0 terminated
	*end = '\0';

	while(start < end)
	{
		sep = strchr(start, ';');
		if (!sep) sep = end;
		sep[0] = '\0';
		sep[-1] = '\0'; // erase final "'"

		Map_Header(s, meta_icy, meta_icy_count, start, &e);
		if (e) return e;

		start = sep + 1;
	}

	return NULL;
}

/*------------------------------------------------------------------------------
 * Server Headers parsing
 */

// Shoutcast returned headers
static const char hdr_icy_metaint[] = "metaint:"; // meta configuration

#define meta_hdr_count 4
static const hdr_map meta_hdr[meta_hdr_count] =
{
	  {0, "name:"       , EMeta_StreamStation}     // station name
	, {0, "description:", EMeta_StreamDescription} // station description
	, {0, "genre:"      , EMeta_StreamGenre}       // station genre
	, {0, "url:"        , EMeta_StreamUrl}         // station home url
};

// Shoutcast returned unused headers
//   icy-br   = bitrate
//   icy-pub  = 1 if public?
//   icy-notice..., notes
//   icy-aim
//   icy-irc
//   icy-icq

// Icecast returned unused headers
//   ice-bitrate
//   ice-public
//   ice-private
//   ice-audio-info: ice-samplerate=44100

// Audiocast returned unused headers
//   x-audiocast-location: WEMU-FM 89.1
//   x-audiocast-admin: wemu@wemu.org
//   x-audiocast-server-url: http://wemu.org
//   x-audiocast-mount:/broadband
//   x-audiocast-bitrate:128
//   x-audiocast-public:1

static int Icy_Header(IStream* s, const char* p, const _kernel_oserror** pe)
{
	ISocket* so = s->reader.data;
	*pe = NULL;

	if (strnicmp(p, "icy-", 4))
		return 0;
	p += 4;

	if (stristr(p, hdr_icy_metaint) == p)
	{
		p += strlen(hdr_icy_metaint);

		while (*p == ' ')
			p++;

		sscanf(p, "%d", &so->icymeta.interval);
		if (so->icymeta.interval == 1)
			so->icymeta.interval = 8192;

		return 1;
	}

	Map_Header(s, meta_hdr, meta_hdr_count, (char*) p, pe);

	// even ignored ones are icy ones
	return 1;
}

static int Ice_Header(IStream* s, const char* p, const _kernel_oserror** pe)
{
	*pe = NULL;

	if (strnicmp(p, "ice-", 4))
		return 0;

	Map_Header(s, meta_hdr, meta_hdr_count, (char*)(p + 4), pe);

	// even ignored ones are ice ones
	return 1;
}

static const char hdr_xudpport[] = "udpport:";     // meta udp port

static int X_Header(IStream* s, const char* p, const _kernel_oserror** pe)
{
	ISocket* so = s->reader.data;
	*pe = NULL;

	if (strnicmp(p, "x-audiocast-", 12))
		return 0;
	p += 12;

	if (stristr(p, hdr_xudpport) == p)
	{
		p += strlen(hdr_xudpport);

		while (*p == ' ')
			p++;

		sscanf(p, "%d", &so->udp.port);

		return 1;
	}

	Map_Header(s, meta_hdr, meta_hdr_count, (char*) p, pe);

	// even ignored ones are x-audiocast ones
	return 1;
}


// Returns -1 if not enough bytes in buffer
// Returns -2 if line too long

static int FromHttp_ReadTextLine(IStream* s, char* p, int size)
{
	char* psrc = (char*) (s->inb + 1);
	int pos = s->inb->start;
	int read = size;

	while ((pos != s->inb->free) && size > 0)
	{
		*p++ = psrc[pos];
		pos++;
		if (pos >= s->inb->size)
			pos = 0;
		if (p[-1] < ' ')
			goto Done;
		size--;
	}

	// failed to read line
	if (size <= 0)
		return -2; // line too long

	return -1;

Done:
	read -= size;
	p--;
	if (((p[0] == '\r') && (psrc[pos] == '\n'))
	||  ((p[0] == '\n') && (psrc[pos] == '\r')))
	{
		pos++;
		if (pos >= s->inb->size)
			pos = 0;
	}
	p[0] = '\0';
	s->inb->start = pos;

	return read;
}

/*------------------------------------------------------------------------------
 * Main stream: connect, disconnect, receive
 */

/*-----------------
 * FromHttp_Connect
 */

static const _kernel_oserror* FromHttp_Connect(IStream* s)
{
	ISocket* so = s->reader.data;
	const _kernel_oserror* e;
	struct hostent* ent;
	int err;
	const surl* u = so->proxy.url ? &so->proxy : &so->server;

	// Resolver_GetHost, multitasking version of
	//     ent = gethostbyname(u->host);
	e = _swix(0x46001, _IN(0)|_OUTR(0,1), u->host, &err, &ent);
	if (e) return e;

	// Not Ready yet?
	if (err == EINPROGRESS)
		return NULL;

	if (err)
		return ErrorFromString(ErrNum_InputError, "Failed to locate Http stream server '%s'.", u->host);

	memcpy((char *) &so->sAddr.sin_addr, ent->h_addr, ent->h_length);
	so->sAddr.sin_family = ent->h_addrtype;
	so->sAddr.sin_port = htons((unsigned short) u->port);

	// Create socket
	so->handle = socket(ent->h_addrtype, SOCK_STREAM, 0);
	if (so->handle < 0)
		return &Err_SocketFailed;

	// Make it non blocking
	{
		int on = 1;
		if (socketioctl(so->handle, FIONBIO, &on) < 0) ;
	}

	if ((connect(so->handle, (struct sockaddr*) &so->sAddr, sizeof(so->sAddr)) < 0)
	&&  (errno != EINPROGRESS))
	{
		return ErrorFromString(ErrNum_InputError, "Failed to contact Http stream server '%s'%s."
						, u->host
						, (so->flags & ISocket_Flag_Redirected) ? " (redirected)" : "");
	}

	if (so->flags & ISocket_Flag_UdpMeta)
	{
		e = FromHttp_UdpConnect(s);
		if (e) return e;
	}

	so->state = EFromHttp_State_WaitConnection;

	return NULL;
}

/*--------------------
 * FromHttp_Disconnect
 */

static const _kernel_oserror* FromHttp_Disconnect(IStream* s)
{
	ISocket* so = s->reader.data;

	if (!so) return NULL;

	// Close socket
	if (so->handle >= 0)
	{
		shutdown(so->handle, 2);
		socketclose(so->handle);
		so->handle = -1;
	}

	return FromHttp_UdpDisconnect(s);

	return NULL;
}

/*-----------------
 * FromHttp_Receive
 */

static const _kernel_oserror* FromHttp_Receive(IStream* s)
{
	ISocket* so = s->reader.data;
	char* buf = (char*)(s->inb + 1);
	int max, toread, read = 0;

	if (s->inb->finishflag)
		return NULL;

	if (so->state != EFromHttp_State_ReadData)
	{
		// read http header and not much more to avoid reading icy metadata
		// check currently available
		max = s->inb->free - s->inb->start;
		if (max < 0)
			max += s->inb->size;

		// enough data for HTTP header
		if (max >= 1024)
			return NULL;

		max = 1024 - max;
	}
	else
	{
		max = s->inb->size;

		switch (so->icymeta.state)
		{
			case EIcy_State_Data: // read normal data
			{
				// check how much data before meta
				if (so->icymeta.interval <= 0)
					break;

				max = so->icymeta.interval - so->icymeta.dataread;
				if (max > 0) break;

				so->icymeta.state = EIcy_State_Size;
			}
			// nobreak;
			case EIcy_State_Size: // read byte size
			{
				read = recv(so->handle, so->meta, 1, 0);
				if (read <= 0) goto CloseOrError;
				so->icymeta.size = so->meta[0] * 16;
				so->icymeta.dataread = 0;
				so->icymeta.state = EIcy_State_Meta;
			}
			// nobreak;
			case EIcy_State_Meta: // read meta data
			{
				if (so->icymeta.size > 0)
				{
					read = recv(so->handle, so->meta + so->icymeta.dataread, so->icymeta.size, 0);
					if (read <= 0) goto CloseOrError;
					so->icymeta.size -= read;
					so->icymeta.dataread += read;
				}

				if (so->icymeta.size > 0)
					return NULL;

				if (so->icymeta.dataread > 0)
				{
					Icy_ParseMeta(s);
				}
				so->icymeta.dataread = 0;
				so->icymeta.state = EIcy_State_Data;
				max = so->icymeta.interval - so->icymeta.dataread;
			}
		}
	}

	toread = s->inb->start - s->inb->free;
	if (toread <= 0)
		toread += s->inb->size;
	toread-= 1024;
	if (toread <= 0) return NULL;

	// limit to interval till next metadata
	if (toread > max)
		toread = max;

	// normal data
	max = s->inb->size - s->inb->free;
	if (max <= toread)
	{
		read = recv(so->handle, buf + s->inb->free, max, 0);
		if (read <= 0) goto CloseOrError;

		s->pGlobHdr->HttpReadBytes += read;
        s->source.pos += read;

		toread -= read;
		so->icymeta.dataread += read;

		if (read < max)
		{
			s->inb->free += read;
			return NULL;
		}
		else
		{
			s->inb->free = 0;
		}
	}
	if (toread > 0)
	{
		read = recv(so->handle, buf + s->inb->free, toread, 0);
		if (read <= 0) goto CloseOrError;

		s->pGlobHdr->HttpReadBytes += read;
        s->source.pos += read;

		so->icymeta.dataread += read;

		s->inb->free += read;
	}

	return NULL;

CloseOrError:
	if (read == 0)
	{
		if (errno == EWOULDBLOCK)
		{
			// recv return EWOULDBLOCK after end of fixed size streams
			if ((so->content_length != -1)
			&&  (s->source.pos >= so->content_length))
				s->inb->finishflag = 1;

			return NULL;
		}

		s->inb->finishflag = 1;
//		if (so->state != EFromHttp_State_ReadData)
//			return ErrorFromString(ErrNum_InputError, "Connection closed by Server.");
		// Fixed size, avoid reconnection
		return NULL;
	}

	if (errno != EWOULDBLOCK)
	{
		return ErrorFromString(ErrNum_InputError, "Http socket recv error %d (%s).", errno, _inet_err());
	}

	return NULL;
}

/*------------------------------------------------------------------------------
 * IInput interface
 */

/*--------------
 * FromHttp_Open
 */

static const _kernel_oserror* FromHttp_Open(IStream* s, const _kernel_swi_regs* r)
{
	ISocket* so;
	const _kernel_oserror* e;

	e = IStream_Alloc(s, (void**) &so, sizeof(*so));
	if (e) return e;

	s->reader.data = so;
	memset(so, 0, sizeof(*so));
	so->handle = -1;
	so->udp.handle = -1;

	if (s->pGlobHdr->flags & glb_flag_icymeta)
		so->flags |= ISocket_Flag_IcyMeta;
	if (s->pGlobHdr->flags & glb_flag_udpmeta)
		so->flags |= ISocket_Flag_UdpMeta;

	return FromHttp_ConnectParams(s, (const char*) r->r[0], (const char*) r->r[1]);
}

/*-----------------
 * FromHttp_DelayIO
 */

static bool FromHttp_DelayIO(IStream* s)
{
	IGNORE(s);

	return false;
}

/*--------------
 * FromHttp_Fill
 */

static const _kernel_oserror* FromHttp_Fill(IStream* s)
{
	ISocket* so = s->reader.data;
	const _kernel_oserror* e;

	while(1)
	{
		EFromHttp_State oldstate = so->state;

		switch(oldstate)
		{
			case EFromHttp_State_Resolving:
			{
				e = FromHttp_Connect(s);
				if (e) goto Error;

				if (oldstate != so->state)
					continue;

				return NULL;
			}
			break;
			case EFromHttp_State_WaitConnection:
			{
				fd_set		sSet;
				struct timeval	sTv;
				int iRet;

				// Check connection establishment
				sTv.tv_sec = 0;
				sTv.tv_usec = 0;
				FD_ZERO(&sSet);
				FD_SET(so->handle, &sSet);
				iRet = select(so->handle + 1, NULL, &sSet, NULL, &sTv);

				if (iRet > 0)
				{
					// Connection OK
					if (FD_ISSET(so->handle, &sSet))
					{
						so->state = EFromHttp_State_SendQuery;
						continue;
					}
					return NULL;
				}

				if (iRet < 0)
				{
					// Connection failed
					e = &Err_Disconnected;
					goto Error;
				}

 				// Must continue to wait
				return NULL;
			}
			break;
			case EFromHttp_State_SendQuery:
			{
				int len, iRet;
				char* p = so->work;
				const char* p_end = so->work + sizeof(so->work);

				if (so->proxy.url)
				{
					// Request stream data
					p += snprintf(p, p_end - p, "GET %s HTTP/1.0\r\n"
							"Host: %s\r\n"
							, so->server.url
							, so->proxy.host);
					if(so->proxy.user && so->proxy.password)
					{
						p += snprintf(p, p_end - p, "Proxy-Authorization: Basic ");
						p += basic_authentication_encode(p, p_end, so->proxy.user, so->proxy.password);
						p += snprintf(p, p_end - p, "\r\n");
					}
				}
				else
				{
					// Request stream data
					p += snprintf(p, p_end - p, "GET /%s HTTP/1.0\r\n"
							  "Host: %s\r\n"
							, so->server.path
							, so->server.host);
				}
				if(so->server.user && so->server.password)
				{
					p += snprintf(p, p_end - p, "Authorization: Basic ");
					p += basic_authentication_encode(p, p_end, so->server.user, so->server.password);
					p += snprintf(p, p_end - p, "\r\n");
				}
				p += snprintf(p, p_end - p, "Accept: */*\r\n"
						"User-Agent: " Module_Title
						"/" Module_VersionString " (RISC OS)\r\n");
				if (so->flags & ISocket_Flag_IcyMeta)
					p += snprintf(p, p_end - p, "Icy-MetaData: 1\r\n");
				if (so->udp.handle >= 0)
					p += snprintf(p, p_end - p, "x-audiocast-udpport: %d\r\n", so->udp.port);
				p += snprintf(p, p_end - p, "\r\n");
				Log("%s", so->work);
				if (p >= p_end)
				{
					e = ErrorFromString(ErrNum_InternalError, "Internal buffer too small");
					goto Error;
				}

				len = p - so->work;
				iRet = send(so->handle, so->work, len, 0);
				if (iRet != len)
				{
					e = ErrorFromString(ErrNum_InputError, "Socket error %d (%s) while sending request to%s Http server '%s'."
						, errno, _inet_err()
						, (so->flags & ISocket_Flag_Redirected) ? " redirected" : ""
						, so->server.host
						);
					goto Error;
				}

				// Wait for Http headers
				so->state = EFromHttp_State_ReadFirstHeader;
				continue;
			}
			break;
			case EFromHttp_State_ReadFirstHeader:
			case EFromHttp_State_ReadHeader:
			case EFromHttp_State_LocationHeader:
			{
				int read, iRet;

				// Copy eventual incomming data
				e = FromHttp_UdpReceive(s);
				if (e) goto Error;

				e = FromHttp_Receive(s);
				if (e) goto Error;

				// Try to read a text line from Http header
				read = FromHttp_ReadTextLine(s, so->work, sizeof(so->work));

				if (read > 0)
				{
					// Check line
					LogInfo("Http header:%s", so->work);

					if (so->state == EFromHttp_State_ReadFirstHeader)
					{
						// Check return code of very first line
						// e.g.: HTTP/1.0 200 OK
						if (!stristr(so->work, hdr_icy)
						&&  !stristr(so->work, hdr_http))
						{
							// could not extract code
							e = &Err_BadHttpHdr;
							goto Error;
						}

						if (!sscanf(so->work, "%*s %d", &iRet))
						{
							// could not extract code
							e = &Err_BadHttpHdr;
							goto Error;
						}

						if ((iRet == 301)
						||  (iRet == 302))
						{
							// Redirector
							so->state = EFromHttp_State_LocationHeader;
							so->flags |= ISocket_Flag_Redirected;
							continue;
						}

						if (iRet != 200)
						{
							// not ok, report given line
							Log("Host %s, port %d, path %s", so->server.host, so->server.port, so->server.path);
							e = ErrorFromString(ErrNum_InputError, "Http server's response to requested stream is: %s.", so->work);
							goto Error;
						}

						if (stristr(so->work, hdr_icy))
						{
							// Force type to mp3
							s->source.os_filetype = IFileType_MP3;
						}

						// ok, read other lines in header
						so->udp.port = 0;
						so->state = EFromHttp_State_ReadHeader;
						continue;
					}
					else if (so->state == EFromHttp_State_LocationHeader)
					{
						if (stristr(so->work, hdr_location))
						{
							char* p = so->work + strlen(hdr_location);

							while (*p == ' ')
								p++;

							e = FromHttp_Disconnect(s);
							if (e) goto Error;
							e = FromHttp_ConnectParams(s, p, NULL);
							if (e) goto Error;
						}

						continue;
					}
					else
					{
						if (stristr(so->work, hdr_contents))
						{
							char* p = so->work + strlen(hdr_contents);

							while (*p == ' ')
								p++;

							// Copy Content-Type header <type>/<subtype>[;<attribute>=<value>]*
							e = IStream_AllocString(s, &s->source.mimetype, p);
							if (e) goto Error;
							// Extract media-type
							p = s->source.mimetype;
							while (*p)
							{
								if ((*p <= ' ') || (*p == ';'))
								{
									*p = 0;
									break;
								}
								p++;
							}
							// Mimemap
							e = _swix(0x50b00, _INR(0,2)|_OUT(3), 2, s->source.mimetype, 0, &s->source.os_filetype);
							if (e) goto Error;
							LogInfo("Mimemap module converted '%s' into RISC OS filetype %x.", s->source.mimetype, s->source.os_filetype);
						}
						else if (stristr(so->work, hdr_length))
						{
							char* p = so->work + strlen(hdr_contents);

							while (*p == ' ')
								p++;

							so->content_length = atoi(p);
						}
						else if (Icy_Header(s, so->work, &e)
						     ||  Ice_Header(s, so->work, &e)
						     ||  X_Header(s, so->work, &e))
						{
							if (e) goto Error;
							continue;
						}
						continue;
					}
				}
				else if (read == 0) // End of HTTP header (double CR/LF)
				{
					int inbuf;

					if((s->source.os_filetype == -1) && so->proxy.url)
					{

						LogInfo("Http header without Content-Type. Assuming stupid Shoutcast server (MP3) behind the proxy.");
						s->source.os_filetype = IFileType_MP3;
					}

					if (!so->udp.port && (so->udp.handle >= 0))
					{
						Log("No audiocast udp, removing udp listener");
						FromHttp_UdpDisconnect(s);
					}

					if(s->source.os_filetype != -1)
					{
						// Last line in header and we have the data type
						so->state = EFromHttp_State_ReadData;
						s->state = EIStream_State_MetaDataLookup;

						// We have already read some bytes past the HTTP header
						// and we need to keep this in mind for icymeta
						inbuf = s->inb->free - s->inb->start;
						if (inbuf < 0)
							inbuf += s->inb->size;
						so->icymeta.dataread = inbuf;

						LogInfo("File type %x", s->source.os_filetype);
						continue;
					}
				}
				else if (read == -1)
					return NULL; // nothing read yet

				// End of buffer without filetype given or line too large
				e = &Err_BadHttpHdr;
				goto Error;
			}
			break;
			case EFromHttp_State_ReadData:
			{
				e = FromHttp_UdpReceive(s);
				if (e) goto Error;

				// Copy eventual incomming data, it's up to the codecs now
				e = FromHttp_Receive(s);
				if (e) goto Error;

				if (s->inb->finishflag)
				{
					e = FromHttp_Disconnect(s);
					if (e) goto Error;
					so->state = EFromHttp_State_Done;
				}

				return NULL;
			}
			break;
			case EFromHttp_State_Done:
			{
				// No looping yet?
				if (s->inb->finishflag)
					return NULL;

				// Reconnect
				so->state = EFromHttp_State_Resolving;
				s->state = EIStream_State_SourceInit;

				continue;
			}
			break;
			default:
				return NULL;
		}
	}

	return NULL;

Error:
	so->state = EFromHttp_State_FatalError;
	s->state = EIStream_State_FatalError;
	Log("Error: %s\n", e->errmess);

	return e;
}

/*---------------
 * FromHttp_Close
 */

static const _kernel_oserror* FromHttp_Close(IStream* s)
{
	ISocket* so = s->reader.data;

	if (!so) return NULL;

	FromHttp_Disconnect(s);

	surl_clear(s, &so->server);
	surl_clear(s, &so->proxy);

	IStream_Free(s, &s->reader.data);

	return NULL;
}

IReader FromHttp =
{
	  FromHttp_Open
	, FromHttp_DelayIO
	, FromHttp_Fill
	, NULL
	, NULL
	, FromHttp_Close
};
