diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index 62efd456..3cad147b 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -22,11 +22,19 @@ general { loglevel = log # Admin password for the web interface - # If not set (default), access to the web interface is only permitted from localhost + # Note that access to the web interface from computers in + # "trusted_network" (see below) does not require password # admin_password = "" + # Websocket port for the web interface. # websocket_port = 3688 + # Sets who is allowed to connect without authorisation. This applies to + # client types like Remotes, DAAP clients (iTunes) and to the web + # interface. Options are "any", "localhost" or the prefix to one or + # more ipv4/6 networks. The default is { "localhost", "192.168", "fd" } +# trusted_networks = { "localhost", "192.168", "fd" } + # Enable/disable IPv6 ipv6 = yes diff --git a/src/Makefile.am b/src/Makefile.am index b1d4ac05..f465e12a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -114,6 +114,7 @@ forked_daapd_SOURCES = main.c \ httpd_dacp.c httpd_dacp.h \ httpd_jsonapi.c httpd_jsonapi.h \ httpd_streaming.c httpd_streaming.h \ + httpd_oauth.c httpd_oauth.h \ http.c http.h \ dmap_common.c dmap_common.h \ $(FFMPEG_SRC) \ diff --git a/src/cache.c b/src/cache.c index a60819f5..7e49716e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -47,13 +47,14 @@ #include "commands.h" -#define CACHE_VERSION 2 +#define CACHE_VERSION 3 struct cache_arg { char *query; // daap query char *ua; // user agent + int is_remote; int msec; char *path; // artwork path @@ -139,6 +140,7 @@ cache_create_tables(void) " id INTEGER PRIMARY KEY NOT NULL," \ " query VARCHAR(4096) UNIQUE NOT NULL," \ " user_agent VARCHAR(1024)," \ + " is_remote INTEGER DEFAULT 0," \ " msec INTEGER DEFAULT 0," \ " timestamp INTEGER DEFAULT 0" \ ");" @@ -605,7 +607,7 @@ cache_daap_reply_add(const char *query, struct evbuffer *evbuf) static enum command_state cache_daap_query_add(void *arg, int *retval) { -#define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, query, msec, timestamp) VALUES ('%q', '%q', %d, %" PRIi64 ");" +#define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, is_remote, query, msec, timestamp) VALUES ('%q', %d, '%q', %d, %" PRIi64 ");" #define Q_CLEANUP "DELETE FROM queries WHERE id NOT IN (SELECT id FROM queries ORDER BY timestamp DESC LIMIT 20);" struct cache_arg *cmdarg; struct timeval delay = { 60, 0 }; @@ -631,7 +633,7 @@ cache_daap_query_add(void *arg, int *retval) remove_tag(cmdarg->query, "session-id"); remove_tag(cmdarg->query, "revision-number"); - query = sqlite3_mprintf(Q_TMPL, cmdarg->ua, cmdarg->query, cmdarg->msec, (int64_t)time(NULL)); + query = sqlite3_mprintf(Q_TMPL, cmdarg->ua, cmdarg->is_remote, cmdarg->query, cmdarg->msec, (int64_t)time(NULL)); if (!query) { DPRINTF(E_LOG, L_CACHE, "Out of memory making query string.\n"); @@ -809,7 +811,7 @@ cache_daap_update_cb(int fd, short what, void *arg) return; } - ret = sqlite3_prepare_v2(g_db_hdl, "SELECT id, user_agent, query FROM queries;", -1, &stmt, 0); + ret = sqlite3_prepare_v2(g_db_hdl, "SELECT id, user_agent, is_remote, query FROM queries;", -1, &stmt, 0); if (ret != SQLITE_OK) { DPRINTF(E_LOG, L_CACHE, "Error preparing for cache update: %s\n", sqlite3_errmsg(g_db_hdl)); @@ -818,9 +820,9 @@ cache_daap_update_cb(int fd, short what, void *arg) while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { - query = strdup((char *)sqlite3_column_text(stmt, 2)); + query = strdup((char *)sqlite3_column_text(stmt, 3)); - evbuf = daap_reply_build(query, (char *)sqlite3_column_text(stmt, 1)); + evbuf = daap_reply_build(query, (char *)sqlite3_column_text(stmt, 1), sqlite3_column_int(stmt, 2)); if (!evbuf) { DPRINTF(E_LOG, L_CACHE, "Error building DAAP reply for query: %s\n", query); @@ -1315,7 +1317,7 @@ cache_daap_resume(void) } int -cache_daap_get(const char *query, struct evbuffer *evbuf) +cache_daap_get(struct evbuffer *evbuf, const char *query) { struct cache_arg cmdarg; @@ -1329,7 +1331,7 @@ cache_daap_get(const char *query, struct evbuffer *evbuf) } void -cache_daap_add(const char *query, const char *ua, int msec) +cache_daap_add(const char *query, const char *ua, int is_remote, int msec) { struct cache_arg *cmdarg; @@ -1345,6 +1347,7 @@ cache_daap_add(const char *query, const char *ua, int msec) cmdarg->query = strdup(query); cmdarg->ua = strdup(ua); + cmdarg->is_remote = is_remote; cmdarg->msec = msec; commands_exec_async(cmdbase, cache_daap_query_add, cmdarg); diff --git a/src/cache.h b/src/cache.h index 69acf4d0..3e7aa8ae 100644 --- a/src/cache.h +++ b/src/cache.h @@ -13,10 +13,10 @@ void cache_daap_resume(void); int -cache_daap_get(const char *query, struct evbuffer *evbuf); +cache_daap_get(struct evbuffer *evbuf, const char *query); void -cache_daap_add(const char *query, const char *ua, int msec); +cache_daap_add(const char *query, const char *ua, int is_remote, int msec); int cache_daap_threshold(void); diff --git a/src/conffile.c b/src/conffile.c index dc463dd8..302ea57e 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -45,14 +45,12 @@ static int cb_loglevel(cfg_t *cfg, cfg_opt_t *opt, const char *value, void *resu static cfg_opt_t sec_general[] = { CFG_STR("uid", "nobody", CFGF_NONE), + CFG_STR("db_path", STATEDIR "/cache/" PACKAGE "/songs3.db", CFGF_NONE), + CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE), + CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel), CFG_STR("admin_password", NULL, CFGF_NONE), CFG_INT("websocket_port", 3688, CFGF_NONE), - CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE), - CFG_STR("db_path", STATEDIR "/cache/" PACKAGE "/songs3.db", CFGF_NONE), - CFG_INT("db_pragma_cache_size", -1, CFGF_NONE), - CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE), - CFG_INT("db_pragma_synchronous", -1, CFGF_NONE), - CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel), + CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", CFGF_NONE), CFG_BOOL("ipv6", cfg_true, CFGF_NONE), CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE), CFG_INT("cache_daap_threshold", 1000, CFGF_NONE), @@ -62,6 +60,10 @@ static cfg_opt_t sec_general[] = #else CFG_BOOL("high_resolution_clock", cfg_true, CFGF_NONE), #endif + // Hidden options + CFG_INT("db_pragma_cache_size", -1, CFGF_NONE), + CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE), + CFG_INT("db_pragma_synchronous", -1, CFGF_NONE), CFG_STR("allow_origin", "*", CFGF_NONE), CFG_END() }; diff --git a/src/db.c b/src/db.c index dbb91e17..0a8f3543 100644 --- a/src/db.c +++ b/src/db.c @@ -451,6 +451,75 @@ free_mfi(struct media_file_info *mfi, int content_only) memset(mfi, 0, sizeof(struct media_file_info)); } +void +free_pli(struct playlist_info *pli, int content_only) +{ + if (!pli) + return; + + free(pli->title); + free(pli->query); + free(pli->path); + free(pli->virtual_path); + + if (!content_only) + free(pli); + else + memset(pli, 0, sizeof(struct playlist_info)); +} + +void +free_di(struct directory_info *di, int content_only) +{ + if (!di) + return; + + free(di->virtual_path); + + if (!content_only) + free(di); + else + memset(di, 0, sizeof(struct directory_info)); +} + +void +free_query_params(struct query_params *qp, int content_only) +{ + if (!qp) + return; + + free(qp->filter); + + if (!content_only) + free(qp); + else + memset(qp, 0, sizeof(struct query_params)); +} + +void +free_queue_item(struct db_queue_item *queue_item, int content_only) +{ + if (!queue_item) + return; + + free(queue_item->path); + free(queue_item->virtual_path); + free(queue_item->title); + free(queue_item->artist); + free(queue_item->album_artist); + free(queue_item->album); + free(queue_item->genre); + free(queue_item->artist_sort); + free(queue_item->album_sort); + free(queue_item->album_artist_sort); + free(queue_item->artwork_url); + + if (!content_only) + free(queue_item); + else + memset(queue_item, 0, sizeof(struct db_queue_item)); +} + void unicode_fixup_mfi(struct media_file_info *mfi) { @@ -485,37 +554,6 @@ unicode_fixup_mfi(struct media_file_info *mfi) } } -void -free_pli(struct playlist_info *pli, int content_only) -{ - if (!pli) - return; - - free(pli->title); - free(pli->query); - free(pli->path); - free(pli->virtual_path); - - if (!content_only) - free(pli); - else - memset(pli, 0, sizeof(struct playlist_info)); -} - -void -free_di(struct directory_info *di, int content_only) -{ - if (!di) - return; - - free(di->virtual_path); - - if (!content_only) - free(di); - else - memset(di, 0, sizeof(struct directory_info)); -} - /* Unlock notification support */ static void @@ -1338,6 +1376,7 @@ db_query_start(struct query_params *qp) int ret; qp->stmt = NULL; + qp->results = -1; switch (qp->type) { @@ -1445,8 +1484,6 @@ db_query_end(struct query_params *qp) if (!qp->stmt) return; - qp->results = -1; - sqlite3_finalize(qp->stmt); qp->stmt = NULL; } @@ -3874,30 +3911,6 @@ db_speaker_clear_all(void) /* Queue */ -void -free_queue_item(struct db_queue_item *queue_item, int content_only) -{ - if (!queue_item) - return; - - free(queue_item->path); - free(queue_item->virtual_path); - free(queue_item->title); - free(queue_item->artist); - free(queue_item->album_artist); - free(queue_item->album); - free(queue_item->genre); - free(queue_item->artist_sort); - free(queue_item->album_sort); - free(queue_item->album_artist_sort); - free(queue_item->artwork_url); - - if (!content_only) - free(queue_item); - else - memset(queue_item, 0, sizeof(struct db_queue_item)); -} - /* * Returns the queue version from the admin table * diff --git a/src/db.h b/src/db.h index 43f506c1..5be667fb 100644 --- a/src/db.h +++ b/src/db.h @@ -436,18 +436,21 @@ free_pi(struct pairing_info *pi, int content_only); void free_mfi(struct media_file_info *mfi, int content_only); -void -unicode_fixup_mfi(struct media_file_info *mfi); - void free_pli(struct playlist_info *pli, int content_only); void free_di(struct directory_info *di, int content_only); +void +free_query_params(struct query_params *qp, int content_only); + void free_queue_item(struct db_queue_item *queue_item, int content_only); +void +unicode_fixup_mfi(struct media_file_info *mfi); + /* Maintenance and DB hygiene */ void db_hook_post_scan(void); diff --git a/src/dmap_common.c b/src/dmap_common.c index b2bc362b..c5a4d0b8 100644 --- a/src/dmap_common.c +++ b/src/dmap_common.c @@ -346,13 +346,24 @@ dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval } } +void +dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg) +{ + int len; + + len = 12 + 8 + 8 + strlen(errmsg); + + CHECK_ERR(L_DMAP, evbuffer_expand(evbuf, len)); + + dmap_add_container(evbuf, container, len - 8); + dmap_add_int(evbuf, "mstt", 500); + dmap_add_string(evbuf, "msts", errmsg); +} void dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg) { struct evbuffer *evbuf; - int len; - int ret; if (!req) return; @@ -366,22 +377,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e return; } - len = 12 + 8 + 8 + strlen(errmsg); - - ret = evbuffer_expand(evbuf, len); - if (ret < 0) - { - DPRINTF(E_LOG, L_DMAP, "Could not expand evbuffer for DMAP error\n"); - - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - - evbuffer_free(evbuf); - return; - } - - dmap_add_container(evbuf, container, len - 8); - dmap_add_int(evbuf, "mstt", 500); - dmap_add_string(evbuf, "msts", errmsg); + dmap_error_make(evbuf, container, errmsg); httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); diff --git a/src/dmap_common.h b/src/dmap_common.h index 73f72d03..704f60ab 100644 --- a/src/dmap_common.h +++ b/src/dmap_common.h @@ -75,6 +75,8 @@ dmap_add_string(struct evbuffer *evbuf, const char *tag, const char *str); void dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval, int32_t intval); +void +dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg); void dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg); diff --git a/src/httpd.c b/src/httpd.c index 4cbbf540..6d004059 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 @@ -61,36 +62,16 @@ #include "httpd_dacp.h" #include "httpd_jsonapi.h" #include "httpd_streaming.h" +#include "httpd_oauth.h" #include "transcode.h" #ifdef LASTFM # include "lastfm.h" #endif -#ifdef HAVE_SPOTIFY_H -# include "spotify.h" -#endif #ifdef HAVE_LIBWEBSOCKETS # include "websocket.h" #endif -/* - * HTTP client quirks by User-Agent, from mt-daapd - * - * - iTunes: - * + Connection: Keep-Alive on HTTP error 401 - * - Hifidelio: - * + Connection: Keep-Alive for streaming (Connection: close not honoured) - * - * These quirks are not implemented. Implement as needed. - * - * Implemented quirks: - * - * - Roku: - * + Does not encode space as + in query string - * - iTunes: - * + Does not encode space as + in query string - */ - #define WEB_ROOT DATADIR "/htdocs" #define STREAM_CHUNK_SIZE (64 * 1024) @@ -121,7 +102,6 @@ struct stream_ctx { struct transcode_ctx *xcode; }; - static const struct content_type_map ext2ctype[] = { { ".html", "text/html; charset=utf-8" }, @@ -135,6 +115,8 @@ static const struct content_type_map ext2ctype[] = { NULL, NULL } }; +static const char *http_reply_401 = "401 UnauthorizedAuthorization required"; + struct event_base *evbase_httpd; #ifdef HAVE_EVENTFD @@ -154,8 +136,307 @@ static int httpd_port; struct stream_ctx *g_st; #endif + +/* -------------------------------- HELPERS --------------------------------- */ + +static int +path_is_legal(const char *path) +{ + return strncmp(WEB_ROOT, path, strlen(WEB_ROOT)); +} + +/* Callback from the worker thread (async operation as it may block) */ static void -redirect_to_admin(struct evhttp_request *req); +playcount_inc_cb(void *arg) +{ + int *id = arg; + + db_file_inc_playcount(*id); +} + +#ifdef LASTFM +/* Callback from the worker thread (async operation as it may block) */ +static void +scrobble_cb(void *arg) +{ + int *id = arg; + + lastfm_scrobble(*id); +} +#endif + +/* + * This disabled in the commit after d8cdc89 because my tests work fine without + * it, and it seems that nowadays iTunes and Remote encodes the query just fine. + * However, I'm keeping it around for a while in case problems show up. If you + * are from the future, you can probably safely remove it for good. + * +static char * +httpd_fixup_uri(struct evhttp_request *req) +{ + struct evkeyvalq *headers; + const char *ua; + const char *uri; + const char *u; + const char *q; + char *fixed; + char *f; + int len; + + uri = evhttp_request_get_uri(req); + if (!uri) + return NULL; + + // No query string, nothing to do + q = strchr(uri, '?'); + if (!q) + return strdup(uri); + + headers = evhttp_request_get_input_headers(req); + ua = evhttp_find_header(headers, "User-Agent"); + if (!ua) + return strdup(uri); + + if ((strncmp(ua, "iTunes", strlen("iTunes")) != 0) + && (strncmp(ua, "Remote", strlen("Remote")) != 0) + && (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 + len = strlen(uri); + + u = q; + while (*u) + { + if (*u == '+') + len += 2; + + u++; + } + + fixed = (char *)malloc(len + 1); + if (!fixed) + return NULL; + + strncpy(fixed, uri, q - uri); + + f = fixed + (q - uri); + while (*q) + { + switch (*q) + { + case '+': + *f = '%'; + f++; + *f = '2'; + f++; + *f = 'B'; + break; + + case ' ': + *f = '+'; + break; + + default: + *f = *q; + break; + } + + q++; + f++; + } + + *f = '\0'; + + return fixed; +} +*/ + + +/* --------------------------- REQUEST HELPERS ------------------------------ */ + +static void +serve_file(struct evhttp_request *req, const char *uri) +{ + char *ext; + char path[PATH_MAX]; + char *deref; + char *ctype; + struct evbuffer *evbuf; + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + struct stat sb; + int fd; + int i; + uint8_t buf[4096]; + const char *modified_since; + char last_modified[1000]; + struct tm *tm_modified; + int ret; + + /* Check authentication */ + if (!httpd_admin_check_auth(req)) + return; + + ret = snprintf(path, sizeof(path), "%s%s", WEB_ROOT, uri); + if ((ret < 0) || (ret >= sizeof(path))) + { + DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + + return; + } + + ret = lstat(path, &sb); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno)); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + + return; + } + + if (S_ISDIR(sb.st_mode)) + { + httpd_redirect_to_index(req, uri); + + return; + } + else if (S_ISLNK(sb.st_mode)) + { + deref = realpath(path, NULL); + if (!deref) + { + DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno)); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + + return; + } + + if (strlen(deref) + 1 > PATH_MAX) + { + DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + + free(deref); + return; + } + + strcpy(path, deref); + free(deref); + + ret = stat(path, &sb); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno)); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + + return; + } + + if (S_ISDIR(sb.st_mode)) + { + httpd_redirect_to_index(req, uri); + + return; + } + } + + if (path_is_legal(path) != 0) + { + httpd_send_error(req, 403, "Forbidden"); + + return; + } + + tm_modified = gmtime(&sb.st_mtime); + strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", tm_modified); + + input_headers = evhttp_request_get_input_headers(req); + modified_since = evhttp_find_header(input_headers, "If-Modified-Since"); + + if (modified_since && strcasecmp(last_modified, modified_since) == 0) + { + httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); + return; + } + + evbuf = evbuffer_new(); + if (!evbuf) + { + DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n"); + + httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); + return; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno)); + + httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + evbuffer_free(evbuf); + return; + } + + ret = evbuffer_expand(evbuf, sb.st_size); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Out of memory for htdocs-file\n"); + goto out_fail; + } + + while ((ret = read(fd, buf, sizeof(buf))) > 0) + evbuffer_add(evbuf, buf, ret); + + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n"); + goto out_fail; + } + + ctype = "application/octet-stream"; + ext = strrchr(path, '.'); + if (ext) + { + for (i = 0; ext2ctype[i].ext; i++) + { + if (strcmp(ext, ext2ctype[i].ext) == 0) + { + ctype = ext2ctype[i].ctype; + break; + } + } + } + + output_headers = evhttp_request_get_output_headers(req); + evhttp_add_header(output_headers, "Content-Type", ctype); + + // Allow browsers to cache the file + evhttp_add_header(output_headers, "Cache-Control", "private"); + evhttp_add_header(output_headers, "Last-Modified", last_modified); + + httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); + + evbuffer_free(evbuf); + close(fd); + return; + + out_fail: + httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); + evbuffer_free(evbuf); + close(fd); +} + + +/* ---------------------------- STREAM HANDLING ----------------------------- */ static void stream_end(struct stream_ctx *st, int failed) @@ -189,81 +470,6 @@ stream_end(struct stream_ctx *st, int failed) free(st); } -/* Callback from the worker thread (async operation as it may block) */ -static void -playcount_inc_cb(void *arg) -{ - int *id = arg; - - db_file_inc_playcount(*id); -} - -#ifdef LASTFM -/* Callback from the worker thread (async operation as it may block) */ -static void -scrobble_cb(void *arg) -{ - int *id = arg; - - lastfm_scrobble(*id); -} -#endif - -static void -oauth_interface(struct evhttp_request *req, const char *uri) -{ -#ifdef HAVE_SPOTIFY_H - struct evkeyvalq query; - const char *req_uri; - const char *ptr; - char redirect_uri[256]; - char *errmsg; - int ret; - - req_uri = evhttp_request_get_uri(req); - - memset(&query, 0, sizeof(struct evkeyvalq)); - - ptr = strchr(req_uri, '?'); - if (ptr) - { - ret = evhttp_parse_query_str(ptr + 1, &query); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "OAuth error: Could not parse parameters in callback (%s)\n", req_uri); - httpd_send_error(req, HTTP_BADREQUEST, "Could not parse parameters in callback"); - return; - } - } - - - if (strncmp(uri, "/oauth/spotify", strlen("/oauth/spotify")) == 0) - { - snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port); - ret = spotify_oauth_callback(&query, redirect_uri, &errmsg); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "OAuth error: Could not parse parameters in callback (%s)\n", req_uri); - httpd_send_error(req, HTTP_INTERNAL, errmsg); - } - else - { - redirect_to_admin(req); - } - evhttp_clear_headers(&query); - free(errmsg); - } - else - { - httpd_send_error(req, HTTP_NOTFOUND, NULL); - } - -#else - DPRINTF(E_LOG, L_HTTPD, "This version was built without modules requiring OAuth support\n"); - httpd_send_error(req, HTTP_NOTFOUND, "No modules with OAuth support"); -#endif -} - static void stream_end_register(struct stream_ctx *st) { @@ -321,7 +527,7 @@ stream_chunk_xcode_cb(int fd, short event, void *arg) if (xcoded <= 0) { if (xcoded == 0) - DPRINTF(E_LOG, L_HTTPD, "Done streaming transcoded file id %d\n", st->id); + DPRINTF(E_INFO, L_HTTPD, "Done streaming transcoded file id %d\n", st->id); else DPRINTF(E_LOG, L_HTTPD, "Transcoding error, file id %d\n", st->id); @@ -408,7 +614,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg) if (ret <= 0) { if (ret == 0) - DPRINTF(E_LOG, L_HTTPD, "Done streaming file id %d\n", st->id); + DPRINTF(E_INFO, L_HTTPD, "Done streaming file id %d\n", st->id); else DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id); @@ -453,6 +659,262 @@ stream_fail_cb(struct evhttp_connection *evcon, void *arg) } +/* ---------------------------- MAIN HTTPD THREAD --------------------------- */ + +static void * +httpd(void *arg) +{ + int ret; + + ret = db_perthread_init(); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n"); + + pthread_exit(NULL); + } + + event_base_dispatch(evbase_httpd); + + if (!httpd_exit) + DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n"); + + db_perthread_deinit(); + + pthread_exit(NULL); +} + +static void +exit_cb(int fd, short event, void *arg) +{ + event_base_loopbreak(evbase_httpd); + + httpd_exit = 1; +} + +static void +httpd_gen_cb(struct evhttp_request *req, void *arg) +{ + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + 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); + if ( input_headers && allow_origin && + (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) && + evhttp_find_header(input_headers, "Origin") && + evhttp_find_header(input_headers, "Access-Control-Request-Method") ) + { + output_headers = evhttp_request_get_output_headers(req); + + evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin); + + // Allow only GET method and authorization header in cross origin requests + evhttp_add_header(output_headers, "Access-Control-Allow-Method", "GET"); + evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization"); + + // In this case there is no reason to go through httpd_send_reply + evhttp_send_reply(req, HTTP_OK, "OK", NULL); + return; + } + + uri = evhttp_request_get_uri(req); + if (!uri) + { + DPRINTF(E_WARN, L_HTTPD, "No URI in request\n"); + httpd_redirect_to_admin(req); + return; + } + + parsed = httpd_uri_parse(uri); + if (!parsed || !parsed->path || (strcmp(parsed->path, "/") == 0)) + { + httpd_redirect_to_admin(req); + goto out; + } + + /* 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 (oauth_is_request(parsed->path)) + { + oauth_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, parsed->path); + + out: + httpd_uri_free(parsed); +} + + +/* ------------------------------- HTTPD API -------------------------------- */ + +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; +} + +struct httpd_request * +httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map) +{ + struct httpd_request *hreq; + struct evkeyvalq *headers; + int i; + int ret; + + CHECK_NULL(L_HTTPD, hreq = calloc(1, sizeof(struct httpd_request))); + + // Note req is allowed to be NULL + hreq->req = req; + hreq->uri_parsed = uri_parsed; + hreq->query = &(uri_parsed->ev_query); + + if (req && !user_agent) + { + headers = evhttp_request_get_input_headers(req); + hreq->user_agent = evhttp_find_header(headers, "User-Agent"); + } + else + hreq->user_agent = user_agent; + + // Find a handler for the path + for (i = 0; uri_map[i].handler; i++) + { + ret = regexec(&uri_map[i].preg, uri_parsed->path, 0, NULL, 0); + if (ret == 0) + { + hreq->handler = uri_map[i].handler; + return hreq; // Success + } + } + + // Handler not found, that's an error + free(hreq); + + return NULL; +} + /* Thread: httpd */ void httpd_stream_file(struct evhttp_request *req, int id) @@ -902,16 +1364,8 @@ httpd_send_error(struct evhttp_request* req, int error, const char* reason) evbuffer_free(evbuf); } -/* Thread: httpd */ -static int -path_is_legal(char *path) -{ - return strncmp(WEB_ROOT, path, strlen(WEB_ROOT)); -} - -/* Thread: httpd */ -static void -redirect_to_admin(struct evhttp_request *req) +void +httpd_redirect_to_admin(struct evhttp_request *req) { struct evkeyvalq *headers; @@ -921,9 +1375,8 @@ redirect_to_admin(struct evhttp_request *req) httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP); } -/* Thread: httpd */ -static void -redirect_to_index(struct evhttp_request *req, char *uri) +void +httpd_redirect_to_index(struct evhttp_request *req, const char *uri) { struct evkeyvalq *headers; char buf[256]; @@ -947,437 +1400,82 @@ redirect_to_index(struct evhttp_request *req, char *uri) httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP); } +bool +httpd_peer_is_trusted(struct evhttp_request *req) +{ + struct evhttp_connection *evcon; + cfg_t *section; + const char *network; + char *addr; + uint16_t port; + int i; + int n; + + evcon = evhttp_request_get_connection(req); + if (!evcon) + { + DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n"); + return false; + } + + evhttp_connection_get_peer(evcon, &addr, &port); + if (strncmp(addr, "::ffff:", strlen("::ffff:")) == 0) + addr += strlen("::ffff:"); + + section = cfg_getsec(cfg, "general"); + + n = cfg_size(section, "trusted_networks"); + for (i = 0; i < n; i++) + { + network = cfg_getnstr(section, "trusted_networks", i); + + if (strncmp(network, addr, strlen(network)) == 0) + return true; + + if ((strcmp(network, "localhost") == 0) && (strcmp(addr, "127.0.0.1") == 0 || strcmp(addr, "::1") == 0)) + return true; + + if (strcmp(network, "any") == 0) + return true; + } + + return false; +} + bool httpd_admin_check_auth(struct evhttp_request *req) { - const char *host; const char *passwd; int ret; + if (httpd_peer_is_trusted(req)) + return true; + passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password"); - if (passwd) + if (!passwd) { - DPRINTF(E_DBG, L_HTTPD, "Checking web interface authentication\n"); + DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: No password set in the config\n", evhttp_request_get_uri(req)); - ret = httpd_basic_auth(req, "admin", passwd, PACKAGE " web interface"); - if (ret != 0) - return false; - - DPRINTF(E_DBG, L_HTTPD, "Authentication successful\n"); + httpd_send_error(req, 403, "Forbidden"); + return false; } - else + + DPRINTF(E_DBG, L_HTTPD, "Checking web interface authentication\n"); + + ret = httpd_basic_auth(req, "admin", passwd, PACKAGE " web interface"); + if (ret != 0) { - host = evhttp_request_get_host(req); - if ((strcmp(host, "::1") != 0) - && (strcmp(host, "127.0.0.1") != 0)) - { - DPRINTF(E_LOG, L_HTTPD, "Remote web interface request denied; no password set\n"); + DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: Incorrect password\n", evhttp_request_get_uri(req)); - httpd_send_error(req, 403, "Forbidden"); - return false; - } + // httpd_basic_auth has sent a reply + return false; } + DPRINTF(E_DBG, L_HTTPD, "Authentication successful\n"); + return true; } -/* Thread: httpd */ -static void -serve_file(struct evhttp_request *req, char *uri) -{ - char *ext; - char path[PATH_MAX]; - char *deref; - char *ctype; - struct evbuffer *evbuf; - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; - struct stat sb; - int fd; - int i; - uint8_t buf[4096]; - const char *modified_since; - char last_modified[1000]; - struct tm *tm_modified; - int ret; - - /* Check authentication */ - if (!httpd_admin_check_auth(req)) - { - DPRINTF(E_DBG, L_HTTPD, "Remote web interface request denied;\n"); - return; - } - - if (strncmp(uri, "/oauth", strlen("/oauth")) == 0) - { - oauth_interface(req, uri); - return; - } - - ret = snprintf(path, sizeof(path), "%s%s", WEB_ROOT, uri); - if ((ret < 0) || (ret >= sizeof(path))) - { - DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - - return; - } - - ret = lstat(path, &sb); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno)); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - - return; - } - - if (S_ISDIR(sb.st_mode)) - { - redirect_to_index(req, uri); - - return; - } - else if (S_ISLNK(sb.st_mode)) - { - deref = realpath(path, NULL); - if (!deref) - { - DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno)); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - - return; - } - - if (strlen(deref) + 1 > PATH_MAX) - { - DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - - free(deref); - return; - } - - strcpy(path, deref); - free(deref); - - ret = stat(path, &sb); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno)); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - - return; - } - - if (S_ISDIR(sb.st_mode)) - { - redirect_to_index(req, uri); - - return; - } - } - - if (path_is_legal(path) != 0) - { - httpd_send_error(req, 403, "Forbidden"); - - return; - } - - tm_modified = gmtime(&sb.st_mtime); - strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", tm_modified); - - input_headers = evhttp_request_get_input_headers(req); - modified_since = evhttp_find_header(input_headers, "If-Modified-Since"); - - if (modified_since && strcasecmp(last_modified, modified_since) == 0) - { - httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); - return; - } - - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n"); - - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); - return; - } - - fd = open(path, O_RDONLY); - if (fd < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno)); - - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); - evbuffer_free(evbuf); - return; - } - - ret = evbuffer_expand(evbuf, sb.st_size); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Out of memory for htdocs-file\n"); - goto out_fail; - } - - while ((ret = read(fd, buf, sizeof(buf))) > 0) - evbuffer_add(evbuf, buf, ret); - - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n"); - goto out_fail; - } - - ctype = "application/octet-stream"; - ext = strrchr(path, '.'); - if (ext) - { - for (i = 0; ext2ctype[i].ext; i++) - { - if (strcmp(ext, ext2ctype[i].ext) == 0) - { - ctype = ext2ctype[i].ctype; - break; - } - } - } - - output_headers = evhttp_request_get_output_headers(req); - evhttp_add_header(output_headers, "Content-Type", ctype); - - // Allow browsers to cache the file - evhttp_add_header(output_headers, "Cache-Control", "private"); - evhttp_add_header(output_headers, "Last-Modified", last_modified); - - httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); - - evbuffer_free(evbuf); - close(fd); - return; - - out_fail: - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error"); - evbuffer_free(evbuf); - close(fd); -} - -/* Thread: httpd */ -static void -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; - - // Did we get a CORS preflight request? - input_headers = evhttp_request_get_input_headers(req); - if ( input_headers && allow_origin && - (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) && - evhttp_find_header(input_headers, "Origin") && - evhttp_find_header(input_headers, "Access-Control-Request-Method") ) - { - output_headers = evhttp_request_get_output_headers(req); - - evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin); - - // Allow only GET method and authorization header in cross origin requests - evhttp_add_header(output_headers, "Access-Control-Allow-Method", "GET"); - evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization"); - - // In this case there is no reason to go through httpd_send_reply - evhttp_send_reply(req, HTTP_OK, "OK", NULL); - return; - } - - req_uri = evhttp_request_get_uri(req); - if (!req_uri || strcmp(req_uri, "/") == 0) - { - redirect_to_admin(req); - - return; - } - - uri = strdup(req_uri); - ptr = strchr(uri, '?'); - if (ptr) - { - 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); - - goto out; - } - - DPRINTF(E_DBG, L_HTTPD, "HTTP request: %s\n", uri); - - /* Serve web interface files */ - serve_file(req, uri); - - out: - free(uri); -} - -/* Thread: httpd */ -static void * -httpd(void *arg) -{ - int ret; - - ret = db_perthread_init(); - if (ret < 0) - { - DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n"); - - pthread_exit(NULL); - } - - event_base_dispatch(evbase_httpd); - - if (!httpd_exit) - DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n"); - - db_perthread_deinit(); - - pthread_exit(NULL); -} - -/* Thread: httpd */ -static void -exit_cb(int fd, short event, void *arg) -{ - event_base_loopbreak(evbase_httpd); - - httpd_exit = 1; -} - -char * -httpd_fixup_uri(struct evhttp_request *req) -{ - struct evkeyvalq *headers; - const char *ua; - const char *uri; - const char *u; - const char *q; - char *fixed; - char *f; - int len; - - uri = evhttp_request_get_uri(req); - if (!uri) - return NULL; - - /* No query string, nothing to do */ - q = strchr(uri, '?'); - if (!q) - return strdup(uri); - - headers = evhttp_request_get_input_headers(req); - ua = evhttp_find_header(headers, "User-Agent"); - if (!ua) - return strdup(uri); - - if ((strncmp(ua, "iTunes", strlen("iTunes")) != 0) - && (strncmp(ua, "Remote", strlen("Remote")) != 0) - && (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 */ - len = strlen(uri); - - u = q; - while (*u) - { - if (*u == '+') - len += 2; - - u++; - } - - fixed = (char *)malloc(len + 1); - if (!fixed) - return NULL; - - strncpy(fixed, uri, q - uri); - - f = fixed + (q - uri); - while (*q) - { - switch (*q) - { - case '+': - *f = '%'; - f++; - *f = '2'; - f++; - *f = 'B'; - break; - - case ' ': - *f = '+'; - break; - - default: - *f = *q; - break; - } - - q++; - f++; - } - - *f = '\0'; - - return fixed; -} - -static const char *http_reply_401 = "401 UnauthorizedAuthorization required"; - int httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm) { @@ -1526,6 +1624,14 @@ httpd_init(void) goto jsonapi_fail; } + ret = oauth_init(); + if (ret < 0) + { + DPRINTF(E_FATAL, L_HTTPD, "OAuth init failed\n"); + + goto oauth_fail; + } + #ifdef HAVE_LIBWEBSOCKETS ret = websocket_init(); if (ret < 0) @@ -1651,6 +1757,8 @@ httpd_init(void) websocket_deinit(); websocket_fail: #endif + oauth_deinit(); + oauth_fail: jsonapi_deinit(); jsonapi_fail: dacp_deinit(); @@ -1702,6 +1810,7 @@ httpd_deinit(void) #ifdef HAVE_LIBWEBSOCKETS websocket_deinit(); #endif + oauth_deinit(); jsonapi_deinit(); rsp_deinit(); dacp_deinit(); diff --git a/src/httpd.h b/src/httpd.h index 6461e3a7..0e830895 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -2,15 +2,97 @@ #ifndef __HTTPD_H__ #define __HTTPD_H__ +#include +#include #include #include -#include +#include 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]; +}; + +/* + * A collection of pointers to request data that the reply handlers may need. + * Also has the function pointer to the reply handler and a pointer to a reply + * evbuffer. + */ +struct httpd_request { + // User-agent (if available) + const char *user_agent; + // The parsed request URI given to us by httpd_uri_parse + struct httpd_uri_parsed *uri_parsed; + // Shortcut to &uri_parsed->ev_query + struct evkeyvalq *query; + // http request struct (if available) + struct evhttp_request *req; + // A pointer to extra data that the module handling the request might need + void *extra_data; + + // Reply evbuffer + struct evbuffer *reply; + + // A pointer to the handler that will process the request + int (*handler)(struct httpd_request *hreq); +}; + +/* + * Maps a regex of the request path to a handler of the request + */ +struct httpd_uri_map +{ + char *regexp; + int (*handler)(struct httpd_request *hreq); + regex_t preg; +}; + +/* + * 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); + +/* + * Parse a request into the httpd_request struct. It can later be freed with + * free(), unless the module has allocated something to *extra_data. Note that + * the pointers in the returned struct are only valid as long as the inputs are + * still valid. If req is not null, then we will find the user-agent from the + * request headers, except if provided as an argument to this function. + */ +struct httpd_request * +httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map); + void httpd_stream_file(struct evhttp_request *req, int id); @@ -53,15 +135,27 @@ 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); +/* + * Redirects to /admin.html + */ +void +httpd_redirect_to_admin(struct evhttp_request *req); -int -httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm); +/* + * Redirects to [uri]/index.html + */ +void +httpd_redirect_to_index(struct evhttp_request *req, const char *uri); + +bool +httpd_peer_is_trusted(struct evhttp_request *req); bool httpd_admin_check_auth(struct evhttp_request *req); +int +httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm); + int httpd_init(void); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 90f483fe..3bc9203d 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016-2017 Espen Jürgensen * Copyright (C) 2009-2011 Julien BLACHE * Copyright (C) 2010 Kai Elwert * @@ -26,11 +27,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -40,22 +41,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; @@ -77,16 +75,25 @@ extern struct event_base *evbase_httpd; /* Database number for the Radio item */ #define DAAP_DB_RADIO 2 -struct uri_map { - regex_t preg; - char *regexp; - int (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua); +/* Errors that the reply handlers may return */ +enum daap_reply_result +{ + DAAP_REPLY_LOGOUT = 4, + DAAP_REPLY_NONE = 3, + DAAP_REPLY_NO_CONTENT = 2, + DAAP_REPLY_OK_NO_GZIP = 1, + DAAP_REPLY_OK = 0, + DAAP_REPLY_NO_CONNECTION = -1, + DAAP_REPLY_ERROR = -2, + DAAP_REPLY_FORBIDDEN = -3, + DAAP_REPLY_BAD_REQUEST = -4, + DAAP_REPLY_SERVUNAVAIL = -5, }; struct daap_session { int id; - char *user_agent; time_t mtime; + bool is_remote; struct daap_session *next; }; @@ -123,12 +130,13 @@ static struct daap_update_request *update_requests; static struct timeval daap_update_refresh_tv = { DAAP_UPDATE_REFRESH, 0 }; -/* Session handling */ +/* -------------------------- SESSION HANDLING ------------------------------ */ + static void daap_session_free(struct daap_session *s) { - if (s->user_agent) - free(s->user_agent); + if (!s) + return; free(s); } @@ -150,7 +158,7 @@ daap_session_remove(struct daap_session *s) if (!ptr) { - DPRINTF(E_LOG, L_DAAP, "Error: Request to remove non-existent session. BUG!\n"); + DPRINTF(E_LOG, L_DAAP, "Error: Request to remove non-existent or ad-hoc session. BUG!\n"); return; } @@ -205,20 +213,13 @@ daap_session_cleanup(void) } static struct daap_session * -daap_session_add(const char *user_agent, int request_session_id) +daap_session_add(bool is_remote, int request_session_id) { struct daap_session *s; daap_session_cleanup(); - s = calloc(1, sizeof(struct daap_session)); - if (!s) - { - DPRINTF(E_LOG, L_DAAP, "Out of memory for DAAP session\n"); - return NULL; - } - - memset(s, 0, sizeof(struct daap_session)); + CHECK_NULL(L_DAAP, s = calloc(1, sizeof(struct daap_session))); if (request_session_id) { @@ -228,7 +229,7 @@ daap_session_add(const char *user_agent, int request_session_id) free(s); return NULL; } - + s->id = request_session_id; } else @@ -238,8 +239,7 @@ daap_session_add(const char *user_agent, int request_session_id) s->mtime = time(NULL); - if (user_agent) - s->user_agent = strdup(user_agent); + s->is_remote = is_remote; if (daap_sessions) s->next = daap_sessions; @@ -249,46 +249,9 @@ daap_session_add(const char *user_agent, int request_session_id) return s; } -struct daap_session * -daap_session_find(struct evhttp_request *req, struct evkeyvalq *query, struct evbuffer *evbuf) -{ - struct daap_session *s; - const char *param; - int id; - int ret; - if (!req) - return NULL; +/* ---------------------- UPDATE REQUESTS HANDLERS -------------------------- */ - param = evhttp_find_header(query, "session-id"); - if (!param) - { - DPRINTF(E_WARN, L_DAAP, "No session-id specified in request\n"); - goto invalid; - } - - ret = safe_atoi32(param, &id); - if (ret < 0) - goto invalid; - - s = daap_session_get(id); - if (!s) - { - DPRINTF(E_LOG, L_DAAP, "DAAP session id %d not found\n", id); - goto invalid; - } - - s->mtime = time(NULL); - - return s; - - invalid: - httpd_send_error(req, 403, "Forbidden"); - return NULL; -} - - -/* Update requests helpers */ static void update_free(struct daap_update_request *ur) { @@ -327,36 +290,24 @@ update_refresh_cb(int fd, short event, void *arg) { struct daap_update_request *ur; struct evhttp_connection *evcon; - struct evbuffer *evbuf; - int ret; + struct evbuffer *reply; ur = (struct daap_update_request *)arg; - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DAAP, "Could not allocate evbuffer for DAAP update data\n"); + CHECK_NULL(L_DAAP, reply = evbuffer_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(reply, 32)); - return; - } - - ret = evbuffer_expand(evbuf, 32); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP update data\n"); - - return; - } + current_rev++; /* Send back current revision */ - dmap_add_container(evbuf, "mupd", 24); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "musr", current_rev); /* 12 */ + dmap_add_container(reply, "mupd", 24); + dmap_add_int(reply, "mstt", 200); /* 12 */ + dmap_add_int(reply, "musr", current_rev); /* 12 */ evcon = evhttp_request_get_connection(ur->req); evhttp_connection_set_closecb(evcon, NULL, NULL); - httpd_send_reply(ur->req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(ur->req, HTTP_OK, "OK", reply, 0); update_remove(ur); } @@ -379,7 +330,8 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg) } -/* DAAP sort headers helpers */ +/* ------------------------- SORT HEADERS HELPERS --------------------------- */ + static struct sort_ctx * daap_sort_context_new(void) { @@ -507,44 +459,36 @@ daap_sort_finalize(struct sort_ctx *ctx) dmap_add_int(ctx->headerlist, "mshn", ctx->misc_mshn); /* 12 */ } -/* 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 int -is_remote(const char *user_agent) -{ - if (!user_agent) - return 0; - if (strcasestr(user_agent, "remote")) - return 1; - if (strstr(user_agent, "Retune")) - return 1; - return 0; -} +/* ----------------------------- OTHER HELPERS ------------------------------ */ /* 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(const char *user_agent, struct query_params *qp) +user_agent_filter(struct query_params *qp, struct httpd_request *hreq) { + struct daap_session *s = hreq->extra_data; char *filter; - if (!user_agent) - return; - - if (is_remote(user_agent)) + if (s->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 @@ -557,34 +501,8 @@ user_agent_filter(const char *user_agent, struct query_params *qp) DPRINTF(E_DBG, L_DAAP, "SQL filter w/client mod: %s\n", qp->filter); } -/* 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; -} - static void -get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params *qp) +query_params_set(struct query_params *qp, int *sort_headers, struct httpd_request *hreq, enum query_type type) { const char *param; char *ptr; @@ -595,7 +513,9 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params low = 0; high = -1; /* No limit */ - param = evhttp_find_header(query, "index"); + memset(qp, 0, sizeof(struct query_params)); + + param = evhttp_find_header(hreq->query, "index"); if (param) { if (param[0] == '-') /* -n, last n entries */ @@ -641,7 +561,7 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params qp->idx_type = I_SUB; qp->sort = S_NONE; - param = evhttp_find_header(query, "sort"); + param = evhttp_find_header(hreq->query, "sort"); if (param) { if (strcmp(param, "name") == 0) @@ -662,7 +582,7 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params if (sort_headers) { *sort_headers = 0; - param = evhttp_find_header(query, "include-sort-headers"); + param = evhttp_find_header(hreq->query, "include-sort-headers"); if (param && (strcmp(param, "1") == 0)) { *sort_headers = 1; @@ -670,9 +590,9 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params } } - param = evhttp_find_header(query, "query"); + param = evhttp_find_header(hreq->query, "query"); if (!param) - param = evhttp_find_header(query, "filter"); + param = evhttp_find_header(hreq->query, "filter"); if (param) { @@ -686,28 +606,24 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params if (qp->sort == S_NONE) qp->sort = S_ALBUM; } + + qp->type = type; + + user_agent_filter(qp, hreq); } static int -parse_meta(struct evhttp_request *req, char *tag, const char *param, const struct dmap_field ***out_meta) +parse_meta(const struct dmap_field ***out_meta, const char *param) { const struct dmap_field **meta; char *ptr; char *field; char *metastr; - int nmeta; int i; int n; - metastr = strdup(param); - if (!metastr) - { - DPRINTF(E_LOG, L_DAAP, "Could not duplicate meta parameter; out of memory\n"); - - dmap_send_error(req, tag, "Out of memory"); - return -1; - } + CHECK_NULL(L_DAAP, metastr = strdup(param)); nmeta = 1; ptr = metastr; @@ -716,17 +632,7 @@ parse_meta(struct evhttp_request *req, char *tag, const char *param, const struc DPRINTF(E_DBG, L_DAAP, "Asking for %d meta tags\n", nmeta); - meta = (const struct dmap_field **)calloc(nmeta, sizeof(const struct dmap_field *)); - if (!meta) - { - DPRINTF(E_LOG, L_DAAP, "Could not allocate meta array; out of memory\n"); - - dmap_send_error(req, tag, "Out of memory"); - - nmeta = -1; - goto out; - } - memset(meta, 0, nmeta * sizeof(struct dmap_field *)); + CHECK_NULL(L_DAAP, meta = calloc(nmeta, sizeof(const struct dmap_field *))); field = strtok_r(metastr, ",", &ptr); for (i = 0; i < nmeta; i++) @@ -758,23 +664,109 @@ parse_meta(struct evhttp_request *req, char *tag, const char *param, const struc break; } + free(metastr); + DPRINTF(E_DBG, L_DAAP, "Found %d meta tags\n", nmeta); *out_meta = meta; - out: - free(metastr); - return nmeta; } +static void +daap_reply_send(struct httpd_request *hreq, enum daap_reply_result result) +{ + switch (result) + { + case DAAP_REPLY_LOGOUT: + httpd_send_reply(hreq->req, 204, "Logout Successful", hreq->reply, 0); + break; + case DAAP_REPLY_NO_CONTENT: + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + break; + case DAAP_REPLY_OK: + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + break; + case DAAP_REPLY_OK_NO_GZIP: + case DAAP_REPLY_ERROR: + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); + break; + case DAAP_REPLY_FORBIDDEN: + httpd_send_error(hreq->req, 403, "Forbidden"); + break; + case DAAP_REPLY_BAD_REQUEST: + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + break; + case DAAP_REPLY_SERVUNAVAIL: + httpd_send_error(hreq->req, HTTP_SERVUNAVAIL, "Internal Server Error"); + break; + case DAAP_REPLY_NO_CONNECTION: + case DAAP_REPLY_NONE: + // Send nothing + break; + } +} static int -daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +daap_request_authorize(struct httpd_request *hreq) +{ + struct daap_session *session = hreq->extra_data; + const char *param; + char *passwd; + int ret; + + if (httpd_peer_is_trusted(hreq->req)) + return 0; + + param = evhttp_find_header(hreq->query, "session-id"); + if (param) + { + if (!session) + { + DPRINTF(E_LOG, L_DAAP, "DAAP session not found: '%s'\n", hreq->uri_parsed->uri); + return -1; + } + + session->mtime = time(NULL); + return 0; + } + + passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password"); + if (!passwd) + return 0; + + // If no valid session then we may need to authenticate + if ((strcmp(hreq->uri_parsed->path, "/server-info") == 0) + || (strcmp(hreq->uri_parsed->path, "/login") == 0) + || (strcmp(hreq->uri_parsed->path, "/logout") == 0) + || (strcmp(hreq->uri_parsed->path, "/content-codes") == 0) + || (strncmp(hreq->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"); + + // We don't care about the username + ret = httpd_basic_auth(hreq->req, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name")); + if (ret != 0) + { + DPRINTF(E_LOG, L_DAAP, "Unsuccessful library authentication\n"); + return -1; + } + + return 0; +} + + +/* --------------------------- REPLY HANDLERS ------------------------------- */ +/* Note that some handlers can be called without a connection (needed for */ +/* cache regeneration), while others cannot. Those that cannot should check */ +/* that hreq->req is not a null pointer. */ + +static enum daap_reply_result +daap_reply_server_info(struct httpd_request *hreq) { struct evbuffer *content; struct evkeyvalq *headers; - cfg_t *lib; char *name; char *passwd; const char *clientver; @@ -782,25 +774,23 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char int mpro; int apro; - lib = cfg_getsec(cfg, "library"); - passwd = cfg_getstr(lib, "password"); - name = cfg_getstr(lib, "name"); - - content = evbuffer_new(); - if (!content) + if (!hreq->req) { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP server-info reply\n"); - - dmap_send_error(req, "msrv", "Out of memory"); - return -1; + DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_server_info() cannot be called without an actual connection\n"); + return DAAP_REPLY_NO_CONNECTION; } + passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password"); + name = cfg_getstr(cfg_getsec(cfg, "library"), "name"); + + CHECK_NULL(L_DAAP, content = evbuffer_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(content, 512)); + mpro = 2 << 16 | 10; apro = 3 << 16 | 12; - headers = evhttp_request_get_input_headers(req); - clientver = evhttp_find_header(headers, "Client-DAAP-Version"); - if (clientver) + headers = evhttp_request_get_input_headers(hreq->req); + if (headers && (clientver = evhttp_find_header(headers, "Client-DAAP-Version"))) { if (strcmp(clientver, "1.0") == 0) { @@ -826,8 +816,7 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char /* Sub-optimal user-agent sniffing to solve the problem that iTunes 12.1 * does not work if we announce support for groups. */ - ua = evhttp_find_header(headers, "User-Agent"); - if (ua && (strncmp(ua, "iTunes", strlen("iTunes")) == 0)) + if (hreq->user_agent && (strncmp(hreq->user_agent, "iTunes", strlen("iTunes")) == 0)) dmap_add_short(content, "asgr", 0); // daap.supportsgroups (1=artists, 2=albums, 3=both) else dmap_add_short(content, "asgr", 3); // daap.supportsgroups (1=artists, 2=albums, 3=both) @@ -870,23 +859,22 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char // Create container len = evbuffer_get_length(content); - dmap_add_container(evbuf, "msrv", len); - evbuffer_add_buffer(evbuf, content); + dmap_add_container(hreq->reply, "msrv", len); + + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, content)); + evbuffer_free(content); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; + return DAAP_REPLY_OK; } -static int -daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_content_codes(struct httpd_request *hreq) { const struct dmap_field *dmap_fields; size_t len; int nfields; int i; - int ret; dmap_fields = dmap_get_fields_table(&nfields); @@ -894,60 +882,43 @@ daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, cha for (i = 0; i < nfields; i++) len += 8 + 12 + 10 + 8 + strlen(dmap_fields[i].desc); - ret = evbuffer_expand(evbuf, len + 8); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP content-codes reply\n"); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, len + 8)); - dmap_send_error(req, "mccr", "Out of memory"); - return -1; - } - - dmap_add_container(evbuf, "mccr", len); - dmap_add_int(evbuf, "mstt", 200); + dmap_add_container(hreq->reply, "mccr", len); + dmap_add_int(hreq->reply, "mstt", 200); for (i = 0; i < nfields; i++) { len = 12 + 10 + 8 + strlen(dmap_fields[i].desc); - dmap_add_container(evbuf, "mdcl", len); - dmap_add_string(evbuf, "mcnm", dmap_fields[i].tag); /* 12 */ - dmap_add_string(evbuf, "mcna", dmap_fields[i].desc); /* 8 + strlen(desc) */ - dmap_add_short(evbuf, "mcty", dmap_fields[i].type); /* 10 */ + dmap_add_container(hreq->reply, "mdcl", len); + dmap_add_string(hreq->reply, "mcnm", dmap_fields[i].tag); /* 12 */ + dmap_add_string(hreq->reply, "mcna", dmap_fields[i].desc); /* 8 + strlen(desc) */ + dmap_add_short(hreq->reply, "mcty", dmap_fields[i].type); /* 10 */ } - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; + return DAAP_REPLY_OK; } -static int -daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_login(struct httpd_request *hreq) { + struct daap_session *adhoc = hreq->extra_data; + struct daap_session *session; struct pairing_info pi; - struct daap_session *s; const char *param; int request_session_id; int ret; - ret = evbuffer_expand(evbuf, 32); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP login reply\n"); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32)); - dmap_send_error(req, "mlog", "Out of memory"); - return -1; - } - - if (ua && (strncmp(ua, "Remote", strlen("Remote")) == 0)) + param = evhttp_find_header(hreq->query, "pairing-guid"); + if (param && !httpd_peer_is_trusted(hreq->req)) { - param = evhttp_find_header(query, "pairing-guid"); - if (!param) + if (strlen(param) < 3) { - DPRINTF(E_LOG, L_DAAP, "Login attempt with U-A: Remote and no pairing-guid\n"); - - httpd_send_error(req, 403, "Forbidden"); - return -1; + DPRINTF(E_LOG, L_DAAP, "Login attempt with invalid pairing-guid: %s\n", param); + return DAAP_REPLY_FORBIDDEN; } memset(&pi, 0, sizeof(struct pairing_info)); @@ -956,18 +927,23 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, ret = db_pairing_fetch_byguid(&pi); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Login attempt with invalid pairing-guid\n"); - + DPRINTF(E_LOG, L_DAAP, "Login attempt with invalid pairing-guid: %s\n", param); free_pi(&pi, 1); - httpd_send_error(req, 403, "Forbidden"); - return -1; + return DAAP_REPLY_FORBIDDEN; } DPRINTF(E_INFO, L_DAAP, "Remote '%s' logging in with GUID %s\n", pi.name, pi.guid); free_pi(&pi, 1); } + else + { + if (hreq->user_agent) + DPRINTF(E_INFO, L_DAAP, "Client '%s' logging in\n", hreq->user_agent); + else + DPRINTF(E_INFO, L_DAAP, "Client (unknown user-agent) logging in\n"); + } - param = evhttp_find_header(query, "request-session-id"); + param = evhttp_find_header(hreq->query, "request-session-id"); if (param) { ret = safe_atoi32(param, &request_session_id); @@ -980,53 +956,49 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, else request_session_id = 0; - s = daap_session_add(ua, request_session_id); - if (!s) + session = daap_session_add(adhoc->is_remote, request_session_id); + if (!session) { - dmap_send_error(req, "mlog", "Could not start session"); - return -1; + dmap_error_make(hreq->reply, "mlog", "Could not start session"); + return DAAP_REPLY_ERROR; } - dmap_add_container(evbuf, "mlog", 24); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "mlid", s->id); /* 12 */ + dmap_add_container(hreq->reply, "mlog", 24); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "mlid", session->id); /* 12 */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; + return DAAP_REPLY_OK; } -static int -daap_reply_logout(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_logout(struct httpd_request *hreq) { - struct daap_session *s; + if (!hreq->extra_data) + return DAAP_REPLY_FORBIDDEN; - s = daap_session_find(req, query, evbuf); - if (!s) - return -1; + daap_session_remove(hreq->extra_data); - daap_session_remove(s); + hreq->extra_data = NULL; - httpd_send_reply(req, 204, "Logout Successful", evbuf, 0); - - return 0; + return DAAP_REPLY_LOGOUT; } -static int -daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_update(struct httpd_request *hreq) { - struct daap_session *s; struct daap_update_request *ur; struct evhttp_connection *evcon; const char *param; int reqd_rev; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return -1; + if (!hreq->req) + { + DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_update() cannot be called without an actual connection\n"); + return DAAP_REPLY_NO_CONNECTION; + } - param = evhttp_find_header(query, "revision-number"); + param = evhttp_find_header(hreq->query, "revision-number"); if (!param) { DPRINTF(E_DBG, L_DAAP, "Missing revision-number in client update request\n"); @@ -1040,29 +1012,20 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri { DPRINTF(E_LOG, L_DAAP, "Parameter revision-number not an integer\n"); - dmap_send_error(req, "mupd", "Invalid request"); - return -1; + dmap_error_make(hreq->reply, "mupd", "Invalid request"); + return DAAP_REPLY_ERROR; } if (reqd_rev == 1) /* Or revision is not valid */ { - ret = evbuffer_expand(evbuf, 32); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP update reply\n"); - - dmap_send_error(req, "mupd", "Out of memory"); - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 32)); /* Send back current revision */ - dmap_add_container(evbuf, "mupd", 24); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "musr", current_rev); /* 12 */ + dmap_add_container(hreq->reply, "mupd", 24); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "musr", current_rev); /* 12 */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; + return DAAP_REPLY_OK; } /* Else, just let the request hang until we have changes to push back */ @@ -1071,10 +1034,9 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri { DPRINTF(E_LOG, L_DAAP, "Out of memory for update request\n"); - dmap_send_error(req, "mupd", "Out of memory"); - return -1; + dmap_error_make(hreq->reply, "mupd", "Out of memory"); + return DAAP_REPLY_ERROR; } - memset(ur, 0, sizeof(struct daap_update_request)); if (DAAP_UPDATE_REFRESH > 0) { @@ -1088,14 +1050,14 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri { DPRINTF(E_LOG, L_DAAP, "Out of memory for update request event\n"); - dmap_send_error(req, "mupd", "Could not register timer"); + dmap_error_make(hreq->reply, "mupd", "Could not register timer"); update_free(ur); - return -1; + return DAAP_REPLY_ERROR; } } /* NOTE: we may need to keep reqd_rev in there too */ - ur->req = req; + ur->req = hreq->req; ur->next = update_requests; update_requests = ur; @@ -1103,61 +1065,39 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri /* 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(hreq->req); if (evcon) evhttp_connection_set_closecb(evcon, update_fail_cb, ur); - return 0; + return DAAP_REPLY_NONE; } -static int -daap_reply_activity(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_activity(struct httpd_request *hreq) { /* That's so nice, thanks for letting us know */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); - - return 0; + return DAAP_REPLY_NO_CONTENT; } -static int -daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_dblist(struct httpd_request *hreq) { struct evbuffer *content; struct evbuffer *item; - struct daap_session *s; - cfg_t *lib; char *name; char *name_radio; size_t len; int count; - s = daap_session_find(req, query, evbuf); - if (!s) - return -1; + name = cfg_getstr(cfg_getsec(cfg, "library"), "name"); + name_radio = cfg_getstr(cfg_getsec(cfg, "library"), "name_radio"); - lib = cfg_getsec(cfg, "library"); - name = cfg_getstr(lib, "name"); - name_radio = cfg_getstr(lib, "name_radio"); - - content = evbuffer_new(); - if (!content) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP dblist reply\n"); - - dmap_send_error(req, "avdb", "Out of memory"); - return -1; - } + CHECK_NULL(L_DAAP, content = evbuffer_new()); + CHECK_NULL(L_DAAP, item = evbuffer_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(item, 512)); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 1024)); // Add db entry for library with dbid = 1 - item = evbuffer_new(); - if (!item) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP dblist library item\n"); - - dmap_send_error(req, "avdb", "Out of memory"); - return -1; - } - dmap_add_int(item, "miid", 1); dmap_add_long(item, "mper", 1); dmap_add_int(item, "mdbk", 1); @@ -1173,18 +1113,11 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri // Create container for library db len = evbuffer_get_length(item); dmap_add_container(content, "mlit", len); - evbuffer_add_buffer(content, item); - evbuffer_free(item); + + CHECK_ERR(L_DAAP, evbuffer_add_buffer(content, item)); // Add second db entry for radio with dbid = DAAP_DB_RADIO - item = evbuffer_new(); - if (!item) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP dblist radio item\n"); - - dmap_send_error(req, "avdb", "Out of memory"); - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_expand(item, 512)); dmap_add_int(item, "miid", DAAP_DB_RADIO); dmap_add_long(item, "mper", DAAP_DB_RADIO); @@ -1200,107 +1133,79 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri // Create container for radio db len = evbuffer_get_length(item); dmap_add_container(content, "mlit", len); - evbuffer_add_buffer(content, item); - evbuffer_free(item); + + CHECK_ERR(L_DAAP, evbuffer_add_buffer(content, item)); // Create container len = evbuffer_get_length(content); - dmap_add_container(evbuf, "avdb", len + 53); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_char(evbuf, "muty", 0); /* 9 */ - dmap_add_int(evbuf, "mtco", 2); /* 12 */ - dmap_add_int(evbuf, "mrco", 2); /* 12 */ - dmap_add_container(evbuf, "mlcl", len); /* 8 */ - evbuffer_add_buffer(evbuf, content); + dmap_add_container(hreq->reply, "avdb", len + 53); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_char(hreq->reply, "muty", 0); /* 9 */ + dmap_add_int(hreq->reply, "mtco", 2); /* 12 */ + dmap_add_int(hreq->reply, "mrco", 2); /* 12 */ + dmap_add_container(hreq->reply, "mlcl", len); /* 8 */ + + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, content)); + + evbuffer_free(item); evbuffer_free(content); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; + return DAAP_REPLY_OK; } -static int -daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, int playlist, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_songlist_generic(struct httpd_request *hreq, int playlist) { - struct daap_session *s; struct query_params qp; struct db_media_file_info dbmfi; struct evbuffer *song; struct evbuffer *songlist; struct evkeyvalq *headers; + struct daap_session *s; const struct dmap_field **meta; struct sort_ctx *sctx; const char *param; const char *client_codecs; + const char *tag; char *last_codectype; - char *tag; size_t len; int nmeta; int sort_headers; int nsongs; - int remote; int transcode; int ret; - s = daap_session_find(req, query, evbuf); - if (!s && req) - return -1; - DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist); + s = hreq->extra_data; + if (!s) + { + DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_songlist_generic() called with NULL session (playlist %d)\n", playlist); + return DAAP_REPLY_ERROR; + } + if (playlist != -1) - tag = "apso"; /* Songs in playlist */ + { + // Songs in playlist + tag = "apso"; + query_params_set(&qp, &sort_headers, hreq, Q_PLITEMS); + qp.id = playlist; + } else - tag = "adbs"; /* Songs in database */ - - ret = evbuffer_expand(evbuf, 61); - if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP song list reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - return -1; + // Songs in database + tag = "adbs"; + query_params_set(&qp, &sort_headers, hreq, Q_ITEMS); } - songlist = evbuffer_new(); - if (!songlist) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP song list\n"); + CHECK_NULL(L_DAAP, songlist = evbuffer_new()); + CHECK_NULL(L_DAAP, song = evbuffer_new()); + CHECK_NULL(L_DAAP, sctx = daap_sort_context_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61)); + CHECK_ERR(L_DAAP, evbuffer_expand(songlist, 4096)); + CHECK_ERR(L_DAAP, evbuffer_expand(song, 512)); - dmap_send_error(req, tag, "Out of memory"); - return -1; - } - - /* Start with a big enough evbuffer - it'll expand as needed */ - ret = evbuffer_expand(songlist, 4096); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP song list\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_list_free; - } - - song = evbuffer_new(); - if (!song) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP song block\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_list_free; - } - - /* The buffer will expand if needed */ - ret = evbuffer_expand(song, 512); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP song block\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_song_free; - } - - param = evhttp_find_header(query, "meta"); + param = evhttp_find_header(hreq->query, "meta"); if (!param) { DPRINTF(E_DBG, L_DAAP, "No meta parameter in query, using default\n"); @@ -1311,12 +1216,11 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, if (param) { - nmeta = parse_meta(req, tag, param, &meta); + nmeta = parse_meta(&meta, param); if (nmeta < 0) { DPRINTF(E_LOG, L_DAAP, "Failed to parse meta parameter in DAAP query\n"); - - goto out_song_free; + goto error; } } else @@ -1325,52 +1229,20 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, nmeta = 0; } - memset(&qp, 0, sizeof(struct query_params)); - get_query_params(query, &sort_headers, &qp); - - if (playlist == -1) - user_agent_filter(ua, &qp); - - sctx = NULL; - if (sort_headers) - { - sctx = daap_sort_context_new(); - if (!sctx) - { - DPRINTF(E_LOG, L_DAAP, "Could not create sort context\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_query_free; - } - } - - if (playlist != -1) - { - qp.type = Q_PLITEMS; - qp.id = playlist; - } - else - qp.type = Q_ITEMS; - ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not start query\n"); - dmap_send_error(req, tag, "Could not start query"); - - if (sort_headers) - daap_sort_context_free(sctx); - - goto out_query_free; + free(meta); + dmap_error_make(hreq->reply, tag, "Could not start query"); + goto error; } - remote = is_remote(ua); - client_codecs = NULL; - if (!remote && req) + if (!s->is_remote && hreq->req) { - headers = evhttp_request_get_input_headers(req); + headers = evhttp_request_get_input_headers(hreq->req); client_codecs = evhttp_find_header(headers, "Accept-Codecs"); } @@ -1386,17 +1258,15 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, transcode = 0; } - else if (remote) + else if (s->is_remote) { transcode = 1; } else if (!last_codectype || (strcmp(last_codectype, dbmfi.codectype) != 0)) { - transcode = transcode_needed(ua, client_codecs, dbmfi.codectype); - - if (last_codectype) - free(last_codectype); + transcode = transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype); + free(last_codectype); last_codectype = strdup(dbmfi.codectype); } @@ -1426,33 +1296,20 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs); - if (last_codectype) - free(last_codectype); + free(last_codectype); + free(meta); + db_query_end(&qp); - if (nmeta > 0) - free(meta); - - evbuffer_free(song); - - if (qp.filter) - free(qp.filter); - - if (ret < 0) + if (ret == -100) { - if (ret == -100) - dmap_send_error(req, tag, "Out of memory"); - else - { - DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); - dmap_send_error(req, tag, "Error fetching query results"); - } - - db_query_end(&qp); - - if (sort_headers) - daap_sort_context_free(sctx); - - goto out_list_free; + dmap_error_make(hreq->reply, tag, "Out of memory"); + goto error; + } + else if (ret < 0) + { + DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); + dmap_error_make(hreq->reply, tag, "Error fetching query results"); + goto error; } /* Add header to evbuf, add songlist to evbuf */ @@ -1460,100 +1317,72 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, if (sort_headers) { daap_sort_finalize(sctx); - dmap_add_container(evbuf, tag, len + evbuffer_get_length(sctx->headerlist) + 61); + dmap_add_container(hreq->reply, tag, len + evbuffer_get_length(sctx->headerlist) + 61); } else - dmap_add_container(evbuf, tag, len + 53); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_char(evbuf, "muty", 0); /* 9 */ - dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ - dmap_add_int(evbuf, "mrco", nsongs); /* 12 */ - dmap_add_container(evbuf, "mlcl", len); /* 8 */ + dmap_add_container(hreq->reply, tag, len + 53); - db_query_end(&qp); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_char(hreq->reply, "muty", 0); /* 9 */ + dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */ + dmap_add_int(hreq->reply, "mrco", nsongs); /* 12 */ + dmap_add_container(hreq->reply, "mlcl", len); /* 8 */ - ret = evbuffer_add_buffer(evbuf, songlist); - evbuffer_free(songlist); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add song list to DAAP song list reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - - if (sort_headers) - daap_sort_context_free(sctx); - - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, songlist)); if (sort_headers) { len = evbuffer_get_length(sctx->headerlist); - dmap_add_container(evbuf, "mshl", len); /* 8 */ - ret = evbuffer_add_buffer(evbuf, sctx->headerlist); - daap_sort_context_free(sctx); + dmap_add_container(hreq->reply, "mshl", len); /* 8 */ - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP song list reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist)); } - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; - - out_query_free: - if (nmeta > 0) - free(meta); - - if (qp.filter) - free(qp.filter); - - out_song_free: + daap_sort_context_free(sctx); evbuffer_free(song); - - out_list_free: evbuffer_free(songlist); + free_query_params(&qp, 1); - return -1; + return DAAP_REPLY_OK; + + error: + daap_sort_context_free(sctx); + evbuffer_free(song); + evbuffer_free(songlist); + free_query_params(&qp, 1); + + return DAAP_REPLY_ERROR; } -static int -daap_reply_dbsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_dbsonglist(struct httpd_request *hreq) { - return daap_reply_songlist_generic(req, evbuf, -1, query, ua); + return daap_reply_songlist_generic(hreq, -1); } -static int -daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_plsonglist(struct httpd_request *hreq) { int playlist; int ret; - ret = safe_atoi32(uri[3], &playlist); + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist); if (ret < 0) { - dmap_send_error(req, "apso", "Invalid playlist ID"); - - return -1; + dmap_error_make(hreq->reply, "apso", "Invalid playlist ID"); + return DAAP_REPLY_ERROR; } - return daap_reply_songlist_generic(req, evbuf, playlist, query, ua); + return daap_reply_songlist_generic(hreq, playlist); } -static int -daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_playlists(struct httpd_request *hreq) { struct query_params qp; struct db_playlist_info dbpli; - struct daap_session *s; struct evbuffer *playlistlist; struct evbuffer *playlist; - cfg_t *lib; const struct dmap_field_map *dfm; const struct dmap_field *df; const struct dmap_field **meta; @@ -1572,69 +1401,26 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char ** int i; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return -1; + cfg_radiopl = cfg_getbool(cfg_getsec(cfg, "library"), "radio_playlists"); - ret = safe_atoi32(uri[1], &database); + ret = safe_atoi32(hreq->uri_parsed->path_parts[1], &database); if (ret < 0) { - dmap_send_error(req, "aply", "Invalid database ID"); - - return -1; + dmap_error_make(hreq->reply, "aply", "Invalid database ID"); + return DAAP_REPLY_ERROR; } - lib = cfg_getsec(cfg, "library"); - cfg_radiopl = cfg_getbool(lib, "radio_playlists"); + query_params_set(&qp, NULL, hreq, Q_PL); + if (qp.sort == S_NONE) + qp.sort = S_PLAYLIST; - ret = evbuffer_expand(evbuf, 61); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP playlists reply\n"); + CHECK_NULL(L_DAAP, playlistlist = evbuffer_new()); + CHECK_NULL(L_DAAP, playlist = evbuffer_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61)); + CHECK_ERR(L_DAAP, evbuffer_expand(playlistlist, 1024)); + CHECK_ERR(L_DAAP, evbuffer_expand(playlist, 128)); - dmap_send_error(req, "aply", "Out of memory"); - return -1; - } - - playlistlist = evbuffer_new(); - if (!playlistlist) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP playlist list\n"); - - dmap_send_error(req, "aply", "Out of memory"); - return -1; - } - - /* Start with a big enough evbuffer - it'll expand as needed */ - ret = evbuffer_expand(playlistlist, 1024); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP playlist list\n"); - - dmap_send_error(req, "aply", "Out of memory"); - goto out_list_free; - } - - playlist = evbuffer_new(); - if (!playlist) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP playlist block\n"); - - dmap_send_error(req, "aply", "Out of memory"); - goto out_list_free; - } - - /* The buffer will expand if needed */ - ret = evbuffer_expand(playlist, 128); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP playlist block\n"); - - dmap_send_error(req, "aply", "Out of memory"); - goto out_pl_free; - } - - param = evhttp_find_header(query, "meta"); + param = evhttp_find_header(hreq->query, "meta"); if (!param) { DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n"); @@ -1642,27 +1428,23 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char ** param = default_meta_pl; } - nmeta = parse_meta(req, "aply", param, &meta); + nmeta = parse_meta(&meta, param); if (nmeta < 0) { DPRINTF(E_LOG, L_DAAP, "Failed to parse meta parameter in DAAP query\n"); - goto out_pl_free; + dmap_error_make(hreq->reply, "aply", "Failed to parse query"); + goto error; } - memset(&qp, 0, sizeof(struct query_params)); - get_query_params(query, NULL, &qp); - qp.type = Q_PL; - if (qp.sort == S_NONE) - qp.sort = S_PLAYLIST; - ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not start query\n"); - dmap_send_error(req, "aply", "Could not start query"); - goto out_query_free; + free(meta); + dmap_error_make(hreq->reply, "aply", "Could not start query"); + goto error; } npls = 0; @@ -1757,7 +1539,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char ** if (plid == 1) dmap_add_char(playlist, "abpl", 1); - DPRINTF(E_DBG, L_DAAP, "Done with playlist\n"); + DPRINTF(E_SPAM, L_DAAP, "Done with playlist\n"); len = evbuffer_get_length(playlist); dmap_add_container(playlistlist, "mlit", len); @@ -1771,73 +1553,53 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char ** } } + db_query_end(&qp); + free(meta); + DPRINTF(E_DBG, L_DAAP, "Done with playlist list, %d playlists\n", npls); - free(meta); - evbuffer_free(playlist); - - if (qp.filter) - free(qp.filter); - - if (ret < 0) + if (ret == -100) { - if (ret == -100) - dmap_send_error(req, "aply", "Out of memory"); - else - { - DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); - dmap_send_error(req, "aply", "Error fetching query results"); - } - - db_query_end(&qp); - goto out_list_free; + dmap_error_make(hreq->reply, "aply", "Out of memory"); + goto error; + } + else if (ret < 0) + { + DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); + dmap_error_make(hreq->reply, "aply", "Error fetching query results"); + goto error; } /* Add header to evbuf, add playlistlist to evbuf */ len = evbuffer_get_length(playlistlist); - dmap_add_container(evbuf, "aply", len + 53); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_char(evbuf, "muty", 0); /* 9 */ - dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ - dmap_add_int(evbuf,"mrco", npls); /* 12 */ - dmap_add_container(evbuf, "mlcl", len); + dmap_add_container(hreq->reply, "aply", len + 53); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_char(hreq->reply, "muty", 0); /* 9 */ + dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */ + dmap_add_int(hreq->reply,"mrco", npls); /* 12 */ + dmap_add_container(hreq->reply, "mlcl", len); - db_query_end(&qp); + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, playlistlist)); - ret = evbuffer_add_buffer(evbuf, playlistlist); - evbuffer_free(playlistlist); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add playlist list to DAAP playlists reply\n"); - - dmap_send_error(req, "aply", "Out of memory"); - return -1; - } - - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; - - out_query_free: - free(meta); - if (qp.filter) - free(qp.filter); - - out_pl_free: evbuffer_free(playlist); - - out_list_free: evbuffer_free(playlistlist); + free_query_params(&qp, 1); - return -1; + return DAAP_REPLY_OK; + + error: + evbuffer_free(playlist); + evbuffer_free(playlistlist); + free_query_params(&qp, 1); + + return DAAP_REPLY_ERROR; } -static int -daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_groups(struct httpd_request *hreq) { struct query_params qp; struct db_group_info dbgri; - struct daap_session *s; struct evbuffer *group; struct evbuffer *grouplist; const struct dmap_field_map *dfm; @@ -1856,24 +1618,14 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri int i; int ret; - s = daap_session_find(req, query, evbuf); - if (!s && req) - return -1; - - memset(&qp, 0, sizeof(struct query_params)); - - get_query_params(query, &sort_headers, &qp); - - user_agent_filter(ua, &qp); - - param = evhttp_find_header(query, "group-type"); + param = evhttp_find_header(hreq->query, "group-type"); if (strcmp(param, "artists") == 0) { // Request from Remote may have the form: // groups?meta=dmap.xxx,dma...&type=music&group-type=artists&sort=album&include-sort-headers=1&query=('...')&session-id=... // Note: Since grouping by artist and sorting by album is crazy we override tag = "agar"; - qp.type = Q_GROUP_ARTISTS; + query_params_set(&qp, &sort_headers, hreq, Q_GROUP_ARTISTS); qp.sort = S_ARTIST; } else @@ -1882,59 +1634,19 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri // groups?meta=dmap.xxx,dma...&type=music&group-type=albums&sort=artist&include-sort-headers=0&query=('...'))&session-id=... // Sort may also be 'album' tag = "agal"; - qp.type = Q_GROUP_ALBUMS; + query_params_set(&qp, &sort_headers, hreq, Q_GROUP_ALBUMS); if (qp.sort == S_NONE) qp.sort = S_ALBUM; } - ret = evbuffer_expand(evbuf, 61); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP groups reply\n"); + CHECK_NULL(L_DAAP, grouplist = evbuffer_new()); + CHECK_NULL(L_DAAP, group = evbuffer_new()); + CHECK_NULL(L_DAAP, sctx = daap_sort_context_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 61)); + CHECK_ERR(L_DAAP, evbuffer_expand(grouplist, 1024)); + CHECK_ERR(L_DAAP, evbuffer_expand(group, 128)); - dmap_send_error(req, tag, "Out of memory"); - goto out_qfilter_free; - } - - grouplist = evbuffer_new(); - if (!grouplist) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP group list\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_qfilter_free; - } - - /* Start with a big enough evbuffer - it'll expand as needed */ - ret = evbuffer_expand(grouplist, 1024); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP group list\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_list_free; - } - - group = evbuffer_new(); - if (!group) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP group block\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_list_free; - } - - /* The buffer will expand if needed */ - ret = evbuffer_expand(group, 128); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP group block\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_group_free; - } - - param = evhttp_find_header(query, "meta"); + param = evhttp_find_header(hreq->query, "meta"); if (!param) { DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n"); @@ -1942,25 +1654,13 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri param = default_meta_group; } - nmeta = parse_meta(req, tag, param, &meta); + nmeta = parse_meta(&meta, param); if (nmeta < 0) { DPRINTF(E_LOG, L_DAAP, "Failed to parse meta parameter in DAAP query\n"); - goto out_group_free; - } - - sctx = NULL; - if (sort_headers) - { - sctx = daap_sort_context_new(); - if (!sctx) - { - DPRINTF(E_LOG, L_DAAP, "Could not create sort context\n"); - - dmap_send_error(req, tag, "Out of memory"); - goto out_query_free; - } + dmap_error_make(hreq->reply, tag, "Failed to parse query"); + goto error; } ret = db_query_start(&qp); @@ -1968,12 +1668,9 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri { DPRINTF(E_LOG, L_DAAP, "Could not start query\n"); - dmap_send_error(req, tag, "Could not start query"); - - if (sort_headers) - daap_sort_context_free(sctx); - - goto out_query_free; + free(meta); + dmap_error_make(hreq->reply, tag, "Could not start query"); + goto error; } ngrp = 0; @@ -2055,33 +1752,21 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri } } + db_query_end(&qp); + free(meta); + DPRINTF(E_DBG, L_DAAP, "Done with group list, %d groups\n", ngrp); - free(meta); - evbuffer_free(group); - - if (qp.filter) + if (ret == -100) { - free(qp.filter); - qp.filter = NULL; + dmap_error_make(hreq->reply, tag, "Out of memory"); + goto error; } - - if (ret < 0) + else if (ret < 0) { - if (ret == -100) - dmap_send_error(req, tag, "Out of memory"); - else - { - DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); - dmap_send_error(req, tag, "Error fetching query results"); - } - - db_query_end(&qp); - - if (sort_headers) - daap_sort_context_free(sctx); - - goto out_list_free; + DPRINTF(E_LOG, L_DAAP, "Error fetching results\n"); + dmap_error_make(hreq->reply, tag, "Error fetching query results"); + goto error; } /* Add header to evbuf, add grouplist to evbuf */ @@ -2089,73 +1774,47 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri if (sort_headers) { daap_sort_finalize(sctx); - dmap_add_container(evbuf, tag, len + evbuffer_get_length(sctx->headerlist) + 61); + dmap_add_container(hreq->reply, tag, len + evbuffer_get_length(sctx->headerlist) + 61); } else - dmap_add_container(evbuf, tag, len + 53); + dmap_add_container(hreq->reply, tag, len + 53); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_char(evbuf, "muty", 0); /* 9 */ - dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ - dmap_add_int(evbuf,"mrco", ngrp); /* 12 */ - dmap_add_container(evbuf, "mlcl", len); /* 8 */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_char(hreq->reply, "muty", 0); /* 9 */ + dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */ + dmap_add_int(hreq->reply,"mrco", ngrp); /* 12 */ + dmap_add_container(hreq->reply, "mlcl", len); /* 8 */ - db_query_end(&qp); - - ret = evbuffer_add_buffer(evbuf, grouplist); - evbuffer_free(grouplist); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add group list to DAAP groups reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - - if (sort_headers) - daap_sort_context_free(sctx); - - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, grouplist)); if (sort_headers) { len = evbuffer_get_length(sctx->headerlist); - dmap_add_container(evbuf, "mshl", len); /* 8 */ - ret = evbuffer_add_buffer(evbuf, sctx->headerlist); - daap_sort_context_free(sctx); + dmap_add_container(hreq->reply, "mshl", len); /* 8 */ - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP groups reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - return -1; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist)); } - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return 0; - - out_query_free: - free(meta); - - out_group_free: + daap_sort_context_free(sctx); evbuffer_free(group); - - out_list_free: evbuffer_free(grouplist); + free_query_params(&qp, 1); - out_qfilter_free: - free(qp.filter); + return DAAP_REPLY_OK; - return -1; + error: + daap_sort_context_free(sctx); + evbuffer_free(group); + evbuffer_free(grouplist); + free_query_params(&qp, 1); + + return DAAP_REPLY_ERROR; } -static int -daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_browse(struct httpd_request *hreq) { struct query_params qp; - struct daap_session *s; struct evbuffer *itemlist; struct sort_ctx *sctx; char *browse_item; @@ -2166,104 +1825,49 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri int nitems; int ret; - s = daap_session_find(req, query, evbuf); - if (!s && req) - return -1; - - memset(&qp, 0, sizeof(struct query_params)); - - get_query_params(query, &sort_headers, &qp); - - user_agent_filter(ua, &qp); - - if (strcmp(uri[3], "artists") == 0) + if (strcmp(hreq->uri_parsed->path_parts[3], "artists") == 0) { tag = "abar"; - qp.type = Q_BROWSE_ARTISTS; + query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ARTISTS); qp.sort = S_ARTIST; } - else if (strcmp(uri[3], "albums") == 0) + else if (strcmp(hreq->uri_parsed->path_parts[3], "albums") == 0) { tag = "abal"; - qp.type = Q_BROWSE_ALBUMS; + query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_ALBUMS); qp.sort = S_ALBUM; } - else if (strcmp(uri[3], "genres") == 0) + else if (strcmp(hreq->uri_parsed->path_parts[3], "genres") == 0) { tag = "abgn"; - qp.type = Q_BROWSE_GENRES; + query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_GENRES); qp.sort = S_GENRE; } - else if (strcmp(uri[3], "composers") == 0) + else if (strcmp(hreq->uri_parsed->path_parts[3], "composers") == 0) { tag = "abcp"; - qp.type = Q_BROWSE_COMPOSERS; + query_params_set(&qp, &sort_headers, hreq, Q_BROWSE_COMPOSERS); qp.sort = S_COMPOSER; } else { - DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", uri[3]); - - dmap_send_error(req, "abro", "Invalid browse type"); - ret = -1; - - goto out_qfilter_free; + DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", hreq->uri_parsed->path_parts[3]); + dmap_error_make(hreq->reply, "abro", "Invalid browse type"); + return DAAP_REPLY_ERROR; } - ret = evbuffer_expand(evbuf, 52); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP browse reply\n"); - - dmap_send_error(req, "abro", "Out of memory"); - goto out_qfilter_free; - } - - itemlist = evbuffer_new(); - if (!itemlist) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP browse item list\n"); - - dmap_send_error(req, "abro", "Out of memory"); - ret = -1; - - goto out_qfilter_free; - } - - /* Start with a big enough evbuffer - it'll expand as needed */ - ret = evbuffer_expand(itemlist, 512); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DMAP browse item list\n"); - - dmap_send_error(req, "abro", "Out of memory"); - - goto out_itemlist_free; - } - - sctx = NULL; - if (sort_headers) - { - sctx = daap_sort_context_new(); - if (!sctx) - { - DPRINTF(E_LOG, L_DAAP, "Could not create sort context\n"); - - dmap_send_error(req, "abro", "Out of memory"); - ret = -1; - - goto out_itemlist_free; - } - } + CHECK_NULL(L_DAAP, itemlist = evbuffer_new()); + CHECK_NULL(L_DAAP, sctx = daap_sort_context_new()); + CHECK_ERR(L_DAAP, evbuffer_expand(hreq->reply, 52)); + CHECK_ERR(L_DAAP, evbuffer_expand(itemlist, 1024)); // Just a starting alloc, it'll expand as needed ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not start query\n"); - dmap_send_error(req, "abro", "Could not start query"); - - goto out_sort_headers_free; + dmap_error_make(hreq->reply, "abro", "Could not start query"); + goto error; } nitems = 0; @@ -2277,7 +1881,6 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not add sort header to DAAP browse reply\n"); - break; } } @@ -2285,84 +1888,60 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri dmap_add_string(itemlist, "mlit", browse_item); } + db_query_end(&qp); + if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Error fetching/building results\n"); - dmap_send_error(req, "abro", "Error fetching/building query results"); - db_query_end(&qp); - - goto out_sort_headers_free; + dmap_error_make(hreq->reply, "abro", "Error fetching/building query results"); + goto error; } len = evbuffer_get_length(itemlist); if (sort_headers) { daap_sort_finalize(sctx); - dmap_add_container(evbuf, "abro", len + evbuffer_get_length(sctx->headerlist) + 52); + dmap_add_container(hreq->reply, "abro", len + evbuffer_get_length(sctx->headerlist) + 52); } else - dmap_add_container(evbuf, "abro", len + 44); + dmap_add_container(hreq->reply, "abro", len + 44); - dmap_add_int(evbuf, "mstt", 200); /* 12 */ - dmap_add_int(evbuf, "mtco", qp.results); /* 12 */ - dmap_add_int(evbuf, "mrco", nitems); /* 12 */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "mtco", qp.results); /* 12 */ + dmap_add_int(hreq->reply, "mrco", nitems); /* 12 */ + dmap_add_container(hreq->reply, tag, len); /* 8 */ - dmap_add_container(evbuf, tag, len); /* 8 */ - - db_query_end(&qp); - - ret = evbuffer_add_buffer(evbuf, itemlist); - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add item list to DAAP browse reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - - goto out_sort_headers_free; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, itemlist)); if (sort_headers) { len = evbuffer_get_length(sctx->headerlist); - dmap_add_container(evbuf, "mshl", len); /* 8 */ - ret = evbuffer_add_buffer(evbuf, sctx->headerlist); + dmap_add_container(hreq->reply, "mshl", len); /* 8 */ - if (ret < 0) - { - DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP browse reply\n"); - - dmap_send_error(req, tag, "Out of memory"); - - goto out_sort_headers_free; - } + CHECK_ERR(L_DAAP, evbuffer_add_buffer(hreq->reply, sctx->headerlist)); } - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - ret = 0; - - out_sort_headers_free: - if (sort_headers) - daap_sort_context_free(sctx); - - out_itemlist_free: + daap_sort_context_free(sctx); evbuffer_free(itemlist); + free_query_params(&qp, 1); - out_qfilter_free: - if (qp.filter) - free(qp.filter); + return DAAP_REPLY_OK; - return ret; + error: + daap_sort_context_free(sctx); + evbuffer_free(itemlist); + free_query_params(&qp, 1); + + return DAAP_REPLY_ERROR; } /* NOTE: We only handle artwork at the moment */ -static int -daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_extra_data(struct httpd_request *hreq) { - char clen[32]; - struct daap_session *s; struct evkeyvalq *headers; + char clen[32]; const char *param; char *ctype; size_t len; @@ -2371,53 +1950,51 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char * int max_h; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return -1; - - ret = safe_atoi32(uri[3], &id); - if (ret < 0) + if (!hreq->req) { - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return -1; + DPRINTF(E_LOG, L_DAAP, "Bug! daap_reply_extra_data() cannot be called without an actual connection\n"); + return DAAP_REPLY_NO_CONNECTION; } - if (evhttp_find_header(query, "mw") && evhttp_find_header(query, "mh")) + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id); + if (ret < 0) { - param = evhttp_find_header(query, "mw"); + DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", hreq->uri_parsed->path_parts[3]); + return DAAP_REPLY_BAD_REQUEST; + } + + if (evhttp_find_header(hreq->query, "mw") && evhttp_find_header(hreq->query, "mh")) + { + param = evhttp_find_header(hreq->query, "mw"); ret = safe_atoi32(param, &max_w); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Could not convert mw parameter to integer\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return -1; + DPRINTF(E_LOG, L_DAAP, "Could not convert mw parameter to integer: '%s'\n", param); + return DAAP_REPLY_BAD_REQUEST; } - param = evhttp_find_header(query, "mh"); + param = evhttp_find_header(hreq->query, "mh"); ret = safe_atoi32(param, &max_h); if (ret < 0) { - DPRINTF(E_LOG, L_DAAP, "Could not convert mh parameter to integer\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return -1; + DPRINTF(E_LOG, L_DAAP, "Could not convert mh parameter to integer: '%s'\n", param); + return DAAP_REPLY_BAD_REQUEST; } } else { - DPRINTF(E_DBG, L_DAAP, "Request for artwork without mw/mh parameter\n"); + DPRINTF(E_DBG, L_DAAP, "Request for artwork without mw or mh parameter\n"); max_w = 0; max_h = 0; } - if (strcmp(uri[2], "groups") == 0) - ret = artwork_get_group(evbuf, id, max_w, max_h); - else if (strcmp(uri[2], "items") == 0) - ret = artwork_get_item(evbuf, id, max_w, max_h); + if (strcmp(hreq->uri_parsed->path_parts[2], "groups") == 0) + ret = artwork_get_group(hreq->reply, id, max_w, max_h); + else if (strcmp(hreq->uri_parsed->path_parts[2], "items") == 0) + ret = artwork_get_item(hreq->reply, id, max_w, max_h); - len = evbuffer_get_length(evbuf); + len = evbuffer_get_length(hreq->reply); switch (ret) { @@ -2431,83 +2008,44 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char * default: if (len > 0) - evbuffer_drain(evbuf, len); + evbuffer_drain(hreq->reply, len); goto no_artwork; } - headers = evhttp_request_get_output_headers(req); + headers = evhttp_request_get_output_headers(hreq->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); - return 0; + return DAAP_REPLY_OK_NO_GZIP; no_artwork: - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); - return -1; + return DAAP_REPLY_NO_CONTENT; } -static int -daap_stream(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_stream(struct httpd_request *hreq) { int id; int ret; - ret = safe_atoi32(uri[3], &id); + if (!hreq->req) + { + DPRINTF(E_LOG, L_DAAP, "Bug! daap_stream() cannot be called without an actual connection\n"); + return DAAP_REPLY_NO_CONNECTION; + } + + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &id); if (ret < 0) - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - else - httpd_stream_file(req, id); + return DAAP_REPLY_BAD_REQUEST; - return ret; + httpd_stream_file(hreq->req, id); + + return DAAP_REPLY_NONE; } -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; -} - - #ifdef DMAP_TEST static const struct dmap_field dmap_TEST = { "test.container", "TEST", NULL, DMAP_TYPE_LIST }; static const struct dmap_field dmap_TST1 = { "test.ubyte", "TST1", NULL, DMAP_TYPE_UBYTE }; @@ -2520,21 +2058,14 @@ static const struct dmap_field dmap_TST7 = { "test.ulong", "TST7", NULL, DMA static const struct dmap_field dmap_TST8 = { "test.long", "TST8", NULL, DMAP_TYPE_LONG }; static const struct dmap_field dmap_TST9 = { "test.string", "TST9", NULL, DMAP_TYPE_STRING }; -static int -daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua) +static enum daap_reply_result +daap_reply_dmap_test(struct httpd_request *hreq) { - char buf[64]; struct evbuffer *test; + char buf[64]; int ret; - test = evbuffer_new(); - if (!test) - { - DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP test\n"); - - dmap_send_error(req, dmap_TEST.tag, "Out of memory"); - return -1; - } + CHECK_NULL(L_DAAP, test = evbuffer_new()); /* UBYTE */ snprintf(buf, sizeof(buf), "%" PRIu8, UINT8_MAX); @@ -2588,29 +2119,25 @@ daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char ** dmap_add_field(test, &dmap_TST8, buf, 0); dmap_add_field(test, &dmap_TST9, buf, 0); - dmap_add_container(evbuf, dmap_TEST.tag, evbuffer_get_length(test)); + dmap_add_container(hreq->reply, dmap_TEST.tag, evbuffer_get_length(test)); - ret = evbuffer_add_buffer(evbuf, test); + ret = evbuffer_add_buffer(hreq->reply, test); evbuffer_free(test); if (ret < 0) { DPRINTF(E_LOG, L_DAAP, "Could not add test results to DMAP test reply\n"); - dmap_send_error(req, dmap_TEST.tag, "Out of memory"); - return -1; + dmap_error_make(hreq->reply, dmap_TEST.tag, "Out of memory"); + return DAAP_REPLY_ERROR; } - httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); - - return 0; + return DAAP_REPLY_OK_NO_GZIP; } #endif /* DMAP_TEST */ - -static struct uri_map daap_handlers[] = +static struct httpd_uri_map daap_handlers[] = { - { .regexp = "^/server-info$", .handler = daap_reply_server_info @@ -2677,324 +2204,203 @@ static struct uri_map daap_handlers[] = .handler = daap_reply_dmap_test }, #endif /* DMAP_TEST */ - { + { .regexp = NULL, .handler = NULL } }; +/* ------------------------------- 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) { - char *full_uri; - char *uri; - char *ptr; - char *uri_parts[7]; - struct evbuffer *evbuf; - struct evkeyvalq query; + struct httpd_request *hreq; struct evkeyvalq *headers; struct timespec start; struct timespec end; - const char *ua; - cfg_t *lib; - char *libname; - char *passwd; - int msec; - int handler; + struct daap_session session; + const char *param; + int32_t id; int ret; - int i; + int msec; - memset(&query, 0, sizeof(struct evkeyvalq)); + DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", uri_parsed->uri); - full_uri = httpd_fixup_uri(req); - if (!full_uri) + hreq = httpd_request_parse(req, uri_parsed, NULL, daap_handlers); + if (!hreq) { + DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", uri_parsed->path, uri_parsed->uri); + httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); return; } - ptr = daap_fix_request_uri(req, full_uri); - if (!ptr) + // Check if we have a session and point hreq->extra_data to it + param = evhttp_find_header(hreq->query, "session-id"); + if (param) { - free(full_uri); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + ret = safe_atoi32(param, &id); + if (ret < 0) + DPRINTF(E_LOG, L_DAAP, "Ignoring non-numeric session id in DAAP request: '%s'\n", uri_parsed->uri); + else + hreq->extra_data = daap_session_get(id); + } + + ret = daap_request_authorize(hreq); + if (ret < 0) + { + httpd_send_error(req, 403, "Forbidden"); + free(hreq); return; } - if (ptr != full_uri) + // Create an ad-hoc session, which is a way of passing is_remote to the handler, even though no real session exists + if (!hreq->extra_data) { - uri = strdup(ptr); - free(full_uri); - - if (!uri) - { - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; - } - - full_uri = uri; - } - - uri = extract_uri(full_uri); - if (!uri) - { - free(full_uri); - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; - } - - DPRINTF(E_DBG, L_DAAP, "DAAP request: %s\n", full_uri); - - handler = -1; - for (i = 0; daap_handlers[i].handler; i++) - { - ret = regexec(&daap_handlers[i].preg, uri, 0, NULL, 0); - if (ret == 0) - { - handler = i; - break; - } - } - - if (handler < 0) - { - DPRINTF(E_LOG, L_DAAP, "Unrecognized DAAP request\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - - free(uri); - free(full_uri); - return; - } - - /* Check authentication */ - lib = cfg_getsec(cfg, "library"); - passwd = cfg_getstr(lib, "password"); - - /* No authentication for these URIs */ - if ((strcmp(uri, "/server-info") == 0) - || (strcmp(uri, "/logout") == 0) - || (strcmp(uri, "/content-codes") == 0) - || (strncmp(uri, "/databases/1/items/", strlen("/databases/1/items/")) == 0)) - passwd = NULL; - - /* Waive HTTP authentication for Remote - * Remotes are authentified by their pairing-guid; DAAP queries require a - * valid session-id that Remote can only obtain if its pairing-guid is in - * our database. So HTTP authentication is waived for Remote. - */ - headers = evhttp_request_get_input_headers(req); - ua = evhttp_find_header(headers, "User-Agent"); - if ((ua) && (strncmp(ua, "Remote", strlen("Remote")) == 0)) - passwd = NULL; - - if (passwd) - { - 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_DAAP, "DAAP 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; + memset(&session, 0, sizeof(struct daap_session)); + session.is_remote = (evhttp_find_header(hreq->query, "pairing-guid") != NULL); + hreq->extra_data = &session; } // Set reply headers headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "Accept-Ranges", "bytes"); evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION); - /* Content-Type for all replies, even the actual audio streaming. Note that - * video streaming will override this Content-Type with a more appropriate - * video/ Content-Type as expected by clients like Front Row. - */ + // Content-Type for all replies, even the actual audio streaming. Note that + // video streaming will override this Content-Type with a more appropriate + // video/ Content-Type as expected by clients like Front Row. evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged"); - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DAAP, "Could not allocate evbuffer for DAAP reply\n"); - - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - - free(uri); - free(full_uri); - return; - } + // Now we create the actual reply + CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new()); // Try the cache - ret = cache_daap_get(full_uri, evbuf); + ret = cache_daap_get(hreq->reply, uri_parsed->uri); if (ret == 0) { // The cache will return the data gzipped, so httpd_send_reply won't need to do it evhttp_add_header(headers, "Content-Encoding", "gzip"); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); // TODO not all want this reply + httpd_send_reply(req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply - evbuffer_free(evbuf); - free(uri); - free(full_uri); + evbuffer_free(hreq->reply); + free(hreq); return; } - // No cache, so prepare handler arguments and send to the handler - evhttp_parse_query(full_uri, &query); - + // No dice, let's call the handler so it can construct a reply and then send it (note that the reply may be an error) clock_gettime(CLOCK_MONOTONIC, &start); - daap_handlers[handler].handler(req, evbuf, uri_parts, &query, ua); + ret = hreq->handler(hreq); + + daap_reply_send(hreq, ret); clock_gettime(CLOCK_MONOTONIC, &end); - msec = (end.tv_sec * 1000 + end.tv_nsec / 1000000) - (start.tv_sec * 1000 + start.tv_nsec / 1000000); DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec); - if (msec > cache_daap_threshold()) - cache_daap_add(full_uri, ua, msec); + if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold()) + cache_daap_add(uri_parsed->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec); - evhttp_clear_headers(&query); - evbuffer_free(evbuf); - free(uri); - free(full_uri); + evbuffer_free(hreq->reply); + free(hreq); } 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 return 0; } -struct evbuffer * -daap_reply_build(char *full_uri, const char *ua) +int +daap_session_is_valid(int id) { - char *uri; - char *ptr; - char *uri_parts[7]; - struct evbuffer *evbuf; - struct evkeyvalq query; - int handler; + struct daap_session *session; + + session = daap_session_get(id); + + if (session) + session->mtime = time(NULL); + + return session ? 1 : 0; +} + +// Thread: Cache +struct evbuffer * +daap_reply_build(const char *uri, const char *user_agent, int is_remote) +{ + struct httpd_request *hreq; + struct httpd_uri_parsed *uri_parsed; + struct evbuffer *reply; + struct daap_session session; int ret; - int i; - DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: %s\n", full_uri); + DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri); - uri = extract_uri(full_uri); - if (!uri) + reply = NULL; + + uri_parsed = httpd_uri_parse(uri); + if (!uri_parsed) + return NULL; + + hreq = httpd_request_parse(NULL, uri_parsed, user_agent, daap_handlers); + if (!hreq) { - DPRINTF(E_LOG, L_DAAP, "Error extracting DAAP request: %s\n", full_uri); - - return NULL; + DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path '%s' in request: '%s'\n", uri_parsed->path, uri_parsed->uri); + goto out_free_uri; } - handler = -1; - for (i = 0; daap_handlers[i].handler; i++) - { - ret = regexec(&daap_handlers[i].preg, uri, 0, NULL, 0); - if (ret == 0) - { - handler = i; - break; - } - } + memset(&session, 0, sizeof(struct daap_session)); + session.is_remote = (bool)is_remote; - if (handler < 0) - { - DPRINTF(E_LOG, L_DAAP, "Unrecognized DAAP request: %s\n", full_uri); + hreq->extra_data = &session; - free(uri); - return NULL; - } + CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new()); - 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_DAAP, "DAAP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0); - - free(uri); - return NULL; - } - - evbuf = evbuffer_new(); - if (!evbuf) - { - DPRINTF(E_LOG, L_DAAP, "Could not allocate evbuffer for building DAAP reply\n"); - - free(uri); - return NULL; - } - - evhttp_parse_query(full_uri, &query); - - ret = daap_handlers[handler].handler(NULL, evbuf, uri_parts, &query, ua); + ret = hreq->handler(hreq); if (ret < 0) { - evbuffer_free(evbuf); - evbuf = NULL; + evbuffer_free(hreq->reply); + goto out_free_hreq; } - evhttp_clear_headers(&query); - free(uri); + reply = hreq->reply; - return evbuf; + out_free_hreq: + free(hreq); + out_free_uri: + httpd_uri_free(uri_parsed); + + return reply; } int diff --git a/src/httpd_daap.h b/src/httpd_daap.h index d5907743..53fb6e55 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,12 +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(char *full_uri, const char *ua); +daap_reply_build(const char *uri, const char *user_agent, int is_remote); #endif /* !__HTTPD_DAAP_H__ */ diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index e4baece2..ccef9194 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -37,15 +36,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_dacp.h" #include "dmap_common.h" #include "db.h" #include "daap_query.h" @@ -55,19 +52,6 @@ /* httpd event base, from httpd.c */ extern struct event_base *evbase_httpd; -/* From httpd_daap.c */ -struct daap_session; - -struct daap_session * -daap_session_find(struct evhttp_request *req, struct evkeyvalq *query, struct evbuffer *evbuf); - - -struct uri_map { - regex_t preg; - char *regexp; - void (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query); -}; - struct dacp_update_request { struct evhttp_request *req; @@ -84,7 +68,8 @@ struct dacp_prop_map { }; -/* Forward - properties getters */ +/* ---------------- FORWARD - PROPERTIES GETTERS AND SETTERS ---------------- */ + static void dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); static void @@ -121,7 +106,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 @@ -174,7 +159,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) { @@ -233,26 +219,378 @@ dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db dmap_add_int(evbuf, "cast", queue_item->song_length); /* Song length in ms */ } +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; +} + +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 int +dacp_request_authorize(struct httpd_request *hreq) +{ + const char *param; + int32_t id; + int ret; + + if (httpd_peer_is_trusted(hreq->req)) + return 0; + + param = evhttp_find_header(hreq->query, "session-id"); + if (!param) + { + DPRINTF(E_LOG, L_DACP, "No session-id specified in request\n"); + goto invalid; + } + + ret = safe_atoi32(param, &id); + if (ret < 0) + { + DPRINTF(E_LOG, L_DACP, "Invalid session-id specified in request: '%s'\n", param); + goto invalid; + } + + if (!daap_session_is_valid(id)) + { + DPRINTF(E_LOG, L_DACP, "Session %d does not exist\n", id); + goto invalid; + } + + return 0; + + invalid: + httpd_send_error(hreq->req, 403, "Forbidden"); + return -1; +} + + +/* ---------------------- UPDATE REQUESTS HANDLERS -------------------------- */ -/* Update requests helpers */ 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); @@ -299,14 +637,11 @@ 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); return 0; } @@ -341,21 +676,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++; @@ -388,7 +710,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); @@ -454,7 +775,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) { @@ -759,247 +1081,45 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query) } -static void -dacp_reply_ctrlint(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +/* --------------------------- REPLY HANDLERS ------------------------------- */ + +static int +dacp_reply_ctrlint(struct httpd_request *hreq) { /* /ctrl-int */ + CHECK_ERR(L_DACP, evbuffer_expand(hreq->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(hreq->reply, "caci", 194); /* 8, unknown dacp container - size of content */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12, dmap.status */ + dmap_add_char(hreq->reply, "muty", 0); /* 9, dmap.updatetype */ + dmap_add_int(hreq->reply, "mtco", 1); /* 12, dmap.specifiedtotalcount */ + dmap_add_int(hreq->reply, "mrco", 1); /* 12, dmap.returnedcount */ + dmap_add_container(hreq->reply, "mlcl", 141); /* 8, dmap.listing - size of content */ + dmap_add_container(hreq->reply, "mlit", 133); /* 8, dmap.listingitem - size of content */ + dmap_add_int(hreq->reply, "miid", 1); /* 12, dmap.itemid - database ID */ + dmap_add_char(hreq->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(hreq->reply, "cmpr", (2 << 16 | 2)); /* 12, dmcp.protocolversion */ + dmap_add_int(hreq->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(hreq->reply, "cmsp", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "aeFR", 0x64); /* 9, unknown */ + dmap_add_char(hreq->reply, "cmsv", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "cass", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "caov", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "casu", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "ceSG", 1); /* 9, unknown */ + dmap_add_char(hreq->reply, "cmrl", 1); /* 9, unknown */ + dmap_add_long(hreq->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); + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + + return 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\n", queuefilter); - - // 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 void -dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +dacp_reply_cue_play(struct httpd_request *hreq) { struct player_status status; const char *sort; @@ -1013,7 +1133,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(hreq->query, "clear-first"); if (param) { ret = safe_atoi32(param, &clear); @@ -1029,18 +1149,18 @@ 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(hreq->query, "query"); if (cuequery) { - sort = evhttp_find_header(query, "sort"); + sort = evhttp_find_header(hreq->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"); - return; + dmap_send_error(hreq->req, "cacr", "Could not build song queue"); + return -1; } } else if (status.status != PLAY_STOPPED) @@ -1048,12 +1168,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(hreq->query, "dacp.shufflestate"); if (param) dacp_propset_shufflestate(param, NULL); pos = 0; - param = evhttp_find_header(query, "index"); + param = evhttp_find_header(hreq->query, "index"); if (param) { ret = safe_atou32(param, &pos); @@ -1062,10 +1182,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(hreq->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(hreq->query, "mode"); if (param && ((strcmp(param, "-1") == 0) || (strcmp(param, "1") == 0))) { /* Play from history queue */ @@ -1079,16 +1199,16 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } } else { DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n"); - dmap_send_error(req, "cacr", "Playback failed to start"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } } else @@ -1102,8 +1222,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } } } @@ -1114,8 +1234,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } } @@ -1125,21 +1245,25 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } 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(hreq->reply, 64)); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + dmap_add_container(hreq->reply, "cacr", 24); /* 8 + len */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "miid", status.id);/* 12 */ + + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + + return 0; } -static void -dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_cue_clear(struct httpd_request *hreq) { /* /cue?command=clear */ @@ -1147,50 +1271,53 @@ 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(hreq->reply, 64)); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + dmap_add_container(hreq->reply, "cacr", 24); /* 8 + len */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "miid", 0); /* 12 */ + + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + + return 0; } -static void -dacp_reply_cue(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_cue(struct httpd_request *hreq) { - struct daap_session *s; const char *param; + int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "command"); + param = evhttp_find_header(hreq->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"); - return; + dmap_send_error(hreq->req, "cacr", "No command in cue request"); + return -1; } if (strcmp(param, "clear") == 0) - dacp_reply_cue_clear(req, evbuf, uri, query); + return dacp_reply_cue_clear(hreq); else if (strcmp(param, "play") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); + return dacp_reply_cue_play(hreq); else { DPRINTF(E_LOG, L_DACP, "Unknown cue command %s\n", param); - dmap_send_error(req, "cacr", "Unknown command in cue request"); - return; + dmap_send_error(hreq->req, "cacr", "Unknown command in cue request"); + return -1; } } -static void -dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playspec(struct httpd_request *hreq) { struct player_status status; - struct daap_session *s; const char *param; const char *shuffle; uint32_t plid; @@ -1204,19 +1331,18 @@ 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 */ - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; /* Check for shuffle */ - shuffle = evhttp_find_header(query, "dacp.shufflestate"); + shuffle = evhttp_find_header(hreq->query, "dacp.shufflestate"); /* Playlist ID */ - param = evhttp_find_header(query, "container-spec"); + param = evhttp_find_header(hreq->query, "container-spec"); if (!param) { DPRINTF(E_LOG, L_DACP, "No container-spec in playspec request\n"); - goto out_fail; } @@ -1224,7 +1350,6 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (!param) { DPRINTF(E_LOG, L_DACP, "Malformed container-spec parameter in playspec request\n"); - goto out_fail; } param++; @@ -1237,19 +1362,17 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Couldn't convert container-spec to an integer in playspec (%s)\n", param); - goto out_fail; } if (!shuffle) { /* Start song ID */ - if ((param = evhttp_find_header(query, "item-spec"))) + if ((param = evhttp_find_header(hreq->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(hreq->query, "container-item-spec"))) { DPRINTF(E_LOG, L_DACP, "No container-item-spec/item-spec in playspec request\n"); - goto out_fail; } @@ -1257,7 +1380,6 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (!param) { DPRINTF(E_LOG, L_DACP, "Malformed container-item-spec/item-spec parameter in playspec request\n"); - goto out_fail; } param++; @@ -1270,7 +1392,6 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Couldn't convert container-item-spec/item-spec to an integer in playspec (%s)\n", param); - goto out_fail; } } @@ -1294,7 +1415,6 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid); - goto out_fail; } @@ -1317,44 +1437,45 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not start playback\n"); - goto out_fail; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); - return; + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + return 0; out_fail: - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(hreq->req, 500, "Internal Server Error"); + + return -1; } -static void -dacp_reply_pause(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_pause(struct httpd_request *hreq) { - struct daap_session *s; + int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; 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(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playpause(struct httpd_request *hreq) { - struct daap_session *s; struct player_status status; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; - + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; player_get_status(&status); if (status.status == PLAY_PLAYING) @@ -1368,32 +1489,33 @@ 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"); - return; + httpd_send_error(hreq->req, 500, "Internal Server Error"); + return -1; } } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_nextitem(struct httpd_request *hreq) { - struct daap_session *s; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; ret = player_playback_next(); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Player returned an error for nextitem\n"); - httpd_send_error(req, 500, "Internal Server Error"); - return; + httpd_send_error(hreq->req, 500, "Internal Server Error"); + return -1; } ret = player_playback_start(); @@ -1401,31 +1523,32 @@ 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"); - return; + httpd_send_error(hreq->req, 500, "Internal Server Error"); + return -1; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_previtem(struct httpd_request *hreq) { - struct daap_session *s; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; ret = player_playback_prev(); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Player returned an error for previtem\n"); - httpd_send_error(req, 500, "Internal Server Error"); - return; + httpd_send_error(hreq->req, 500, "Internal Server Error"); + return -1; } ret = player_playback_start(); @@ -1433,155 +1556,70 @@ 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"); - return; - } - - /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); -} - -static void -dacp_reply_beginff(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) -{ - struct daap_session *s; - - s = daap_session_find(req, query, evbuf); - if (!s) - return; - - /* TODO */ - - /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); -} - -static void -dacp_reply_beginrew(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) -{ - struct daap_session *s; - - s = daap_session_find(req, query, evbuf); - if (!s) - return; - - /* TODO */ - - /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); -} - -static void -dacp_reply_playresume(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) -{ - struct daap_session *s; - - s = daap_session_find(req, query, evbuf); - if (!s) - 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"); + httpd_send_error(hreq->req, 500, "Internal Server Error"); 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; - } + /* 204 No Content is the canonical reply */ + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); return 0; } static int -playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item *queue_item, int pos_in_queue, uint32_t plid) +dacp_reply_beginff(struct httpd_request *hreq) { - 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; - } + ret = dacp_request_authorize(hreq); + if (ret < 0) + 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); + /* TODO */ - dmap_add_container(songlist, "mlit", evbuffer_get_length(song)); + /* 204 No Content is the canonical reply */ + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); - ret = evbuffer_add_buffer(songlist, song); - evbuffer_free(song); - - return ret; + return 0; } -static void -dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, - struct evkeyvalq *query) +static int +dacp_reply_beginrew(struct httpd_request *hreq) +{ + int ret; + + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; + + /* TODO */ + + /* 204 No Content is the canonical reply */ + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; +} + +static int +dacp_reply_playresume(struct httpd_request *hreq) +{ + int ret; + + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; + + /* TODO */ + + /* 204 No Content is the canonical reply */ + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; +} + +static int +dacp_reply_playqueuecontents(struct httpd_request *hreq) { - struct daap_session *s; struct evbuffer *songlist; struct evbuffer *playlists; struct player_status status; @@ -1598,14 +1636,14 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */ - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; DPRINTF(E_DBG, L_DACP, "Fetching playqueue contents\n"); span = 50; /* Default */ - param = evhttp_find_header(query, "span"); + param = evhttp_find_header(hreq->query, "span"); if (param) { ret = safe_atoi32(param, &span); @@ -1613,14 +1651,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(hreq->reply, 128)); player_get_status(&status); @@ -1683,11 +1715,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 */ @@ -1695,7 +1726,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 */ @@ -1714,46 +1745,43 @@ 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(hreq->reply, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12, dmap.status */ + dmap_add_int(hreq->reply, "mtco", abs(span)); /* 12 */ + dmap_add_int(hreq->reply, "mrco", count); /* 12 */ + dmap_add_char(hreq->reply, "ceQu", 0); /* 9 */ + dmap_add_container(hreq->reply, "mlcl", 8 + playlist_length + songlist_length); /* 8 */ + dmap_add_container(hreq->reply, "ceQS", playlist_length); /* 8 */ + + CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->reply, playlists)); + CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->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(hreq->reply, "apsm", status.shuffle); /* 9, daap.playlistshufflemode - not part of mlcl container */ + dmap_add_char(hreq->reply, "aprm", status.repeat); /* 9, daap.playlistrepeatmode - not part of mlcl container */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); - return; + return 0; 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(hreq->req, "ceQR", "Database error"); + + return -1; } -static void -dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playqueueedit_clear(struct httpd_request *hreq) { const char *param; struct player_status status; - param = evhttp_find_header(query, "mode"); + param = evhttp_find_header(hreq->query, "mode"); /* * The mode parameter contains the playlist to be cleared. @@ -1768,15 +1796,17 @@ 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(hreq->reply, "cacr", 24); /* 8 + len */ + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + dmap_add_int(hreq->reply, "miid", 0); /* 12 */ - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + + return 0; } -static void -dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playqueueedit_add(struct httpd_request *hreq) { //?command=add&query='dmap.itemid:156'&sort=album&mode=3&session-id=100 // -> mode=3: add to playqueue position 0 (play next) @@ -1802,7 +1832,7 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf, mode = 1; - param = evhttp_find_header(query, "mode"); + param = evhttp_find_header(hreq->query, "mode"); if (param) { ret = safe_atoi32(param, &mode); @@ -1810,8 +1840,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } } @@ -1824,16 +1854,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(hreq->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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } - sort = evhttp_find_header(query, "sort"); + sort = evhttp_find_header(hreq->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:"))) @@ -1842,9 +1872,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(hreq->query, "queuefilter"); - querymodifier = evhttp_find_header(query, "query-modifier"); + querymodifier = evhttp_find_header(hreq->query, "query-modifier"); if (!querymodifier || (strcmp(querymodifier, "containers") != 0)) { quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)")); @@ -1858,8 +1888,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid); @@ -1870,8 +1900,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } if (ret > 0) @@ -1903,16 +1933,18 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Playback failed to start"); + return -1; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playqueueedit_move(struct httpd_request *hreq) { /* * Handles the move command. @@ -1924,12 +1956,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(hreq->query, "edit-params"); if (param) { ret = safe_atoi32(strchr(param, ':') + 1, &src); @@ -1937,8 +1968,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } ret = safe_atoi32(strchr(param, ',') + 1, &dst); @@ -1946,8 +1977,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } player_get_status(&status); @@ -1955,11 +1986,13 @@ 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(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playqueueedit_remove(struct httpd_request *hreq) { /* * Handles the remove command. @@ -1967,12 +2000,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(hreq->query, "items"); if (param) { ret = safe_atoi32(param, &item_index); @@ -1980,8 +2012,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cacr", "Invalid request"); + return -1; } player_get_status(&status); @@ -1990,14 +2022,16 @@ 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(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playqueueedit(struct httpd_request *hreq) { - struct daap_session *s; const char *param; + int ret; /* Variations of /ctrl-int/1/playqueue-edit and expected behaviour User selected play (album or artist tab): @@ -2042,59 +2076,58 @@ dacp_reply_playqueueedit(struct evhttp_request *req, struct evbuffer *evbuf, cha -> remove song on position 1 from the playqueue */ - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "command"); + param = evhttp_find_header(hreq->query, "command"); if (!param) { DPRINTF(E_LOG, L_DACP, "No command in playqueue-edit request\n"); - dmap_send_error(req, "cmst", "Invalid request"); - return; + dmap_send_error(hreq->req, "cmst", "Invalid request"); + return -1; } if (strcmp(param, "clear") == 0) - dacp_reply_playqueueedit_clear(req, evbuf, uri, query); + return dacp_reply_playqueueedit_clear(hreq); else if (strcmp(param, "playnow") == 0) - dacp_reply_cue_play(req, evbuf, uri, query); + return dacp_reply_cue_play(hreq); else if (strcmp(param, "add") == 0) - dacp_reply_playqueueedit_add(req, evbuf, uri, query); + return dacp_reply_playqueueedit_add(hreq); else if (strcmp(param, "move") == 0) - dacp_reply_playqueueedit_move(req, evbuf, uri, query); + return dacp_reply_playqueueedit_move(hreq); else if (strcmp(param, "remove") == 0) - dacp_reply_playqueueedit_remove(req, evbuf, uri, query); + return dacp_reply_playqueueedit_remove(hreq); else { DPRINTF(E_LOG, L_DACP, "Unknown playqueue-edit command %s\n", param); - dmap_send_error(req, "cmst", "Invalid request"); - return; + dmap_send_error(hreq->req, "cmst", "Invalid request"); + return -1; } } -static void -dacp_reply_playstatusupdate(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_playstatusupdate(struct httpd_request *hreq) { - struct daap_session *s; struct dacp_update_request *ur; struct evhttp_connection *evcon; const char *param; int reqd_rev; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "revision-number"); + param = evhttp_find_header(hreq->query, "revision-number"); if (!param) { DPRINTF(E_LOG, L_DACP, "Missing revision-number in update request\n"); - dmap_send_error(req, "cmst", "Invalid request"); - return; + dmap_send_error(hreq->req, "cmst", "Invalid request"); + return -1; } ret = safe_atoi32(param, &reqd_rev); @@ -2102,19 +2135,19 @@ 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"); - return; + dmap_send_error(hreq->req, "cmst", "Invalid request"); + return -1; } if ((reqd_rev == 0) || (reqd_rev == 1)) { - ret = make_playstatusupdate(evbuf); + ret = make_playstatusupdate(hreq->reply); if (ret < 0) - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(hreq->req, 500, "Internal Server Error"); else - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); - return; + return ret; } /* Else, just let the request hang until we have changes to push back */ @@ -2123,11 +2156,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"); - return; + dmap_send_error(hreq->req, "cmst", "Out of memory"); + return -1; } - ur->req = req; + ur->req = hreq->req; ur->next = update_requests; update_requests = ur; @@ -2135,16 +2168,17 @@ 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(hreq->req); if (evcon) evhttp_connection_set_closecb(evcon, update_fail_cb, ur); + + return 0; } -static void -dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_nowplayingartwork(struct httpd_request *hreq) { char clen[32]; - struct daap_session *s; struct evkeyvalq *headers; const char *param; char *ctype; @@ -2154,52 +2188,44 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, int max_h; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "mw"); + param = evhttp_find_header(hreq->query, "mw"); if (!param) { DPRINTF(E_LOG, L_DACP, "Request for artwork without mw parameter\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; + goto error; } ret = safe_atoi32(param, &max_w); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not convert mw parameter to integer\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; + goto error; } - param = evhttp_find_header(query, "mh"); + param = evhttp_find_header(hreq->query, "mh"); if (!param) { DPRINTF(E_LOG, L_DACP, "Request for artwork without mh parameter\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; + goto error; } ret = safe_atoi32(param, &max_h); if (ret < 0) { DPRINTF(E_LOG, L_DACP, "Could not convert mh parameter to integer\n"); - - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - return; + goto error; } ret = player_now_playing(&id); 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(hreq->reply, id, max_w, max_h); + len = evbuffer_get_length(hreq->reply); switch (ret) { @@ -2213,29 +2239,33 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf, default: if (len > 0) - evbuffer_drain(evbuf, len); + evbuffer_drain(hreq->reply, len); goto no_artwork; } - headers = evhttp_request_get_output_headers(req); + headers = evhttp_request_get_output_headers(hreq->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); - return; + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); + return 0; no_artwork: - httpd_send_error(req, HTTP_NOTFOUND, "Not Found"); + httpd_send_error(hreq->req, HTTP_NOTFOUND, "Not Found"); + return 0; + + error: + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + return -1; } -static void -dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_getproperty(struct httpd_request *hreq) { struct player_status status; - struct daap_session *s; const struct dacp_prop_map *dpm; struct db_queue_item *queue_item = NULL; struct evbuffer *proplist; @@ -2246,17 +2276,17 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char size_t len; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "properties"); + param = evhttp_find_header(hreq->query, "properties"); if (!param) { DPRINTF(E_WARN, L_DACP, "Invalid DACP getproperty request, no properties\n"); - dmap_send_error(req, "cmgt", "Invalid request"); - return; + dmap_send_error(hreq->req, "cmgt", "Invalid request"); + return -1; } propstr = strdup(param); @@ -2264,8 +2294,8 @@ 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"); - return; + dmap_send_error(hreq->req, "cmgt", "Out of memory"); + return -1; } proplist = evbuffer_new(); @@ -2273,7 +2303,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(hreq->req, "cmgt", "Out of memory"); goto out_free_propstr; } @@ -2286,7 +2316,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(hreq->req, "cmgt", "Server error"); goto out_free_proplist; } } @@ -2314,40 +2344,36 @@ 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(hreq->reply, "cmgt", 12 + len); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ + + CHECK_ERR(L_DACP, evbuffer_add_buffer(hreq->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(hreq->req, HTTP_OK, "OK", hreq->reply, 0); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); - - return; + return 0; out_free_proplist: evbuffer_free(proplist); out_free_propstr: free(propstr); + + return -1; } -static void -dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_setproperty(struct httpd_request *hreq) { - struct daap_session *s; const struct dacp_prop_map *dpm; struct evkeyval *param; + int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; /* Known properties: * dacp.shufflestate 0/1 @@ -2358,7 +2384,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, hreq->query, next) { dpm = dacp_find_prop(param->key, strlen(param->key)); @@ -2369,82 +2395,48 @@ dacp_reply_setproperty(struct evhttp_request *req, struct evbuffer *evbuf, char } if (dpm->propset) - dpm->propset(param->value, query); + dpm->propset(param->value, hreq->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(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } -static void -speaker_enum_cb(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg) +static int +dacp_reply_getspeakers(struct httpd_request *hreq) { - 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) -{ - struct daap_session *s; struct evbuffer *spklist; size_t len; + int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - 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(hreq->reply, "casp", 12 + len); + dmap_add_int(hreq->reply, "mstt", 200); /* 12 */ - evbuffer_add_buffer(evbuf, spklist); + evbuffer_add_buffer(hreq->reply, spklist); evbuffer_free(spklist); - httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0); + httpd_send_reply(hreq->req, HTTP_OK, "OK", hreq->reply, 0); + + return 0; } -static void -dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query) +static int +dacp_reply_setspeakers(struct httpd_request *hreq) { - struct daap_session *s; const char *param; const char *ptr; uint64_t *ids; @@ -2452,17 +2444,17 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char int i; int ret; - s = daap_session_find(req, query, evbuf); - if (!s) - return; + ret = dacp_request_authorize(hreq); + if (ret < 0) + return -1; - param = evhttp_find_header(query, "speaker-id"); + param = evhttp_find_header(hreq->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"); - return; + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + return -1; } if (strlen(param) == 0) @@ -2476,14 +2468,7 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char while ((ptr = strchr(ptr + 1, ','))) nspk++; - ids = calloc((nspk + 1), sizeof(uint64_t)); - if (!ids) - { - DPRINTF(E_LOG, L_DACP, "Out of memory for speaker ids\n"); - - httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error"); - return; - } + CHECK_NULL(L_DACP, ids = calloc((nspk + 1), sizeof(uint64_t))); param--; i = 1; @@ -2526,19 +2511,20 @@ dacp_reply_setspeakers(struct evhttp_request *req, struct evbuffer *evbuf, char /* Password problem */ if (ret == -2) - httpd_send_error(req, 902, ""); + httpd_send_error(hreq->req, 902, ""); else - httpd_send_error(req, 500, "Internal Server Error"); + httpd_send_error(hreq->req, 500, "Internal Server Error"); - return; + return -1; } /* 204 No Content is the canonical reply */ - httpd_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf, HTTPD_SEND_NO_GZIP); + httpd_send_reply(hreq->req, HTTP_NOCONTENT, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + + return 0; } - -static struct uri_map dacp_handlers[] = +static struct httpd_uri_map dacp_handlers[] = { { .regexp = "^/ctrl-int$", @@ -2618,132 +2604,50 @@ 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 httpd_request *hreq; 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) + hreq = httpd_request_parse(req, uri_parsed, NULL, dacp_handlers); + if (!hreq) { + DPRINTF(E_LOG, L_DACP, "Unrecognized path '%s' in DACP request: '%s'\n", uri_parsed->path, uri_parsed->uri); + 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\n"); - - 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, hreq->reply = evbuffer_new()); - evbuffer_free(evbuf); - evhttp_clear_headers(&query); - free(uri); - free(full_uri); + hreq->handler(hreq); + + evbuffer_free(hreq->reply); + free(hreq); } 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..0c594b5d 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,11 @@ # include "spotify.h" #endif -struct uri_map -{ - regex_t preg; - char *regexp; - int (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query); -}; - +/* -------------------------------- 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 +62,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 +90,7 @@ pairing_kickoff(struct evhttp_request* req) } /* - * Endpoint to retrieve pairing information + * Retrieves pairing information * * Example response: * @@ -372,33 +103,269 @@ 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; +} + + +/* --------------------------- REPLY HANDLERS ------------------------------- */ + +/* + * Endpoint to retrieve configuration values + * + * Example response: + * + * { + * "websocket_port": 6603, + * "version": "25.0" + * } + */ +static int +jsonapi_reply_config(struct httpd_request *hreq) +{ + 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(hreq->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 httpd_request *hreq) +{ + 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(hreq->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 httpd_request *hreq) +{ + 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 httpd_request *hreq) +{ + 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(hreq->reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); + + return 0; +} + +static int +jsonapi_reply_spotify_login(struct httpd_request *hreq) +{ +#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(hreq->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(hreq->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 +376,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 httpd_request *hreq) { - if (evhttp_request_get_command(req) == EVHTTP_REQ_POST) + if (evhttp_request_get_command(hreq->req) == EVHTTP_REQ_POST) { - return pairing_kickoff(req); + return pairing_kickoff(hreq->req); } - return pairing_get(evbuf); + return pairing_get(hreq->reply); } static int -jsonapi_reply_lastfm(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query) +jsonapi_reply_lastfm(struct httpd_request *hreq) { - 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(hreq->reply, "%s", json_object_to_json_string(jreply))); + + jparse_free(jreply); return 0; } @@ -451,21 +414,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 httpd_request *hreq) { #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(hreq->req); request = jparse_obj_from_evbuffer(in_evbuf); if (!request) { @@ -473,7 +436,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 +445,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 +463,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(hreq->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 +484,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 httpd_request *hreq) { #ifdef LASTFM lastfm_logout(); @@ -533,7 +492,7 @@ jsonapi_reply_lastfm_logout(struct evhttp_request *req, struct evbuffer *evbuf, return 0; } -static struct uri_map adm_handlers[] = +static struct httpd_uri_map adm_handlers[] = { { .regexp = "^/api/config", .handler = jsonapi_reply_config }, { .regexp = "^/api/library", .handler = jsonapi_reply_library }, @@ -547,119 +506,57 @@ static struct uri_map adm_handlers[] = { .regexp = NULL, .handler = NULL } }; + +/* ------------------------------- JSON API --------------------------------- */ + void -jsonapi_request(struct evhttp_request *req) +jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) { - char *full_uri; - char *uri; - char *ptr; - struct evbuffer *evbuf; - struct evkeyvalq query; + struct httpd_request *hreq; struct evkeyvalq *headers; - int handler; int ret; - int i; - /* Check authentication */ + DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri); + if (!httpd_admin_check_auth(req)) + return; + + hreq = httpd_request_parse(req, uri_parsed, NULL, adm_handlers); + if (!hreq) { - DPRINTF(E_DBG, L_WEB, "JSON api request denied;\n"); + DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in JSON api request: '%s'\n", uri_parsed->path, uri_parsed->uri); + + httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); return; } - memset(&query, 0, sizeof(struct evkeyvalq)); - - full_uri = httpd_fixup_uri(req); - if (!full_uri) - { - evhttp_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, hreq->reply = evbuffer_new()); + + ret = hreq->handler(hreq); 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", hreq->reply, 0); + + error: + evbuffer_free(hreq->reply); + free(hreq); } 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 +576,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; } } @@ -695,4 +592,3 @@ jsonapi_deinit(void) for (i = 0; adm_handlers[i].handler; i++) regfree(&adm_handlers[i].preg); } - 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_oauth.c b/src/httpd_oauth.c new file mode 100644 index 00000000..a8553f4f --- /dev/null +++ b/src/httpd_oauth.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 Espen Jürgensen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "httpd_oauth.h" +#include "logger.h" +#include "misc.h" +#include "conffile.h" +#ifdef HAVE_SPOTIFY_H +# include "spotify.h" +#endif + + +/* --------------------------- REPLY HANDLERS ------------------------------- */ + +#ifdef HAVE_SPOTIFY_H +static int +oauth_reply_spotify(struct httpd_request *hreq) +{ + char redirect_uri[256]; + char *errmsg; + int httpd_port; + int ret; + + httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); + + snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port); + ret = spotify_oauth_callback(hreq->query, redirect_uri, &errmsg); + if (ret < 0) + { + DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback: '%s'\n", hreq->uri_parsed->uri); + httpd_send_error(hreq->req, HTTP_INTERNAL, errmsg); + free(errmsg); + return -1; + } + + httpd_redirect_to_admin(hreq->req); + + return 0; +} +#else +static int +oauth_reply_spotify(struct httpd_request *hreq) +{ + DPRINTF(E_LOG, L_WEB, "This version of forked-daapd was built without support for Spotify\n"); + + httpd_send_error(hreq->req, HTTP_NOTFOUND, "This version of forked-daapd was built without support for Spotify"); + + return -1; +} +#endif + +static struct httpd_uri_map oauth_handlers[] = + { + { + .regexp = "^/oauth/spotify$", + .handler = oauth_reply_spotify + }, + { + .regexp = NULL, + .handler = NULL + } + }; + + +/* ------------------------------- OAUTH API -------------------------------- */ + +void +oauth_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct httpd_request *hreq; + + DPRINTF(E_LOG, L_WEB, "OAuth request: '%s'\n", uri_parsed->uri); + + hreq = httpd_request_parse(req, uri_parsed, NULL, oauth_handlers); + if (!hreq) + { + DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in OAuth request: '%s'\n", uri_parsed->path, uri_parsed->uri); + + httpd_send_error(req, HTTP_NOTFOUND, NULL); + return; + } + + hreq->handler(hreq); + + free(hreq); +} + +int +oauth_is_request(const char *path) +{ + if (strncmp(path, "/oauth/", strlen("/oauth/")) == 0) + return 1; + if (strcmp(path, "/oauth") == 0) + return 1; + + return 0; +} + +int +oauth_init(void) +{ + char buf[64]; + int i; + int ret; + + for (i = 0; oauth_handlers[i].handler; i++) + { + ret = regcomp(&oauth_handlers[i].preg, oauth_handlers[i].regexp, REG_EXTENDED | REG_NOSUB); + if (ret != 0) + { + regerror(ret, &oauth_handlers[i].preg, buf, sizeof(buf)); + + DPRINTF(E_FATAL, L_WEB, "OAuth init failed; regexp error: %s\n", buf); + return -1; + } + } + + return 0; +} + +void +oauth_deinit(void) +{ + int i; + + for (i = 0; oauth_handlers[i].handler; i++) + regfree(&oauth_handlers[i].preg); +} diff --git a/src/httpd_oauth.h b/src/httpd_oauth.h new file mode 100644 index 00000000..71ff1495 --- /dev/null +++ b/src/httpd_oauth.h @@ -0,0 +1,18 @@ +#ifndef __HTTPD_OAUTH_H__ +#define __HTTPD_OAUTH_H__ + +#include "httpd.h" + +void +oauth_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); + +int +oauth_is_request(const char *path); + +int +oauth_init(void); + +void +oauth_deinit(void); + +#endif /* !__HTTPD_OAUTH_H__ */ diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index 77b9b5ce..3ce9e49d 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -28,20 +28,17 @@ #include #include #include -#include #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" @@ -60,12 +57,6 @@ struct field_map { int flags; }; -struct uri_map { - regex_t preg; - char *regexp; - void (*handler)(struct evhttp_request *req, char **uri, struct evkeyvalq *query); -}; - static const struct field_map pl_fields[] = { { "id", dbpli_offsetof(id), F_ALWAYS }, @@ -124,6 +115,8 @@ static const struct field_map rsp_fields[] = }; +/* -------------------------------- HELPERS --------------------------------- */ + static struct evbuffer * mxml_to_evbuf(mxml_node_t *tree) { @@ -161,61 +154,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 +203,56 @@ rsp_send_error(struct evhttp_request *req, char *errmsg) evbuffer_free(evbuf); } +static int +query_params_set(struct query_params *qp, struct httpd_request *hreq) +{ + const char *param; + int ret; + + qp->offset = 0; + param = evhttp_find_header(hreq->query, "offset"); + if (param) + { + ret = safe_atoi32(param, &qp->offset); + if (ret < 0) + { + rsp_send_error(hreq->req, "Invalid offset"); + return -1; + } + } + + qp->limit = 0; + param = evhttp_find_header(hreq->query, "limit"); + if (param) + { + ret = safe_atoi32(param, &qp->limit); + if (ret < 0) + { + rsp_send_error(hreq->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(hreq->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 +278,37 @@ rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply) evbuffer_free(evbuf); } +static int +rsp_request_authorize(struct httpd_request *hreq) +{ + char *passwd; + int ret; -static void -rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query) + if (httpd_peer_is_trusted(hreq->req)) + 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(hreq->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 int +rsp_reply_info(struct httpd_request *hreq) { mxml_node_t *reply; mxml_node_t *status; @@ -342,11 +358,13 @@ 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(hreq->req, reply); + + return 0; } -static void -rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +static int +rsp_reply_db(struct httpd_request *hreq) { struct query_params qp; struct db_playlist_info dbpli; @@ -369,8 +387,8 @@ 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"); - return; + rsp_send_error(hreq->req, "Could not start query"); + return -1; } /* We'd use mxmlNewXML(), but then we can't put any attributes @@ -419,8 +437,8 @@ 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"); - return; + rsp_send_error(hreq->req, "Error fetching query results"); + return -1; } /* HACK @@ -433,11 +451,13 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query) db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(hreq->req, reply); + + return 0; } -static void -rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +static int +rsp_reply_playlist(struct httpd_request *hreq) { struct query_params qp; struct db_media_file_info dbmfi; @@ -460,11 +480,11 @@ 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(hreq->uri_parsed->path_parts[2], &qp.id); if (ret < 0) { - rsp_send_error(req, "Invalid playlist ID"); - return; + rsp_send_error(hreq->req, "Invalid playlist ID"); + return -1; } if (qp.id == 0) @@ -473,7 +493,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(hreq->query, "type"); if (param) { if (strcasecmp(param, "full") == 0) @@ -488,20 +508,20 @@ 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, hreq); if (ret < 0) - return; + return -1; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); - rsp_send_error(req, "Could not start query"); + rsp_send_error(hreq->req, "Could not start query"); if (qp.filter) free(qp.filter); - return; + return -1; } if (qp.offset > qp.results) @@ -536,7 +556,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(hreq->req); ua = evhttp_find_header(headers, "User-Agent"); client_codecs = evhttp_find_header(headers, "Accept-Codecs"); @@ -607,8 +627,8 @@ 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"); - return; + rsp_send_error(hreq->req, "Error fetching query results"); + return -1; } /* HACK @@ -621,11 +641,13 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(hreq->req, reply); + + return 0; } -static void -rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +static int +rsp_reply_browse(struct httpd_request *hreq) { struct query_params qp; char *browse_item; @@ -638,43 +660,43 @@ 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(hreq->uri_parsed->path_parts[3], "artist") == 0) qp.type = Q_BROWSE_ARTISTS; - else if (strcmp(uri[3], "genre") == 0) + else if (strcmp(hreq->uri_parsed->path_parts[3], "genre") == 0) qp.type = Q_BROWSE_GENRES; - else if (strcmp(uri[3], "album") == 0) + else if (strcmp(hreq->uri_parsed->path_parts[3], "album") == 0) qp.type = Q_BROWSE_ALBUMS; - else if (strcmp(uri[3], "composer") == 0) + else if (strcmp(hreq->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", hreq->uri_parsed->path_parts[3]); - rsp_send_error(req, "Unsupported browse type"); - return; + rsp_send_error(hreq->req, "Unsupported browse type"); + return -1; } - ret = safe_atoi32(uri[2], &qp.id); + ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id); if (ret < 0) { - rsp_send_error(req, "Invalid playlist ID"); - return; + rsp_send_error(hreq->req, "Invalid playlist ID"); + return -1; } - ret = get_query_params(req, query, &qp); + ret = query_params_set(&qp, hreq); if (ret < 0) - return; + return -1; ret = db_query_start(&qp); if (ret < 0) { DPRINTF(E_LOG, L_RSP, "Could not start query\n"); - rsp_send_error(req, "Could not start query"); + rsp_send_error(hreq->req, "Could not start query"); if (qp.filter) free(qp.filter); - return; + return -1; } if (qp.offset > qp.results) @@ -722,8 +744,8 @@ 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"); - return; + rsp_send_error(hreq->req, "Error fetching query results"); + return -1; } /* HACK @@ -736,24 +758,31 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query db_query_end(&qp); - rsp_send_reply(req, reply); + rsp_send_reply(hreq->req, reply); + + return 0; } -static void -rsp_stream(struct evhttp_request *req, char **uri, struct evkeyvalq *query) +static int +rsp_stream(struct httpd_request *hreq) { int id; int ret; - ret = safe_atoi32(uri[2], &id); + ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &id); if (ret < 0) - httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); - else - httpd_stream_file(req, id); + { + httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request"); + return -1; + } + + httpd_stream_file(hreq->req, id); + + return 0; } -static struct uri_map rsp_handlers[] = +static struct httpd_uri_map rsp_handlers[] = { { .regexp = "^/rsp/info$", @@ -782,127 +811,42 @@ 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 httpd_request *hreq; 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) + hreq = httpd_request_parse(req, uri_parsed, NULL, rsp_handlers); + if (!hreq) { + DPRINTF(E_LOG, L_RSP, "Unrecognized path '%s' in RSP request: '%s'\n", uri_parsed->path, uri_parsed->uri); + 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(hreq); + if (ret < 0) { - rsp_send_error(req, "Server error"); - - free(full_uri); + rsp_send_error(req, "Access denied"); + free(hreq); return; } - if (ptr) - *ptr = '?'; + hreq->handler(hreq); - 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) - { - 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(hreq); } 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); diff --git a/src/spotify.c b/src/spotify.c index 471308cb..b201d450 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -1244,12 +1244,13 @@ artwork_get_bh(void *arg, int *retval) sp_imageformat imageformat; sp_error err; const void *data; + char *path; size_t data_size; int ret; artwork = arg; sp_image *image = artwork->image; - char *path = artwork->path; + path = artwork->path; fptr_sp_image_remove_load_callback(image, artwork_loaded_cb, artwork); @@ -1273,17 +1274,25 @@ artwork_get_bh(void *arg, int *retval) goto fail; } + data_size = 0; data = fptr_sp_image_data(image, &data_size); - if (!data || (data_size == 0)) + if (!data) { DPRINTF(E_LOG, L_SPOTIFY, "Getting artwork failed, no image data from Spotify: %s\n", path); goto fail; } + if ((data_size < 200) || (data_size > 20000000)) + { + // Sometimes we get strange data size even though fptr_sp_image_data returns success + DPRINTF(E_LOG, L_SPOTIFY, "Skipping artwork, data size is weird (%zu)\n", data_size); + goto fail; + } + ret = evbuffer_expand(artwork->evbuf, data_size); if (ret < 0) { - DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for artwork\n"); + DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for artwork (data size requested was %zu)\n", data_size); goto fail; }