/* -*- c-basic-offset: 8; -*- */ /* shout.c: Implementation of public libshout interface shout.h * * Copyright (C) 2002-2004 the Icecast team * * 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 #endif #include #include #include #include #include #include #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; }