From a9728c54be7e048a6931324921b2866d8dcbe5c3 Mon Sep 17 00:00:00 2001 From: Moritz Grimm Date: Thu, 24 Mar 2016 18:16:00 +0100 Subject: [PATCH] ezstream.c/libshout untangle 1/2 --- src/ezstream.c | 254 +++++------------------------- src/ezstream.h | 4 - src/metadata.c | 59 +++---- src/metadata.h | 24 +-- src/stream.c | 414 +++++++++++++++++++++++++++++++++++++++++++++++++ src/stream.h | 17 ++ src/util.c | 215 ++++++++----------------- src/util.h | 5 +- 8 files changed, 581 insertions(+), 411 deletions(-) diff --git a/src/ezstream.c b/src/ezstream.c index 37cb715..8b49081 100644 --- a/src/ezstream.c +++ b/src/ezstream.c @@ -75,20 +75,16 @@ typedef struct tag_ID3Tag { } ID3Tag; int urlParse(const char *, char **, unsigned short *, char **); -char * shellQuote(const char *); -char * replaceString(const char *, const char *, const char *); -char * buildReencodeCommand(const char *, const char *, metadata_t *); -char * getMetadataString(const char *, metadata_t *); -metadata_t * getMetadata(const char *); -int setMetadata(shout_t *, metadata_t *, char **); -FILE * openResource(shout_t *, const char *, int *, metadata_t **, +char * buildReencodeCommand(const char *, const char *, metadata_t); +metadata_t getMetadata(const char *); +FILE * openResource(stream_t, const char *, int *, metadata_t *, int *, long *); int reconnectServer(shout_t *, int); const char * getTimeString(long); -int sendStream(shout_t *, FILE *, const char *, int, const char *, +int sendStream(stream_t, FILE *, const char *, int, const char *, struct timespec *); -int streamFile(shout_t *, const char *); -int streamPlaylist(shout_t *); +int streamFile(stream_t, const char *); +int streamPlaylist(stream_t); int ez_shutdown(int); #ifdef HAVE_SIGNALS @@ -173,72 +169,9 @@ urlParse(const char *url, char **hostname, unsigned short *port, return (1); } -#define SHELLQUOTE_INLEN_MAX 8191UL - -char * -shellQuote(const char *in) -{ - char *out, *out_p; - size_t out_len; - const char *in_p; - - out_len = (strlen(in) > SHELLQUOTE_INLEN_MAX) - ? SHELLQUOTE_INLEN_MAX - : strlen(in); - out_len = out_len * 2 + 2; - out = xcalloc(out_len + 1, sizeof(char)); - - out_p = out; - in_p = in; - - *out_p++ = '\''; - out_len--; - while (*in_p && out_len > 2) { - switch (*in_p) { - case '\'': - case '\\': - *out_p++ = '\\'; - out_len--; - break; - default: - break; - } - *out_p++ = *in_p++; - out_len--; - } - *out_p++ = '\''; - - return (out); -} - -char * -replaceString(const char *source, const char *from, const char *to) -{ - char *to_quoted, *dest; - size_t dest_size; - const char *p1, *p2; - - to_quoted = shellQuote(to); - dest_size = strlen(source) + strlen(to_quoted) + 1; - dest = xcalloc(dest_size, sizeof(char)); - - p1 = source; - p2 = strstr(p1, from); - if (p2 != NULL) { - strncat(dest, p1, (size_t)(p2 - p1)); - strlcat(dest, to_quoted, dest_size); - p1 = p2 + strlen(from); - } - strlcat(dest, p1, dest_size); - - xfree(to_quoted); - - return (dest); -} - char * buildReencodeCommand(const char *extension, const char *fileName, - metadata_t *mdata) + metadata_t mdata) { cfg_decoder_t decoder; cfg_encoder_t encoder; @@ -292,7 +225,7 @@ buildReencodeCommand(const char *extension, const char *fileName, if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) { if (cfg_get_metadata_program() && cfg_get_metadata_format_str()) { - char *mdataString = getMetadataString(cfg_get_metadata_format_str(), + char *mdataString = stream_get_metadata_str(cfg_get_metadata_format_str(), mdata); char *tmpStr = replaceString(dec_str, PLACEHOLDER_METADATA, mdataString); @@ -329,7 +262,7 @@ buildReencodeCommand(const char *extension, const char *fileName, if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) { if (cfg_get_metadata_program() && cfg_get_metadata_format_str()) { - char *mdataString = getMetadataString(cfg_get_metadata_format_str(), + char *mdataString = stream_get_metadata_str(cfg_get_metadata_format_str(), mdata); char *tmpStr = replaceString(enc_str, PLACEHOLDER_METADATA, mdataString); @@ -367,48 +300,10 @@ buildReencodeCommand(const char *extension, const char *fileName, return (commandString); } -char * -getMetadataString(const char *format, metadata_t *mdata) -{ - char *tmp, *str; - - if (format == NULL) - return (NULL); - - str = xstrdup(format); - - if (strstr(format, PLACEHOLDER_ARTIST) != NULL) { - tmp = replaceString(str, PLACEHOLDER_ARTIST, - metadata_get_artist(mdata)); - xfree(str); - str = tmp; - } - if (strstr(format, PLACEHOLDER_TITLE) != NULL) { - tmp = replaceString(str, PLACEHOLDER_TITLE, - metadata_get_title(mdata)); - xfree(str); - str = tmp; - } - if (strstr(format, PLACEHOLDER_STRING) != NULL) { - tmp = replaceString(str, PLACEHOLDER_STRING, - metadata_get_string(mdata)); - xfree(str); - str = tmp; - } - if (strstr(format, PLACEHOLDER_TRACK) != NULL) { - tmp = replaceString(str, PLACEHOLDER_TRACK, - metadata_get_filename(mdata)); - xfree(str); - str = tmp; - } - - return (str); -} - -metadata_t * +metadata_t getMetadata(const char *fileName) { - metadata_t *mdata; + metadata_t mdata; if (cfg_get_metadata_program()) { if (NULL == (mdata = metadata_program(fileName, @@ -433,92 +328,15 @@ getMetadata(const char *fileName) return (mdata); } -int -setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy) -{ - shout_metadata_t *shout_mdata = NULL; - char *songInfo; - const char *artist, *title; - int ret = SHOUTERR_SUCCESS; - - if (cfg_get_metadata_no_updates()) - return (SHOUTERR_SUCCESS); - - if (mdata == NULL) - return 1; - - if ((shout_mdata = shout_metadata_new()) == NULL) { - log_syserr(ALERT, ENOMEM, "shout_metadata_new"); - exit(1); - } - - artist = metadata_get_artist(mdata); - title = metadata_get_title(mdata); - - /* - * We can do this, because we know how libshout works. This adds - * "charset=UTF-8" to the HTTP metadata update request and has the - * desired effect of letting newer-than-2.3.1 versions of Icecast know - * which encoding we're using. - */ - if (shout_metadata_add(shout_mdata, "charset", "UTF-8") != SHOUTERR_SUCCESS) { - /* Assume SHOUTERR_MALLOC */ - log_syserr(ALERT, ENOMEM, "shout_metadata_add"); - exit(1); - } - - songInfo = getMetadataString(cfg_get_metadata_format_str(), mdata); - if (songInfo == NULL) { - if (artist[0] == '\0' && title[0] == '\0') - songInfo = xstrdup(metadata_get_string(mdata)); - else - songInfo = metadata_assemble_string(mdata); - if (artist[0] != '\0' && title[0] != '\0') { - if (shout_metadata_add(shout_mdata, "artist", artist) != SHOUTERR_SUCCESS) { - log_syserr(ALERT, ENOMEM, - "shout_metadata_add"); - exit(1); - } - if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) { - log_syserr(ALERT, ENOMEM, - "shout_metadata_add"); - exit(1); - } - } else { - if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) { - log_syserr(ALERT, ENOMEM, - "shout_metadata_add"); - exit(1); - } - } - } else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) { - log_syserr(ALERT, ENOMEM, "shout_metadata_add"); - exit(1); - } - - if ((ret = shout_set_metadata(shout, shout_mdata)) != SHOUTERR_SUCCESS) - log_warning("shout_set_metadata: %s", shout_get_error(shout)); - - shout_metadata_free(shout_mdata); - - if (ret == SHOUTERR_SUCCESS) { - if (mdata_copy != NULL && *mdata_copy == NULL) - *mdata_copy = xstrdup(songInfo); - } - - xfree(songInfo); - return (ret); -} - FILE * -openResource(shout_t *shout, const char *fileName, int *popenFlag, - metadata_t **mdata_p, int *isStdin, long *songLen) +openResource(stream_t stream, const char *fileName, int *popenFlag, + metadata_t *mdata_p, int *isStdin, long *songLen) { FILE *filep = NULL; char extension[25]; char *p = NULL; char *pCommandString = NULL; - metadata_t *mdata; + metadata_t mdata; if (mdata_p != NULL) *mdata_p = NULL; @@ -529,7 +347,7 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag, if (cfg_get_metadata_program()) { if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL) return (NULL); - if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) { + if (stream_set_metadata(stream, mdata, NULL) != SHOUTERR_SUCCESS) { metadata_free(&mdata); return (NULL); } @@ -699,15 +517,16 @@ getTimeString(long seconds) } int -sendStream(shout_t *shout, FILE *filepstream, const char *fileName, +sendStream(stream_t stream, FILE *filepstream, const char *fileName, int isStdin, const char *songLenStr, struct timespec *tv) { - unsigned char buff[4096]; - size_t bytes_read, total, oldTotal; - int ret; - double kbps = -1.0; - struct timespec timeStamp, *startTime = tv; - struct timespec callTime, currentTime; + unsigned char buff[4096]; + size_t bytes_read, total, oldTotal; + int ret; + double kbps = -1.0; + struct timespec timeStamp, *startTime = tv; + struct timespec callTime, currentTime; + shout_t *shout = stream_get_shout(stream); clock_gettime(CLOCK_MONOTONIC, &callTime); @@ -820,7 +639,7 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName, } int -streamFile(shout_t *shout, const char *fileName) +streamFile(stream_t stream, const char *fileName) { FILE *filepstream = NULL; int popenFlag = 0; @@ -828,10 +647,10 @@ streamFile(shout_t *shout, const char *fileName) int isStdin = 0; int ret, retval = 0; long songLen; - metadata_t *mdata; + metadata_t mdata; struct timespec startTime; - if ((filepstream = openResource(shout, fileName, &popenFlag, &mdata, &isStdin, &songLen)) + if ((filepstream = openResource(stream, fileName, &popenFlag, &mdata, &isStdin, &songLen)) == NULL) { if (++resource_errors > 100) { log_error("too many errors; giving up"); @@ -854,7 +673,7 @@ streamFile(shout_t *shout, const char *fileName) /* MP3 streams are special, so set the metadata explicitly: */ if (CFG_STREAM_MP3 == cfg_get_stream_format()) - setMetadata(shout, mdata, NULL); + stream_set_metadata(stream, mdata, NULL); metadata_free(&mdata); } else if (isStdin) @@ -864,7 +683,7 @@ streamFile(shout_t *shout, const char *fileName) songLenStr = xstrdup(getTimeString(songLen)); clock_gettime(CLOCK_MONOTONIC, &startTime); do { - ret = sendStream(shout, filepstream, fileName, isStdin, + ret = sendStream(stream, filepstream, fileName, isStdin, songLenStr, &startTime); if (quit) break; @@ -891,7 +710,7 @@ streamFile(shout_t *shout, const char *fileName) continue; if (cfg_get_metadata_program()) { char *mdataStr = NULL; - metadata_t *prog_mdata; + metadata_t prog_mdata; log_info("running metadata program: %s", cfg_get_metadata_program()); @@ -900,7 +719,7 @@ streamFile(shout_t *shout, const char *fileName) ret = STREAM_DONE; continue; } - if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) { + if (stream_set_metadata(stream, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) { retval = 0; ret = STREAM_DONE; continue; @@ -930,7 +749,7 @@ streamFile(shout_t *shout, const char *fileName) } int -streamPlaylist(shout_t *shout) +streamPlaylist(stream_t stream) { const char *song; char lastSong[PATH_MAX]; @@ -968,7 +787,7 @@ streamPlaylist(shout_t *shout) while ((song = playlist_get_next(playlist)) != NULL) { strlcpy(lastSong, song, sizeof(lastSong)); - if (!streamFile(shout, song)) + if (!streamFile(stream, song)) return (0); if (quit) break; @@ -1010,6 +829,7 @@ main(int argc, char *argv[]) { int ret; const char *errstr; + stream_t stream; shout_t *shout; extern char *optarg; extern int optind; @@ -1034,8 +854,10 @@ main(int argc, char *argv[]) return (ez_shutdown(2)); } - if (NULL == (shout = stream_setup())) + stream = stream_get(STREAM_DEFAULT); + if (0 > stream_setup(stream)) return (ez_shutdown(1)); + shout = stream_get_shout(stream); #ifdef HAVE_SIGNALS memset(&act, 0, sizeof(act)); @@ -1078,9 +900,9 @@ main(int argc, char *argv[]) do { if (playlistMode) { - cont = streamPlaylist(shout); + cont = streamPlaylist(stream); } else { - cont = streamFile(shout, + cont = streamFile(stream, cfg_get_media_filename()); } if (quit) diff --git a/src/ezstream.h b/src/ezstream.h index 04f09e9..1867386 100644 --- a/src/ezstream.h +++ b/src/ezstream.h @@ -50,10 +50,6 @@ #include "cfg.h" #include "log.h" -#ifndef STDIN_FILENO -# define STDIN_FILENO 0 -#endif /* !STDIN_FILENO */ - #ifndef _PATH_DEVNULL # define _PATH_DEVNULL "/dev/null" #endif /* !_PATH_DEVNULL */ diff --git a/src/metadata.c b/src/metadata.c index b616db9..b329f91 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -73,21 +73,22 @@ struct ID3Tag { char genre; }; -static metadata_t * metadata_create(const char *); -static void metadata_use_taglib(metadata_t *, FILE **); -static void metadata_use_self(metadata_t *, FILE **); -static void metadata_clean_md(metadata_t *); -static void metadata_get_extension(char *, size_t, const char *); -static char * metadata_get_name(const char *); -static void metadata_process_md(metadata_t *); -static void metadata_normalize_string(char **); +static struct metadata * + metadata_create(const char *); +static void metadata_use_taglib(struct metadata *, FILE **); +static void metadata_use_self(struct metadata *, FILE **); +static void metadata_clean_md(struct metadata *); +static void metadata_get_extension(char *, size_t, const char *); +static char * metadata_get_name(const char *); +static void metadata_process_md(struct metadata *); +static void metadata_normalize_string(char **); -static metadata_t * +static struct metadata * metadata_create(const char *filename) { - metadata_t *md; + metadata_t md; - md = xcalloc(1UL, sizeof(metadata_t)); + md = xcalloc(1UL, sizeof(*md)); md->filename = xstrdup(filename); md->songLen = -1; @@ -95,7 +96,7 @@ metadata_create(const char *filename) } static void -metadata_use_taglib(metadata_t *md, FILE **filep) +metadata_use_taglib(struct metadata *md, FILE **filep) #ifdef HAVE_TAGLIB { TagLib_File *tf; @@ -156,7 +157,7 @@ metadata_use_taglib(metadata_t *md, FILE **filep) #endif /* HAVE_TAGLIB */ static void -metadata_use_self(metadata_t *md, FILE **filep) +metadata_use_self(struct metadata *md, FILE **filep) #ifdef HAVE_TAGLIB { (void)md; @@ -245,7 +246,7 @@ metadata_use_self(metadata_t *md, FILE **filep) #endif /* HAVE_TAGLIB */ static void -metadata_clean_md(metadata_t *md) +metadata_clean_md(struct metadata *md) { if (md->string != NULL) { xfree(md->string); @@ -299,7 +300,7 @@ metadata_get_name(const char *file) } static void -metadata_process_md(metadata_t *md) +metadata_process_md(struct metadata *md) { if (md->string == NULL) md->string = metadata_assemble_string(md); @@ -342,10 +343,10 @@ metadata_normalize_string(char **s) *s = xreallocarray(tmpstr, strlen(tmpstr) + 1, sizeof(char)); } -metadata_t * +struct metadata * metadata_file(const char *filename, int normalize) { - metadata_t *md; + struct metadata *md; md = metadata_create(filename); if (!metadata_file_update(md)) { @@ -358,10 +359,10 @@ metadata_file(const char *filename, int normalize) return (md); } -metadata_t * +struct metadata * metadata_program(const char *program, int normalize) { - metadata_t *md; + struct metadata *md; struct stat st; if (stat(program, &st) == -1) { @@ -386,9 +387,9 @@ metadata_program(const char *program, int normalize) } void -metadata_free(metadata_t **md_p) +metadata_free(struct metadata **md_p) { - metadata_t *md; + struct metadata *md; if (md_p == NULL || (md = *md_p) == NULL) return; @@ -404,7 +405,7 @@ metadata_free(metadata_t **md_p) int -metadata_file_update(metadata_t *md) +metadata_file_update(struct metadata *md) { FILE *filep; @@ -427,7 +428,7 @@ metadata_file_update(metadata_t *md) } int -metadata_program_update(metadata_t *md, enum metadata_request md_req) +metadata_program_update(struct metadata *md, enum metadata_request md_req) { FILE *filep; char buf[METADATA_MAX + 1]; @@ -534,14 +535,14 @@ metadata_program_update(metadata_t *md, enum metadata_request md_req) } const char * -metadata_get_string(metadata_t *md) +metadata_get_string(struct metadata *md) { assert(md->string); return (md->string); } const char * -metadata_get_artist(metadata_t *md) +metadata_get_artist(struct metadata *md) { if (md->artist == NULL) return (blankString); @@ -550,7 +551,7 @@ metadata_get_artist(metadata_t *md) } const char * -metadata_get_title(metadata_t *md) +metadata_get_title(struct metadata *md) { if (md->title == NULL) return (blankString); @@ -559,7 +560,7 @@ metadata_get_title(metadata_t *md) } const char * -metadata_get_filename(metadata_t *md) +metadata_get_filename(struct metadata *md) { if (md->filename == NULL) /* Should never happen: */ @@ -569,13 +570,13 @@ metadata_get_filename(metadata_t *md) } int -metadata_get_length(metadata_t *md) +metadata_get_length(struct metadata *md) { return (md->songLen); } char * -metadata_assemble_string(metadata_t *md) +metadata_assemble_string(struct metadata *md) { size_t len; char *str; diff --git a/src/metadata.h b/src/metadata.h index 95a5a91..eb95ac0 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -26,14 +26,14 @@ enum metadata_request { METADATA_TITLE }; -typedef struct metadata metadata_t; +typedef struct metadata * metadata_t; /* * Read the metadata of a media file and return a new metadata handle on * success, or NULL on failure. The returned handle is "branded" for reading * metadata from media files. */ -metadata_t * metadata_file(const char * /* filename */, +metadata_t metadata_file(const char * /* filename */, int /* normalize strings */); /* @@ -53,26 +53,26 @@ metadata_t * metadata_file(const char * /* filename */, * metadata, or an empty string if no artist information is available. * - Return at most METADATA_MAX characters, or the result will be truncated. */ -metadata_t * metadata_program(const char * /* program name */, +metadata_t metadata_program(const char * /* program name */, int /* normalize strings */); /* * Free all memory used by a metadata handle that has been created with * metadata_file() or metadata_program(). */ -void metadata_free(metadata_t **); +void metadata_free(metadata_t *); /* * Update/read the metadata for the given handle. Returns 1 on success, and 0 * on failure. */ -int metadata_file_update(metadata_t *); +int metadata_file_update(metadata_t); /* * Update/read the specified metadata for the given program-handle. Returns 1 * on success, and 0 on failure. */ -int metadata_program_update(metadata_t *, enum metadata_request); +int metadata_program_update(metadata_t, enum metadata_request); /* * Returns a pointer to a metadata string ``artist - title'', or just @@ -81,34 +81,34 @@ int metadata_program_update(metadata_t *, enum metadata_request); * is returned for metadata_program() handles that didn't supply any generic * information. */ -const char * metadata_get_string(metadata_t *); +const char * metadata_get_string(metadata_t); /* * Returns a pointer to the artist string, which may be empty. */ -const char * metadata_get_artist(metadata_t *); +const char * metadata_get_artist(metadata_t); /* * Returns a pointer to the title string, which may be empty. */ -const char * metadata_get_title(metadata_t *); +const char * metadata_get_title(metadata_t); /* * Returns a pointer to the filename used in the metadata handle. */ -const char * metadata_get_filename(metadata_t *); +const char * metadata_get_filename(metadata_t); /* * Returns the length of the song, in seconds, or -1 if the information is not * available. */ -int metadata_get_length(metadata_t *); +int metadata_get_length(metadata_t); /* * Allocates and returns a meaningful string based on a metadata handle's * content. The result is what metadata_get_string() defaults to if an external * program is not used. */ -char * metadata_assemble_string(metadata_t *); +char * metadata_assemble_string(metadata_t); #endif /* __METADATA_H__ */ diff --git a/src/stream.c b/src/stream.c index ff56d6b..11663af 100644 --- a/src/stream.c +++ b/src/stream.c @@ -18,14 +18,252 @@ # include "config.h" #endif /* HAVE_CONFIG_H */ +#include + +#include +#include +#include +#include + #include #include "cfg.h" +#include "log.h" #include "stream.h" +#include "util.h" +#include "xalloc.h" + +struct stream { + TAILQ_ENTRY(stream) entry; + char *name; + shout_t *shout; +}; +TAILQ_HEAD(stream_list, stream); + +static struct stream_list streams; + +static int _stream_cfg_server(struct stream *); +static int _stream_cfg_tls(struct stream *); +static int _stream_cfg_stream(struct stream *); + +static int +_stream_cfg_server(struct stream *s) +{ + switch (cfg_get_server_protocol()) { + case CFG_PROTO_HTTP: + if (SHOUTERR_SUCCESS != + shout_set_protocol(s->shout, SHOUT_PROTOCOL_HTTP)) { + log_error("%s: protocol: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + break; + default: + log_error("%s: protocol: unsupported: %s", + s->name, cfg_get_server_protocol_str()); + return (-1); + } + if (SHOUTERR_SUCCESS != + shout_set_host(s->shout, cfg_get_server_hostname())) { + log_error("%s: hostname: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (SHOUTERR_SUCCESS != + shout_set_port(s->shout, (unsigned short)cfg_get_server_port())) { + log_error("%s: port: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (SHOUTERR_SUCCESS != + shout_set_user(s->shout, cfg_get_server_user())) { + log_error("%s: user: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (SHOUTERR_SUCCESS != + shout_set_password(s->shout, cfg_get_server_password())) { + log_error("%s: password: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + + return (0); +} + +static int +_stream_cfg_tls(struct stream *s) +{ +#ifdef SHOUT_TLS_AUTO + int tls_req; + + switch (cfg_get_server_tls()) { + case CFG_TLS_NONE: + tls_req = SHOUT_TLS_DISABLED; + break; + case CFG_TLS_MAY: + tls_req = SHOUT_TLS_AUTO; + break; + case CFG_TLS_REQUIRED: + tls_req = SHOUT_TLS_AUTO_NO_PLAIN; + break; + default: + log_error("%s: tls: invalid", s->name); + return (-1); + } + if (SHOUTERR_SUCCESS != shout_set_tls(s->shout, tls_req)) { + log_error("%s: tls: %s", s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_server_ca_dir() && + SHOUTERR_SUCCESS != + shout_set_ca_directory(s->shout, cfg_get_server_ca_dir())) { + log_error("%s: ca_dir: %s", s->name, + shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_server_ca_file() && + SHOUTERR_SUCCESS != + shout_set_ca_file(s->shout, cfg_get_server_ca_file())) { + log_error("%s: ca_file: %s", s->name, + shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_server_tls_cipher_suite() && + SHOUTERR_SUCCESS != + shout_set_allowed_ciphers(s->shout, cfg_get_server_tls_cipher_suite())) { + log_error("%s: tls_cipher_suite: %s", s->name, + shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_server_client_cert() && + SHOUTERR_SUCCESS != + shout_set_client_certificate(s->shout, cfg_get_server_client_cert())) { + log_error("%s: client_cert: %s", s->name, + shout_get_error(s->shout)); + return (-1); + } +#else /* SHOUT_TLS_AUTO */ +# warning "libshout library does not support TLS" + switch (cfg_get_server_tls()) { + case CFG_TLS_MAY: + log_warning("%s: TLS optional but not supported by libshout", + s->name); + break; + case CFG_TLS_REQUIRED: + log_error("%s: TLS required by not supported by libshout", + s->name); + return (-1); + default: + break; + } +#endif /* SHOUT_TLS_AUTO */ + + return (0); +} + +static int +_stream_cfg_stream(struct stream *s) +{ + if (SHOUTERR_SUCCESS != + shout_set_mount(s->shout, cfg_get_stream_mountpoint())) { + log_error("%s: mountpoint: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + switch (cfg_get_stream_format()) { + case CFG_STREAM_VORBIS: + case CFG_STREAM_THEORA: + if (SHOUTERR_SUCCESS != + shout_set_format(s->shout, SHOUT_FORMAT_OGG)) { + log_error("%s: format_ogg: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + break; + case CFG_STREAM_MP3: + if (SHOUTERR_SUCCESS != + shout_set_format(s->shout, SHOUT_FORMAT_MP3)) { + log_error("%s: format_mp3: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + break; + default: + log_error("%s: format: unsupported: %s", + s->name, cfg_get_stream_format_str()); + return (-1); + } + if (cfg_get_stream_name() && + SHOUTERR_SUCCESS != + shout_set_name(s->shout, cfg_get_stream_name())) { + log_error("%s: name: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_url() && + SHOUTERR_SUCCESS != + shout_set_url(s->shout, cfg_get_stream_url())) { + log_error("%s: url: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_genre() && + SHOUTERR_SUCCESS != + shout_set_genre(s->shout, cfg_get_stream_genre())) { + log_error("%s: genre: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_description() && + SHOUTERR_SUCCESS != + shout_set_description(s->shout, cfg_get_stream_description())) { + log_error("%s: description: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_quality() && + SHOUTERR_SUCCESS != + shout_set_audio_info(s->shout, SHOUT_AI_QUALITY, cfg_get_stream_quality())) { + log_error("%s: ai_quality: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_bitrate() && + SHOUTERR_SUCCESS != + shout_set_audio_info(s->shout, SHOUT_AI_BITRATE, cfg_get_stream_bitrate())) { + log_error("%s: ai_bitrate: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_samplerate() && + SHOUTERR_SUCCESS != + shout_set_audio_info(s->shout, SHOUT_AI_SAMPLERATE, cfg_get_stream_samplerate())) { + log_error("%s: ai_samplerate: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (cfg_get_stream_channels() && + SHOUTERR_SUCCESS != + shout_set_audio_info(s->shout, SHOUT_AI_CHANNELS, cfg_get_stream_channels())) { + log_error("%s: ai_channels: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + if (SHOUTERR_SUCCESS != + shout_set_public(s->shout, (unsigned int)cfg_get_stream_server_public())) { + log_error("%s: public: %s", + s->name, shout_get_error(s->shout)); + return (-1); + } + + return (0); +} int stream_init(void) { + TAILQ_INIT(&streams); shout_init(); return (0); } @@ -33,5 +271,181 @@ stream_init(void) void stream_exit(void) { + struct stream *e; + + while (NULL != (e = TAILQ_FIRST(&streams))) { + TAILQ_REMOVE(&streams, e, entry); + xfree(e->name); + shout_free(e->shout); + xfree(e); + } shout_shutdown(); } + +struct stream * +stream_get(const char *name) +{ + struct stream *e; + + if (!name || !name[0]) { + log_alert("stream_get: empty name"); + exit(1); + } + + TAILQ_FOREACH(e, &streams, entry) { + if (0 == strcasecmp(e->name, name)) + return (e); + } + + e = xcalloc(1UL, sizeof(*e)); + e->name = xstrdup(name); + e->shout = shout_new(); + if (NULL == e->shout) { + log_syserr(ALERT, ENOMEM, "shout_new"); + exit(1); + } + + TAILQ_INSERT_TAIL(&streams, e, entry); + + return (e); +} + +int +stream_setup(struct stream *s) +{ + if (0 != _stream_cfg_server(s) || + 0 != _stream_cfg_tls(s) || + 0 != _stream_cfg_stream(s)) { + /* Reset handle on error */ + shout_free(s->shout); + s->shout = shout_new(); + if (NULL == s->shout) { + log_syserr(ALERT, ENOMEM, "shout_new"); + exit(1); + } + return (-1); + } + + return (0); +} + +int +stream_set_metadata(struct stream *s, metadata_t md, char **md_str) +{ + shout_metadata_t *shout_md = NULL; + char *songInfo; + const char *artist, *title; + int ret = SHOUTERR_SUCCESS; + + if (cfg_get_metadata_no_updates()) + return (SHOUTERR_SUCCESS); + + if (md == NULL) + return 1; + + if ((shout_md = shout_metadata_new()) == NULL) { + log_syserr(ALERT, ENOMEM, "shout_metadata_new"); + exit(1); + } + + artist = metadata_get_artist(md); + title = metadata_get_title(md); + + /* + * We can do this, because we know how libshout works. This adds + * "charset=UTF-8" to the HTTP metadata update request and has the + * desired effect of letting newer-than-2.3.1 versions of Icecast know + * which encoding we're using. + */ + if (shout_metadata_add(shout_md, "charset", "UTF-8") != SHOUTERR_SUCCESS) { + /* Assume SHOUTERR_MALLOC */ + log_syserr(ALERT, ENOMEM, "shout_metadata_add"); + exit(1); + } + + songInfo = stream_get_metadata_str(cfg_get_metadata_format_str(), md); + if (songInfo == NULL) { + if (artist[0] == '\0' && title[0] == '\0') + songInfo = xstrdup(metadata_get_string(md)); + else + songInfo = metadata_assemble_string(md); + if (artist[0] != '\0' && title[0] != '\0') { + if (shout_metadata_add(shout_md, "artist", artist) != SHOUTERR_SUCCESS) { + log_syserr(ALERT, ENOMEM, + "shout_metadata_add"); + exit(1); + } + if (shout_metadata_add(shout_md, "title", title) != SHOUTERR_SUCCESS) { + log_syserr(ALERT, ENOMEM, + "shout_metadata_add"); + exit(1); + } + } else { + if (shout_metadata_add(shout_md, "song", songInfo) != SHOUTERR_SUCCESS) { + log_syserr(ALERT, ENOMEM, + "shout_metadata_add"); + exit(1); + } + } + } else if (shout_metadata_add(shout_md, "song", songInfo) != SHOUTERR_SUCCESS) { + log_syserr(ALERT, ENOMEM, "shout_metadata_add"); + exit(1); + } + + if ((ret = shout_set_metadata(s->shout, shout_md)) != SHOUTERR_SUCCESS) + log_warning("shout_set_metadata: %s", shout_get_error(s->shout)); + + shout_metadata_free(shout_md); + + if (ret == SHOUTERR_SUCCESS) { + if (md_str != NULL && *md_str == NULL) + *md_str = xstrdup(songInfo); + } + + xfree(songInfo); + return (ret); +} + +char * +stream_get_metadata_str(const char *format, metadata_t mdata) +{ + char *tmp, *str; + + if (format == NULL) + return (NULL); + + str = xstrdup(format); + + if (strstr(format, PLACEHOLDER_ARTIST) != NULL) { + tmp = replaceString(str, PLACEHOLDER_ARTIST, + metadata_get_artist(mdata)); + xfree(str); + str = tmp; + } + if (strstr(format, PLACEHOLDER_TITLE) != NULL) { + tmp = replaceString(str, PLACEHOLDER_TITLE, + metadata_get_title(mdata)); + xfree(str); + str = tmp; + } + if (strstr(format, PLACEHOLDER_STRING) != NULL) { + tmp = replaceString(str, PLACEHOLDER_STRING, + metadata_get_string(mdata)); + xfree(str); + str = tmp; + } + if (strstr(format, PLACEHOLDER_TRACK) != NULL) { + tmp = replaceString(str, PLACEHOLDER_TRACK, + metadata_get_filename(mdata)); + xfree(str); + str = tmp; + } + + return (str); +} + +shout_t * +stream_get_shout(struct stream *s) +{ + return (s->shout); +} diff --git a/src/stream.h b/src/stream.h index 2cc1676..f54c429 100644 --- a/src/stream.h +++ b/src/stream.h @@ -17,7 +17,24 @@ #ifndef __STREAM_H__ #define __STREAM_H__ +#include + +#include "metadata.h" + +#define STREAM_DEFAULT "default" + +typedef struct stream * stream_t; + int stream_init(void); void stream_exit(void); +stream_t + stream_get(const char *); +int stream_setup(stream_t); +int stream_set_metadata(stream_t, metadata_t, char **); +char * stream_get_metadata_str(const char *, metadata_t); + +shout_t * + stream_get_shout(stream_t); + #endif /* __STREAM_H__ */ diff --git a/src/util.c b/src/util.c index 57ed1c2..63b67a1 100644 --- a/src/util.c +++ b/src/util.c @@ -22,14 +22,18 @@ # include "config.h" #endif -#include "ezstream.h" +#include "compat.h" +#include +#include #ifdef HAVE_LANGINFO_H # include #endif #ifdef HAVE_LOCALE_H # include #endif +#include +#include #ifdef HAVE_ICONV # include @@ -81,152 +85,6 @@ strrcasecmp(const char *s, const char *sub) return (ret); } -shout_t * -stream_setup(void) -{ - shout_t *shout; - - if ((shout = shout_new()) == NULL) { - log_syserr(ERROR, ENOMEM, "shout_new"); - return (NULL); - } - - switch (cfg_get_server_protocol()) { - case CFG_PROTO_HTTP: - if (SHOUTERR_SUCCESS != - shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP)) { - log_error("shout_set_protocol: %s", - shout_get_error(shout)); - goto error; - } - break; - default: - log_error("unsupported protocol: %s", - cfg_get_server_protocol_str()); - goto error; - } - if (SHOUTERR_SUCCESS != - shout_set_host(shout, cfg_get_server_hostname())) { - log_error("shout_set_host: %s", shout_get_error(shout)); - goto error; - } - if (SHOUTERR_SUCCESS != - shout_set_port(shout, (unsigned short)cfg_get_server_port())) { - log_error("shout_set_port: %s", shout_get_error(shout)); - goto error; - } - if (SHOUTERR_SUCCESS != - shout_set_user(shout, cfg_get_server_user())) { - log_error("shout_set_user: %s", shout_get_error(shout)); - goto error; - } - if (SHOUTERR_SUCCESS != - shout_set_password(shout, cfg_get_server_password())) { - log_error("shout_set_password: %s", shout_get_error(shout)); - goto error; - } - - if (SHOUTERR_SUCCESS != - shout_set_mount(shout, cfg_get_stream_mountpoint())) { - log_error("shout_set_mount: %s", shout_get_error(shout)); - goto error; - } - - switch (cfg_get_stream_format()) { - case CFG_STREAM_VORBIS: - case CFG_STREAM_THEORA: - if (SHOUTERR_SUCCESS != - shout_set_format(shout, SHOUT_FORMAT_OGG)) { - log_error("shout_set_format: OGG: %s", - shout_get_error(shout)); - goto error; - } - break; - case CFG_STREAM_MP3: - if (SHOUTERR_SUCCESS != - shout_set_format(shout, SHOUT_FORMAT_MP3)) { - log_error("shout_set_format: MP3: %s", - shout_get_error(shout)); - goto error; - } - break; - default: - log_error("unsupported stream format: %s", - cfg_get_stream_format_str()); - goto error; - } - - if (cfg_get_stream_name() && - SHOUTERR_SUCCESS != - shout_set_name(shout, cfg_get_stream_name())) { - log_error("shout_set_name: %s", shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_url() && - SHOUTERR_SUCCESS != - shout_set_url(shout, cfg_get_stream_url())) { - log_error("shout_set_url: %s", shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_genre() && - SHOUTERR_SUCCESS != - shout_set_genre(shout, cfg_get_stream_genre())) { - log_error("shout_set_genre: %s", shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_description() && - SHOUTERR_SUCCESS != - shout_set_description(shout, cfg_get_stream_description())) { - log_error("shout_set_description: %s", - shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_quality() && - SHOUTERR_SUCCESS != - shout_set_audio_info(shout, SHOUT_AI_QUALITY, - cfg_get_stream_quality())) { - log_error("shout_set_audio_info: QUALITY: %s", - shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_bitrate() && - SHOUTERR_SUCCESS != - shout_set_audio_info(shout, SHOUT_AI_BITRATE, - cfg_get_stream_bitrate())) { - log_error("shout_set_audio_info: BITRATE: %s", - shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_samplerate() && - SHOUTERR_SUCCESS != - shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, - cfg_get_stream_samplerate())) { - log_error("shout_set_audio_info: SAMPLERATE: %s", - shout_get_error(shout)); - goto error; - } - if (cfg_get_stream_channels() && - SHOUTERR_SUCCESS != - shout_set_audio_info(shout, SHOUT_AI_CHANNELS, - cfg_get_stream_channels())) { - log_error("shout_set_audio_info: CHANNELS: %s", - shout_get_error(shout)); - goto error; - } - - if (SHOUTERR_SUCCESS != - shout_set_public(shout, (unsigned int)cfg_get_stream_server_public())) { - log_error("shout_set_public: %s", shout_get_error(shout)); - goto error; - } - - return (shout); - -error: - shout_free(shout); - return (NULL); -} - char * CHARtoUTF8(const char *in_str, int mode) { @@ -358,3 +216,66 @@ iconvert(const char *in_str, const char *from, const char *to, int mode) return (xstrdup(in_str)); #endif /* HAVE_ICONV */ } + +char * +replaceString(const char *source, const char *from, const char *to) +{ + char *to_quoted, *dest; + size_t dest_size; + const char *p1, *p2; + + to_quoted = shellQuote(to); + dest_size = strlen(source) + strlen(to_quoted) + 1; + dest = xcalloc(dest_size, sizeof(char)); + + p1 = source; + p2 = strstr(p1, from); + if (p2 != NULL) { + strncat(dest, p1, (size_t)(p2 - p1)); + strlcat(dest, to_quoted, dest_size); + p1 = p2 + strlen(from); + } + strlcat(dest, p1, dest_size); + + xfree(to_quoted); + + return (dest); +} + +#define SHELLQUOTE_INLEN_MAX 8191UL + +char * +shellQuote(const char *in) +{ + char *out, *out_p; + size_t out_len; + const char *in_p; + + out_len = (strlen(in) > SHELLQUOTE_INLEN_MAX) + ? SHELLQUOTE_INLEN_MAX + : strlen(in); + out_len = out_len * 2 + 2; + out = xcalloc(out_len + 1, sizeof(char)); + + out_p = out; + in_p = in; + + *out_p++ = '\''; + out_len--; + while (*in_p && out_len > 2) { + switch (*in_p) { + case '\'': + case '\\': + *out_p++ = '\\'; + out_len--; + break; + default: + break; + } + *out_p++ = *in_p++; + out_len--; + } + *out_p++ = '\''; + + return (out); +} diff --git a/src/util.h b/src/util.h index a8c1e4c..fef6323 100644 --- a/src/util.h +++ b/src/util.h @@ -16,16 +16,15 @@ #ifndef __UTIL_H__ #define __UTIL_H__ -#include - #define ICONV_REPLACE 0 #define ICONV_TRANSLIT 1 #define ICONV_IGNORE 2 int strrcmp(const char *, const char *); int strrcasecmp(const char *, const char *); -shout_t * stream_setup(void); char * CHARtoUTF8(const char *, int); char * UTF8toCHAR(const char *, int); +char * replaceString(const char *, const char *, const char *); +char * shellQuote(const char *); #endif /* __UTIL_H__ */