diff --git a/src/client.c b/src/client.c index 7672b959..4dedbc80 100644 --- a/src/client.c +++ b/src/client.c @@ -45,6 +45,16 @@ void client_destroy(client_t *client) free(client); } +void client_send_400(client_t *client, char *message) { + int bytes; + bytes = sock_write(client->con->sock, "HTTP/1.0 404 File Not Found\r\n" + "Content-Type: text/html\r\n\r\n" + "%s\r\n", message); + if(bytes > 0) client->con->sent_bytes = bytes; + client->respcode = 404; + client_destroy(client); +} + void client_send_404(client_t *client, char *message) { int bytes; diff --git a/src/client.h b/src/client.h index 0fd4c397..4ae65bf3 100644 --- a/src/client.h +++ b/src/client.h @@ -31,5 +31,6 @@ client_t *client_create(connection_t *con, http_parser_t *parser); void client_destroy(client_t *client); void client_send_404(client_t *client, char *message); void client_send_401(client_t *client); +void client_send_400(client_t *client, char *message); #endif /* __CLIENT_H__ */ diff --git a/src/connection.c b/src/connection.c index 217c2dc3..a4d79cae 100644 --- a/src/connection.c +++ b/src/connection.c @@ -36,6 +36,7 @@ #include "fserve.h" #include "source.h" #include "format.h" +#include "format_mp3.h" #define CATMODULE "connection" @@ -418,6 +419,65 @@ static int _check_source_pass(http_parser_t *parser) return ret; } +static void handle_metadata_request(client_t *client) +{ + source_t *source; + char *action; + char *mount; + char *value; + mp3_state *state; + int bytes; + + if(!_check_source_pass(client->parser)) { + INFO0("Metadata request with wrong or missing password"); + client_send_401(client); + return; + } + + action = httpp_get_query_param(client->parser, "mode"); + mount = httpp_get_query_param(client->parser, "mount"); + value = httpp_get_query_param(client->parser, "song"); + + if(value == NULL || action == NULL || mount == NULL) { + client_send_400(client, "Missing parameter"); + return; + } + + avl_tree_rlock(global.source_tree); + source = source_find_mount(mount); + avl_tree_unlock(global.source_tree); + + if(source == NULL) { + client_send_400(client, "No such mountpoint"); + return; + } + + if(source->format->type != FORMAT_TYPE_MP3) { + client_send_400(client, "Not mp3, cannot update metadata"); + return; + } + + if(strcmp(action, "updinfo") != 0) { + client_send_400(client, "No such action"); + return; + } + + state = source->format->_state; + thread_mutex_lock(&(state->lock)); + free(state->metadata); + state->metadata = strdup(value); + state->metadata_age++; + thread_mutex_unlock(&(state->lock)); + + DEBUG2("Metadata on mountpoint %s changed to \"%s\"", mount, value); + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" + "Update successful"); + if(bytes > 0) client->con->sent_bytes = bytes; + client_destroy(client); +} + static void _handle_source_request(connection_t *con, http_parser_t *parser, char *uri) { @@ -500,7 +560,7 @@ static void _handle_get_request(connection_t *con, ** aren't subject to the limits. */ /* TODO: add GUID-xxxxxx */ - if (strcmp(uri, "/stats.xml") == 0) { + if (strcmp(uri, "/admin/stats.xml") == 0) { if (!_check_source_pass(parser)) { INFO0("Request for stats.xml with incorrect or no password"); client_send_401(client); @@ -512,6 +572,12 @@ static void _handle_get_request(connection_t *con, return; } + if(strcmp(uri, "/admin/metadata") == 0) { + DEBUG0("Got metadata update request"); + handle_metadata_request(client); + return; + } + /* Here we are parsing the URI request to see ** if the extension is .xsl, if so, then process ** this request as an XSLT request diff --git a/src/httpp/httpp.c b/src/httpp/httpp.c index f4717902..db5d55f6 100644 --- a/src/httpp/httpp.c +++ b/src/httpp/httpp.c @@ -40,6 +40,7 @@ void httpp_initialize(http_parser_t *parser, http_varlist_t *defaults) parser->req_type = httpp_req_none; parser->uri = NULL; parser->vars = avl_tree_new(_compare_vars, NULL); + parser->queryvars = avl_tree_new(_compare_vars, NULL); /* now insert the default variables */ list = defaults; @@ -178,6 +179,100 @@ int httpp_parse_response(http_parser_t *parser, char *http_data, unsigned long l return 1; } +static int hex(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static char *url_escape(char *src) +{ + int len = strlen(src); + unsigned char *decoded; + int i; + char *dst; + int done = 0; + + decoded = calloc(1, len + 1); + + dst = decoded; + + for(i=0; i < len; i++) { + switch(src[i]) { + case '%': + if(i+2 >= len) { + free(decoded); + return NULL; + } + if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { + free(decoded); + return NULL; + } + + *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); + i+= 2; + break; + case '#': + done = 1; + break; + case 0: + free(decoded); + return NULL; + break; + default: + *dst++ = src[i]; + break; + } + if(done) + break; + } + + *dst = 0; /* null terminator */ + + return decoded; +} + +/** TODO: This is almost certainly buggy in some cases */ +static void parse_query(http_parser_t *parser, char *query) +{ + int len; + int i=0; + char *key = query; + char *val=NULL; + + if(!query || !*query) + return; + + len = strlen(query); + + while(iuri = strdup(uri); + } else parser->uri = NULL; @@ -335,7 +439,7 @@ char *httpp_getvar(http_parser_t *parser, char *name) http_var_t *found; var.name = name; - var.value = NULL; + var.value = NULL; if (avl_get_by_key(parser->vars, (void *)&var, (void **)&found) == 0) return found->value; @@ -343,6 +447,41 @@ char *httpp_getvar(http_parser_t *parser, char *name) return NULL; } +void httpp_set_query_param(http_parser_t *parser, char *name, char *value) +{ + http_var_t *var; + + if (name == NULL || value == NULL) + return; + + var = (http_var_t *)malloc(sizeof(http_var_t)); + if (var == NULL) return; + + var->name = strdup(name); + var->value = url_escape(value); + + if (httpp_get_query_param(parser, name) == NULL) { + avl_insert(parser->queryvars, (void *)var); + } else { + avl_delete(parser->queryvars, (void *)var, _free_vars); + avl_insert(parser->queryvars, (void *)var); + } +} + +char *httpp_get_query_param(http_parser_t *parser, char *name) +{ + http_var_t var; + http_var_t *found; + + var.name = name; + var.value = NULL; + + if (avl_get_by_key(parser->queryvars, (void *)&var, (void **)&found) == 0) + return found->value; + else + return NULL; +} + void httpp_clear(http_parser_t *parser) { parser->req_type = httpp_req_none; @@ -350,6 +489,7 @@ void httpp_clear(http_parser_t *parser) free(parser->uri); parser->uri = NULL; avl_tree_free(parser->vars, _free_vars); + avl_tree_free(parser->queryvars, _free_vars); parser->vars = NULL; } diff --git a/src/httpp/httpp.h b/src/httpp/httpp.h index cf011b5d..e34f26eb 100644 --- a/src/httpp/httpp.h +++ b/src/httpp/httpp.h @@ -33,6 +33,7 @@ typedef struct http_parser_tag { httpp_request_type_e req_type; char *uri; avl_tree *vars; + avl_tree *queryvars; } http_parser_t; http_parser_t *httpp_create_parser(void); @@ -41,6 +42,8 @@ int httpp_parse(http_parser_t *parser, char *http_data, unsigned long len); int httpp_parse_response(http_parser_t *parser, char *http_data, unsigned long len, char *uri); void httpp_setvar(http_parser_t *parser, char *name, char *value); char *httpp_getvar(http_parser_t *parser, char *name); +void httpp_set_query_param(http_parser_t *parser, char *name, char *value); +char *httpp_get_query_param(http_parser_t *parser, char *name); void httpp_destroy(http_parser_t *parser); void httpp_clear(http_parser_t *parser); diff --git a/src/util.c b/src/util.c index 7e165096..9e4a504a 100644 --- a/src/util.c +++ b/src/util.c @@ -208,50 +208,43 @@ char *util_get_path_from_normalised_uri(char *uri) { return fullpath; } -/* Get an absolute path (from the webroot dir) from a URI. Return NULL if the - * path contains 'disallowed' sequences like foo/../ (which could be used to - * escape from the webroot) or if it cannot be URI-decoded. - * Caller should free the path. - */ -char *util_normalise_uri(char *uri) { - int urilen = strlen(uri); - unsigned char *path; - char *dst; +char *util_url_escape(char *src) +{ + int len = strlen(src); + unsigned char *decoded; int i; + char *dst; int done = 0; - if(uri[0] != '/') - return NULL; + decoded = calloc(1, len + 1); - path = calloc(1, urilen + 1); + dst = decoded; - dst = path; - - for(i=0; i < urilen; i++) { - switch(uri[i]) { + for(i=0; i < len; i++) { + switch(src[i]) { case '%': - if(i+2 >= urilen) { - free(path); + if(i+2 >= len) { + free(decoded); return NULL; } - if(hex(uri[i+1]) == -1 || hex(uri[i+2]) == -1 ) { - free(path); + if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { + free(decoded); return NULL; } - *dst++ = hex(uri[i+1]) * 16 + hex(uri[i+2]); + *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); i+= 2; break; case '#': done = 1; break; case 0: - ERROR0("Fatal internal logic error in util_get_path_from_uri()"); - free(path); + ERROR0("Fatal internal logic error in util_url_escape()"); + free(decoded); return NULL; break; default: - *dst++ = uri[i]; + *dst++ = src[i]; break; } if(done) @@ -260,9 +253,30 @@ char *util_normalise_uri(char *uri) { *dst = 0; /* null terminator */ + return decoded; +} + +/* Get an absolute path (from the webroot dir) from a URI. Return NULL if the + * path contains 'disallowed' sequences like foo/../ (which could be used to + * escape from the webroot) or if it cannot be URI-decoded. + * Caller should free the path. + */ +char *util_normalise_uri(char *uri) { + char *path; + + if(uri[0] != '/') + return NULL; + + path = util_url_escape(uri); + + if(path == NULL) { + WARN1("Error decoding URI: %s\n", uri); + return NULL; + } + /* We now have a full URI-decoded path. Check it for allowability */ if(verify_path(path)) - return (char *)path; + return path; else { WARN1("Rejecting invalid path \"%s\"", path); free(path); diff --git a/src/util.h b/src/util.h index 53b4236e..b350882f 100644 --- a/src/util.h +++ b/src/util.h @@ -14,4 +14,6 @@ char *util_normalise_uri(char *uri); char *util_base64_encode(char *data); char *util_base64_decode(unsigned char *input); +char *util_url_escape(char *src); + #endif /* __UTIL_H__ */