diff --git a/src/httpd.c b/src/httpd.c index a740332e..0c99446a 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -43,7 +43,8 @@ # include #endif #include -#include +#include +#include #ifdef HAVE_LIBEVENT2_OLD # include # include @@ -453,6 +454,99 @@ stream_fail_cb(struct evhttp_connection *evcon, void *arg) } +void +httpd_uri_free(struct httpd_uri_parsed *parsed) +{ + if (!parsed) + return; + + free(parsed->uri_decoded); + free(parsed->path); + free(parsed->path_parts[0]); + + evhttp_clear_headers(&(parsed->ev_query)); + + if (parsed->ev_uri) + evhttp_uri_free(parsed->ev_uri); + + free(parsed); +} + +struct httpd_uri_parsed * +httpd_uri_parse(const char *uri) +{ + struct httpd_uri_parsed *parsed; + const char *path; + const char *query; + char *ptr; + int i; + int ret; + + CHECK_NULL(L_HTTPD, parsed = calloc(1, sizeof(struct httpd_uri_parsed))); + + parsed->uri = uri; + + parsed->ev_uri = evhttp_uri_parse_with_flags(parsed->uri, EVHTTP_URI_NONCONFORMANT); + if (!parsed->ev_uri) + { + DPRINTF(E_LOG, L_HTTPD, "Could not parse request: '%s'\n", parsed->uri); + goto error; + } + + parsed->uri_decoded = evhttp_uridecode(parsed->uri, 0, NULL); + if (!parsed->uri_decoded) + { + DPRINTF(E_LOG, L_HTTPD, "Could not URI decode request: '%s'\n", parsed->uri); + goto error; + } + + query = evhttp_uri_get_query(parsed->ev_uri); + if (query) + { + ret = evhttp_parse_query_str(query, &(parsed->ev_query)); + if (ret < 0) + { + DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in request: '%s'\n", query, parsed->uri); + goto error; + } + } + + path = evhttp_uri_get_path(parsed->ev_uri); + if (!path) + { + DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri); + return parsed; + } + + parsed->path = evhttp_uridecode(path, 0, NULL); + if (!parsed->path) + { + DPRINTF(E_LOG, L_HTTPD, "Could not URI decode path: '%s'\n", path); + goto error; + } + + CHECK_NULL(L_HTTPD, parsed->path_parts[0] = strdup(parsed->path)); + + strtok_r(parsed->path_parts[0], "/", &ptr); + for (i = 1; (i < sizeof(parsed->path_parts) / sizeof(parsed->path_parts[0])) && parsed->path_parts[i - 1]; i++) + { + parsed->path_parts[i] = strtok_r(NULL, "/", &ptr); + } + + if (!parsed->path_parts[0] || parsed->path_parts[i - 1] || (i < 2)) + { + DPRINTF(E_LOG, L_HTTPD, "URI path has too many/few components (%d): '%s'\n", (parsed->path_parts[0]) ? i : 0, parsed->path); + goto error; + } + + return parsed; + + error: + httpd_uri_free(parsed); + return NULL; +} + + /* Thread: httpd */ void httpd_stream_file(struct evhttp_request *req, int id) @@ -923,7 +1017,7 @@ redirect_to_admin(struct evhttp_request *req) /* Thread: httpd */ static void -redirect_to_index(struct evhttp_request *req, char *uri) +redirect_to_index(struct evhttp_request *req, const char *uri) { struct evkeyvalq *headers; char buf[256]; @@ -986,7 +1080,7 @@ httpd_admin_check_auth(struct evhttp_request *req) /* Thread: httpd */ static void -serve_file(struct evhttp_request *req, char *uri) +serve_file(struct evhttp_request *req, const char *uri) { char *ext; char path[PATH_MAX]; @@ -1179,9 +1273,12 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) { struct evkeyvalq *input_headers; struct evkeyvalq *output_headers; - const char *req_uri; - char *uri; - char *ptr; + struct httpd_uri_parsed *parsed; + const char *uri; + + // Clear the proxy request flag set by evhttp if the request URI was absolute. + // It has side-effects on Connection: keep-alive + req->flags &= ~EVHTTP_PROXY_REQUEST; // Did we get a CORS preflight request? input_headers = evhttp_request_get_input_headers(req); @@ -1203,66 +1300,55 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) return; } - req_uri = evhttp_request_get_uri(req); - if (!req_uri || strcmp(req_uri, "/") == 0) + uri = evhttp_request_get_uri(req); + if (!uri) { + DPRINTF(E_WARN, L_HTTPD, "No URI in request\n"); redirect_to_admin(req); - return; } - uri = strdup(req_uri); - ptr = strchr(uri, '?'); - if (ptr) + parsed = httpd_uri_parse(uri); + if (!parsed || !parsed->path || (strcmp(parsed->path, "/") == 0)) { - DPRINTF(E_SPAM, L_HTTPD, "Found query string\n"); - - *ptr = '\0'; - } - - ptr = uri; - uri = evhttp_decode_uri(uri); - free(ptr); - - /* Dispatch protocol-specific URIs */ - if (rsp_is_request(req, uri)) - { - rsp_request(req); - - goto out; - } - else if (daap_is_request(req, uri)) - { - daap_request(req); - - goto out; - } - else if (dacp_is_request(req, uri)) - { - dacp_request(req); - - goto out; - } - else if (jsonapi_is_request(req, uri)) - { - jsonapi_request(req); - - goto out; - } - else if (streaming_is_request(req, uri)) - { - streaming_request(req); - + redirect_to_admin(req); goto out; } - DPRINTF(E_DBG, L_HTTPD, "HTTP request: %s\n", uri); + /* Dispatch protocol-specific handlers */ + if (dacp_is_request(parsed->path)) + { + dacp_request(req, parsed); + goto out; + } + else if (daap_is_request(parsed->path)) + { + daap_request(req, parsed); + goto out; + } + else if (jsonapi_is_request(parsed->path)) + { + jsonapi_request(req, parsed); + goto out; + } + else if (streaming_is_request(parsed->path)) + { + streaming_request(req, parsed); + goto out; + } + else if (rsp_is_request(parsed->path)) + { + rsp_request(req, parsed); + goto out; + } + + DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri); /* Serve web interface files */ - serve_file(req, uri); + serve_file(req, parsed->path); out: - free(uri); + httpd_uri_free(parsed); } /* Thread: httpd */ @@ -1298,7 +1384,7 @@ exit_cb(int fd, short event, void *arg) httpd_exit = 1; } -char * +/*static char * httpd_fixup_uri(struct evhttp_request *req) { struct evkeyvalq *headers; @@ -1314,7 +1400,7 @@ httpd_fixup_uri(struct evhttp_request *req) if (!uri) return NULL; - /* No query string, nothing to do */ + // No query string, nothing to do q = strchr(uri, '?'); if (!q) return strdup(uri); @@ -1329,8 +1415,8 @@ httpd_fixup_uri(struct evhttp_request *req) && (strncmp(ua, "Roku", strlen("Roku")) != 0)) return strdup(uri); - /* Reencode + as %2B and space as + in the query, - which iTunes and Roku devices don't do */ + // Reencode + as %2B and space as + in the query, + // which iTunes and Roku devices don't do len = strlen(uri); u = q; @@ -1377,7 +1463,7 @@ httpd_fixup_uri(struct evhttp_request *req) *f = '\0'; return fixed; -} +}*/ static const char *http_reply_401 = "401 UnauthorizedAuthorization required"; diff --git a/src/httpd.h b/src/httpd.h index 6461e3a7..3bce64b9 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -4,6 +4,7 @@ #include #include +#include #include enum httpd_send_flags @@ -11,6 +12,43 @@ enum httpd_send_flags HTTPD_SEND_NO_GZIP = (1 << 0), }; +/* + * Contains a parsed version of the URI httpd got. The URI may have been + * complete: + * scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] + * or relative: + * [/path][?query][#fragment] + * + * We are interested in the path and the query, so they are disassembled to + * path_parts and ev_query. If the request is http://x:3689/foo/bar?key1=val1, + * then part_parts[1] is "foo", [2] is "bar" and the rest is null (the first + * element points to the copy of the path so it can be freed). + * + * The allocated strings are URI decoded. + */ +struct httpd_uri_parsed +{ + const char *uri; + struct evhttp_uri *ev_uri; + struct evkeyvalq ev_query; + char *uri_decoded; + char *path; + char *path_parts[7]; +}; + +/* + * Helper to free the parsed uri struct + */ +void +httpd_uri_free(struct httpd_uri_parsed *parsed); + +/* + * Parse an URI into the struct + */ +struct httpd_uri_parsed * +httpd_uri_parse(const char *uri); + + void httpd_stream_file(struct evhttp_request *req, int id); @@ -53,9 +91,6 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc void httpd_send_error(struct evhttp_request *req, int error, const char *reason); -char * -httpd_fixup_uri(struct evhttp_request *req); - int httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 33547c3a..09f029ed 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -42,22 +42,19 @@ #include #include +#include + +#include "httpd_daap.h" #include "logger.h" #include "db.h" #include "conffile.h" #include "misc.h" -#include "httpd.h" #include "transcode.h" #include "artwork.h" -#include "httpd_daap.h" #include "daap_query.h" #include "dmap_common.h" #include "cache.h" -#include -#include -#include -#include /* httpd event base, from httpd.c */ extern struct event_base *evbase_httpd; @@ -105,20 +102,16 @@ struct daap_session { // Will be filled out by daap_request_parse() struct daap_request { - // The request URI - const char *source_uri; - // http request struct - null when the request is from daap_reply_build(), i.e. the cache - struct evhttp_request *req; - // If the request is http://x:3689/foo/bar?key1=val1, then path is "foo/bar" - const char *path; - // If the request is http://x:3689/foo/bar?key1=val1, then part_part[1] is "foo", [2] is "bar" and the rest is null - char *path_parts[7]; - // A the request query was ?key1=val1&key2=val2 then this will be parsed into this struct - struct evkeyvalq query; // User-agent const char *user_agent; // Was the request made by a remote, e.g. Apple Remote bool is_remote; + // The parsed request URI given to us by httpd.c + struct httpd_uri_parsed *uri_parsed; + // Shortcut to &uri_parsed->ev_query + struct evkeyvalq *query; + // http request struct - null when the request is from daap_reply_build(), i.e. the cache + struct evhttp_request *req; // Pointer to the session matching the request struct daap_session *session; // A pointer to the handler that will process the request @@ -507,112 +500,32 @@ daap_sort_finalize(struct sort_ctx *ctx) /* ----------------------------- OTHER HELPERS ------------------------------ */ -/* Remotes are clients that will issue DACP commands. For these clients we will - * do the playback, and we will not stream to them. This is a crude function to - * identify them, so we can give them appropriate treatment. - */ -static bool -is_remote(const char *user_agent) -{ - if (!user_agent) - return false; - if (strcasestr(user_agent, "remote")) - return true; - if (strstr(user_agent, "Retune")) - return true; - - return false; -} - -static char * -uri_relative(char *uri, const char *protocol) -{ - char *ret; - - if (strncmp(uri, protocol, strlen(protocol)) != 0) - return NULL; - - ret = strchr(uri + strlen(protocol), '/'); - if (!ret) - { - DPRINTF(E_LOG, L_DAAP, "Malformed DAAP Request URI '%s'\n", uri); - return NULL; - } - - return ret; -} - -static char * -daap_fix_request_uri(struct evhttp_request *req, char *uri) -{ - char *ret; - - /* iTunes 9 gives us an absolute request-uri like - * daap://10.1.1.20:3689/server-info - * iTunes 12.1 gives us an absolute request-uri for streaming like - * http://10.1.1.20:3689/databases/1/items/1.mp3 - */ - - if ( (ret = uri_relative(uri, "daap://")) || (ret = uri_relative(uri, "http://")) ) - { - /* Clear the proxy request flag set by evhttp - * due to the request URI being absolute. - * It has side-effects on Connection: keep-alive - */ - req->flags &= ~EVHTTP_PROXY_REQUEST; - return ret; - } - - return uri; -} - -/* Returns eg /databases/1/containers from /databases/1/containers?meta=dmap.item... */ -static char * -extract_uri(char *full_uri) -{ - char *uri; - char *ptr; - - ptr = strchr(full_uri, '?'); - if (ptr) - *ptr = '\0'; - - uri = strdup(full_uri); - - if (ptr) - *ptr = '?'; - - if (!uri) - return NULL; - - ptr = uri; - uri = evhttp_decode_uri(uri); - free(ptr); - - return uri; -} - /* We try not to return items that the client cannot play (like Spotify and * internet streams in iTunes), or which are inappropriate (like internet streams - * in the album tab of remotes) + * in the album tab of remotes). Note that the function must never append a + * filter if the SELECT is not from the files table. */ static void user_agent_filter(struct query_params *qp, struct daap_request *dreq) { char *filter; - if (!(qp->type == Q_ITEMS || (qp->type & Q_F_BROWSE))) - return; - if (dreq->is_remote) { - if (qp->filter) - filter = safe_asprintf("%s AND (f.data_kind <> %d)", qp->filter, DATA_KIND_HTTP); - else - filter = safe_asprintf("(f.data_kind <> %d)", DATA_KIND_HTTP); + // This makes sure 1) the SELECT is from files, 2) that the Remote query + // contained extended_media_kind:1, which characterise the queries we want + // to filter. TODO: Not a really nice way of doing this, but best I could + // think of. + if (!qp->filter || !strstr(qp->filter, "f.media_kind")) + return; + + filter = safe_asprintf("%s AND (f.data_kind <> %d)", qp->filter, DATA_KIND_HTTP); } else { + if (qp->type != Q_ITEMS) + return; + if (qp->filter) filter = safe_asprintf("%s AND (f.data_kind = %d)", qp->filter, DATA_KIND_FILE); else @@ -639,7 +552,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request memset(qp, 0, sizeof(struct query_params)); - param = evhttp_find_header(&dreq->query, "index"); + param = evhttp_find_header(dreq->query, "index"); if (param) { if (param[0] == '-') /* -n, last n entries */ @@ -685,7 +598,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request qp->idx_type = I_SUB; qp->sort = S_NONE; - param = evhttp_find_header(&dreq->query, "sort"); + param = evhttp_find_header(dreq->query, "sort"); if (param) { if (strcmp(param, "name") == 0) @@ -706,7 +619,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request if (sort_headers) { *sort_headers = 0; - param = evhttp_find_header(&dreq->query, "include-sort-headers"); + param = evhttp_find_header(dreq->query, "include-sort-headers"); if (param && (strcmp(param, "1") == 0)) { *sort_headers = 1; @@ -714,9 +627,9 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request } } - param = evhttp_find_header(&dreq->query, "query"); + param = evhttp_find_header(dreq->query, "query"); if (!param) - param = evhttp_find_header(&dreq->query, "filter"); + param = evhttp_find_header(dreq->query, "filter"); if (param) { @@ -832,14 +745,11 @@ daap_reply_send(struct evhttp_request *req, struct evbuffer *reply, enum daap_re } static struct daap_request * -daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp_request *req) +daap_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent) { struct daap_request *dreq; struct evkeyvalq *headers; - struct evhttp_uri *uri; - const char *query; const char *param; - char *ptr; int32_t id; int ret; int i; @@ -847,32 +757,18 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp CHECK_NULL(L_DAAP, dreq = calloc(1, sizeof(struct daap_request))); dreq->req = req; - dreq->source_uri = source_uri; + dreq->uri_parsed = uri_parsed; + dreq->query = &(uri_parsed->ev_query); if (user_agent) dreq->user_agent = user_agent; else if (req && (headers = evhttp_request_get_input_headers(req))) dreq->user_agent = evhttp_find_header(headers, "User-Agent"); - uri = evhttp_uri_parse_with_flags(source_uri, EVHTTP_URI_NONCONFORMANT); - if (!uri) - { - DPRINTF(E_LOG, L_DAAP, "Could not parse URI in DAAP request: '%s'\n", source_uri); - free(dreq); - return NULL; - } - - // Find a handler for the path (note, path includes the leading '/') - dreq->path = evhttp_uri_get_path(uri); - if (!dreq->path || dreq->path[0] == '\0') - { - DPRINTF(E_LOG, L_DAAP, "Missing path in the DAAP request: '%s'\n", source_uri); - goto error; - } - + // Find a handler for the path for (i = 0; daap_handlers[i].handler; i++) { - ret = regexec(&daap_handlers[i].preg, dreq->path, 0, NULL, 0); + ret = regexec(&daap_handlers[i].preg, uri_parsed->path, 0, NULL, 0); if (ret == 0) { dreq->handler = daap_handlers[i].handler; @@ -882,27 +778,18 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp if (!dreq->handler) { - DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", dreq->path, source_uri); - goto error; - } - - // Parse the query - query = evhttp_uri_get_query(uri); - ret = evhttp_parse_query_str(query, &dreq->query); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in DAAP request: '%s'\n", query, source_uri); + DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", uri_parsed->path, uri_parsed->uri); goto error; } // Check if we have a session - param = evhttp_find_header(&dreq->query, "session-id"); + param = evhttp_find_header(dreq->query, "session-id"); if (param) { ret = safe_atoi32(param, &id); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Invalid session id in DAAP request: '%s'\n", source_uri); + DPRINTF(E_LOG, L_DAAP, "Invalid session id in DAAP request: '%s'\n", uri_parsed->uri); goto error; } @@ -910,48 +797,17 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp } // Are we making a reply for a remote? - param = evhttp_find_header(&dreq->query, "pairing-guid"); + param = evhttp_find_header(dreq->query, "pairing-guid"); dreq->is_remote = (param || (dreq->session && dreq->session->is_remote)); - // Copy path and split it in parts - CHECK_NULL(L_DAAP, dreq->path_parts[0] = strdup(dreq->path)); - - strtok_r(dreq->path_parts[0], "/", &ptr); - for (i = 1; (i < sizeof(dreq->path_parts) / sizeof(dreq->path_parts[0])) && dreq->path_parts[i - 1]; i++) - { - dreq->path_parts[i] = strtok_r(NULL, "/", &ptr); - } - - if (!dreq->path_parts[0] || dreq->path_parts[i - 1] || (i < 2)) - { - DPRINTF(E_LOG, L_DAAP, "DAAP URI has too many/few components (%d): '%s'\n", (dreq->path_parts[0]) ? i : 0, dreq->path); - goto error; - } - - evhttp_uri_free(uri); return dreq; error: - evhttp_clear_headers(&dreq->query); - free(dreq->path_parts[0]); - evhttp_uri_free(uri); free(dreq); return NULL; } -static void -daap_request_free(struct daap_request *dreq) -{ - if (!dreq) - return; - - evhttp_clear_headers(&dreq->query); - free(dreq->path_parts[0]); - - free(dreq); -} - static int daap_request_authorize(struct daap_request *dreq) { @@ -962,12 +818,12 @@ daap_request_authorize(struct daap_request *dreq) if (cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode")) return 0; - param = evhttp_find_header(&dreq->query, "session-id"); + param = evhttp_find_header(dreq->query, "session-id"); if (param) { if (!dreq->session) { - DPRINTF(E_LOG, L_DAAP, "DAAP session not found: '%s'\n", dreq->source_uri); + DPRINTF(E_LOG, L_DAAP, "DAAP session not found: '%s'\n", dreq->uri_parsed->uri); return -1; } @@ -980,11 +836,11 @@ daap_request_authorize(struct daap_request *dreq) return 0; // If no valid session then we may need to authenticate - if ((strcmp(dreq->path, "/server-info") == 0) - || (strcmp(dreq->path, "/login") == 0) - || (strcmp(dreq->path, "/logout") == 0) - || (strcmp(dreq->path, "/content-codes") == 0) - || (strncmp(dreq->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0)) + if ((strcmp(dreq->uri_parsed->path, "/server-info") == 0) + || (strcmp(dreq->uri_parsed->path, "/login") == 0) + || (strcmp(dreq->uri_parsed->path, "/logout") == 0) + || (strcmp(dreq->uri_parsed->path, "/content-codes") == 0) + || (strncmp(dreq->uri_parsed->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0)) return 0; // No authentication DPRINTF(E_DBG, L_DAAP, "Checking authentication for library\n"); @@ -1155,7 +1011,7 @@ daap_reply_login(struct evbuffer *reply, struct daap_request *dreq) CHECK_ERR(L_DAAP, evbuffer_expand(reply, 32)); - is_remote = (param = evhttp_find_header(&dreq->query, "pairing-guid")); + is_remote = (param = evhttp_find_header(dreq->query, "pairing-guid")); if (param && !cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode")) { if (strlen(param) < 3) @@ -1186,7 +1042,7 @@ daap_reply_login(struct evbuffer *reply, struct daap_request *dreq) DPRINTF(E_INFO, L_DAAP, "Client (unknown user-agent) logging in\n"); } - param = evhttp_find_header(&dreq->query, "request-session-id"); + param = evhttp_find_header(dreq->query, "request-session-id"); if (param) { ret = safe_atoi32(param, &request_session_id); @@ -1241,7 +1097,7 @@ daap_reply_update(struct evbuffer *reply, struct daap_request *dreq) return DAAP_REPLY_NO_CONNECTION; } - param = evhttp_find_header(&dreq->query, "revision-number"); + param = evhttp_find_header(dreq->query, "revision-number"); if (!param) { DPRINTF(E_DBG, L_DAAP, "Missing revision-number in client update request\n"); @@ -1440,7 +1296,7 @@ daap_reply_songlist_generic(struct evbuffer *reply, struct daap_request *dreq, i CHECK_ERR(L_DAAP, evbuffer_expand(songlist, 4096)); CHECK_ERR(L_DAAP, evbuffer_expand(song, 512)); - param = evhttp_find_header(&dreq->query, "meta"); + param = evhttp_find_header(dreq->query, "meta"); if (!param) { DPRINTF(E_DBG, L_DAAP, "No meta parameter in query, using default\n"); @@ -1601,7 +1457,7 @@ daap_reply_plsonglist(struct evbuffer *reply, struct daap_request *dreq) int playlist; int ret; - ret = safe_atoi32(dreq->path_parts[3], &playlist); + ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &playlist); if (ret < 0) { dmap_error_make(reply, "apso", "Invalid playlist ID"); @@ -1638,7 +1494,7 @@ daap_reply_playlists(struct evbuffer *reply, struct daap_request *dreq) cfg_radiopl = cfg_getbool(cfg_getsec(cfg, "library"), "radio_playlists"); - ret = safe_atoi32(dreq->path_parts[1], &database); + ret = safe_atoi32(dreq->uri_parsed->path_parts[1], &database); if (ret < 0) { dmap_error_make(reply, "aply", "Invalid database ID"); @@ -1655,7 +1511,7 @@ daap_reply_playlists(struct evbuffer *reply, struct daap_request *dreq) CHECK_ERR(L_DAAP, evbuffer_expand(playlistlist, 1024)); CHECK_ERR(L_DAAP, evbuffer_expand(playlist, 128)); - param = evhttp_find_header(&dreq->query, "meta"); + param = evhttp_find_header(dreq->query, "meta"); if (!param) { DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n"); @@ -1853,7 +1709,7 @@ daap_reply_groups(struct evbuffer *reply, struct daap_request *dreq) int i; int ret; - param = evhttp_find_header(&dreq->query, "group-type"); + param = evhttp_find_header(dreq->query, "group-type"); if (strcmp(param, "artists") == 0) { // Request from Remote may have the form: @@ -1881,7 +1737,7 @@ daap_reply_groups(struct evbuffer *reply, struct daap_request *dreq) CHECK_ERR(L_DAAP, evbuffer_expand(grouplist, 1024)); CHECK_ERR(L_DAAP, evbuffer_expand(group, 128)); - param = evhttp_find_header(&dreq->query, "meta"); + param = evhttp_find_header(dreq->query, "meta"); if (!param) { DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n"); @@ -2060,25 +1916,25 @@ daap_reply_browse(struct evbuffer *reply, struct daap_request *dreq) int nitems; int ret; - if (strcmp(dreq->path_parts[3], "artists") == 0) + if (strcmp(dreq->uri_parsed->path_parts[3], "artists") == 0) { tag = "abar"; query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_ARTISTS); qp.sort = S_ARTIST; } - else if (strcmp(dreq->path_parts[3], "albums") == 0) + else if (strcmp(dreq->uri_parsed->path_parts[3], "albums") == 0) { tag = "abal"; query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_ALBUMS); qp.sort = S_ALBUM; } - else if (strcmp(dreq->path_parts[3], "genres") == 0) + else if (strcmp(dreq->uri_parsed->path_parts[3], "genres") == 0) { tag = "abgn"; query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_GENRES); qp.sort = S_GENRE; } - else if (strcmp(dreq->path_parts[3], "composers") == 0) + else if (strcmp(dreq->uri_parsed->path_parts[3], "composers") == 0) { tag = "abcp"; query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_COMPOSERS); @@ -2086,7 +1942,7 @@ daap_reply_browse(struct evbuffer *reply, struct daap_request *dreq) } else { - DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", dreq->path_parts[3]); + DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", dreq->uri_parsed->path_parts[3]); dmap_error_make(reply, "abro", "Invalid browse type"); return DAAP_REPLY_ERROR; } @@ -2191,16 +2047,16 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq) return DAAP_REPLY_NO_CONNECTION; } - ret = safe_atoi32(dreq->path_parts[3], &id); + ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &id); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", dreq->path_parts[3]); + DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", dreq->uri_parsed->path_parts[3]); return DAAP_REPLY_BAD_REQUEST; } - if (evhttp_find_header(&dreq->query, "mw") && evhttp_find_header(&dreq->query, "mh")) + if (evhttp_find_header(dreq->query, "mw") && evhttp_find_header(dreq->query, "mh")) { - param = evhttp_find_header(&dreq->query, "mw"); + param = evhttp_find_header(dreq->query, "mw"); ret = safe_atoi32(param, &max_w); if (ret < 0) { @@ -2208,7 +2064,7 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq) return DAAP_REPLY_BAD_REQUEST; } - param = evhttp_find_header(&dreq->query, "mh"); + param = evhttp_find_header(dreq->query, "mh"); ret = safe_atoi32(param, &max_h); if (ret < 0) { @@ -2224,9 +2080,9 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq) max_h = 0; } - if (strcmp(dreq->path_parts[2], "groups") == 0) + if (strcmp(dreq->uri_parsed->path_parts[2], "groups") == 0) ret = artwork_get_group(reply, id, max_w, max_h); - else if (strcmp(dreq->path_parts[2], "items") == 0) + else if (strcmp(dreq->uri_parsed->path_parts[2], "items") == 0) ret = artwork_get_item(reply, id, max_w, max_h); len = evbuffer_get_length(reply); @@ -2272,7 +2128,7 @@ daap_stream(struct evbuffer *reply, struct daap_request *dreq) return DAAP_REPLY_NO_CONNECTION; } - ret = safe_atoi32(dreq->path_parts[3], &id); + ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &id); if (ret < 0) return DAAP_REPLY_BAD_REQUEST; @@ -2448,33 +2304,25 @@ static struct uri_map daap_handlers[] = /* ------------------------------- DAAP API --------------------------------- */ +/* iTunes 9 gives us an absolute request-uri like + * daap://10.1.1.20:3689/server-info + * iTunes 12.1 gives us an absolute request-uri for streaming like + * http://10.1.1.20:3689/databases/1/items/1.mp3 + */ void -daap_request(struct evhttp_request *req) +daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { struct daap_request *dreq; struct evkeyvalq *headers; struct timespec start; struct timespec end; struct evbuffer *reply; - const char *source_uri; int ret; int msec; - // Clear the proxy request flag set by evhttp if the request URI was absolute. - // It has side-effects on Connection: keep-alive - req->flags &= ~EVHTTP_PROXY_REQUEST; + DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", uri_parsed->uri); - source_uri = evhttp_request_get_uri(req); - if (!source_uri) - { - DPRINTF(E_LOG, L_DAAP, "Could not get source URI from DAAP request\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; - } - - DPRINTF(E_DBG, L_DAAP, "DAAP request: %s\n", source_uri); - - dreq = daap_request_parse(source_uri, NULL, req); + dreq = daap_request_parse(req, uri_parsed, NULL); if (!dreq) { httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); @@ -2485,7 +2333,7 @@ daap_request(struct evhttp_request *req) if (ret < 0) { httpd_send_error(req, 403, "Forbidden"); - daap_request_free(dreq); + free(dreq); return; } @@ -2503,7 +2351,7 @@ daap_request(struct evhttp_request *req) CHECK_NULL(L_DAAP, reply = evbuffer_new()); // Try the cache - ret = cache_daap_get(reply, source_uri); + ret = cache_daap_get(reply, uri_parsed->uri); if (ret == 0) { // The cache will return the data gzipped, so httpd_send_reply won't need to do it @@ -2511,7 +2359,7 @@ daap_request(struct evhttp_request *req) httpd_send_reply(req, HTTP_OK, "OK", reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply evbuffer_free(reply); - daap_request_free(dreq); + free(dreq); return; } @@ -2525,38 +2373,34 @@ daap_request(struct evhttp_request *req) DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec); if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold()) - cache_daap_add(source_uri, dreq->user_agent, msec); + cache_daap_add(uri_parsed->uri, dreq->user_agent, msec); evbuffer_free(reply); - daap_request_free(dreq); + free(dreq); } int -daap_is_request(struct evhttp_request *req, char *uri) +daap_is_request(const char *path) { - uri = daap_fix_request_uri(req, uri); - if (!uri) - return 0; - - if (strncmp(uri, "/databases/", strlen("/databases/")) == 0) + if (strncmp(path, "/databases/", strlen("/databases/")) == 0) return 1; - if (strcmp(uri, "/databases") == 0) + if (strcmp(path, "/databases") == 0) return 1; - if (strcmp(uri, "/server-info") == 0) + if (strcmp(path, "/server-info") == 0) return 1; - if (strcmp(uri, "/content-codes") == 0) + if (strcmp(path, "/content-codes") == 0) return 1; - if (strcmp(uri, "/login") == 0) + if (strcmp(path, "/login") == 0) return 1; - if (strcmp(uri, "/update") == 0) + if (strcmp(path, "/update") == 0) return 1; - if (strcmp(uri, "/activity") == 0) + if (strcmp(path, "/activity") == 0) return 1; - if (strcmp(uri, "/logout") == 0) + if (strcmp(path, "/logout") == 0) return 1; #ifdef DMAP_TEST - if (strcmp(uri, "/dmap-test") == 0) + if (strcmp(path, "/dmap-test") == 0) return 1; #endif @@ -2577,32 +2421,39 @@ daap_session_is_valid(int id) } struct evbuffer * -daap_reply_build(const char *source_uri, const char *user_agent) +daap_reply_build(const char *uri, const char *user_agent) { struct daap_request *dreq; + struct httpd_uri_parsed *uri_parsed; struct evbuffer *reply; int ret; - DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: %s\n", source_uri); + DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri); - CHECK_NULL(L_DAAP, reply = evbuffer_new()); + uri_parsed = httpd_uri_parse(uri); + if (!uri_parsed) + return NULL; - dreq = daap_request_parse(source_uri, user_agent, NULL); + dreq = daap_request_parse(NULL, uri_parsed, user_agent); if (!dreq) { - evbuffer_free(reply); + httpd_uri_free(uri_parsed); return NULL; } + CHECK_NULL(L_DAAP, reply = evbuffer_new()); + ret = dreq->handler(reply, dreq); if (ret < 0) { evbuffer_free(reply); - daap_request_free(dreq); + free(dreq); + httpd_uri_free(uri_parsed); return NULL; } - daap_request_free(dreq); + free(dreq); + httpd_uri_free(uri_parsed); return reply; } diff --git a/src/httpd_daap.h b/src/httpd_daap.h index 830755b0..fff99a85 100644 --- a/src/httpd_daap.h +++ b/src/httpd_daap.h @@ -2,7 +2,7 @@ #ifndef __HTTPD_DAAP_H__ #define __HTTPD_DAAP_H__ -#include +#include "httpd.h" int daap_init(void); @@ -11,15 +11,15 @@ void daap_deinit(void); void -daap_request(struct evhttp_request *req); +daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); int -daap_is_request(struct evhttp_request *req, char *uri); +daap_is_request(const char *path); int daap_session_is_valid(int id); struct evbuffer * -daap_reply_build(const char *source_uri, const char *user_agent); +daap_reply_build(const char *uri, const char *user_agent); #endif /* !__HTTPD_DAAP_H__ */ diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index f3c0c55f..359e5914 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -37,16 +37,13 @@ #endif #include -#include -#include +#include "httpd_dacp.h" +#include "httpd_daap.h" #include "logger.h" #include "misc.h" #include "conffile.h" #include "artwork.h" -#include "httpd.h" -#include "httpd_daap.h" -#include "httpd_dacp.h" #include "dmap_common.h" #include "db.h" #include "daap_query.h" @@ -56,11 +53,22 @@ /* httpd event base, from httpd.c */ extern struct event_base *evbase_httpd; +// Will be filled out by dacp_request_parse() +struct dacp_request { + // The parsed request URI given to us by httpd.c + struct httpd_uri_parsed *uri_parsed; + // Shortcut to &uri_parsed->ev_query + struct evkeyvalq *query; + // http request struct + struct evhttp_request *req; + // A pointer to the handler that will process the request + void (*handler)(struct evbuffer *reply, struct dacp_request *dreq); +}; struct uri_map { regex_t preg; char *regexp; - void (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query); + void (*handler)(struct evbuffer *reply, struct dacp_request *dreq); }; struct dacp_update_request { @@ -78,8 +86,11 @@ struct dacp_prop_map { dacp_propset propset; }; +/* Forward declaration of handlers */ +static struct uri_map dacp_handlers[]; + +/* ---------------- FORWARD - PROPERTIES GETTERS AND SETTERS ---------------- */ -/* Forward - properties getters */ static void dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void @@ -116,7 +127,7 @@ dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, str static void dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); -/* Forward - properties setters */ + static void dacp_propset_volume(const char *value, struct evkeyvalq *query); static void @@ -169,7 +180,8 @@ static struct db_queue_item dummy_queue_item = }; -/* DACP helpers */ +/* -------------------------------- HELPERS --------------------------------- */ + static void dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { @@ -229,7 +241,365 @@ dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db } static int -dacp_request_authorize(struct evhttp_request *req, struct evkeyvalq *query) +find_first_song_id(const char *query) +{ + struct db_media_file_info dbmfi; + struct query_params qp; + int id; + int ret; + + id = 0; + memset(&qp, 0, sizeof(struct query_params)); + + /* We only want the id of the first song */ + qp.type = Q_ITEMS; + qp.idx_type = I_FIRST; + qp.sort = S_NONE; + qp.offset = 0; + qp.limit = 1; + qp.filter = daap_query_parse_sql(query); + if (!qp.filter) + { + DPRINTF(E_LOG, L_DACP, "Improper DAAP query!\n"); + + return -1; + } + + ret = db_query_start(&qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not start query\n"); + + goto no_query_start; + } + + if (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) + { + ret = safe_atoi32(dbmfi.id, &id); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid song id in query result!\n"); + + goto no_result; + } + + DPRINTF(E_DBG, L_DACP, "Found index song (id %d)\n", id); + ret = 1; + } + else + { + DPRINTF(E_LOG, L_DACP, "No song matches query (results %d): %s\n", qp.results, qp.filter); + + goto no_result; + } + + no_result: + db_query_end(&qp); + + no_query_start: + if (qp.filter) + free(qp.filter); + + if (ret == 1) + return id; + else + return -1; +} + +static int +dacp_queueitem_add(const char *query, const char *queuefilter, const char *sort, int quirk, int mode) +{ + struct media_file_info *mfi; + struct query_params qp; + int64_t albumid; + int64_t artistid; + int plid; + int id; + int ret; + int len; + char buf[1024]; + struct player_status status; + + if (query) + { + id = find_first_song_id(query); + if (id < 0) + return -1; + } + else + id = 0; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.offset = 0; + qp.limit = 0; + qp.sort = S_NONE; + qp.idx_type = I_NONE; + + if (quirk) + { + qp.sort = S_ALBUM; + qp.type = Q_ITEMS; + mfi = db_file_fetch_byid(id); + if (!mfi) + return -1; + snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, mfi->songalbumid); + free_mfi(mfi, 0); + qp.filter = strdup(buf); + } + else if (queuefilter) + { + len = strlen(queuefilter); + if ((len > 6) && (strncmp(queuefilter, "album:", 6) == 0)) + { + qp.type = Q_ITEMS; + ret = safe_atoi64(strchr(queuefilter, ':') + 1, &albumid); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid album id in queuefilter: '%s'\n", queuefilter); + + return -1; + } + snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, albumid); + qp.filter = strdup(buf); + } + else if ((len > 7) && (strncmp(queuefilter, "artist:", 7) == 0)) + { + qp.type = Q_ITEMS; + ret = safe_atoi64(strchr(queuefilter, ':') + 1, &artistid); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid artist id in queuefilter: '%s'\n", queuefilter); + + return -1; + } + snprintf(buf, sizeof(buf), "f.songartistid = %" PRIi64, artistid); + qp.filter = strdup(buf); + } + else if ((len > 9) && (strncmp(queuefilter, "playlist:", 9) == 0)) + { + qp.type = Q_PLITEMS; + ret = safe_atoi32(strchr(queuefilter, ':') + 1, &plid); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid playlist id in queuefilter: '%s'\n", queuefilter); + + return -1; + } + qp.id = plid; + qp.filter = strdup("1 = 1"); + } + else if ((len > 6) && (strncmp(queuefilter, "genre:", 6) == 0)) + { + qp.type = Q_ITEMS; + ret = db_snprintf(buf, sizeof(buf), "f.genre = %Q", queuefilter + 6); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid genre in queuefilter: '%s'\n", queuefilter); + + return -1; + } + qp.filter = strdup(buf); + } + else + { + DPRINTF(E_LOG, L_DACP, "Unknown queuefilter '%s', query is '%s'\n", queuefilter, query); + + // If the queuefilter is unkown, ignore it and use the query parameter instead to build the sql query + id = 0; + qp.type = Q_ITEMS; + qp.filter = daap_query_parse_sql(query); + } + } + else + { + id = 0; + qp.type = Q_ITEMS; + qp.filter = daap_query_parse_sql(query); + } + + if (sort) + { + if (strcmp(sort, "name") == 0) + qp.sort = S_NAME; + else if (strcmp(sort, "album") == 0) + qp.sort = S_ALBUM; + else if (strcmp(sort, "artist") == 0) + qp.sort = S_ARTIST; + } + + player_get_status(&status); + + if (mode == 3) + ret = db_queue_add_by_queryafteritemid(&qp, status.item_id); + else + ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); + + if (qp.filter) + free(qp.filter); + + if (ret < 0) + return -1; + + if (status.shuffle && mode != 1) + return 0; + + return id; +} + +static int +playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int pos_in_queue, uint32_t plid) +{ + struct evbuffer *song; + struct media_file_info *mfi; + int ret; + + CHECK_NULL(L_DACP, song = evbuffer_new()); + CHECK_ERR(L_DACP, evbuffer_expand(song, 256)); + + mfi = db_file_fetch_byid(source_id); + if (!mfi) + { + DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", source_id); + mfi = &dummy_mfi; + } + dmap_add_container(song, "ceQs", 16); + dmap_add_raw_uint32(song, 1); /* Database */ + dmap_add_raw_uint32(song, plid); + dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ + dmap_add_raw_uint32(song, mfi->id); + dmap_add_string(song, "ceQn", mfi->title); + dmap_add_string(song, "ceQr", mfi->artist); + dmap_add_string(song, "ceQa", mfi->album); + dmap_add_string(song, "ceQg", mfi->genre); + dmap_add_long(song, "asai", mfi->songalbumid); + dmap_add_int(song, "cmmk", mfi->media_kind); + dmap_add_int(song, "casa", 1); /* Unknown */ + dmap_add_int(song, "astm", mfi->song_length); + dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(song, "caks", 6); /* Unknown */ + dmap_add_int(song, "ceQI", pos_in_queue); + + dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); + + ret = evbuffer_add_buffer(songlist, song); + evbuffer_free(song); + if (mfi != &dummy_mfi) + free_mfi(mfi, 0); + + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); + return ret; + } + + return 0; +} + +static int +playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item *queue_item, int pos_in_queue, uint32_t plid) +{ + struct evbuffer *song; + int ret; + + CHECK_NULL(L_DACP, song = evbuffer_new()); + CHECK_ERR(L_DACP, evbuffer_expand(song, 256)); + + dmap_add_container(song, "ceQs", 16); + dmap_add_raw_uint32(song, 1); /* Database */ + dmap_add_raw_uint32(song, plid); + dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ + dmap_add_raw_uint32(song, queue_item->file_id); + dmap_add_string(song, "ceQn", queue_item->title); + dmap_add_string(song, "ceQr", queue_item->artist); + dmap_add_string(song, "ceQa", queue_item->album); + dmap_add_string(song, "ceQg", queue_item->genre); + dmap_add_long(song, "asai", queue_item->songalbumid); + dmap_add_int(song, "cmmk", queue_item->media_kind); + dmap_add_int(song, "casa", 1); /* Unknown */ + dmap_add_int(song, "astm", queue_item->song_length); + dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ + dmap_add_char(song, "caks", 6); /* Unknown */ + dmap_add_int(song, "ceQI", pos_in_queue); + + dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); + + ret = evbuffer_add_buffer(songlist, song); + evbuffer_free(song); + + return ret; +} + +static void +speaker_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg) +{ + struct evbuffer *evbuf; + int len; + + evbuf = (struct evbuffer *)arg; + + len = 8 + strlen(name) + 28; + if (flags.selected) + len += 9; + if (flags.has_password) + len += 9; + if (flags.has_video) + len += 9; + + CHECK_ERR(L_DACP, evbuffer_expand(evbuf, 71 + len)); + + dmap_add_container(evbuf, "mdcl", len); /* 8 + len */ + if (flags.selected) + dmap_add_char(evbuf, "caia", 1); /* 9 */ + if (flags.has_password) + dmap_add_char(evbuf, "cahp", 1); /* 9 */ + if (flags.has_video) + dmap_add_char(evbuf, "caiv", 1); /* 9 */ + dmap_add_string(evbuf, "minm", name); /* 8 + len */ + dmap_add_long(evbuf, "msma", id); /* 16 */ + + dmap_add_int(evbuf, "cmvo", relvol); /* 12 */ +} + +static struct dacp_request * +dacp_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct dacp_request *dreq; + int i; + int ret; + + CHECK_NULL(L_DACP, dreq = calloc(1, sizeof(struct dacp_request))); + + dreq->req = req; + dreq->uri_parsed = uri_parsed; + dreq->query = &(uri_parsed->ev_query); + + for (i = 0; dacp_handlers[i].handler; i++) + { + ret = regexec(&dacp_handlers[i].preg, uri_parsed->path, 0, NULL, 0); + if (ret == 0) + { + dreq->handler = dacp_handlers[i].handler; + break; + } + } + + if (!dreq->handler) + { + DPRINTF(E_LOG, L_DACP, "Unrecognized path '%s' in DACP request: '%s'\n", uri_parsed->path, uri_parsed->uri); + goto error; + } + + return dreq; + + error: + free(dreq); + + return NULL; +} + +static int +dacp_request_authorize(struct dacp_request *dreq) { const char *param; int32_t id; @@ -238,7 +608,7 @@ dacp_request_authorize(struct evhttp_request *req, struct evkeyvalq *query) if (cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode")) return 0; - param = evhttp_find_header(query, "session-id"); + param = evhttp_find_header(dreq->query, "session-id"); if (!param) { DPRINTF(E_LOG, L_DACP, "No session-id specified in request\n"); @@ -261,30 +631,24 @@ dacp_request_authorize(struct evhttp_request *req, struct evkeyvalq *query) return 0; invalid: - httpd_send_error(req, 403, "Forbidden"); + httpd_send_error(dreq->req, 403, "Forbidden"); return -1; } -/* Update requests helpers */ +/* ---------------------- UPDATE REQUESTS HANDLERS -------------------------- */ + static int make_playstatusupdate(struct evbuffer *evbuf) { struct player_status status; struct db_queue_item *queue_item = NULL; struct evbuffer *psu; - int ret; - psu = evbuffer_new(); - if (!psu) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for playstatusupdate\n"); - - return -1; - } + CHECK_NULL(L_DACP, psu = evbuffer_new()); + CHECK_ERR(L_DACP, evbuffer_expand(psu, 256)); player_get_status(&status); - if (status.status != PLAY_STOPPED) { queue_item = db_queue_fetch_byitemid(status.item_id); @@ -331,14 +695,9 @@ make_playstatusupdate(struct evbuffer *evbuf) dmap_add_container(evbuf, "cmst", evbuffer_get_length(psu)); /* 8 + len */ - ret = evbuffer_add_buffer(evbuf, psu); - evbuffer_free(psu); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not add status data to playstatusupdate reply\n"); + CHECK_ERR(L_DACP, evbuffer_add_buffer(evbuf, psu)); - return -1; - } + evbuffer_free(psu); DPRINTF(E_DBG, L_DACP, "Replying to playstatusupdate with status %d and current_rev %d\n", status.status, current_rev); @@ -375,21 +734,8 @@ playstatusupdate_cb(int fd, short what, void *arg) if (!update_requests) goto readd; - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for playstatusupdate reply\n"); - - goto readd; - } - - update = evbuffer_new(); - if (!update) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for playstatusupdate data\n"); - - goto out_free_evbuf; - } + CHECK_NULL(L_DACP, evbuf = evbuffer_new()); + CHECK_NULL(L_DACP, update = evbuffer_new()); current_rev++; @@ -422,7 +768,6 @@ playstatusupdate_cb(int fd, short what, void *arg) out_free_update: evbuffer_free(update); - out_free_evbuf: evbuffer_free(evbuf); readd: ret = event_add(updateev, NULL); @@ -488,7 +833,8 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg) } -/* Properties getters */ +/* --------------------- PROPERTIES GETTERS AND SETTERS --------------------- */ + static void dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item) { @@ -793,247 +1139,43 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query) } +/* --------------------------- REPLY HANDLERS ------------------------------- */ + static void -dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_ctrlint(struct evbuffer *reply, struct dacp_request *dreq) { /* /ctrl-int */ + CHECK_ERR(L_DACP, evbuffer_expand(reply, 256)); + /* If tags are added or removed container sizes should be adjusted too */ - dmap_add_container(evbuf, "caci", 194); /* 8, unknown dacp container - size of content */ - dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ - dmap_add_char(evbuf, "muty", 0); /* 9, dmap.updatetype */ - dmap_add_int(evbuf, "mtco", 1); /* 12, dmap.specifiedtotalcount */ - dmap_add_int(evbuf, "mrco", 1); /* 12, dmap.returnedcount */ - dmap_add_container(evbuf, "mlcl", 141); /* 8, dmap.listing - size of content */ - dmap_add_container(evbuf, "mlit", 133); /* 8, dmap.listingitem - size of content */ - dmap_add_int(evbuf, "miid", 1); /* 12, dmap.itemid - database ID */ - dmap_add_char(evbuf, "cmik", 1); /* 9, unknown */ + dmap_add_container(reply, "caci", 194); /* 8, unknown dacp container - size of content */ + dmap_add_int(reply, "mstt", 200); /* 12, dmap.status */ + dmap_add_char(reply, "muty", 0); /* 9, dmap.updatetype */ + dmap_add_int(reply, "mtco", 1); /* 12, dmap.specifiedtotalcount */ + dmap_add_int(reply, "mrco", 1); /* 12, dmap.returnedcount */ + dmap_add_container(reply, "mlcl", 141); /* 8, dmap.listing - size of content */ + dmap_add_container(reply, "mlit", 133); /* 8, dmap.listingitem - size of content */ + dmap_add_int(reply, "miid", 1); /* 12, dmap.itemid - database ID */ + dmap_add_char(reply, "cmik", 1); /* 9, unknown */ - dmap_add_int(evbuf, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */ - dmap_add_int(evbuf, "capr", (2 << 16 | 5)); /* 12, dacp.protocolversion */ + dmap_add_int(reply, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */ + dmap_add_int(reply, "capr", (2 << 16 | 5)); /* 12, dacp.protocolversion */ - dmap_add_char(evbuf, "cmsp", 1); /* 9, unknown */ - dmap_add_char(evbuf, "aeFR", 0x64); /* 9, unknown */ - dmap_add_char(evbuf, "cmsv", 1); /* 9, unknown */ - dmap_add_char(evbuf, "cass", 1); /* 9, unknown */ - dmap_add_char(evbuf, "caov", 1); /* 9, unknown */ - dmap_add_char(evbuf, "casu", 1); /* 9, unknown */ - dmap_add_char(evbuf, "ceSG", 1); /* 9, unknown */ - dmap_add_char(evbuf, "cmrl", 1); /* 9, unknown */ - dmap_add_long(evbuf, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */ + dmap_add_char(reply, "cmsp", 1); /* 9, unknown */ + dmap_add_char(reply, "aeFR", 0x64); /* 9, unknown */ + dmap_add_char(reply, "cmsv", 1); /* 9, unknown */ + dmap_add_char(reply, "cass", 1); /* 9, unknown */ + dmap_add_char(reply, "caov", 1); /* 9, unknown */ + dmap_add_char(reply, "casu", 1); /* 9, unknown */ + dmap_add_char(reply, "ceSG", 1); /* 9, unknown */ + dmap_add_char(reply, "cmrl", 1); /* 9, unknown */ + dmap_add_long(reply, "ceSX", (1 << 1 | 1)); /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); -} - -static int -find_first_song_id(const char *query) -{ - struct db_media_file_info dbmfi; - struct query_params qp; - int id; - int ret; - - id = 0; - memset(&qp, 0, sizeof(struct query_params)); - - /* We only want the id of the first song */ - qp.type = Q_ITEMS; - qp.idx_type = I_FIRST; - qp.sort = S_NONE; - qp.offset = 0; - qp.limit = 1; - qp.filter = daap_query_parse_sql(query); - if (!qp.filter) - { - DPRINTF(E_LOG, L_DACP, "Improper DAAP query!\n"); - - return -1; - } - - ret = db_query_start(&qp); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not start query\n"); - - goto no_query_start; - } - - if (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) - { - ret = safe_atoi32(dbmfi.id, &id); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid song id in query result!\n"); - - goto no_result; - } - - DPRINTF(E_DBG, L_DACP, "Found index song (id %d)\n", id); - ret = 1; - } - else - { - DPRINTF(E_LOG, L_DACP, "No song matches query (results %d): %s\n", qp.results, qp.filter); - - goto no_result; - } - - no_result: - db_query_end(&qp); - - no_query_start: - if (qp.filter) - free(qp.filter); - - if (ret == 1) - return id; - else - return -1; -} - - -static int -dacp_queueitem_add(const char *query, const char *queuefilter, const char *sort, int quirk, int mode) -{ - struct media_file_info *mfi; - struct query_params qp; - int64_t albumid; - int64_t artistid; - int plid; - int id; - int ret; - int len; - char buf[1024]; - struct player_status status; - - if (query) - { - id = find_first_song_id(query); - if (id < 0) - return -1; - } - else - id = 0; - - memset(&qp, 0, sizeof(struct query_params)); - - qp.offset = 0; - qp.limit = 0; - qp.sort = S_NONE; - qp.idx_type = I_NONE; - - if (quirk) - { - qp.sort = S_ALBUM; - qp.type = Q_ITEMS; - mfi = db_file_fetch_byid(id); - if (!mfi) - return -1; - snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, mfi->songalbumid); - free_mfi(mfi, 0); - qp.filter = strdup(buf); - } - else if (queuefilter) - { - len = strlen(queuefilter); - if ((len > 6) && (strncmp(queuefilter, "album:", 6) == 0)) - { - qp.type = Q_ITEMS; - ret = safe_atoi64(strchr(queuefilter, ':') + 1, &albumid); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid album id in queuefilter: '%s'\n", queuefilter); - - return -1; - } - snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, albumid); - qp.filter = strdup(buf); - } - else if ((len > 7) && (strncmp(queuefilter, "artist:", 7) == 0)) - { - qp.type = Q_ITEMS; - ret = safe_atoi64(strchr(queuefilter, ':') + 1, &artistid); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid artist id in queuefilter: '%s'\n", queuefilter); - - return -1; - } - snprintf(buf, sizeof(buf), "f.songartistid = %" PRIi64, artistid); - qp.filter = strdup(buf); - } - else if ((len > 9) && (strncmp(queuefilter, "playlist:", 9) == 0)) - { - qp.type = Q_PLITEMS; - ret = safe_atoi32(strchr(queuefilter, ':') + 1, &plid); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid playlist id in queuefilter: '%s'\n", queuefilter); - - return -1; - } - qp.id = plid; - qp.filter = strdup("1 = 1"); - } - else if ((len > 6) && (strncmp(queuefilter, "genre:", 6) == 0)) - { - qp.type = Q_ITEMS; - ret = db_snprintf(buf, sizeof(buf), "f.genre = %Q", queuefilter + 6); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Invalid genre in queuefilter: '%s'\n", queuefilter); - - return -1; - } - qp.filter = strdup(buf); - } - else - { - DPRINTF(E_LOG, L_DACP, "Unknown queuefilter '%s', query is '%s'\n", queuefilter, query); - - // If the queuefilter is unkown, ignore it and use the query parameter instead to build the sql query - id = 0; - qp.type = Q_ITEMS; - qp.filter = daap_query_parse_sql(query); - } - } - else - { - id = 0; - qp.type = Q_ITEMS; - qp.filter = daap_query_parse_sql(query); - } - - if (sort) - { - if (strcmp(sort, "name") == 0) - qp.sort = S_NAME; - else if (strcmp(sort, "album") == 0) - qp.sort = S_ALBUM; - else if (strcmp(sort, "artist") == 0) - qp.sort = S_ARTIST; - } - - player_get_status(&status); - - if (mode == 3) - ret = db_queue_add_by_queryafteritemid(&qp, status.item_id); - else - ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id); - - if (qp.filter) - free(qp.filter); - - if (ret < 0) - return -1; - - if (status.shuffle && mode != 1) - return 0; - - return id; + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); } static void -dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_cue_play(struct evbuffer *reply, struct dacp_request *dreq) { struct player_status status; const char *sort; @@ -1047,7 +1189,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u /* /cue?command=play&query=...&sort=...&index=N */ - param = evhttp_find_header(query, "clear-first"); + param = evhttp_find_header(dreq->query, "clear-first"); if (param) { ret = safe_atoi32(param, &clear); @@ -1063,17 +1205,17 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u player_get_status(&status); - cuequery = evhttp_find_header(query, "query"); + cuequery = evhttp_find_header(dreq->query, "query"); if (cuequery) { - sort = evhttp_find_header(query, "sort"); + sort = evhttp_find_header(dreq->query, "sort"); ret = dacp_queueitem_add(cuequery, NULL, sort, 0, 0); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - dmap_send_error(req, "cacr", "Could not build song queue"); + dmap_send_error(dreq->req, "cacr", "Could not build song queue"); return; } } @@ -1082,12 +1224,12 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u player_playback_stop(); } - param = evhttp_find_header(query, "dacp.shufflestate"); + param = evhttp_find_header(dreq->query, "dacp.shufflestate"); if (param) dacp_propset_shufflestate(param, NULL); pos = 0; - param = evhttp_find_header(query, "index"); + param = evhttp_find_header(dreq->query, "index"); if (param) { ret = safe_atou32(param, &pos); @@ -1096,10 +1238,10 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u } /* If selection was from Up Next queue or history queue (command will be playnow), then index is relative */ - if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0)) + if ((param = evhttp_find_header(dreq->query, "command")) && (strcmp(param, "playnow") == 0)) { /* If mode parameter is -1 or 1, the index is relative to the history queue, otherwise to the Up Next queue */ - param = evhttp_find_header(query, "mode"); + param = evhttp_find_header(dreq->query, "mode"); if (param && ((strcmp(param, "-1") == 0) || (strcmp(param, "1") == 0))) { /* Play from history queue */ @@ -1113,7 +1255,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } } @@ -1121,7 +1263,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } } @@ -1136,7 +1278,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d, now playing=%d\n", pos, status.item_id); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } } @@ -1148,7 +1290,7 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d\n", pos); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } } @@ -1159,21 +1301,23 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } player_get_status(&status); - dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "miid", status.id);/* 12 */ + CHECK_ERR(L_DACP, evbuffer_expand(reply, 64)); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + dmap_add_container(reply, "cacr", 24); /* 8 + len */ + dmap_add_int(reply, "mstt", 200); /* 12 */ + dmap_add_int(reply, "miid", status.id);/* 12 */ + + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); } static void -dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_cue_clear(struct evbuffer *reply, struct dacp_request *dreq) { /* /cue?command=clear */ @@ -1181,47 +1325,49 @@ dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char ** db_queue_clear(0); - dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "miid", 0); /* 12 */ + CHECK_ERR(L_DACP, evbuffer_expand(reply, 64)); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + dmap_add_container(reply, "cacr", 24); /* 8 + len */ + dmap_add_int(reply, "mstt", 200); /* 12 */ + dmap_add_int(reply, "miid", 0); /* 12 */ + + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); } static void -dacp_reply_cue(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_cue(struct evbuffer *reply, struct dacp_request *dreq) { const char *param; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "command"); + param = evhttp_find_header(dreq->query, "command"); if (!param) { - DPRINTF(E_DBG, L_DACP, "No command in cue request\n"); + DPRINTF(E_LOG, L_DACP, "No command in cue request\n"); - dmap_send_error(req, "cacr", "No command in cue request"); + dmap_send_error(dreq->req, "cacr", "No command in cue request"); return; } if (strcmp(param, "clear") == 0) - dacp_reply_cue_clear(req, evbuf, uri, query); + dacp_reply_cue_clear(reply, dreq); else if (strcmp(param, "play") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); + dacp_reply_cue_play(reply, dreq); else { DPRINTF(E_LOG, L_DACP, "Unknown cue command %s\n", param); - dmap_send_error(req, "cacr", "Unknown command in cue request"); + dmap_send_error(dreq->req, "cacr", "Unknown command in cue request"); return; } } static void -dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playspec(struct evbuffer *reply, struct dacp_request *dreq) { struct player_status status; const char *param; @@ -1237,15 +1383,15 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u * With our DAAP implementation, container-spec is the playlist ID and container-item-spec/item-spec is the song ID */ - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; /* Check for shuffle */ - shuffle = evhttp_find_header(query, "dacp.shufflestate"); + shuffle = evhttp_find_header(dreq->query, "dacp.shufflestate"); /* Playlist ID */ - param = evhttp_find_header(query, "container-spec"); + param = evhttp_find_header(dreq->query, "container-spec"); if (!param) { DPRINTF(E_LOG, L_DACP, "No container-spec in playspec request\n"); @@ -1277,9 +1423,9 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (!shuffle) { /* Start song ID */ - if ((param = evhttp_find_header(query, "item-spec"))) + if ((param = evhttp_find_header(dreq->query, "item-spec"))) plid = 0; // This is a podcast/audiobook - just play a single item, not a playlist - else if (!(param = evhttp_find_header(query, "container-item-spec"))) + else if (!(param = evhttp_find_header(dreq->query, "container-item-spec"))) { DPRINTF(E_LOG, L_DACP, "No container-item-spec/item-spec in playspec request\n"); @@ -1355,35 +1501,35 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); return; out_fail: - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); } static void -dacp_reply_pause(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_pause(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; player_playback_pause(); /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playpause(struct evbuffer *reply, struct dacp_request *dreq) { struct player_status status; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; @@ -1399,21 +1545,21 @@ dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char ** { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after pause\n"); - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_nextitem(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; @@ -1422,7 +1568,7 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Player returned an error for nextitem\n"); - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } @@ -1431,20 +1577,20 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after nextitem\n"); - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_previtem(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; @@ -1453,7 +1599,7 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Player returned an error for previtem\n"); - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } @@ -1462,153 +1608,61 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u { DPRINTF(E_LOG, L_DACP, "Player returned an error for start after previtem\n"); - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_beginff(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_beginff(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_beginrew(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_beginrew(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playresume(struct evbuffer *reply, struct dacp_request *dreq) { int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; /* TODO */ /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); -} - -static int -playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int pos_in_queue, uint32_t plid) -{ - struct evbuffer *song; - struct media_file_info *mfi; - int ret; - - song = evbuffer_new(); - if (!song) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); - return -1; - } - - mfi = db_file_fetch_byid(source_id); - if (!mfi) - { - DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", source_id); - mfi = &dummy_mfi; - } - dmap_add_container(song, "ceQs", 16); - dmap_add_raw_uint32(song, 1); /* Database */ - dmap_add_raw_uint32(song, plid); - dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ - dmap_add_raw_uint32(song, mfi->id); - dmap_add_string(song, "ceQn", mfi->title); - dmap_add_string(song, "ceQr", mfi->artist); - dmap_add_string(song, "ceQa", mfi->album); - dmap_add_string(song, "ceQg", mfi->genre); - dmap_add_long(song, "asai", mfi->songalbumid); - dmap_add_int(song, "cmmk", mfi->media_kind); - dmap_add_int(song, "casa", 1); /* Unknown */ - dmap_add_int(song, "astm", mfi->song_length); - dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ - dmap_add_char(song, "caks", 6); /* Unknown */ - dmap_add_int(song, "ceQI", pos_in_queue); - - dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); - - ret = evbuffer_add_buffer(songlist, song); - evbuffer_free(song); - if (mfi != &dummy_mfi) - free_mfi(mfi, 0); - - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n"); - return ret; - } - - return 0; -} - -static int -playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item *queue_item, int pos_in_queue, uint32_t plid) -{ - struct evbuffer *song; - int ret; - - song = evbuffer_new(); - if (!song) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n"); - return -1; - } - - dmap_add_container(song, "ceQs", 16); - dmap_add_raw_uint32(song, 1); /* Database */ - dmap_add_raw_uint32(song, plid); - dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */ - dmap_add_raw_uint32(song, queue_item->file_id); - dmap_add_string(song, "ceQn", queue_item->title); - dmap_add_string(song, "ceQr", queue_item->artist); - dmap_add_string(song, "ceQa", queue_item->album); - dmap_add_string(song, "ceQg", queue_item->genre); - dmap_add_long(song, "asai", queue_item->songalbumid); - dmap_add_int(song, "cmmk", queue_item->media_kind); - dmap_add_int(song, "casa", 1); /* Unknown */ - dmap_add_int(song, "astm", queue_item->song_length); - dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */ - dmap_add_char(song, "caks", 6); /* Unknown */ - dmap_add_int(song, "ceQI", pos_in_queue); - - dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); - - ret = evbuffer_add_buffer(songlist, song); - evbuffer_free(song); - - return ret; + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, - struct evkeyvalq *query) +dacp_reply_playqueuecontents(struct evbuffer *reply, struct dacp_request *dreq) { struct evbuffer *songlist; struct evbuffer *playlists; @@ -1626,14 +1680,14 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n"); span = 50; /* Default */ - param = evhttp_find_header(query, "span"); + param = evhttp_find_header(dreq->query, "span"); if (param) { ret = safe_atoi32(param, &span); @@ -1641,14 +1695,8 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n"); } - songlist = evbuffer_new(); - if (!songlist) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate songlist evbuffer for playqueue-contents\n"); - - dmap_send_error(req, "ceQR", "Out of memory"); - return; - } + CHECK_NULL(L_DACP, songlist = evbuffer_new()); + CHECK_ERR(L_DACP, evbuffer_expand(reply, 128)); player_get_status(&status); @@ -1711,11 +1759,10 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, } /* Playlists are hist, curr and main. */ - playlists = evbuffer_new(); - if (!playlists) - goto error; + CHECK_NULL(L_DACP, playlists = evbuffer_new()); + CHECK_ERR(L_DACP, evbuffer_expand(playlists, 256)); - dmap_add_container(playlists, "mlit", 61); + dmap_add_container(playlists, "mlit", 61); /* 8 */ dmap_add_string(playlists, "ceQk", "hist"); /* 12 */ dmap_add_int(playlists, "ceQi", -200); /* 12 */ dmap_add_int(playlists, "ceQm", 200); /* 12 */ @@ -1723,7 +1770,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, if (count > 0) { - dmap_add_container(playlists, "mlit", 36); + dmap_add_container(playlists, "mlit", 36); /* 8 */ dmap_add_string(playlists, "ceQk", "curr"); /* 12 */ dmap_add_int(playlists, "ceQi", 0); /* 12 */ dmap_add_int(playlists, "ceQm", 1); /* 12 */ @@ -1742,46 +1789,41 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* Final construction of reply */ playlist_length = evbuffer_get_length(playlists); - dmap_add_container(evbuf, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */ - dmap_add_int(evbuf, "mstt", 200); /* 12, dmap.status */ - dmap_add_int(evbuf, "mtco", abs(span)); /* 12 */ - dmap_add_int(evbuf, "mrco", count); /* 12 */ - dmap_add_char(evbuf, "ceQu", 0); /* 9 */ - dmap_add_container(evbuf, "mlcl", 8 + playlist_length + songlist_length); /* 8 */ - dmap_add_container(evbuf, "ceQS", playlist_length); /* 8 */ + dmap_add_container(reply, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */ + dmap_add_int(reply, "mstt", 200); /* 12, dmap.status */ + dmap_add_int(reply, "mtco", abs(span)); /* 12 */ + dmap_add_int(reply, "mrco", count); /* 12 */ + dmap_add_char(reply, "ceQu", 0); /* 9 */ + dmap_add_container(reply, "mlcl", 8 + playlist_length + songlist_length); /* 8 */ + dmap_add_container(reply, "ceQS", playlist_length); /* 8 */ + + CHECK_ERR(L_DACP, evbuffer_add_buffer(reply, playlists)); + CHECK_ERR(L_DACP, evbuffer_add_buffer(reply, songlist)); - ret = evbuffer_add_buffer(evbuf, playlists); evbuffer_free(playlists); - if (ret < 0) - goto error; - - ret = evbuffer_add_buffer(evbuf, songlist); - if (ret < 0) - goto error; - evbuffer_free(songlist); - dmap_add_char(evbuf, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */ - dmap_add_char(evbuf, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */ + dmap_add_char(reply, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */ + dmap_add_char(reply, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); return; error: - DPRINTF(E_LOG, L_DACP, "Out of memory in dacp_reply_playqueuecontents or database error\n"); + DPRINTF(E_LOG, L_DACP, "Database error in dacp_reply_playqueuecontents\n"); evbuffer_free(songlist); - dmap_send_error(req, "ceQR", "Out of memory"); + dmap_send_error(dreq->req, "ceQR", "Database error"); } static void -dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueueedit_clear(struct evbuffer *reply, struct dacp_request *dreq) { const char *param; struct player_status status; - param = evhttp_find_header(query, "mode"); + param = evhttp_find_header(dreq->query, "mode"); /* * The mode parameter contains the playlist to be cleared. @@ -1796,15 +1838,15 @@ dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbu db_queue_clear(status.item_id); } - dmap_add_container(evbuf, "cacr", 24); /* 8 + len */ - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "miid", 0); /* 12 */ + dmap_add_container(reply, "cacr", 24); /* 8 + len */ + dmap_add_int(reply, "mstt", 200); /* 12 */ + dmap_add_int(reply, "miid", 0); /* 12 */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); } static void -dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueueedit_add(struct evbuffer *reply, struct dacp_request *dreq) { //?command=add&query='dmap.itemid:156'&sort=album&mode=3&session-id=100 // -> mode=3: add to playqueue position 0 (play next) @@ -1830,7 +1872,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, mode = 1; - param = evhttp_find_header(query, "mode"); + param = evhttp_find_header(dreq->query, "mode"); if (param) { ret = safe_atoi32(param, &mode); @@ -1838,7 +1880,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Invalid mode value in playqueue-edit request\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } } @@ -1852,16 +1894,16 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, if (mode == 2) player_shuffle_set(1); - editquery = evhttp_find_header(query, "query"); + editquery = evhttp_find_header(dreq->query, "query"); if (!editquery) { DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } - sort = evhttp_find_header(query, "sort"); + sort = evhttp_find_header(dreq->query, "sort"); // if sort param is missing and an album or artist is added to the queue, set sort to "album" if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:"))) @@ -1870,9 +1912,9 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, } // only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next) - queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(query, "queuefilter"); + queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(dreq->query, "queuefilter"); - querymodifier = evhttp_find_header(query, "query-modifier"); + querymodifier = evhttp_find_header(dreq->query, "query-modifier"); if (!querymodifier || (strcmp(querymodifier, "containers") != 0)) { quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)")); @@ -1886,7 +1928,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } @@ -1898,7 +1940,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not build song queue\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } @@ -1931,16 +1973,16 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - dmap_send_error(req, "cacr", "Playback failed to start"); + dmap_send_error(dreq->req, "cacr", "Playback failed to start"); return; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueueedit_move(struct evbuffer *reply, struct dacp_request *dreq) { /* * Handles the move command. @@ -1952,12 +1994,11 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf */ struct player_status status; int ret; - const char *param; int src; int dst; - param = evhttp_find_header(query, "edit-params"); + param = evhttp_find_header(dreq->query, "edit-params"); if (param) { ret = safe_atoi32(strchr(param, ':') + 1, &src); @@ -1965,7 +2006,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf { DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-from value in playqueue-edit request\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } @@ -1974,7 +2015,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf { DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-to value in playqueue-edit request\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } @@ -1983,11 +2024,11 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueueedit_remove(struct evbuffer *reply, struct dacp_request *dreq) { /* * Handles the remove command. @@ -1995,12 +2036,11 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb * ?command=remove&items=1&session-id=100 */ struct player_status status; - int ret; - const char *param; int item_index; + int ret; - param = evhttp_find_header(query, "items"); + param = evhttp_find_header(dreq->query, "items"); if (param) { ret = safe_atoi32(param, &item_index); @@ -2008,7 +2048,7 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb { DPRINTF(E_LOG, L_DACP, "Invalid edit-params remove item value in playqueue-edit request\n"); - dmap_send_error(req, "cacr", "Invalid request"); + dmap_send_error(dreq->req, "cacr", "Invalid request"); return; } @@ -2018,11 +2058,11 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playqueueedit(struct evbuffer *reply, struct dacp_request *dreq) { const char *param; int ret; @@ -2070,40 +2110,40 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha -> remove song on position 1 from the playqueue */ - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "command"); + param = evhttp_find_header(dreq->query, "command"); if (!param) { DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(dreq->req, "cmst", "Invalid request"); return; } if (strcmp(param, "clear") == 0) - dacp_reply_playqueueedit_clear(req, evbuf, uri, query); + dacp_reply_playqueueedit_clear(reply, dreq); else if (strcmp(param, "playnow") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); + dacp_reply_cue_play(reply, dreq); else if (strcmp(param, "add") == 0) - dacp_reply_playqueueedit_add(req, evbuf, uri, query); + dacp_reply_playqueueedit_add(reply, dreq); else if (strcmp(param, "move") == 0) - dacp_reply_playqueueedit_move(req, evbuf, uri, query); + dacp_reply_playqueueedit_move(reply, dreq); else if (strcmp(param, "remove") == 0) - dacp_reply_playqueueedit_remove(req, evbuf, uri, query); + dacp_reply_playqueueedit_remove(reply, dreq); else { DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(dreq->req, "cmst", "Invalid request"); return; } } static void -dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_playstatusupdate(struct evbuffer *reply, struct dacp_request *dreq) { struct dacp_update_request *ur; struct evhttp_connection *evcon; @@ -2111,16 +2151,16 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, int reqd_rev; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "revision-number"); + param = evhttp_find_header(dreq->query, "revision-number"); if (!param) { DPRINTF(E_LOG, L_DACP, "Missing revision-number in update request\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(dreq->req, "cmst", "Invalid request"); return; } @@ -2129,17 +2169,17 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Parameter revision-number not an integer\n"); - dmap_send_error(req, "cmst", "Invalid request"); + dmap_send_error(dreq->req, "cmst", "Invalid request"); return; } if ((reqd_rev == 0) || (reqd_rev == 1)) { - ret = make_playstatusupdate(evbuf); + ret = make_playstatusupdate(reply); if (ret < 0) - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); else - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); return; } @@ -2150,11 +2190,11 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Out of memory for update request\n"); - dmap_send_error(req, "cmst", "Out of memory"); + dmap_send_error(dreq->req, "cmst", "Out of memory"); return; } - ur->req = req; + ur->req = dreq->req; ur->next = update_requests; update_requests = ur; @@ -2162,13 +2202,13 @@ dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, /* If the connection fails before we have an update to push out * to the client, we need to know. */ - evcon = evhttp_request_get_connection(req); + evcon = evhttp_request_get_connection(dreq->req); if (evcon) evhttp_connection_set_closecb(evcon, update_fail_cb, ur); } static void -dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_nowplayingartwork(struct evbuffer *reply, struct dacp_request *dreq) { char clen[32]; struct evkeyvalq *headers; @@ -2180,16 +2220,16 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, int max_h; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "mw"); + param = evhttp_find_header(dreq->query, "mw"); if (!param) { DPRINTF(E_LOG, L_DACP, "Request for artwork without mw parameter\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(dreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -2198,16 +2238,16 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not convert mw parameter to integer\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(dreq->req, HTTP_BADREQUEST, "Bad Request"); return; } - param = evhttp_find_header(query, "mh"); + param = evhttp_find_header(dreq->query, "mh"); if (!param) { DPRINTF(E_LOG, L_DACP, "Request for artwork without mh parameter\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(dreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -2216,7 +2256,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, { DPRINTF(E_LOG, L_DACP, "Could not convert mh parameter to integer\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(dreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -2224,8 +2264,8 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, if (ret < 0) goto no_artwork; - ret = artwork_get_item(evbuf, id, max_w, max_h); - len = evbuffer_get_length(evbuf); + ret = artwork_get_item(reply, id, max_w, max_h); + len = evbuffer_get_length(reply); switch (ret) { @@ -2239,26 +2279,26 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, default: if (len > 0) - evbuffer_drain(evbuf, len); + evbuffer_drain(reply, len); goto no_artwork; } - headers = evhttp_request_get_output_headers(req); + headers = evhttp_request_get_output_headers(dreq->req); evhttp_remove_header(headers, "Content-Type"); evhttp_add_header(headers, "Content-Type", ctype); snprintf(clen, sizeof(clen), "%ld", (long)len); evhttp_add_header(headers, "Content-Length", clen); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, HTTPD_SEND_NO_GZIP); return; no_artwork: - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + httpd_send_error(dreq->req, HTTP_NOTFOUND, "Not Found"); } static void -dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_getproperty(struct evbuffer *reply, struct dacp_request *dreq) { struct player_status status; const struct dacp_prop_map *dpm; @@ -2271,16 +2311,16 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char size_t len; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "properties"); + param = evhttp_find_header(dreq->query, "properties"); if (!param) { DPRINTF(E_WARN, L_DACP, "Invalid DACP getproperty request, no properties\n"); - dmap_send_error(req, "cmgt", "Invalid request"); + dmap_send_error(dreq->req, "cmgt", "Invalid request"); return; } @@ -2289,7 +2329,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char { DPRINTF(E_LOG, L_DACP, "Could not duplicate properties parameter; out of memory\n"); - dmap_send_error(req, "cmgt", "Out of memory"); + dmap_send_error(dreq->req, "cmgt", "Out of memory"); return; } @@ -2298,7 +2338,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char { DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for properties list\n"); - dmap_send_error(req, "cmgt", "Out of memory"); + dmap_send_error(dreq->req, "cmgt", "Out of memory"); goto out_free_propstr; } @@ -2311,7 +2351,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char { DPRINTF(E_LOG, L_DACP, "Could not fetch queue_item for item-id %d\n", status.item_id); - dmap_send_error(req, "cmgt", "Server error"); + dmap_send_error(dreq->req, "cmgt", "Server error"); goto out_free_proplist; } } @@ -2339,20 +2379,14 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char free_queue_item(queue_item, 0); len = evbuffer_get_length(proplist); - dmap_add_container(evbuf, "cmgt", 12 + len); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ + dmap_add_container(reply, "cmgt", 12 + len); + dmap_add_int(reply, "mstt", 200); /* 12 */ + + CHECK_ERR(L_DACP, evbuffer_add_buffer(reply, proplist)); - ret = evbuffer_add_buffer(evbuf, proplist); evbuffer_free(proplist); - if (ret < 0) - { - DPRINTF(E_LOG, L_DACP, "Could not add properties to getproperty reply\n"); - dmap_send_error(req, "cmgt", "Out of memory"); - return; - } - - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); return; @@ -2364,13 +2398,13 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char } static void -dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_setproperty(struct evbuffer *reply, struct dacp_request *dreq) { const struct dacp_prop_map *dpm; struct evkeyval *param; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; @@ -2383,7 +2417,7 @@ dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char /* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */ - TAILQ_FOREACH(param, query, next) + TAILQ_FOREACH(param, dreq->query, next) { dpm = dacp_find_prop(param->key, strlen(param->key)); @@ -2394,80 +2428,43 @@ dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char } if (dpm->propset) - dpm->propset(param->value, query); + dpm->propset(param->value, dreq->query); else DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc); } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } static void -speaker_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg) -{ - struct evbuffer *evbuf; - int len; - - evbuf = (struct evbuffer *)arg; - - len = 8 + strlen(name) + 28; - if (flags.selected) - len += 9; - if (flags.has_password) - len += 9; - if (flags.has_video) - len += 9; - - dmap_add_container(evbuf, "mdcl", len); /* 8 + len */ - if (flags.selected) - dmap_add_char(evbuf, "caia", 1); /* 9 */ - if (flags.has_password) - dmap_add_char(evbuf, "cahp", 1); /* 9 */ - if (flags.has_video) - dmap_add_char(evbuf, "caiv", 1); /* 9 */ - dmap_add_string(evbuf, "minm", name); /* 8 + len */ - dmap_add_long(evbuf, "msma", id); /* 16 */ - - dmap_add_int(evbuf, "cmvo", relvol); /* 12 */ -} - -static void -dacp_reply_getspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_getspeakers(struct evbuffer *reply, struct dacp_request *dreq) { struct evbuffer *spklist; size_t len; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - spklist = evbuffer_new(); - if (!spklist) - { - DPRINTF(E_LOG, L_DACP, "Could not create evbuffer for speaker list\n"); - - dmap_send_error(req, "casp", "Out of memory"); - - return; - } + CHECK_NULL(L_DACP, spklist = evbuffer_new()); player_speaker_enumerate(speaker_enum_cb, spklist); len = evbuffer_get_length(spklist); - dmap_add_container(evbuf, "casp", 12 + len); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ + dmap_add_container(reply, "casp", 12 + len); + dmap_add_int(reply, "mstt", 200); /* 12 */ - evbuffer_add_buffer(evbuf, spklist); + evbuffer_add_buffer(reply, spklist); evbuffer_free(spklist); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(dreq->req, HTTP_OK, "OK", reply, 0); } static void -dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_setspeakers(struct evbuffer *reply, struct dacp_request *dreq) { const char *param; const char *ptr; @@ -2476,16 +2473,16 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char int i; int ret; - ret = dacp_request_authorize(req, query); + ret = dacp_request_authorize(dreq); if (ret < 0) return; - param = evhttp_find_header(query, "speaker-id"); + param = evhttp_find_header(dreq->query, "speaker-id"); if (!param) { DPRINTF(E_LOG, L_DACP, "Missing speaker-id parameter in DACP setspeakers request\n"); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(dreq->req, HTTP_BADREQUEST, "Bad Request"); return; } @@ -2505,7 +2502,7 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char { DPRINTF(E_LOG, L_DACP, "Out of memory for speaker ids\n"); - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); + httpd_send_error(dreq->req, HTTP_SERVUNAVAIL, "Internal Server Error"); return; } @@ -2550,18 +2547,17 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char /* Password problem */ if (ret == -2) - httpd_send_error(req, 902, ""); + httpd_send_error(dreq->req, 902, ""); else - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(dreq->req, 500, "Internal Server Error"); return; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(dreq->req, HTTP_NOCONTENT, "No Content", reply, HTTPD_SEND_NO_GZIP); } - static struct uri_map dacp_handlers[] = { { @@ -2642,132 +2638,49 @@ static struct uri_map dacp_handlers[] = } }; + +/* ------------------------------- DACP API --------------------------------- */ + void -dacp_request(struct evhttp_request *req) +dacp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { - char *full_uri; - char *uri; - char *ptr; - char *uri_parts[7]; - struct evbuffer *evbuf; - struct evkeyvalq query; + struct dacp_request *dreq; + struct evbuffer *reply; struct evkeyvalq *headers; - int handler; - int ret; - int i; - memset(&query, 0, sizeof(struct evkeyvalq)); + DPRINTF(E_DBG, L_DACP, "DACP request: '%s'\n", uri_parsed->uri); - full_uri = httpd_fixup_uri(req); - if (!full_uri) + dreq = dacp_request_parse(req, uri_parsed); + if (!dreq) { httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); return; } - ptr = strchr(full_uri, '?'); - if (ptr) - *ptr = '\0'; - - uri = strdup(full_uri); - if (!uri) - { - free(full_uri); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; - } - - if (ptr) - *ptr = '?'; - - ptr = uri; - uri = evhttp_decode_uri(uri); - free(ptr); - - DPRINTF(E_DBG, L_DACP, "DACP request: %s\n", full_uri); - - handler = -1; - for (i = 0; dacp_handlers[i].handler; i++) - { - ret = regexec(&dacp_handlers[i].preg, uri, 0, NULL, 0); - if (ret == 0) - { - handler = i; - break; - } - } - - if (handler < 0) - { - DPRINTF(E_LOG, L_DACP, "Unrecognized DACP request: '%s'\n", uri); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - - free(uri); - free(full_uri); - return; - } - - /* DACP has no HTTP authentication - Remote is identified by its pairing-guid */ - - memset(uri_parts, 0, sizeof(uri_parts)); - - uri_parts[0] = strtok_r(uri, "/", &ptr); - for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++) - { - uri_parts[i] = strtok_r(NULL, "/", &ptr); - } - - if (!uri_parts[0] || uri_parts[i - 1] || (i < 2)) - { - DPRINTF(E_LOG, L_DACP, "DACP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - - free(uri); - free(full_uri); - return; - } - - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DACP, "Could not allocate evbuffer for DACP reply\n"); - - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - - free(uri); - free(full_uri); - return; - } - - evhttp_parse_query(full_uri, &query); - headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION); /* Content-Type for all DACP replies; can be overriden as needed */ evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged"); - dacp_handlers[handler].handler(req, evbuf, uri_parts, &query); + CHECK_NULL(L_DACP, reply = evbuffer_new()); - evbuffer_free(evbuf); - evhttp_clear_headers(&query); - free(uri); - free(full_uri); + dreq->handler(reply, dreq); + + evbuffer_free(reply); + free(dreq); } int -dacp_is_request(struct evhttp_request *req, char *uri) +dacp_is_request(const char *path) { - if (strncmp(uri, "/ctrl-int/", strlen("/ctrl-int/")) == 0) + if (strncmp(path, "/ctrl-int/", strlen("/ctrl-int/")) == 0) return 1; - if (strcmp(uri, "/ctrl-int") == 0) + if (strcmp(path, "/ctrl-int") == 0) return 1; return 0; } - int dacp_init(void) { diff --git a/src/httpd_dacp.h b/src/httpd_dacp.h index ba9c1421..307543dc 100644 --- a/src/httpd_dacp.h +++ b/src/httpd_dacp.h @@ -2,7 +2,7 @@ #ifndef __HTTPD_DACP_H__ #define __HTTPD_DACP_H__ -#include +#include "httpd.h" int dacp_init(void); @@ -11,9 +11,9 @@ void dacp_deinit(void); void -dacp_request(struct evhttp_request *req); +dacp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); int -dacp_is_request(struct evhttp_request *req, char *uri); +dacp_is_request(const char *path); #endif /* !__HTTPD_DACP_H__ */ diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 37001fee..b39d9918 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -26,21 +26,16 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "httpd_jsonapi.h" - #ifdef HAVE_CONFIG_H # include #endif -#include -#include -#include #include #include #include +#include "httpd_jsonapi.h" #include "conffile.h" #include "db.h" -#include "httpd.h" #ifdef LASTFM # include "lastfm.h" #endif @@ -54,275 +49,31 @@ # include "spotify.h" #endif +struct json_request { + // The parsed request URI given to us by httpd.c + struct httpd_uri_parsed *uri_parsed; + // Shortcut to &uri_parsed->ev_query + struct evkeyvalq *query; + // http request struct + struct evhttp_request *req; + // A pointer to the handler that will process the request + int (*handler)(struct evbuffer *reply, struct json_request *jreq); +}; + struct uri_map { regex_t preg; char *regexp; - int (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query); + int (*handler)(struct evbuffer *reply, struct json_request *jreq); }; +/* Forward declaration of handlers */ +static struct uri_map adm_handlers[]; +/* -------------------------------- HELPERS --------------------------------- */ /* - * Endpoint to retrieve configuration values - * - * Example response: - * - * { - * "websocket_port": 6603, - * "version": "25.0" - * } - */ -static int -jsonapi_reply_config(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) -{ - json_object *reply; - json_object *buildopts; - int websocket_port; - char **buildoptions; - int i; - int ret; - - reply = json_object_new_object(); - - // Websocket port -#ifdef HAVE_LIBWEBSOCKETS - websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port"); -#else - websocket_port = 0; -#endif - json_object_object_add(reply, "websocket_port", json_object_new_int(websocket_port)); - - // forked-daapd version - json_object_object_add(reply, "version", json_object_new_string(VERSION)); - - // enabled build options - buildopts = json_object_new_array(); - buildoptions = buildopts_get(); - for (i = 0; buildoptions[i]; i++) - { - json_object_array_add(buildopts, json_object_new_string(buildoptions[i])); - } - json_object_object_add(reply, "buildoptions", buildopts); - - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "config: Couldn't add config data to response buffer.\n"); - return -1; - } - - return 0; -} - -/* - * Endpoint to retrieve informations about the library - * - * Example response: - * - * { - * "artists": 84, - * "albums": 151, - * "songs": 3085, - * "db_playtime": 687824, - * "updating": false - *} - */ -static int -jsonapi_reply_library(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) -{ - struct query_params qp; - struct filecount_info fci; - int artists; - int albums; - bool is_scanning; - json_object *reply; - int ret; - - // Fetch values for response - - memset(&qp, 0, sizeof(struct query_params)); - qp.type = Q_COUNT_ITEMS; - ret = db_filecount_get(&fci, &qp); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n"); - return -1; - } - - artists = db_files_get_artist_count(); - albums = db_files_get_album_count(); - - is_scanning = library_is_scanning(); - - - // Build json response - - reply = json_object_new_object(); - - json_object_object_add(reply, "artists", json_object_new_int(artists)); - json_object_object_add(reply, "albums", json_object_new_int(albums)); - json_object_object_add(reply, "songs", json_object_new_int(fci.count)); - json_object_object_add(reply, "db_playtime", json_object_new_int64((fci.length / 1000))); - json_object_object_add(reply, "updating", json_object_new_boolean(is_scanning)); - - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "library: Couldn't add library information data to response buffer.\n"); - return -1; - } - - return 0; -} - -/* - * Endpoint to trigger a library rescan - */ -static int -jsonapi_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) -{ - library_rescan(); - return 0; -} - -/* - * Endpoint to retrieve information about the spotify integration - * - * Exampe response: - * - * { - * "enabled": true, - * "oauth_uri": "https://accounts.spotify.com/authorize/?client_id=... - * } - */ -static int -jsonapi_reply_spotify(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) -{ - json_object *reply; - int ret; - - reply = json_object_new_object(); - -#ifdef HAVE_SPOTIFY_H - int httpd_port; - char redirect_uri[256]; - char *oauth_uri; - struct spotify_status_info info; - - json_object_object_add(reply, "enabled", json_object_new_boolean(true)); - - httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port); - - oauth_uri = spotifywebapi_oauth_uri_get(redirect_uri); - if (!uri) - { - DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n"); - } - else - { - json_object_object_add(reply, "oauth_uri", json_object_new_string(oauth_uri)); - free(oauth_uri); - } - - spotify_status_info_get(&info); - json_object_object_add(reply, "libspotify_installed", json_object_new_boolean(info.libspotify_installed)); - json_object_object_add(reply, "libspotify_logged_in", json_object_new_boolean(info.libspotify_logged_in)); - json_object_object_add(reply, "libspotify_user", json_object_new_string(info.libspotify_user)); - json_object_object_add(reply, "webapi_token_valid", json_object_new_boolean(info.webapi_token_valid)); - json_object_object_add(reply, "webapi_user", json_object_new_string(info.webapi_user)); - -#else - json_object_object_add(reply, "enabled", json_object_new_boolean(false)); -#endif - - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "spotify: Couldn't add spotify information data to response buffer.\n"); - return -1; - } - - return 0; -} - -static int -jsonapi_reply_spotify_login(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) -{ -#ifdef HAVE_SPOTIFY_H - struct evbuffer *in_evbuf; - json_object* request; - const char *user; - const char *password; - char *errmsg = NULL; - json_object* reply; - json_object* errors; - int ret; - - DPRINTF(E_DBG, L_WEB, "Received spotify login request\n"); - - in_evbuf = evhttp_request_get_input_buffer(req); - request = jparse_obj_from_evbuffer(in_evbuf); - if (!request) - { - DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n"); - return -1; - } - - reply = json_object_new_object(); - - user = jparse_str_from_obj(request, "user"); - password = jparse_str_from_obj(request, "password"); - if (user && strlen(user) > 0 && password && strlen(password) > 0) - { - ret = spotify_login_user(user, password, &errmsg); - if (ret < 0) - { - json_object_object_add(reply, "success", json_object_new_boolean(false)); - errors = json_object_new_object(); - json_object_object_add(errors, "error", json_object_new_string(errmsg)); - json_object_object_add(reply, "errors", errors); - } - else - { - json_object_object_add(reply, "success", json_object_new_boolean(true)); - } - free(errmsg); - } - else - { - DPRINTF(E_LOG, L_WEB, "No user or password in spotify login post request\n"); - - json_object_object_add(reply, "success", json_object_new_boolean(false)); - errors = json_object_new_object(); - if (!user || strlen(user) == 0) - json_object_object_add(errors, "user", json_object_new_string("Username is required")); - if (!password || strlen(password) == 0) - json_object_object_add(errors, "password", json_object_new_string("Password is required")); - json_object_object_add(reply, "errors", errors); - } - - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "spotify: Couldn't add spotify login data to response buffer.\n"); - return -1; - } - -#else - DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n"); -#endif - - return 0; -} - -/* - * Endpoint to kickoff pairing of a daap/dacp client + * Kicks off pairing of a daap/dacp client * * Expects the paring pin to be present in the post request body, e. g.: * @@ -331,7 +82,7 @@ jsonapi_reply_spotify_login(struct evhttp_request *req, struct evbuffer *evbuf, * } */ static int -pairing_kickoff(struct evhttp_request* req) +pairing_kickoff(struct evhttp_request *req) { struct evbuffer *evbuf; json_object* request; @@ -359,7 +110,7 @@ pairing_kickoff(struct evhttp_request* req) } /* - * Endpoint to retrieve pairing information + * Retrieves pairing information * * Example response: * @@ -372,33 +123,309 @@ static int pairing_get(struct evbuffer *evbuf) { char *remote_name; - json_object *reply; - int ret; + json_object *jreply; remote_name = remote_pairing_get_name(); - reply = json_object_new_object(); + CHECK_NULL(L_WEB, jreply = json_object_new_object()); if (remote_name) { - json_object_object_add(reply, "active", json_object_new_boolean(true)); - json_object_object_add(reply, "remote", json_object_new_string(remote_name)); + json_object_object_add(jreply, "active", json_object_new_boolean(true)); + json_object_object_add(jreply, "remote", json_object_new_string(remote_name)); } else { - json_object_object_add(reply, "active", json_object_new_boolean(false)); + json_object_object_add(jreply, "active", json_object_new_boolean(false)); } - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); + CHECK_ERRNO(L_WEB, evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); free(remote_name); + return 0; +} + +static struct json_request * +json_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct json_request *jreq; + int ret; + int i; + + CHECK_NULL(L_WEB, jreq = calloc(1, sizeof(struct json_request))); + + jreq->req = req; + jreq->uri_parsed = uri_parsed; + jreq->query = &(uri_parsed->ev_query); + + // Find a handler for the path + for (i = 0; adm_handlers[i].handler; i++) + { + ret = regexec(&adm_handlers[i].preg, uri_parsed->path, 0, NULL, 0); + if (ret == 0) + { + jreq->handler = adm_handlers[i].handler; + break; + } + } + + if (!jreq->handler) + { + DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in JSON api request: '%s'\n", uri_parsed->path, uri_parsed->uri); + goto error; + } + + return jreq; + + error: + free(jreq); + + return NULL; +} + + +/* --------------------------- REPLY HANDLERS ------------------------------- */ + +/* + * Endpoint to retrieve configuration values + * + * Example response: + * + * { + * "websocket_port": 6603, + * "version": "25.0" + * } + */ +static int +jsonapi_reply_config(struct evbuffer *reply, struct json_request *jreq) +{ + json_object *jreply; + json_object *buildopts; + int websocket_port; + char **buildoptions; + int i; + + CHECK_NULL(L_WEB, jreply = json_object_new_object()); + + // Websocket port +#ifdef HAVE_LIBWEBSOCKETS + websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port"); +#else + websocket_port = 0; +#endif + json_object_object_add(jreply, "websocket_port", json_object_new_int(websocket_port)); + + // forked-daapd version + json_object_object_add(jreply, "version", json_object_new_string(VERSION)); + + // enabled build options + buildopts = json_object_new_array(); + buildoptions = buildopts_get(); + for (i = 0; buildoptions[i]; i++) + { + json_object_array_add(buildopts, json_object_new_string(buildoptions[i])); + } + json_object_object_add(jreply, "buildoptions", buildopts); + + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); + + return 0; +} + +/* + * Endpoint to retrieve informations about the library + * + * Example response: + * + * { + * "artists": 84, + * "albums": 151, + * "songs": 3085, + * "db_playtime": 687824, + * "updating": false + *} + */ +static int +jsonapi_reply_library(struct evbuffer *reply, struct json_request *jreq) +{ + struct query_params qp; + struct filecount_info fci; + int artists; + int albums; + bool is_scanning; + json_object *jreply; + int ret; + + // Fetch values for response + + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_COUNT_ITEMS; + ret = db_filecount_get(&fci, &qp); if (ret < 0) { - DPRINTF(E_LOG, L_WEB, "pairing: Couldn't add pairing information data to response buffer.\n"); + DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n"); return -1; } + artists = db_files_get_artist_count(); + albums = db_files_get_album_count(); + + is_scanning = library_is_scanning(); + + + // Build json response + + CHECK_NULL(L_WEB, jreply = json_object_new_object()); + + json_object_object_add(jreply, "artists", json_object_new_int(artists)); + json_object_object_add(jreply, "albums", json_object_new_int(albums)); + json_object_object_add(jreply, "songs", json_object_new_int(fci.count)); + json_object_object_add(jreply, "db_playtime", json_object_new_int64((fci.length / 1000))); + json_object_object_add(jreply, "updating", json_object_new_boolean(is_scanning)); + + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); + + return 0; +} + +/* + * Endpoint to trigger a library rescan + */ +static int +jsonapi_reply_update(struct evbuffer *reply, struct json_request *jreq) +{ + library_rescan(); + return 0; +} + +/* + * Endpoint to retrieve information about the spotify integration + * + * Exampe response: + * + * { + * "enabled": true, + * "oauth_uri": "https://accounts.spotify.com/authorize/?client_id=... + * } + */ +static int +jsonapi_reply_spotify(struct evbuffer *reply, struct json_request *jreq) +{ + json_object *jreply; + + CHECK_NULL(L_WEB, jreply = json_object_new_object()); + +#ifdef HAVE_SPOTIFY_H + int httpd_port; + char redirect_uri[256]; + char *oauth_uri; + struct spotify_status_info info; + + json_object_object_add(jreply, "enabled", json_object_new_boolean(true)); + + httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); + snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port); + + oauth_uri = spotifywebapi_oauth_uri_get(redirect_uri); + if (!oauth_uri) + { + DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n"); + jparse_free(jreply); + return -1; + } + + json_object_object_add(jreply, "oauth_uri", json_object_new_string(oauth_uri)); + free(oauth_uri); + + spotify_status_info_get(&info); + json_object_object_add(jreply, "libspotify_installed", json_object_new_boolean(info.libspotify_installed)); + json_object_object_add(jreply, "libspotify_logged_in", json_object_new_boolean(info.libspotify_logged_in)); + json_object_object_add(jreply, "libspotify_user", json_object_new_string(info.libspotify_user)); + json_object_object_add(jreply, "webapi_token_valid", json_object_new_boolean(info.webapi_token_valid)); + json_object_object_add(jreply, "webapi_user", json_object_new_string(info.webapi_user)); + +#else + json_object_object_add(jreply, "enabled", json_object_new_boolean(false)); +#endif + + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); + + return 0; +} + +static int +jsonapi_reply_spotify_login(struct evbuffer *reply, struct json_request *jreq) +{ +#ifdef HAVE_SPOTIFY_H + struct evbuffer *in_evbuf; + json_object* request; + const char *user; + const char *password; + char *errmsg = NULL; + json_object* jreply; + json_object* errors; + int ret; + + DPRINTF(E_DBG, L_WEB, "Received Spotify login request\n"); + + in_evbuf = evhttp_request_get_input_buffer(jreq->req); + + request = jparse_obj_from_evbuffer(in_evbuf); + if (!request) + { + DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n"); + return -1; + } + + CHECK_NULL(L_WEB, jreply = json_object_new_object()); + + user = jparse_str_from_obj(request, "user"); + password = jparse_str_from_obj(request, "password"); + if (user && strlen(user) > 0 && password && strlen(password) > 0) + { + ret = spotify_login_user(user, password, &errmsg); + if (ret < 0) + { + json_object_object_add(jreply, "success", json_object_new_boolean(false)); + errors = json_object_new_object(); + json_object_object_add(errors, "error", json_object_new_string(errmsg)); + json_object_object_add(jreply, "errors", errors); + } + else + { + json_object_object_add(jreply, "success", json_object_new_boolean(true)); + } + free(errmsg); + } + else + { + DPRINTF(E_LOG, L_WEB, "No user or password in spotify login post request\n"); + + json_object_object_add(jreply, "success", json_object_new_boolean(false)); + errors = json_object_new_object(); + if (!user || strlen(user) == 0) + json_object_object_add(errors, "user", json_object_new_string("Username is required")); + if (!password || strlen(password) == 0) + json_object_object_add(errors, "password", json_object_new_string("Password is required")); + json_object_object_add(jreply, "errors", errors); + } + + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); + +#else + DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n"); +#endif + return 0; } @@ -409,40 +436,36 @@ pairing_get(struct evbuffer *evbuf) * If request is a POST request, tries to pair the active remote with the given pin. */ static int -jsonapi_reply_pairing(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) +jsonapi_reply_pairing(struct evbuffer *reply, struct json_request *jreq) { - if (evhttp_request_get_command(req) == EVHTTP_REQ_POST) + if (evhttp_request_get_command(jreq->req) == EVHTTP_REQ_POST) { - return pairing_kickoff(req); + return pairing_kickoff(jreq->req); } - return pairing_get(evbuf); + return pairing_get(reply); } static int -jsonapi_reply_lastfm(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) +jsonapi_reply_lastfm(struct evbuffer *reply, struct json_request *jreq) { - json_object *reply; + json_object *jreply; bool enabled = false; bool scrobbling_enabled = false; - int ret; #ifdef LASTFM enabled = true; scrobbling_enabled = lastfm_is_enabled(); #endif - reply = json_object_new_object(); - json_object_object_add(reply, "enabled", json_object_new_boolean(enabled)); - json_object_object_add(reply, "scrobbling_enabled", json_object_new_boolean(scrobbling_enabled)); + CHECK_NULL(L_WEB, jreply = json_object_new_object()); - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "LastFM: Couldn't add LastFM enabled to response buffer.\n"); - return -1; - } + json_object_object_add(jreply, "enabled", json_object_new_boolean(enabled)); + json_object_object_add(jreply, "scrobbling_enabled", json_object_new_boolean(scrobbling_enabled)); + + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); return 0; } @@ -451,21 +474,21 @@ jsonapi_reply_lastfm(struct evhttp_request *req, struct evbuffer *evbuf, char *u * Endpoint to log into LastFM */ static int -jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) +jsonapi_reply_lastfm_login(struct evbuffer *reply, struct json_request *jreq) { #ifdef LASTFM struct evbuffer *in_evbuf; - json_object* request; + json_object *request; const char *user; const char *password; char *errmsg = NULL; - json_object* reply; - json_object* errors; + json_object *jreply; + json_object *errors; int ret; DPRINTF(E_DBG, L_WEB, "Received LastFM login request\n"); - in_evbuf = evhttp_request_get_input_buffer(req); + in_evbuf = evhttp_request_get_input_buffer(jreq->req); request = jparse_obj_from_evbuffer(in_evbuf); if (!request) { @@ -473,7 +496,7 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c return -1; } - reply = json_object_new_object(); + CHECK_NULL(L_WEB, jreply = json_object_new_object()); user = jparse_str_from_obj(request, "user"); password = jparse_str_from_obj(request, "password"); @@ -482,17 +505,17 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c ret = lastfm_login_user(user, password, &errmsg); if (ret < 0) { - json_object_object_add(reply, "success", json_object_new_boolean(false)); + json_object_object_add(jreply, "success", json_object_new_boolean(false)); errors = json_object_new_object(); if (errmsg) json_object_object_add(errors, "error", json_object_new_string(errmsg)); else json_object_object_add(errors, "error", json_object_new_string("Unknown error")); - json_object_object_add(reply, "errors", errors); + json_object_object_add(jreply, "errors", errors); } else { - json_object_object_add(reply, "success", json_object_new_boolean(true)); + json_object_object_add(jreply, "success", json_object_new_boolean(true)); } free(errmsg); } @@ -500,22 +523,18 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c { DPRINTF(E_LOG, L_WEB, "No user or password in LastFM login post request\n"); - json_object_object_add(reply, "success", json_object_new_boolean(false)); + json_object_object_add(jreply, "success", json_object_new_boolean(false)); errors = json_object_new_object(); if (!user || strlen(user) == 0) json_object_object_add(errors, "user", json_object_new_string("Username is required")); if (!password || strlen(password) == 0) json_object_object_add(errors, "password", json_object_new_string("Password is required")); - json_object_object_add(reply, "errors", errors); + json_object_object_add(jreply, "errors", errors); } - ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply)); - jparse_free(reply); - if (ret < 0) - { - DPRINTF(E_LOG, L_WEB, "LastFM: Couldn't add LastFM login data to response buffer.\n"); - return -1; - } + CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); #else DPRINTF(E_LOG, L_WEB, "Received LastFM login request but was not compiled with enable-lastfm\n"); @@ -525,7 +544,7 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c } static int -jsonapi_reply_lastfm_logout(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) +jsonapi_reply_lastfm_logout(struct evbuffer *reply, struct json_request *jreq) { #ifdef LASTFM lastfm_logout(); @@ -547,119 +566,59 @@ static struct uri_map adm_handlers[] = { .regexp = NULL, .handler = NULL } }; -void -jsonapi_request(struct evhttp_request *req) -{ - char *full_uri; - char *uri; - char *ptr; - struct evbuffer *evbuf; - struct evkeyvalq query; - struct evkeyvalq *headers; - int handler; - int ret; - int i; - /* Check authentication */ +/* ------------------------------- JSON API --------------------------------- */ + +void +jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct json_request *jreq; + struct evbuffer *reply; + struct evkeyvalq *headers; + int ret; + + DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri); + if (!httpd_admin_check_auth(req)) { - DPRINTF(E_DBG, L_WEB, "JSON api request denied;\n"); + DPRINTF(E_DBG, L_WEB, "JSON api request denied\n"); return; } - memset(&query, 0, sizeof(struct evkeyvalq)); - - full_uri = httpd_fixup_uri(req); - if (!full_uri) + jreq = json_request_parse(req, uri_parsed); + if (!jreq) { - evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); return; } - ptr = strchr(full_uri, '?'); - if (ptr) - *ptr = '\0'; - - uri = strdup(full_uri); - if (!uri) - { - free(full_uri); - evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; - } - - if (ptr) - *ptr = '?'; - - ptr = uri; - uri = evhttp_decode_uri(uri); - free(ptr); - - DPRINTF(E_DBG, L_WEB, "Web admin request: %s\n", full_uri); - - handler = -1; - for (i = 0; adm_handlers[i].handler; i++) - { - ret = regexec(&adm_handlers[i].preg, uri, 0, NULL, 0); - if (ret == 0) - { - handler = i; - break; - } - } - - if (handler < 0) - { - DPRINTF(E_LOG, L_WEB, "Unrecognized web admin request\n"); - - evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); - - free(uri); - free(full_uri); - return; - } - - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_WEB, "Could not allocate evbuffer for Web Admin reply\n"); - - evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - - free(uri); - free(full_uri); - return; - } - - evhttp_parse_query(full_uri, &query); - headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION); - ret = adm_handlers[handler].handler(req, evbuf, uri, &query); + CHECK_NULL(L_WEB, reply = evbuffer_new()); + + ret = jreq->handler(reply, jreq); if (ret < 0) { - evhttp_send_error(req, 500, "Internal Server Error"); - } - else - { - headers = evhttp_request_get_output_headers(req); - evhttp_add_header(headers, "Content-Type", "application/json"); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_error(req, 500, "Internal Server Error"); + goto error; } - evbuffer_free(evbuf); - evhttp_clear_headers(&query); - free(uri); - free(full_uri); + evhttp_add_header(headers, "Content-Type", "application/json"); + + httpd_send_reply(req, HTTP_OK, "OK", reply, 0); + + error: + evbuffer_free(reply); + free(jreq); } int -jsonapi_is_request(struct evhttp_request *req, char *uri) +jsonapi_is_request(const char *path) { - if (strncmp(uri, "/api/", strlen("/api/")) == 0) + if (strncmp(path, "/api/", strlen("/api/")) == 0) return 1; - if (strcmp(uri, "/api") == 0) + if (strcmp(path, "/api") == 0) return 1; return 0; @@ -679,7 +638,7 @@ jsonapi_init(void) { regerror(ret, &adm_handlers[i].preg, buf, sizeof(buf)); - DPRINTF(E_FATAL, L_WEB, "Admin web interface init failed; regexp error: %s\n", buf); + DPRINTF(E_FATAL, L_WEB, "JSON api init failed; regexp error: %s\n", buf); return -1; } } diff --git a/src/httpd_jsonapi.h b/src/httpd_jsonapi.h index 2a305343..e6113ea6 100644 --- a/src/httpd_jsonapi.h +++ b/src/httpd_jsonapi.h @@ -2,7 +2,7 @@ #ifndef __HTTPD_JSONAPI_H__ #define __HTTPD_JSONAPI_H__ -#include +#include "httpd.h" int jsonapi_init(void); @@ -11,9 +11,9 @@ void jsonapi_deinit(void); void -jsonapi_request(struct evhttp_request *req); +jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); int -jsonapi_is_request(struct evhttp_request *req, char *uri); +jsonapi_is_request(const char *path); #endif /* !__HTTPD_JSONAPI_H__ */ diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index db2eebb2..bb495fc0 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -32,16 +32,14 @@ #include #include -#include -#include +#include "httpd_rsp.h" #include "logger.h" #include "db.h" #include "conffile.h" #include "misc.h" #include "httpd.h" #include "transcode.h" -#include "httpd_rsp.h" #include "rsp_query.h" #define RSP_VERSION "1.0" @@ -54,16 +52,28 @@ #define F_DETAILED (1 << 3) #define F_ALWAYS (F_FULL | F_BROWSE | F_ID | F_DETAILED) -struct field_map { - char *field; - size_t offset; - int flags; +// Will be filled out by rsp_request_parse() +struct rsp_request { + // The parsed request URI given to us by httpd.c + struct httpd_uri_parsed *uri_parsed; + // Shortcut to &uri_parsed->ev_query + struct evkeyvalq *query; + // http request struct + struct evhttp_request *req; + // A pointer to the handler that will process the request + void (*handler)(struct rsp_request *rreq); }; struct uri_map { regex_t preg; char *regexp; - void (*handler)(struct evhttp_request *req, char **uri, struct evkeyvalq *query); + void (*handler)(struct rsp_request *rreq); +}; + +struct field_map { + char *field; + size_t offset; + int flags; }; static const struct field_map pl_fields[] = @@ -123,6 +133,11 @@ static const struct field_map rsp_fields[] = { NULL, 0, 0 } }; +/* Forward declaration of handlers */ +static struct uri_map rsp_handlers[]; + + +/* -------------------------------- HELPERS --------------------------------- */ static struct evbuffer * mxml_to_evbuf(mxml_node_t *tree) @@ -161,61 +176,6 @@ mxml_to_evbuf(mxml_node_t *tree) return evbuf; } -/* Forward */ -static void -rsp_send_error(struct evhttp_request *req, char *errmsg); - -static int -get_query_params(struct evhttp_request *req, struct evkeyvalq *query, struct query_params *qp) -{ - const char *param; - int ret; - - qp->offset = 0; - param = evhttp_find_header(query, "offset"); - if (param) - { - ret = safe_atoi32(param, &qp->offset); - if (ret < 0) - { - rsp_send_error(req, "Invalid offset"); - return -1; - } - } - - qp->limit = 0; - param = evhttp_find_header(query, "limit"); - if (param) - { - ret = safe_atoi32(param, &qp->limit); - if (ret < 0) - { - rsp_send_error(req, "Invalid limit"); - return -1; - } - } - - if (qp->offset || qp->limit) - qp->idx_type = I_SUB; - else - qp->idx_type = I_NONE; - - qp->sort = S_NONE; - - param = evhttp_find_header(query, "query"); - if (param) - { - DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param); - - qp->filter = rsp_query_parse_sql(param); - if (!qp->filter) - DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n"); - } - - return 0; -} - - static void rsp_send_error(struct evhttp_request *req, char *errmsg) { @@ -265,6 +225,56 @@ rsp_send_error(struct evhttp_request *req, char *errmsg) evbuffer_free(evbuf); } +static int +query_params_set(struct query_params *qp, struct rsp_request *rreq) +{ + const char *param; + int ret; + + qp->offset = 0; + param = evhttp_find_header(rreq->query, "offset"); + if (param) + { + ret = safe_atoi32(param, &qp->offset); + if (ret < 0) + { + rsp_send_error(rreq->req, "Invalid offset"); + return -1; + } + } + + qp->limit = 0; + param = evhttp_find_header(rreq->query, "limit"); + if (param) + { + ret = safe_atoi32(param, &qp->limit); + if (ret < 0) + { + rsp_send_error(rreq->req, "Invalid limit"); + return -1; + } + } + + if (qp->offset || qp->limit) + qp->idx_type = I_SUB; + else + qp->idx_type = I_NONE; + + qp->sort = S_NONE; + + param = evhttp_find_header(rreq->query, "query"); + if (param) + { + DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param); + + qp->filter = rsp_query_parse_sql(param); + if (!qp->filter) + DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n"); + } + + return 0; +} + static void rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply) { @@ -290,9 +300,75 @@ rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply) evbuffer_free(evbuf); } +static struct rsp_request * +rsp_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct rsp_request *rreq; + int ret; + int i; + + CHECK_NULL(L_DAAP, rreq = calloc(1, sizeof(struct rsp_request))); + + rreq->req = req; + rreq->uri_parsed = uri_parsed; + rreq->query = &(uri_parsed->ev_query); + + // Find a handler for the path + for (i = 0; rsp_handlers[i].handler; i++) + { + ret = regexec(&rsp_handlers[i].preg, uri_parsed->path, 0, NULL, 0); + if (ret == 0) + { + rreq->handler = rsp_handlers[i].handler; + break; + } + } + + if (!rreq->handler) + { + DPRINTF(E_LOG, L_RSP, "Unrecognized path '%s' in RSP request: '%s'\n", uri_parsed->path, uri_parsed->uri); + goto error; + } + + return rreq; + + error: + free(rreq); + + return NULL; +} + +static int +rsp_request_authorize(struct rsp_request *rreq) +{ + char *passwd; + int ret; + + if (cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode")) + return 0; + + passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password"); + if (!passwd) + return 0; + + DPRINTF(E_DBG, L_RSP, "Checking authentication for library\n"); + + // We don't care about the username + ret = httpd_basic_auth(rreq->req, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name")); + if (ret != 0) + { + DPRINTF(E_LOG, L_RSP, "Unsuccessful library authentication\n"); + return -1; + } + + return 0; +} + + +/* --------------------------- REPLY HANDLERS ------------------------------- */ static void -rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +rsp_reply_info(struct rsp_request *rreq) { mxml_node_t *reply; mxml_node_t *status; @@ -342,11 +418,11 @@ rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query) node = mxmlNewElement(info, "name"); mxmlNewText(node, 0, library); - rsp_send_reply(req, reply); + rsp_send_reply(rreq->req, reply); } static void -rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +rsp_reply_db(struct rsp_request *rreq) { struct query_params qp; struct db_playlist_info dbpli; @@ -369,7 +445,7 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); - rsp_send_error(req, "Could not start query"); + rsp_send_error(rreq->req, "Could not start query"); return; } @@ -419,7 +495,7 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) mxmlDelete(reply); db_query_end(&qp); - rsp_send_error(req, "Error fetching query results"); + rsp_send_error(rreq->req, "Error fetching query results"); return; } @@ -433,11 +509,11 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(rreq->req, reply); } static void -rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +rsp_reply_playlist(struct rsp_request *rreq) { struct query_params qp; struct db_media_file_info dbmfi; @@ -460,10 +536,10 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que memset(&qp, 0, sizeof(struct query_params)); - ret = safe_atoi32(uri[2], &qp.id); + ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &qp.id); if (ret < 0) { - rsp_send_error(req, "Invalid playlist ID"); + rsp_send_error(rreq->req, "Invalid playlist ID"); return; } @@ -473,7 +549,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que qp.type = Q_PLITEMS; mode = F_FULL; - param = evhttp_find_header(query, "type"); + param = evhttp_find_header(rreq->query, "type"); if (param) { if (strcasecmp(param, "full") == 0) @@ -488,7 +564,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que DPRINTF(E_LOG, L_RSP, "Unknown browse mode %s\n", param); } - ret = get_query_params(req, query, &qp); + ret = query_params_set(&qp, rreq); if (ret < 0) return; @@ -497,7 +573,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); - rsp_send_error(req, "Could not start query"); + rsp_send_error(rreq->req, "Could not start query"); if (qp.filter) free(qp.filter); @@ -536,7 +612,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que /* Items block (all items) */ while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) { - headers = evhttp_request_get_input_headers(req); + headers = evhttp_request_get_input_headers(rreq->req); ua = evhttp_find_header(headers, "User-Agent"); client_codecs = evhttp_find_header(headers, "Accept-Codecs"); @@ -607,7 +683,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que mxmlDelete(reply); db_query_end(&qp); - rsp_send_error(req, "Error fetching query results"); + rsp_send_error(rreq->req, "Error fetching query results"); return; } @@ -621,11 +697,11 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(rreq->req, reply); } static void -rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +rsp_reply_browse(struct rsp_request *rreq) { struct query_params qp; char *browse_item; @@ -638,30 +714,30 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query memset(&qp, 0, sizeof(struct query_params)); - if (strcmp(uri[3], "artist") == 0) + if (strcmp(rreq->uri_parsed->path_parts[3], "artist") == 0) qp.type = Q_BROWSE_ARTISTS; - else if (strcmp(uri[3], "genre") == 0) + else if (strcmp(rreq->uri_parsed->path_parts[3], "genre") == 0) qp.type = Q_BROWSE_GENRES; - else if (strcmp(uri[3], "album") == 0) + else if (strcmp(rreq->uri_parsed->path_parts[3], "album") == 0) qp.type = Q_BROWSE_ALBUMS; - else if (strcmp(uri[3], "composer") == 0) + else if (strcmp(rreq->uri_parsed->path_parts[3], "composer") == 0) qp.type = Q_BROWSE_COMPOSERS; else { - DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", uri[3]); + DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", rreq->uri_parsed->path_parts[3]); - rsp_send_error(req, "Unsupported browse type"); + rsp_send_error(rreq->req, "Unsupported browse type"); return; } - ret = safe_atoi32(uri[2], &qp.id); + ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &qp.id); if (ret < 0) { - rsp_send_error(req, "Invalid playlist ID"); + rsp_send_error(rreq->req, "Invalid playlist ID"); return; } - ret = get_query_params(req, query, &qp); + ret = query_params_set(&qp, rreq); if (ret < 0) return; @@ -670,7 +746,7 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); - rsp_send_error(req, "Could not start query"); + rsp_send_error(rreq->req, "Could not start query"); if (qp.filter) free(qp.filter); @@ -722,7 +798,7 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query mxmlDelete(reply); db_query_end(&qp); - rsp_send_error(req, "Error fetching query results"); + rsp_send_error(rreq->req, "Error fetching query results"); return; } @@ -736,20 +812,20 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(rreq->req, reply); } static void -rsp_stream(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +rsp_stream(struct rsp_request *rreq) { int id; int ret; - ret = safe_atoi32(uri[2], &id); + ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &id); if (ret < 0) - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + httpd_send_error(rreq->req, HTTP_BADREQUEST, "Bad Request"); else - httpd_stream_file(req, id); + httpd_stream_file(rreq->req, id); } @@ -782,127 +858,40 @@ static struct uri_map rsp_handlers[] = }; +/* -------------------------------- RSP API --------------------------------- */ + void -rsp_request(struct evhttp_request *req) +rsp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { - char *full_uri; - char *uri; - char *ptr; - char *uri_parts[5]; - struct evkeyvalq query; - cfg_t *lib; - char *libname; - char *passwd; - int handler; - int i; + struct rsp_request *rreq; int ret; - memset(&query, 0, sizeof(struct evkeyvalq)); + DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", uri_parsed->uri); - full_uri = httpd_fixup_uri(req); - if (!full_uri) + rreq = rsp_request_parse(req, uri_parsed); + if (!rreq) { rsp_send_error(req, "Server error"); return; } - ptr = strchr(full_uri, '?'); - if (ptr) - *ptr = '\0'; - - uri = strdup(full_uri); - if (!uri) + ret = rsp_request_authorize(rreq); + if (ret < 0) { - rsp_send_error(req, "Server error"); - - free(full_uri); + rsp_send_error(req, "Access denied"); + free(rreq); return; } - if (ptr) - *ptr = '?'; + rreq->handler(rreq); - ptr = uri; - uri = evhttp_decode_uri(uri); - free(ptr); - - DPRINTF(E_DBG, L_RSP, "RSP request: %s\n", full_uri); - - handler = -1; - for (i = 0; rsp_handlers[i].handler; i++) - { - ret = regexec(&rsp_handlers[i].preg, uri, 0, NULL, 0); - if (ret == 0) - { - handler = i; - break; - } - } - - if (handler < 0) - { - DPRINTF(E_LOG, L_RSP, "Unrecognized RSP request\n"); - - rsp_send_error(req, "Bad path"); - - free(uri); - free(full_uri); - return; - } - - /* Check authentication */ - lib = cfg_getsec(cfg, "library"); - passwd = cfg_getstr(lib, "password"); - if (passwd && !cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode")) - { - libname = cfg_getstr(lib, "name"); - - DPRINTF(E_DBG, L_HTTPD, "Checking authentication for library '%s'\n", libname); - - /* We don't care about the username */ - ret = httpd_basic_auth(req, NULL, passwd, libname); - if (ret != 0) - { - free(uri); - free(full_uri); - return; - } - - DPRINTF(E_DBG, L_HTTPD, "Library authentication successful\n"); - } - - memset(uri_parts, 0, sizeof(uri_parts)); - - uri_parts[0] = strtok_r(uri, "/", &ptr); - for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++) - { - uri_parts[i] = strtok_r(NULL, "/", &ptr); - } - - if (!uri_parts[0] || uri_parts[i - 1] || (i < 2)) - { - DPRINTF(E_LOG, L_RSP, "RSP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0); - - rsp_send_error(req, "Bad path"); - - free(uri); - free(full_uri); - return; - } - - evhttp_parse_query(full_uri, &query); - - rsp_handlers[handler].handler(req, uri_parts, &query); - - evhttp_clear_headers(&query); - free(uri); - free(full_uri); + free(rreq); } int -rsp_is_request(struct evhttp_request *req, char *uri) +rsp_is_request(const char *path) { - if (strncmp(uri, "/rsp/", strlen("/rsp/")) == 0) + if (strncmp(path, "/rsp/", strlen("/rsp/")) == 0) return 1; return 0; diff --git a/src/httpd_rsp.h b/src/httpd_rsp.h index 1e5a130a..637afcfe 100644 --- a/src/httpd_rsp.h +++ b/src/httpd_rsp.h @@ -2,7 +2,7 @@ #ifndef __HTTPD_RSP_H__ #define __HTTPD_RSP_H__ -#include +#include "httpd.h" int rsp_init(void); @@ -11,9 +11,9 @@ void rsp_deinit(void); void -rsp_request(struct evhttp_request *req); +rsp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); int -rsp_is_request(struct evhttp_request *req, char *uri); +rsp_is_request(const char *path); #endif /* !__HTTPD_RSP_H__ */ diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 721509c3..c0e0e25b 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -30,16 +30,13 @@ #include #include -#include -#include +#include "httpd_streaming.h" #include "logger.h" #include "conffile.h" #include "transcode.h" #include "player.h" #include "listener.h" -#include "httpd.h" -#include "httpd_streaming.h" /* httpd event base, from httpd.c */ extern struct event_base *evbase_httpd; @@ -213,19 +210,7 @@ streaming_write(uint8_t *buf, uint64_t rtptime) } int -streaming_is_request(struct evhttp_request *req, char *uri) -{ - char *ptr; - - ptr = strrchr(uri, '/'); - if (!ptr || (strcasecmp(ptr, "/stream.mp3") != 0)) - return 0; - - return 1; -} - -int -streaming_request(struct evhttp_request *req) +streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { struct streaming_session *session; struct evhttp_connection *evcon; @@ -284,6 +269,18 @@ streaming_request(struct evhttp_request *req) return 0; } +int +streaming_is_request(const char *path) +{ + char *ptr; + + ptr = strrchr(path, '/'); + if (ptr && (strcasecmp(ptr, "/stream.mp3") == 0)) + return 1; + + return 0; +} + int streaming_init(void) { diff --git a/src/httpd_streaming.h b/src/httpd_streaming.h index 2fcbc805..fed2838a 100644 --- a/src/httpd_streaming.h +++ b/src/httpd_streaming.h @@ -2,7 +2,7 @@ #ifndef __HTTPD_STREAMING_H__ #define __HTTPD_STREAMING_H__ -#include +#include "httpd.h" /* httpd_streaming takes care of incoming requests to /stream.mp3 * It will receive decoded audio from the player, and encode it, and @@ -14,10 +14,10 @@ void streaming_write(uint8_t *buf, uint64_t rtptime); int -streaming_is_request(struct evhttp_request *req, char *uri); +streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); int -streaming_request(struct evhttp_request *req); +streaming_is_request(const char *path); int streaming_init(void);