From 80078cce1d022d48e39fc9f729ed675d6d4aba48 Mon Sep 17 00:00:00 2001 From: oddsock Date: Thu, 11 Nov 2004 15:47:33 +0000 Subject: [PATCH] * support for the Shoutcast DSP (yay!). You can now use the Shoutcast DSP as a source client. The connection protocol is a bit odd, and we had to handle it separately, and thus we've added a new config option () that is set at the listener port level. * support for NSV (and the nsvscsrc source client). After adding support for the connection protocol of the shoutcast DSP, adding NSV was just a simple of a few special handling cases. * removed all traces of the earlier attempt at the shoutcast DSP connection protocol * Due to the growing complexity of the config files, I've also created a few alternate config files, namely one for a "shoutcast compat" setup as well as a "minimal" one for quick basic configurations. svn path=/icecast/trunk/icecast/; revision=8191 --- conf/Makefile.am | 12 ++- doc/icecast2_config_file.html | 9 ++ src/admin.c | 121 +++++++++++++++++++++++--- src/cfgfile.c | 6 ++ src/cfgfile.h | 1 + src/connection.c | 156 ++++++++++++++++++++++++++++------ src/format.c | 13 +++ src/format.h | 1 + src/slave.c | 2 +- src/source.c | 12 ++- src/source.h | 1 + src/util.c | 17 +++- src/util.h | 5 +- src/yp.c | 14 ++- web/status.xsl | 9 +- 15 files changed, 326 insertions(+), 53 deletions(-) diff --git a/conf/Makefile.am b/conf/Makefile.am index 3d4bec06..6e9dd98a 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -2,11 +2,11 @@ AUTOMAKE_OPTIONS = foreign -EXTRA_DIST = icecast.xml.in -DISTCLEANFILES = icecast.xml.dist +EXTRA_DIST = icecast.xml.in icecast_minimal.xml.in icecast_shoutcast_compat.xml.in +DISTCLEANFILES = icecast.xml.dist icecast_minimal.xml.dist icecast_shoutcast_compat.xml.dist docdir = $(datadir)/$(PACKAGE)/doc -doc_DATA = icecast.xml.dist +doc_DATA = icecast.xml.dist icecast_minimal.xml.dist icecast_shoutcast_compat.xml.dist install-data-hook: $(mkinstalldirs) $(DESTDIR)$(sysconfdir) @@ -20,6 +20,12 @@ edit = sed -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ icecast.xml.dist: $(srcdir)/icecast.xml.in $(edit) $(srcdir)/icecast.xml.in > icecast.xml.dist +icecast_minimal.xml.dist: $(srcdir)/icecast_minimal.xml.in + $(edit) $(srcdir)/icecast_minimal.xml.in > icecast_minimal.xml.dist + +icecast_shoutcast_compat.xml.dist: $(srcdir)/icecast_shoutcast_compat.xml.in + $(edit) $(srcdir)/icecast_shoutcast_compat.xml.in > icecast_shoutcast_compat.xml.dist + debug: $(MAKE) all CFLAGS="@DEBUG@" diff --git a/doc/icecast2_config_file.html b/doc/icecast2_config_file.html index 50c2483d..e40418e4 100644 --- a/doc/icecast2_config_file.html +++ b/doc/icecast2_config_file.html @@ -157,6 +157,11 @@ The URL which icecast2 uses to communicate with the Directory server. The value <port>8000</port> <bind-address>127.0.0.1</bind-address> </listen-socket> + <listen-socket> + <port>8001</port> + <bind-address>127.0.0.1</bind-address> + <shoutcast-compat>1</shoutcast-compat> + </listen-socket> <fileserve>1</fileserve> @@ -170,6 +175,10 @@ The TCP port that will be used to accept client connections.
And option IP address that can be used to bind to a specific network card. If not supplied, then <hostname> will be used.
+

shoutcast-compat

+
+This optional flag will indicate that this port will operate in 'shoutcast-compatibility' mode. Due to major differences in the source client connection protocol, if you wish to use any of the shoutcast DJ tools, you will need to configure at least one socket as shoutcast-compatible. Note that when in this mode, only source clients (and specifically shoutcast source clients) will be able to attach to this port. All listeners may connect to any of the ports defined without this flag. Also, for proper Shoutcast DSP compatibility, you must define a listen socket with a port one less than the one defined as 'shoutcast-compat'. This means if you define 8001 as shoutcast-compat, then you will need to define a listen port of 8000 and it must not also be defined as shoutcast-compat. See the example config file is the distribution for more info. +

fileserve

This flag turns on the icecast2 fileserver from which static files can be served. All files are served relative to the path specified in the <paths><webroot> configuration setting. diff --git a/src/admin.c b/src/admin.c index 773e8b07..589a99c8 100644 --- a/src/admin.c +++ b/src/admin.c @@ -52,6 +52,7 @@ #define COMMAND_RAW_SHOW_LISTENERS 3 #define COMMAND_RAW_MOVE_CLIENTS 4 #define COMMAND_RAW_MANAGEAUTH 5 +#define COMMAND_SHOUTCAST_METADATA_UPDATE 6 #define COMMAND_TRANSFORMED_FALLBACK 50 #define COMMAND_TRANSFORMED_SHOW_LISTENERS 53 @@ -78,6 +79,7 @@ #define FALLBACK_RAW_REQUEST "fallbacks" #define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl" +#define SHOUTCAST_METADATA_REQUEST "admin.cgi" #define METADATA_REQUEST "metadata" #define LISTCLIENTS_RAW_REQUEST "listclients" #define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl" @@ -112,6 +114,8 @@ int admin_get_command(char *command) return COMMAND_TRANSFORMED_FALLBACK; else if(!strcmp(command, METADATA_REQUEST)) return COMMAND_METADATA_UPDATE; + else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST)) + return COMMAND_SHOUTCAST_METADATA_UPDATE; else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST)) return COMMAND_RAW_SHOW_LISTENERS; else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST)) @@ -158,6 +162,7 @@ int admin_get_command(char *command) static void command_fallback(client_t *client, source_t *source, int response); static void command_metadata(client_t *client, source_t *source); +static void command_shoutcast_metadata(client_t *client, source_t *source); static void command_show_listeners(client_t *client, source_t *source, int response); static void command_move_clients(client_t *client, source_t *source, @@ -269,13 +274,20 @@ void admin_handle_request(client_t *client, char *uri) int command; int noauth = 0; - if(strncmp("/admin/", uri, 7)) { + DEBUG1("Admin request (%s)", uri); + if (!((strcmp(uri, "/admin.cgi") == 0) || + (strncmp("/admin/", uri, 7) == 0))) { ERROR0("Internal error: admin request isn't"); client_send_401(client); return; } - command_string = uri + 7; + if (strcmp(uri, "/admin.cgi") == 0) { + command_string = uri + 1; + } + else { + command_string = uri + 7; + } DEBUG1("Got command (%s)", command_string); command = admin_get_command(command_string); @@ -289,6 +301,31 @@ void admin_handle_request(client_t *client, char *uri) mount = httpp_get_query_param(client->parser, "mount"); + if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) { + source_t *source; + + mount = "/"; + noauth = 1; + avl_tree_rlock(global.source_tree); + source = source_find_mount_raw(mount); + if (source == NULL) { + WARN2("Admin command %s on non-existent source %s", + command_string, mount); + avl_tree_unlock(global.source_tree); + client_send_400(client, "Mount / does not exist"); + return; + } + else { + if (source->shoutcast_compat == 0) { + ERROR0("Illegal call to change metadata, source not shoutcast compatible"); + avl_tree_unlock (global.source_tree); + client_send_400 (client, "Illegal metadata call"); + return; + } + } + avl_tree_unlock(global.source_tree); + } + if(mount != NULL) { source_t *source; @@ -319,13 +356,15 @@ void admin_handle_request(client_t *client, char *uri) } else { - if (source->running == 0) - { - INFO2("Received admin command %s on unavailable mount \"%s\"", - command_string, mount); - avl_tree_unlock (global.source_tree); - client_send_400 (client, "Source is not available"); - return; + if (!source->shoutcast_compat) { + if (source->running == 0) + { + INFO2("Received admin command %s on unavailable mount \"%s\"", + command_string, mount); + avl_tree_unlock (global.source_tree); + client_send_400 (client, "Source is not available"); + return; + } } INFO2("Received admin command %s on mount \"%s\"", command_string, mount); @@ -403,6 +442,9 @@ static void admin_handle_mount_request(client_t *client, source_t *source, case COMMAND_METADATA_UPDATE: command_metadata(client, source); break; + case COMMAND_SHOUTCAST_METADATA_UPDATE: + command_shoutcast_metadata(client, source); + break; case COMMAND_RAW_SHOW_LISTENERS: command_show_listeners(client, source, RAW); break; @@ -784,7 +826,8 @@ static void command_metadata(client_t *client, source_t *source) COMMAND_REQUIRE(client, "mode", action); COMMAND_REQUIRE(client, "song", value); - if (source->format->type != FORMAT_TYPE_MP3) + if ((source->format->type != FORMAT_TYPE_MP3) && + (source->format->type != FORMAT_TYPE_NSV)) { client_send_400 (client, "Not mp3, cannot update metadata"); return; @@ -811,6 +854,64 @@ static void command_metadata(client_t *client, source_t *source) html_success(client, "Metadata update successful"); } +static void command_shoutcast_metadata(client_t *client, source_t *source) +{ + char *action; + char *value; + char *source_pass; + char *config_source_pass; + ice_config_t *config; + mp3_state *state; + + DEBUG0("Got shoutcast metadata update request"); + + COMMAND_REQUIRE(client, "mode", action); + COMMAND_REQUIRE(client, "song", value); + COMMAND_REQUIRE(client, "pass", source_pass); + + config = config_get_config(); + config_source_pass = strdup(config->source_password); + config_release_config(); + + if ((source->format->type != FORMAT_TYPE_MP3) && + (source->format->type != FORMAT_TYPE_NSV)) + { + client_send_400 (client, "Not mp3 or NSV, cannot update metadata"); + return; + } + + if (strcmp (action, "updinfo") != 0) + { + client_send_400 (client, "No such action"); + return; + } + + if (strcmp(source_pass, config_source_pass) != 0) + { + ERROR0("Invalid source password specified, metadata not updated"); + client_send_400 (client, "Invalid source password"); + return; + } + + if (config_source_pass) { + free(config_source_pass); + } + + state = source->format->_state; + + mp3_set_tag (source->format, "title", value); + + DEBUG2("Metadata on mountpoint %s changed to \"%s\"", + source->mount, value); + stats_event(source->mount, "title", value); + + /* If we get an update on the mountpoint, force a + yp touch */ + yp_touch (source->mount); + + html_success(client, "Metadata update successful"); +} + static void command_stats(client_t *client, int response) { xmlDocPtr doc; diff --git a/src/cfgfile.c b/src/cfgfile.c index 3f6c90eb..81eddd1d 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -321,6 +321,7 @@ static void _set_defaults(ice_config_t *configuration) configuration->port = 0; configuration->listeners[0].port = 0; configuration->listeners[0].bind_address = NULL; + configuration->listeners[0].shoutcast_compat = 0; configuration->master_server = NULL; configuration->master_server_port = 0; configuration->master_update_interval = CONFIG_MASTER_UPDATE_INTERVAL; @@ -667,6 +668,11 @@ static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, listener->port = atoi(tmp); if(tmp) xmlFree(tmp); } + else if (strcmp(node->name, "shoutcast-compat") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + listener->shoutcast_compat = atoi(tmp); + if(tmp) xmlFree(tmp); + } else if (strcmp(node->name, "bind-address") == 0) { listener->bind_address = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); diff --git a/src/cfgfile.h b/src/cfgfile.h index 28ead446..d3d80676 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -75,6 +75,7 @@ typedef struct _aliases { typedef struct { int port; char *bind_address; + int shoutcast_compat; } listener_t; typedef struct ice_config_tag diff --git a/src/connection.c b/src/connection.c index dbc0e038..36614c7c 100644 --- a/src/connection.c +++ b/src/connection.c @@ -65,6 +65,23 @@ #define CATMODULE "connection" +/* Two different major types of source authentication. + Shoutcast style is used only by the Shoutcast DSP + and is a crazy version of HTTP. It looks like : + Source Client -> Connects to port + 1 + Source Client -> sends encoder password (plaintext)\r\n + Icecast -> reads encoder password, if ok, sends OK2\r\n, else disconnects + Source Client -> reads OK2\r\n, then sends http-type request headers + that contain the stream details (icy-name, etc..) + Icecast -> reads headers, stores them + Source Client -> starts sending MP3 data + Source Client -> periodically updates metadata via admin.cgi call + + Icecast auth style uses HTTP and Basic Authorization. +*/ +#define SHOUTCAST_SOURCE_AUTH 1 +#define ICECAST_SOURCE_AUTH 0 + typedef struct con_queue_tag { connection_t *con; struct con_queue_tag *next; @@ -664,7 +681,7 @@ int connection_check_source_pass(http_parser_t *parser, char *mount) static void _handle_source_request(connection_t *con, - http_parser_t *parser, char *uri) + http_parser_t *parser, char *uri, int auth_style) { client_t *client; source_t *source; @@ -680,18 +697,23 @@ static void _handle_source_request(connection_t *con, return; } - if (!connection_check_source_pass(parser, uri)) { - /* We commonly get this if the source client is using the wrong - * protocol: attempt to diagnose this and return an error - */ - /* TODO: Do what the above comment says */ - INFO1("Source (%s) attempted to login with invalid or missing password", uri); - client_send_401(client); - return; + if (auth_style == ICECAST_SOURCE_AUTH) { + if (!connection_check_source_pass(parser, uri)) { + /* We commonly get this if the source client is using the wrong + * protocol: attempt to diagnose this and return an error + */ + /* TODO: Do what the above comment says */ + INFO1("Source (%s) attempted to login with invalid or missing password", uri); + client_send_401(client); + return; + } } source = source_reserve (uri); if (source) { + if (auth_style == SHOUTCAST_SOURCE_AUTH) { + source->shoutcast_compat = 1; + } source->client = client; source->parser = parser; source->con = con; @@ -796,7 +818,8 @@ static void _handle_get_request(connection_t *con, stats_event_inc(NULL, "client_connections"); /* Dispatch all admin requests */ - if (strncmp(uri, "/admin/", 7) == 0) { + if ((strcmp(uri, "/admin.cgi") == 0) || + (strncmp(uri, "/admin/", 7) == 0)) { admin_handle_request(client, uri); if (uri != passed_uri) free (uri); return; @@ -965,6 +988,74 @@ static void _handle_get_request(connection_t *con, if (uri != passed_uri) free (uri); } +void _handle_shoutcast_compatible(connection_t *con, char *source_password) { + char shoutcast_password[256]; + char shoutcast_source[256]; + char *http_compliant; + int http_compliant_len = 0; + char header[4096]; + http_parser_t *parser; + + memset(shoutcast_password, 0, sizeof (shoutcast_password)); + /* Step one of shoutcast auth protocol, read encoder password (1 line) */ + if (util_read_header(con->sock, shoutcast_password, + sizeof (shoutcast_password), + READ_LINE) == 0) { + /* either we didn't get a complete line, or we timed out */ + connection_close(con); + return; + } + /* Get rid of trailing \n */ + shoutcast_password[strlen(shoutcast_password)-1] = '\000'; + if (strcmp(shoutcast_password, source_password)) { + ERROR0("Invalid source password"); + connection_close(con); + return; + } + /* Step two of shoutcast auth protocol, send OK2. For those + interested, OK2 means it supports metadata updates via admin.cgi, + and the string "OK" can also be sent, but will indicate to the + shoutcast source client to not send metadata updates. + I believe icecast 1.x used to send OK. */ + sock_write(con->sock, "%s\r\n", "OK2"); + + memset(header, 0, sizeof (header)); + /* Step three of shoutcast auth protocol, read HTTP-style + request headers and process them.*/ + if (util_read_header(con->sock, header, sizeof (header), + READ_ENTIRE_HEADER) == 0) { + /* either we didn't get a complete header, or we timed out */ + connection_close(con); + return; + } + /* Here we create a valid HTTP request based of the information + that was passed in via the non-HTTP style protocol above. This + means we can use some of our existing code to handle this case */ + memset(shoutcast_source, 0, sizeof (shoutcast_source)); + strcpy(shoutcast_source, "SOURCE / HTTP/1.0\r\n"); + http_compliant_len = strlen(shoutcast_source) + + strlen(header) + 1; + http_compliant = (char *)calloc(1, http_compliant_len); + sprintf(http_compliant, "%s%s", shoutcast_source, + header); + parser = httpp_create_parser(); + httpp_initialize(parser, NULL); + if (httpp_parse(parser, http_compliant, + strlen(http_compliant))) { + _handle_source_request(con, parser, "/", SHOUTCAST_SOURCE_AUTH); + free(http_compliant); + return; + } + else { + ERROR0("Invalid source request"); + connection_close(con); + free(http_compliant); + httpp_destroy(parser); + return; + } + return; +} + static void *_handle_connection(void *arg) { char header[4096]; @@ -972,6 +1063,10 @@ static void *_handle_connection(void *arg) http_parser_t *parser; char *rawuri, *uri; client_t *client; + int i = 0; + int continue_flag = 0; + ice_config_t *config; + char *source_password; while (global.running == ICE_RUNNING) { @@ -996,9 +1091,30 @@ static void *_handle_connection(void *arg) sock_set_blocking(con->sock, SOCK_BLOCK); + continue_flag = 0; + /* Check for special shoutcast compatability processing */ + for(i = 0; i < MAX_LISTEN_SOCKETS; i++) { + if(global.serversock[i] == con->serversock) { + config = config_get_config(); + if (config->listeners[i].shoutcast_compat) { + source_password = strdup(config->source_password); + config_release_config(); + _handle_shoutcast_compatible(con, source_password); + free(source_password); + continue_flag = 1; + break; + } + config_release_config(); + } + } + if(continue_flag) { + continue; + } + /* fill header with the http header */ memset(header, 0, sizeof (header)); - if (util_read_header(con->sock, header, sizeof (header)) == 0) { + if (util_read_header(con->sock, header, sizeof (header), + READ_ENTIRE_HEADER) == 0) { /* either we didn't get a complete header, or we timed out */ connection_close(con); continue; @@ -1027,7 +1143,7 @@ static void *_handle_connection(void *arg) } if (parser->req_type == httpp_req_source) { - _handle_source_request(con, parser, uri); + _handle_source_request(con, parser, uri, ICECAST_SOURCE_AUTH); } else if (parser->req_type == httpp_req_stats) { _handle_stats_request(con, parser, uri); @@ -1044,22 +1160,6 @@ static void *_handle_connection(void *arg) free(uri); continue; } - else if(httpp_parse_icy(parser, header, strlen(header))) { - /* TODO: Map incoming icy connections to /icy_0, etc. */ - char mount[20]; - unsigned i = 0; - - strcpy(mount, "/"); - - avl_tree_rlock(global.source_tree); - while (source_find_mount (mount) != NULL) { - snprintf (mount, sizeof (mount), "/icy_%u", i++); - } - avl_tree_unlock(global.source_tree); - - _handle_source_request(con, parser, mount); - continue; - } else { ERROR0("HTTP request parsing failed"); connection_close(con); diff --git a/src/format.c b/src/format.c index 8280dc2c..d498a3af 100644 --- a/src/format.c +++ b/src/format.c @@ -40,6 +40,7 @@ #include "format_mp3.h" #include "logging.h" +#include "stats.h" #define CATMODULE "format" #ifdef WIN32 @@ -57,6 +58,8 @@ format_type_t format_get_type(char *contenttype) return FORMAT_TYPE_MP3; else if(strcmp(contenttype, "audio/x-mpeg") == 0) return FORMAT_TYPE_MP3; /* Relay-compatibility for some servers */ + else if(strcmp(contenttype, "video/nsv") == 0) + return FORMAT_TYPE_NSV; else return FORMAT_ERROR; } @@ -70,6 +73,9 @@ char *format_get_mimetype(format_type_t type) case FORMAT_TYPE_MP3: return "audio/mpeg"; break; + case FORMAT_TYPE_NSV: + return "video/nsv"; + break; default: return NULL; } @@ -86,9 +92,16 @@ int format_get_plugin(format_type_t type, source_t *source) case FORMAT_TYPE_MP3: ret = format_mp3_get_plugin (source); break; + case FORMAT_TYPE_NSV: + ret = format_mp3_get_plugin (source); + source->format->format_description = "NSV Video"; + source->format->type = FORMAT_TYPE_NSV; + break; default: break; } + stats_event (source->mount, "content-type", + format_get_mimetype(source->format->type)); return ret; } diff --git a/src/format.h b/src/format.h index ed55efc1..52d150ba 100644 --- a/src/format.h +++ b/src/format.h @@ -28,6 +28,7 @@ typedef enum _format_type_tag { FORMAT_TYPE_VORBIS, FORMAT_TYPE_MP3, + FORMAT_TYPE_NSV, FORMAT_ERROR /* No format, source not processable */ } format_type_t; diff --git a/src/slave.c b/src/slave.c index fc0ed7cf..ba2e85a4 100644 --- a/src/slave.c +++ b/src/slave.c @@ -169,7 +169,7 @@ static void *start_relay_stream (void *arg) "\r\n", relay->mount, relay->mp3metadata?"Icy-MetaData: 1\r\n":""); memset (header, 0, sizeof(header)); - if (util_read_header (con->sock, header, 4096) == 0) + if (util_read_header (con->sock, header, 4096, READ_ENTIRE_HEADER) == 0) { WARN0("Header read failed"); break; diff --git a/src/source.c b/src/source.c index cd60867e..42bcde36 100644 --- a/src/source.c +++ b/src/source.c @@ -239,6 +239,7 @@ void source_clear_source (source_t *source) source->queue_size_limit = 0; source->listeners = 0; source->no_mount = 0; + source->shoutcast_compat = 0; source->max_listeners = -1; source->yp_public = 0; util_dict_free (source->audio_info); @@ -484,15 +485,20 @@ static void source_init (source_t *source) char *listenurl, *str; int listen_url_size; char *s; + char *extra = ""; + + if (source->format->type == FORMAT_TYPE_NSV) { + extra = "?file=stream.nsv"; + } /* 6 for max size of port */ listen_url_size = strlen("http://") + strlen(config->hostname) + - strlen(":") + 6 + strlen(source->mount) + 1; + strlen(":") + 6 + strlen(source->mount) + strlen(extra) + 1; listenurl = malloc (listen_url_size); memset (listenurl, '\000', listen_url_size); - snprintf (listenurl, listen_url_size, "http://%s:%d%s", - config->hostname, config->port, source->mount); + snprintf (listenurl, listen_url_size, "http://%s:%d%s%s", + config->hostname, config->port, source->mount, extra); config_release_config(); do diff --git a/src/source.h b/src/source.h index 529bc8b4..59ba6802 100644 --- a/src/source.h +++ b/src/source.h @@ -54,6 +54,7 @@ typedef struct source_tag struct auth_tag *authenticator; int fallback_override; int no_mount; + int shoutcast_compat; /* per source burst handling for connecting clients */ unsigned int burst_size; /* trigger level for burst on connect */ diff --git a/src/util.c b/src/util.c index 86349d77..6e2db5c0 100644 --- a/src/util.c +++ b/src/util.c @@ -84,7 +84,7 @@ int util_timed_wait_for_fd(int fd, int timeout) #endif } -int util_read_header(int sock, char *buff, unsigned long len) +int util_read_header(int sock, char *buff, unsigned long len, int entire) { int read_bytes, ret; unsigned long pos; @@ -107,9 +107,18 @@ int util_read_header(int sock, char *buff, unsigned long len) if ((read_bytes = recv(sock, &c, 1, 0))) { if (c != '\r') buff[pos++] = c; - if ((pos > 1) && (buff[pos - 1] == '\n' && buff[pos - 2] == '\n')) { - ret = 1; - break; + if (entire) { + if ((pos > 1) && (buff[pos - 1] == '\n' && + buff[pos - 2] == '\n')) { + ret = 1; + break; + } + } + else { + if ((pos > 1) && (buff[pos - 1] == '\n')) { + ret = 1; + break; + } } } } else { diff --git a/src/util.h b/src/util.h index 1fd5d002..882c4d73 100644 --- a/src/util.h +++ b/src/util.h @@ -16,8 +16,11 @@ #define XSLT_CONTENT 1 #define HTML_CONTENT 2 +#define READ_ENTIRE_HEADER 1 +#define READ_LINE 0 + int util_timed_wait_for_fd(int fd, int timeout); -int util_read_header(int sock, char *buff, unsigned long len); +int util_read_header(int sock, char *buff, unsigned long len, int entire); int util_check_valid_extension(char *uri); char *util_get_extension(char *path); char *util_get_path_from_uri(char *uri); diff --git a/src/yp.c b/src/yp.c index a6ec0d3d..7349e6f5 100644 --- a/src/yp.c +++ b/src/yp.c @@ -479,12 +479,22 @@ static ypdata_t *create_yp_entry (source_t *source) if (url == NULL) break; config = config_get_config(); - ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, source->mount); + if (source->format->type == FORMAT_TYPE_NSV) { + ret = snprintf (url, len, "http://%s:%d%s?stream.nsv", config->hostname, config->port, source->mount); + } + else { + ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, source->mount); + } if (ret >= (signed)len) { s = realloc (url, ++ret); if (s) url = s; - snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, source->mount); + if (source->format->type == FORMAT_TYPE_NSV) { + snprintf (url, ret, "http://%s:%d%s?file=stream.nsv", config->hostname, config->port, source->mount); + } + else { + snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, source->mount); + } } config_release_config(); yp->listen_url = util_url_escape (url); diff --git a/web/status.xsl b/web/status.xsl index 2fd3d5a2..a3cfdb47 100644 --- a/web/status.xsl +++ b/web/status.xsl @@ -54,7 +54,14 @@ Click to Listen -Click to Listen + + + Click to Listen + + + Click to Listen + +