diff --git a/src/httpd.c b/src/httpd.c index 05144f14..12f237d7 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -883,12 +883,17 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) void httpd_uri_free(struct httpd_uri_parsed *parsed) { + int i; + if (!parsed) return; free(parsed->uri_decoded); free(parsed->path); - free(parsed->path_parts[0]); + for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++) + { + free(parsed->path_parts[i]); + } evhttp_clear_headers(&(parsed->ev_query)); @@ -902,8 +907,9 @@ struct httpd_uri_parsed * httpd_uri_parse(const char *uri) { struct httpd_uri_parsed *parsed; - const char *path; + char *path = NULL; const char *query; + char *path_part; char *ptr; int i; int ret; @@ -937,7 +943,7 @@ httpd_uri_parse(const char *uri) } } - path = evhttp_uri_get_path(parsed->ev_uri); + path = strdup(evhttp_uri_get_path(parsed->ev_uri)); if (!path) { DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri); @@ -951,23 +957,26 @@ httpd_uri_parse(const char *uri) 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++) + path_part = strtok_r(path, "/", &ptr); + for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++) { - parsed->path_parts[i] = strtok_r(NULL, "/", &ptr); + parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL); + path_part = strtok_r(NULL, "/", &ptr); } - if (!parsed->path_parts[0] || parsed->path_parts[i - 1] || (i < 2)) + if (path_part) { - DPRINTF(E_LOG, L_HTTPD, "URI path has too many/few components (%d): '%s'\n", (parsed->path_parts[0]) ? i : 0, parsed->path); + // If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array + DPRINTF(E_LOG, L_HTTPD, "URI path has too many components (%d): '%s'\n", i, parsed->path); goto error; } + free(path); + return parsed; error: + free(path); httpd_uri_free(parsed); return NULL; } diff --git a/src/httpd.h b/src/httpd.h index 15ec4739..10252479 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -23,10 +23,9 @@ enum httpd_send_flags * * 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). + * then part_parts[0] is "foo", [1] is "bar" and the rest is null. * - * The allocated strings are URI decoded. + * Each path_part is an allocated URI decoded string. */ struct httpd_uri_parsed { diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 5989c440..ecb1bb41 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -3959,10 +3959,11 @@ jsonapi_reply_queue_save(struct httpd_request *hreq) } static int -jsonapi_reply_library_genres(struct httpd_request *hreq) +jsonapi_reply_library_browse(struct httpd_request *hreq) { struct query_params query_params; const char *param; + const char *browse_type; enum media_kind media_kind; json_object *reply; json_object *items; @@ -3972,6 +3973,9 @@ jsonapi_reply_library_genres(struct httpd_request *hreq) if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE)) return HTTP_NOTMODIFIED; + browse_type = hreq->uri_parsed->path_parts[2]; + DPRINTF(E_DBG, L_WEB, "Browse query with type '%s'\n", browse_type); + media_kind = 0; param = evhttp_find_header(hreq->query, "media_kind"); if (param) @@ -3994,8 +3998,23 @@ jsonapi_reply_library_genres(struct httpd_request *hreq) if (ret < 0) goto error; - query_params.type = Q_BROWSE_GENRES; - query_params.idx_type = I_NONE; + if (strcmp(browse_type, "genres") == 0) + { + query_params.type = Q_BROWSE_GENRES; + query_params.sort = S_GENRE; + query_params.idx_type = I_NONE; + } + else if (strcmp(browse_type, "composers") == 0) + { + query_params.type = Q_BROWSE_COMPOSERS; + query_params.sort = S_COMPOSER; + query_params.idx_type = I_NONE; + } + else + { + DPRINTF(E_LOG, L_WEB, "Invalid browse type '%s'\n", browse_type); + goto error; + } if (media_kind) query_params.filter = db_mprintf("(f.media_kind = %d)", media_kind); @@ -4010,7 +4029,7 @@ jsonapi_reply_library_genres(struct httpd_request *hreq) ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply)); if (ret < 0) - DPRINTF(E_LOG, L_WEB, "browse: Couldn't add genres to response buffer.\n"); + DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse items to response buffer.\n"); error: jparse_free(reply); @@ -4023,34 +4042,23 @@ jsonapi_reply_library_genres(struct httpd_request *hreq) } static int -jsonapi_reply_library_composers(struct httpd_request *hreq) +jsonapi_reply_library_browseitem(struct httpd_request *hreq) { struct query_params query_params; - const char *param; - enum media_kind media_kind; + const char *browse_type; + const char *item_name; + struct db_browse_info dbbi; json_object *reply; - json_object *items; - int total; int ret; if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE)) return HTTP_NOTMODIFIED; - media_kind = 0; - param = evhttp_find_header(hreq->query, "media_kind"); - if (param) - { - media_kind = db_media_kind_enum(param); - if (!media_kind) - { - DPRINTF(E_LOG, L_WEB, "Invalid media kind '%s'\n", param); - return HTTP_BADREQUEST; - } - } + browse_type = hreq->uri_parsed->path_parts[2]; + item_name = hreq->uri_parsed->path_parts[3]; + DPRINTF(E_DBG, L_WEB, "Browse item query with type '%s'\n", browse_type); reply = json_object_new_object(); - items = json_object_new_array(); - json_object_object_add(reply, "items", items); memset(&query_params, 0, sizeof(struct query_params)); @@ -4058,26 +4066,46 @@ jsonapi_reply_library_composers(struct httpd_request *hreq) if (ret < 0) goto error; - query_params.type = Q_BROWSE_COMPOSERS; - query_params.sort = S_COMPOSER; - query_params.idx_type = I_NONE; + if (strcmp(browse_type, "genres") == 0) + { + query_params.type = Q_BROWSE_GENRES; + query_params.sort = S_GENRE; + query_params.idx_type = I_NONE; + query_params.filter = db_mprintf("(f.genre = %Q)", item_name); + } + else if (strcmp(browse_type, "composers") == 0) + { + query_params.type = Q_BROWSE_COMPOSERS; + query_params.sort = S_COMPOSER; + query_params.idx_type = I_NONE; + query_params.filter = db_mprintf("(f.composer = %Q)", item_name); + } + else + { + DPRINTF(E_LOG, L_WEB, "Invalid browse type '%s'\n", browse_type); + goto error; + } - if (media_kind) - query_params.filter = db_mprintf("(f.media_kind = %d)", media_kind); - - ret = fetch_browse_info(&query_params, items, &total); + ret = db_query_start(&query_params); if (ret < 0) goto error; - json_object_object_add(reply, "total", json_object_new_int(total)); - json_object_object_add(reply, "offset", json_object_new_int(query_params.offset)); - json_object_object_add(reply, "limit", json_object_new_int(query_params.limit)); + if ((ret = db_query_fetch_browse(&dbbi, &query_params)) == 0) + { + reply = browse_info_to_json(&dbbi); + } + if (!reply) + { + ret = -1; + goto error; + } ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply)); if (ret < 0) - DPRINTF(E_LOG, L_WEB, "browse: Couldn't add composers to response buffer.\n"); + DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse item to response buffer.\n"); error: + db_query_end(&query_params); jparse_free(reply); free_query_params(&query_params, 1); @@ -4087,7 +4115,6 @@ jsonapi_reply_library_composers(struct httpd_request *hreq) return HTTP_OK; } - static int jsonapi_reply_library_count(struct httpd_request *hreq) { @@ -4737,8 +4764,8 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_get_byid }, { EVHTTP_REQ_PUT, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_put_byid }, { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+/playlists$", jsonapi_reply_library_track_playlists }, - { EVHTTP_REQ_GET, "^/api/library/genres$", jsonapi_reply_library_genres}, - { EVHTTP_REQ_GET, "^/api/library/composers$", jsonapi_reply_library_composers }, + { EVHTTP_REQ_GET, "^/api/library/(genres|composers)$", jsonapi_reply_library_browse }, + { EVHTTP_REQ_GET, "^/api/library/(genres|composers)/.*$", jsonapi_reply_library_browseitem }, { EVHTTP_REQ_GET, "^/api/library/count$", jsonapi_reply_library_count }, { EVHTTP_REQ_GET, "^/api/library/files$", jsonapi_reply_library_files }, { EVHTTP_REQ_POST, "^/api/library/add$", jsonapi_reply_library_add },