Blob Blame History Raw
/* -*- c-basic-offset: 8; -*- */
/* shout.c: Implementation of public libshout interface shout.h
 *
 *  Copyright (C) 2002-2004 the Icecast team <team@icecast.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: shout.c 11554 2006-06-09 23:09:33Z brendan $
 */

#ifdef HAVE_CONFIG_H
 #include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <shout/shout.h>
#include <net/sock.h>
#include "timing/timing.h"
#include "httpp/httpp.h"

#include "shout_private.h"
#include "util.h"

/* -- local prototypes -- */
static int queue_data(shout_queue_t *queue, const unsigned char *data, size_t len);
static int queue_str(shout_t *self, const char *str);
static int queue_printf(shout_t *self, const char *fmt, ...);
static inline void queue_free(shout_queue_t *queue);
static int send_queue(shout_t *self);
static int get_response(shout_t *self);
static int try_connect (shout_t *self);
static int try_write (shout_t *self, const void *data, size_t len);

static int create_request(shout_t *self);
static int create_http_request(shout_t *self);
static int create_xaudiocast_request(shout_t *self);
static int create_icy_request(shout_t *self);
static int parse_response(shout_t *self);
static int parse_http_response(shout_t *self);
static int parse_xaudiocast_response(shout_t *self);

static char *http_basic_authorization(shout_t *self);

/* -- static data -- */
static int _initialized = 0;

/* -- public functions -- */

void shout_init(void)
{
	if (_initialized)
		return;

	sock_initialize();
	_initialized = 1;
}

void shout_shutdown(void)
{
	if (!_initialized)
		return;

	sock_shutdown();
	_initialized = 0;
}

shout_t *shout_new(void)
{
	shout_t *self;

	/* in case users haven't done this explicitly. Should we error
	 * if not initialized instead? */
	shout_init();
	
	if (!(self = (shout_t *)calloc(1, sizeof(shout_t)))) {
		return NULL;
	}

	if (shout_set_host(self, LIBSHOUT_DEFAULT_HOST) != SHOUTERR_SUCCESS) {
		shout_free(self);

		return NULL;
	}
	if (shout_set_user(self, LIBSHOUT_DEFAULT_USER) != SHOUTERR_SUCCESS) {
		shout_free(self);

		return NULL;
	}
	if (shout_set_agent(self, LIBSHOUT_DEFAULT_USERAGENT) != SHOUTERR_SUCCESS) {
		shout_free(self);

		return NULL;
	}
	if (!(self->audio_info = _shout_util_dict_new())) {
		shout_free(self);

		return NULL;
	}

	self->port = LIBSHOUT_DEFAULT_PORT;
	self->format = LIBSHOUT_DEFAULT_FORMAT;
	self->protocol = LIBSHOUT_DEFAULT_PROTOCOL;

	return self;
}

void shout_free(shout_t *self)
{
	if (!self) return;

	if (self->host) free(self->host);
	if (self->password) free(self->password);
	if (self->mount) free(self->mount);
	if (self->name) free(self->name);
	if (self->url) free(self->url);
	if (self->genre) free(self->genre);
	if (self->description) free(self->description);
	if (self->user) free(self->user);
	if (self->useragent) free(self->useragent);
	if (self->audio_info) _shout_util_dict_free (self->audio_info);

	free(self);
}

int shout_open(shout_t *self)
{
	/* sanity check */
	if (!self)
		return SHOUTERR_INSANE;
	if (self->state != SHOUT_STATE_UNCONNECTED)
		return SHOUTERR_CONNECTED;
	if (!self->host || !self->password || !self->port)
		return self->error = SHOUTERR_INSANE;
	if (self->format == SHOUT_FORMAT_OGG && self->protocol != SHOUT_PROTOCOL_HTTP)
		return self->error = SHOUTERR_UNSUPPORTED;

	return self->error = try_connect(self);
}


int shout_close(shout_t *self)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state == SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_UNCONNECTED;

	if (self->state == SHOUT_STATE_CONNECTED && self->close)
		self->close(self);

	sock_close(self->socket);
	self->state = SHOUT_STATE_UNCONNECTED;
	self->starttime = 0;
	self->senttime = 0;
	queue_free(&self->rqueue);
	queue_free(&self->wqueue);

	return self->error = SHOUTERR_SUCCESS;
}

int shout_send(shout_t *self, const unsigned char *data, size_t len)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_CONNECTED)
		return self->error = SHOUTERR_UNCONNECTED;

	if (self->starttime <= 0)
		self->starttime = timing_get_time();

	if (!len)
		return send_queue(self);

	return self->send(self, data, len);
}

ssize_t shout_send_raw(shout_t *self, const unsigned char *data, size_t len)
{
	ssize_t ret;

	if (!self) 
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_CONNECTED)
		return SHOUTERR_UNCONNECTED;

	self->error = SHOUTERR_SUCCESS;

	/* send immediately if possible (should be the common case) */
	if (len && ! self->wqueue.len) {
		if ((ret = try_write(self, data, len)) < 0)
			return self->error;
		if (ret < len) {
			self->error = queue_data(&self->wqueue, data + ret, len - ret);
			if (self->error != SHOUTERR_SUCCESS)
				return self->error;
		}

		return len;
	}

	self->error = queue_data(&self->wqueue, data, len);
	if (self->error != SHOUTERR_SUCCESS)
		return self->error;

	ret = send_queue(self);
	if (ret == SHOUTERR_SUCCESS || (len && ret == SHOUTERR_BUSY))
		return len;

	return ret;
}

ssize_t shout_queuelen(shout_t *self)
{
	if (!self)
		return SHOUTERR_INSANE;

	return (ssize_t)self->wqueue.len;
}


void shout_sync(shout_t *self)
{
	int64_t sleep;

	if (!self)
		return;

	if (self->senttime == 0)
		return;

	sleep = self->senttime / 1000 - (timing_get_time() - self->starttime);
	if (sleep > 0)
		timing_sleep((uint64_t)sleep);
		
}

int shout_delay(shout_t *self)
{

	if (!self)
		return 0;

	if (self->senttime == 0)
		return 0;

	/* Is this cast to double needed? */
	return self->senttime / 1000 - (timing_get_time() - self->starttime);
}
  
shout_metadata_t *shout_metadata_new(void)
{
	return _shout_util_dict_new();
}

void shout_metadata_free(shout_metadata_t *self)
{
	if (!self)
		return;

	_shout_util_dict_free(self);
}

int shout_metadata_add(shout_metadata_t *self, const char *name, const char *value)
{
	if (!self || !name)
		return SHOUTERR_INSANE;

	return _shout_util_dict_set(self, name, value);
}

/* open second socket to server, send HTTP request to change metadata.
 * TODO: prettier error-handling. */
int shout_set_metadata(shout_t *self, shout_metadata_t *metadata)
{
	sock_t socket;
	int rv;
	char *encvalue;

	if (!self || !metadata)
		return SHOUTERR_INSANE;

	if (!(encvalue = _shout_util_dict_urlencode(metadata, '&')))
		return SHOUTERR_MALLOC;

	if ((socket = sock_connect(self->host, self->port)) <= 0)
		return SHOUTERR_NOCONNECT;

	if (self->protocol == SHOUT_PROTOCOL_ICY)
		rv = sock_write(socket, "GET /admin.cgi?mode=updinfo&pass=%s&%s HTTP/1.0\r\nUser-Agent: %s (Mozilla compatible)\r\n\r\n",
		  self->password, encvalue, shout_get_agent(self));
	else if (self->protocol == SHOUT_PROTOCOL_HTTP) {
		char *auth = http_basic_authorization(self);

		rv = sock_write(socket, "GET /admin/metadata?mode=updinfo&mount=%s&%s HTTP/1.0\r\nUser-Agent: %s\r\n%s\r\n",
		  self->mount, encvalue, shout_get_agent(self), auth ? auth : "");
                free(auth);
	} else
		rv = sock_write(socket, "GET /admin.cgi?mode=updinfo&pass=%s&mount=%s&%s HTTP/1.0\r\nUser-Agent: %s\r\n\r\n",
		  self->password, self->mount, encvalue, shout_get_agent(self));
	free(encvalue);
	if (!rv) {
		sock_close(socket);
		return SHOUTERR_SOCKET;
	}

	sock_close(socket);

	return SHOUTERR_SUCCESS;
}

/* getters/setters */
const char *shout_version(int *major, int *minor, int *patch)
{
	if (major)
		*major = LIBSHOUT_MAJOR;
	if (minor)
		*minor = LIBSHOUT_MINOR;
	if (patch)
		*patch = LIBSHOUT_MICRO;

	return VERSION;
}

int shout_get_errno(shout_t *self)
{
	return self->error;
}

const char *shout_get_error(shout_t *self)
{
	if (!self)
		return "Invalid shout_t";

	switch (self->error) {
	case SHOUTERR_SUCCESS:
		return "No error";
	case SHOUTERR_INSANE:
		return "Nonsensical arguments";
	case SHOUTERR_NOCONNECT:
		return "Couldn't connect";
	case SHOUTERR_NOLOGIN:
		return "Login failed";
	case SHOUTERR_SOCKET:
		return "Socket error";
	case SHOUTERR_MALLOC:
		return "Out of memory";
	case SHOUTERR_CONNECTED:
		return "Cannot set parameter while connected";
	case SHOUTERR_UNCONNECTED:
		return "Not connected";
        case SHOUTERR_BUSY:
                return "Socket is busy";
	case SHOUTERR_UNSUPPORTED:
		return "This libshout doesn't support the requested option";
	default:
		return "Unknown error";
	}
}

/* Returns:
 *   SHOUTERR_CONNECTED if the connection is open,
 *   SHOUTERR_UNCONNECTED if it has not yet been opened,
 *   or an error from try_connect, including SHOUTERR_BUSY
 */
int shout_get_connected(shout_t *self)
{
	int rc;

	if (!self)
		return SHOUTERR_INSANE;

	if (self->state == SHOUT_STATE_CONNECTED)
		return SHOUTERR_CONNECTED;
	if (self->state != SHOUT_STATE_UNCONNECTED) {
		if ((rc = try_connect(self)) == SHOUTERR_SUCCESS)
			return SHOUTERR_CONNECTED;
		return rc;
	}

	return SHOUTERR_UNCONNECTED;
}

int shout_set_host(shout_t *self, const char *host)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->host)
		free(self->host);

	if (!(self->host = _shout_util_strdup(host)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_host(shout_t *self)
{
	if (!self)
		return NULL;

	return self->host;
}

int shout_set_port(shout_t *self, unsigned short port)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	self->port = port;

	return self->error = SHOUTERR_SUCCESS;
}

unsigned short shout_get_port(shout_t *self)
{
	if (!self)
		return 0;

	return self->port;
}

int shout_set_password(shout_t *self, const char *password)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->password)
		free (self->password);

	if (!(self->password = _shout_util_strdup(password)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char* shout_get_password(shout_t *self)
{
	if (!self)
		return NULL;

	return self->password;
}

int shout_set_mount(shout_t *self, const char *mount)
{
	size_t len;

	if (!self || !mount)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;
	
	if (self->mount)
		free(self->mount);

	len = strlen (mount) + 1;
	if (mount[0] != '/')
		len++;

	if (!(self->mount = malloc(len)))
		return self->error = SHOUTERR_MALLOC;

	sprintf (self->mount, "%s%s", mount[0] == '/' ? "" : "/", mount);

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_mount(shout_t *self)
{
	if (!self)
		return NULL;

	return self->mount;
}

int shout_set_name(shout_t *self, const char *name)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->name)
		free(self->name);

	if (!(self->name = _shout_util_strdup(name)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_name(shout_t *self)
{
	if (!self)
		return NULL;

	return self->name;
}

int shout_set_url(shout_t *self, const char *url)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->url)
		free(self->url);

	if (!(self->url = _shout_util_strdup(url)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_url(shout_t *self)
{
	if (!self)
		return NULL;

	return self->url;
}

int shout_set_genre(shout_t *self, const char *genre)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->genre)
		free(self->genre);

	if (! (self->genre = _shout_util_strdup (genre)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_genre(shout_t *self)
{
	if (!self)
		return NULL;

	return self->genre;
}

int shout_set_agent(shout_t *self, const char *agent)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->useragent)
		free(self->useragent);

	if (! (self->useragent = _shout_util_strdup (agent)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_agent(shout_t *self)
{
	if (!self)
		return NULL;

	return self->useragent;
}


int shout_set_user(shout_t *self, const char *username)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->user)
		free(self->user);

	if (! (self->user = _shout_util_strdup (username)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_user(shout_t *self)
{
	if (!self)
		return NULL;

	return self->user;
}

int shout_set_description(shout_t *self, const char *description)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (self->description)
		free(self->description);

	if (! (self->description = _shout_util_strdup (description)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_description(shout_t *self)
{
	if (!self)
		return NULL;

	return self->description;
}

int shout_set_dumpfile(shout_t *self, const char *dumpfile)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return SHOUTERR_CONNECTED;

	if (self->dumpfile)
		free(self->dumpfile);

	if (! (self->dumpfile = _shout_util_strdup (dumpfile)))
		return self->error = SHOUTERR_MALLOC;

	return self->error = SHOUTERR_SUCCESS;
}

const char *shout_get_dumpfile(shout_t *self)
{
	if (!self)
		return NULL;

	return self->dumpfile;
}

int shout_set_audio_info(shout_t *self, const char *name, const char *value)
{
	return self->error = _shout_util_dict_set(self->audio_info, name, value);
}

const char *shout_get_audio_info(shout_t *self, const char *name)
{
	return _shout_util_dict_get(self->audio_info, name);
}

int shout_set_public(shout_t *self, unsigned int public)
{
	if (!self || (public != 0 && public != 1))
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	self->public = public;

	return self->error = SHOUTERR_SUCCESS;
}

unsigned int shout_get_public(shout_t *self)
{
	if (!self)
		return 0;

	return self->public;
}

int shout_set_format(shout_t *self, unsigned int format)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (format != SHOUT_FORMAT_OGG && format != SHOUT_FORMAT_MP3)
		return self->error = SHOUTERR_UNSUPPORTED;

	self->format = format;

	return self->error = SHOUTERR_SUCCESS;
}

unsigned int shout_get_format(shout_t* self)
{
	if (!self)
		return 0;

	return self->format;
}

int shout_set_protocol(shout_t *self, unsigned int protocol)
{
	if (!self)
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	if (protocol != SHOUT_PROTOCOL_HTTP &&
	    protocol != SHOUT_PROTOCOL_XAUDIOCAST &&
	    protocol != SHOUT_PROTOCOL_ICY)
		return self->error = SHOUTERR_UNSUPPORTED;

	self->protocol = protocol;

	return self->error = SHOUTERR_SUCCESS;
}

unsigned int shout_get_protocol(shout_t *self)
{
	if (!self)
		return 0;

	return self->protocol;
}

int shout_set_nonblocking(shout_t *self, unsigned int nonblocking)
{
	if (!self || (nonblocking != 0 && nonblocking != 1))
		return SHOUTERR_INSANE;

	if (self->state != SHOUT_STATE_UNCONNECTED)
		return self->error = SHOUTERR_CONNECTED;

	self->nonblocking = nonblocking;

	return SHOUTERR_SUCCESS;
}

unsigned int shout_get_nonblocking(shout_t *self)
{
	if (!self)
		return 0;

	return self->nonblocking;
}

/* -- static function definitions -- */

/* queue data in pages of SHOUT_BUFSIZE bytes */
static int queue_data(shout_queue_t *queue, const unsigned char *data, size_t len)
{
	shout_buf_t *buf;
	size_t plen;

	if (!len)
		return SHOUTERR_SUCCESS;

	if (!queue->len) {
		queue->head = calloc(1, sizeof (shout_buf_t));
		if (! queue->head)
			return SHOUTERR_MALLOC;
	}

	for (buf = queue->head; buf->next; buf = buf->next);

	/* Maybe any added data should be freed if we hit a malloc error?
	 * Otherwise it'd be impossible to tell where to start requeueing.
	 * (As if anyone ever tried to recover from a malloc error.) */
	while (len > 0) {
		if (buf->len == SHOUT_BUFSIZE) {
			buf->next = calloc(1, sizeof (shout_buf_t));
			if (! buf->next)
				return SHOUTERR_MALLOC;
			buf->next->prev = buf;
			buf = buf->next;
		}

		plen = len > SHOUT_BUFSIZE - buf->len ? SHOUT_BUFSIZE - buf->len : len;
		memcpy (buf->data + buf->len, data, plen);
		buf->len += plen;
		data += plen;
		len -= plen;
		queue->len += plen;
	}

	return SHOUTERR_SUCCESS;
}

static inline int queue_str(shout_t *self, const char *str)
{
	return queue_data(&self->wqueue, (const unsigned char*)str, strlen(str));
}

/* this should be shared with sock_write. Create libicecommon. */
static int queue_printf(shout_t *self, const char *fmt, ...)
{
	char buffer[1024];
	char *buf;
	va_list ap, ap_retry;
	int len;

	buf = buffer;

	va_start(ap, fmt);
	va_copy(ap_retry, ap);

	len = vsnprintf(buf, sizeof(buffer), fmt, ap);

	self->error = SHOUTERR_SUCCESS;
	if (len > 0) {
		if ((size_t)len < sizeof(buffer))
			queue_data(&self->wqueue, (unsigned char*)buf, len);
		else {
			buf = malloc(++len);
			if (buf) {
				len = vsnprintf(buf, len, fmt, ap_retry);
				queue_data(&self->wqueue, (unsigned char*)buf, len);
				free(buf);
			} else
				self->error = SHOUTERR_MALLOC;
		}
	}

	va_end(ap_retry);
	va_end(ap);

	return self->error;
}

static inline void queue_free(shout_queue_t *queue)
{
	shout_buf_t *prev;

	while (queue->head) {
		prev = queue->head;
		queue->head = queue->head->next;
		free(prev);
	}
	queue->len = 0;
}

static int get_response(shout_t *self)
{
	char buf[1024];
	int rc, blen;
	char *pc;
	shout_buf_t *queue;
	int newlines = 0;

	rc = sock_read_bytes(self->socket, buf, sizeof(buf));

	if (rc < 0 && sock_recoverable(sock_error()))
		return SHOUTERR_BUSY;
	if (rc <= 0)
		return SHOUTERR_SOCKET;

	if ((rc = queue_data(&self->rqueue, (unsigned char*)buf, rc)))
		return rc;

	/* work from the back looking for \r?\n\r?\n. Anything else means more
	 * is coming. */
	for (queue = self->rqueue.head; queue->next; queue = queue->next);
	pc = (char*)queue->data + queue->len - 1;
	blen = queue->len;
	while (blen) {
		if (*pc == '\n')
			newlines++;
		/* we may have to scan the entire queue if we got a response with
		 * data after the head line (this can happen with eg 401) */
		else if (*pc != '\r')
			newlines = 0;

		if (newlines == 2)
			return SHOUTERR_SUCCESS;

		blen--;
		pc--;

		if (!blen && queue->prev) {
			queue = queue->prev;
			pc = (char*)queue->data + queue->len - 1;
			blen = queue->len;
		}
	}

	return SHOUTERR_BUSY;
}

static int try_connect (shout_t *self)
{
	int rc;
	int port;

	/* the breaks between cases are omitted intentionally */
	switch (self->state) {
	case SHOUT_STATE_UNCONNECTED:
		port = self->port;
		if (shout_get_protocol(self) == SHOUT_PROTOCOL_ICY)
			port++;

		if (shout_get_nonblocking(self)) {
			if ((self->socket = sock_connect_non_blocking(self->host, port)) < 0)
				return self->error = SHOUTERR_NOCONNECT;
			self->state = SHOUT_STATE_CONNECT_PENDING;
		} else {
			if ((self->socket = sock_connect(self->host, port)) < 0)
				return self->error = SHOUTERR_NOCONNECT;
			if ((rc = create_request(self)) != SHOUTERR_SUCCESS)
				return rc;
			self->state = SHOUT_STATE_REQ_PENDING;
		}

	case SHOUT_STATE_CONNECT_PENDING:
		if (shout_get_nonblocking(self)) {
			if ((rc = sock_connected(self->socket, 0)) < 1) {
				if (rc == SOCK_ERROR) {
                                        rc = SHOUTERR_SOCKET;
                                        goto failure;
				} else
					return SHOUTERR_BUSY;
			}
			if ((rc = create_request(self)) != SHOUTERR_SUCCESS)
                                goto failure;
		}
		self->state = SHOUT_STATE_REQ_PENDING;

	case SHOUT_STATE_REQ_PENDING:
		do
			rc = send_queue(self);
		while (!shout_get_nonblocking(self) && rc == SHOUTERR_BUSY);
                if (rc == SHOUTERR_BUSY)
                        return rc;
		if (rc != SHOUTERR_SUCCESS)
                        goto failure;
		self->state = SHOUT_STATE_RESP_PENDING;

	case SHOUT_STATE_RESP_PENDING:
		do
			rc = get_response(self);
		while (!shout_get_nonblocking(self) && rc == SHOUTERR_BUSY);
                if (rc == SHOUTERR_BUSY)
                        return rc;

		if (rc != SHOUTERR_SUCCESS)
                        goto failure;

		if ((rc = parse_response(self)) != SHOUTERR_SUCCESS)
                        goto failure;

		if (self->format == SHOUT_FORMAT_OGG) {
			if ((rc = self->error = shout_open_ogg(self)) != SHOUTERR_SUCCESS)
                                goto failure;
		} else if (self->format == SHOUT_FORMAT_MP3) {
			if ((rc = self->error = shout_open_mp3(self)) != SHOUTERR_SUCCESS)
                                goto failure;
		} else {
                        rc = SHOUTERR_INSANE;
                        goto failure;
		}

	case SHOUT_STATE_CONNECTED:
		self->state = SHOUT_STATE_CONNECTED;
	}
	
	return SHOUTERR_SUCCESS;

failure:
        shout_close(self);
	return rc;
}

static int try_write (shout_t *self, const void *data, size_t len)
{
    int ret;
    size_t pos = 0;

    /* loop until whole buffer is written (unless it would block) */
    do {
        ret = sock_write_bytes (self->socket, data + pos, len - pos);
        if (ret > 0)
            pos += ret;
    } while (pos < len && ret >= 0);

    if (ret < 0)
    {
        if (sock_recoverable (sock_error()))
        {
            self->error = SHOUTERR_BUSY;
            return pos;
        }
        self->error = SHOUTERR_SOCKET;
        return ret;
    }

    return pos;
}

/* collect nodes of a queue into a single buffer */
static int collect_queue(shout_buf_t *queue, char **buf)
{
	shout_buf_t *node;
	int pos = 0;
	int len = 0;

	for (node = queue; node; node = node->next)
		len += node->len;

	if (!(*buf = malloc(len)))
		return SHOUTERR_MALLOC;

	for (node = queue; node; node = node->next) {
		memcpy(*buf + pos, node->data, node->len);
		pos += node->len;
	}

	return len;
}

static int send_queue(shout_t *self)
{
	shout_buf_t *buf;
	int ret;

	if (!self->wqueue.len)
		return SHOUTERR_SUCCESS;

	buf = self->wqueue.head;
	while (buf) {
		ret = try_write (self, buf->data + buf->pos, buf->len - buf->pos);
		if (ret < 0)
			return self->error;

		buf->pos += ret;
		self->wqueue.len -= ret;
		if (buf->pos == buf->len) {
			self->wqueue.head = buf->next;
			free(buf);
			buf = self->wqueue.head;
			if (buf)
				buf->prev = NULL;
		} else /* incomplete write */
			return SHOUTERR_BUSY;
	}

	return self->error = SHOUTERR_SUCCESS;
}

static int create_request(shout_t *self)
{
	if (self->protocol == SHOUT_PROTOCOL_HTTP)
		return create_http_request(self);
	else if (self->protocol == SHOUT_PROTOCOL_XAUDIOCAST)
		return create_xaudiocast_request(self);
	else if (self->protocol == SHOUT_PROTOCOL_ICY)
		return create_icy_request(self);

	return self->error = SHOUTERR_UNSUPPORTED;
}

static int create_http_request(shout_t *self)
{
	char *auth;
	char *ai;
	int ret = SHOUTERR_MALLOC;

	/* this is lazy code that relies on the only error from queue_* being
	 * SHOUTERR_MALLOC */
	do {
		if (queue_printf(self, "SOURCE %s HTTP/1.0\r\n", self->mount))
			break;
		if (self->password) {
			if (! (auth = http_basic_authorization(self)))
				break;
			if (queue_str(self, auth)) {
				free(auth);
				break;
			}
			free(auth);
		}
		if (self->useragent && queue_printf(self, "User-Agent: %s\r\n", self->useragent))
			break;
		if (self->format == SHOUT_FORMAT_OGG && queue_printf(self, "Content-Type: application/ogg\r\n"))
			break;
		if (self->format == SHOUT_FORMAT_MP3 && queue_printf(self, "Content-Type: audio/mpeg\r\n"))
			break;
		if (queue_printf(self, "ice-name: %s\r\n", self->name ? self->name : "no name"))
			break;
		if (queue_printf(self, "ice-public: %d\r\n", self->public))
			break;

		if (self->url && queue_printf(self, "ice-url: %s\r\n", self->url))
			break;
		if (self->genre && queue_printf(self, "ice-genre: %s\r\n", self->genre))
			break;
		if ((ai = _shout_util_dict_urlencode(self->audio_info, ';'))) {
			if (queue_printf(self, "ice-audio-info: %s\r\n", ai)) {
				free(ai);
				break;
			}
			free(ai);
		}
		if (self->description && queue_printf(self, "ice-description: %s\r\n", self->description))
			break;
		if (queue_str(self, "\r\n"))
			break;
		
		ret = SHOUTERR_SUCCESS;
	} while (0);

	return ret;
}

static char *http_basic_authorization(shout_t *self)
{
	char *out, *in;
	int len;

	if (!self || !self->user || !self->password)
		return NULL;

	len = strlen(self->user) + strlen(self->password) + 2;
	if (!(in = malloc(len)))
		return NULL;
	sprintf(in, "%s:%s", self->user, self->password);
	out = _shout_util_base64_encode(in);
	free(in);

	len = strlen(out) + 24;
	if (!(in = malloc(len))) {
		free(out);
		return NULL;
	}
	sprintf(in, "Authorization: Basic %s\r\n", out);
	free(out);
	
	return in;
}

static int parse_response(shout_t *self)
{
	if (self->protocol == SHOUT_PROTOCOL_HTTP)
		return parse_http_response(self);
	else if (self->protocol == SHOUT_PROTOCOL_XAUDIOCAST ||
		 self->protocol == SHOUT_PROTOCOL_ICY)
		return parse_xaudiocast_response(self);

	return self->error = SHOUTERR_UNSUPPORTED;
}

static int parse_http_response(shout_t *self)
{
	http_parser_t *parser;
	char *header = NULL;
	int hlen = 0;
	int code;
	char *retcode;
#if 0
	char *realm;
#endif

	/* all this copying! */
	hlen = collect_queue(self->rqueue.head, &header);
	if (hlen <= 0)
		return SHOUTERR_MALLOC;
	queue_free(&self->rqueue);

	parser = httpp_create_parser();
	httpp_initialize(parser, NULL);
	if (httpp_parse_response(parser, header, hlen, self->mount)) {
		retcode = httpp_getvar(parser, HTTPP_VAR_ERROR_CODE);
		code = atoi(retcode);
		if(code >= 200 && code < 300) {
			httpp_destroy(parser);
			free (header);
			return SHOUTERR_SUCCESS;
		}
	}

	free(header);
	httpp_destroy(parser);
	return self->error = SHOUTERR_NOLOGIN;
}

static int create_xaudiocast_request(shout_t *self)
{
	const char *bitrate;
	int ret;

	bitrate = shout_get_audio_info(self, SHOUT_AI_BITRATE);
	if (!bitrate)
		bitrate = "0";

	ret = SHOUTERR_MALLOC;
	do {
		if (queue_printf(self, "SOURCE %s %s\n", self->password, self->mount))
			break;
		if (queue_printf(self, "x-audiocast-name: %s\n", self->name ? self->name : "unnamed"))
			break;
		if (queue_printf(self, "x-audiocast-url: %s\n", self->url ? self->url : "http://www.icecast.org/"))
			break;
		if (queue_printf(self, "x-audiocast-genre: %s\n", self->genre ? self->genre : "icecast"))
			break;
		if (queue_printf(self, "x-audiocast-bitrate: %s\n", bitrate))
			break;
		if (queue_printf(self, "x-audiocast-public: %i\n", self->public))
			break;
		if (queue_printf(self, "x-audiocast-description: %s\n", self->description ? self->description : "Broadcasting with the icecast streaming media server!"))
			break;
		if (self->dumpfile && queue_printf(self, "x-audiocast-dumpfile: %s\n", self->dumpfile))
			break;
		if (queue_str(self, "\n"))
			break;

		ret = SHOUTERR_SUCCESS;
	} while (0);
		
	return ret;
}

static int parse_xaudiocast_response(shout_t *self)
{
	char *response;

	if (collect_queue(self->rqueue.head, &response) <= 0)
		return SHOUTERR_MALLOC;
	queue_free(&self->rqueue);

	if (!strstr(response, "OK")) {
		free(response);
		return SHOUTERR_NOLOGIN;
	}
	free(response);

	return SHOUTERR_SUCCESS;
}

static int create_icy_request(shout_t *self)
{
	const char *bitrate;
	int ret;

	bitrate = shout_get_audio_info(self, SHOUT_AI_BITRATE);
	if (!bitrate)
		bitrate = "0";

	ret = SHOUTERR_MALLOC;
	do {
		if (queue_printf(self, "%s\n", self->password))
			break;
		if (queue_printf(self, "icy-name:%s\n", self->name ? self->name : "unnamed"))
			break;
		if (queue_printf(self, "icy-url:%s\n", self->url ? self->url : "http://www.icecast.org/"))
			break;
		if (queue_str(self, "icy-irc:\nicy-aim:\nicy-icq:\n"))
			break;
		if (queue_printf(self, "icy-pub:%i\n", self->public))
			break;
		if (queue_printf(self, "icy-genre:%s\n", self->genre ? self->genre : "icecast"))
			break;
		if (queue_printf(self, "icy-br:%s\n\n", bitrate))
			break;

		ret = SHOUTERR_SUCCESS;
	} while (0);

	return ret;
}