mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
[httpd] Major refactor of the httpd request handling
Make it easier to add new parameters later, get rid of redundant code, clean up, align between httpd_xxx modules and introduce new bugs. Yes, the refactor got a bit out of hand.
This commit is contained in:
parent
0ebdd89715
commit
709d99d4c4
202
src/httpd.c
202
src/httpd.c
@ -43,7 +43,8 @@
|
||||
# include <sys/eventfd.h>
|
||||
#endif
|
||||
#include <event2/event.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include <event2/http.h>
|
||||
#include <event2/http_struct.h>
|
||||
#ifdef HAVE_LIBEVENT2_OLD
|
||||
# include <event2/bufferevent.h>
|
||||
# include <event2/bufferevent_struct.h>
|
||||
@ -453,6 +454,99 @@ stream_fail_cb(struct evhttp_connection *evcon, void *arg)
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
httpd_uri_free(struct httpd_uri_parsed *parsed)
|
||||
{
|
||||
if (!parsed)
|
||||
return;
|
||||
|
||||
free(parsed->uri_decoded);
|
||||
free(parsed->path);
|
||||
free(parsed->path_parts[0]);
|
||||
|
||||
evhttp_clear_headers(&(parsed->ev_query));
|
||||
|
||||
if (parsed->ev_uri)
|
||||
evhttp_uri_free(parsed->ev_uri);
|
||||
|
||||
free(parsed);
|
||||
}
|
||||
|
||||
struct httpd_uri_parsed *
|
||||
httpd_uri_parse(const char *uri)
|
||||
{
|
||||
struct httpd_uri_parsed *parsed;
|
||||
const char *path;
|
||||
const char *query;
|
||||
char *ptr;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
CHECK_NULL(L_HTTPD, parsed = calloc(1, sizeof(struct httpd_uri_parsed)));
|
||||
|
||||
parsed->uri = uri;
|
||||
|
||||
parsed->ev_uri = evhttp_uri_parse_with_flags(parsed->uri, EVHTTP_URI_NONCONFORMANT);
|
||||
if (!parsed->ev_uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not parse request: '%s'\n", parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
parsed->uri_decoded = evhttp_uridecode(parsed->uri, 0, NULL);
|
||||
if (!parsed->uri_decoded)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode request: '%s'\n", parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
query = evhttp_uri_get_query(parsed->ev_uri);
|
||||
if (query)
|
||||
{
|
||||
ret = evhttp_parse_query_str(query, &(parsed->ev_query));
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in request: '%s'\n", query, parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
path = evhttp_uri_get_path(parsed->ev_uri);
|
||||
if (!path)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
parsed->path = evhttp_uridecode(path, 0, NULL);
|
||||
if (!parsed->path)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode path: '%s'\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_HTTPD, parsed->path_parts[0] = strdup(parsed->path));
|
||||
|
||||
strtok_r(parsed->path_parts[0], "/", &ptr);
|
||||
for (i = 1; (i < sizeof(parsed->path_parts) / sizeof(parsed->path_parts[0])) && parsed->path_parts[i - 1]; i++)
|
||||
{
|
||||
parsed->path_parts[i] = strtok_r(NULL, "/", &ptr);
|
||||
}
|
||||
|
||||
if (!parsed->path_parts[0] || parsed->path_parts[i - 1] || (i < 2))
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "URI path has too many/few components (%d): '%s'\n", (parsed->path_parts[0]) ? i : 0, parsed->path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
|
||||
error:
|
||||
httpd_uri_free(parsed);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Thread: httpd */
|
||||
void
|
||||
httpd_stream_file(struct evhttp_request *req, int id)
|
||||
@ -923,7 +1017,7 @@ redirect_to_admin(struct evhttp_request *req)
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
redirect_to_index(struct evhttp_request *req, char *uri)
|
||||
redirect_to_index(struct evhttp_request *req, const char *uri)
|
||||
{
|
||||
struct evkeyvalq *headers;
|
||||
char buf[256];
|
||||
@ -986,7 +1080,7 @@ httpd_admin_check_auth(struct evhttp_request *req)
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
serve_file(struct evhttp_request *req, char *uri)
|
||||
serve_file(struct evhttp_request *req, const char *uri)
|
||||
{
|
||||
char *ext;
|
||||
char path[PATH_MAX];
|
||||
@ -1179,9 +1273,12 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
struct evkeyvalq *input_headers;
|
||||
struct evkeyvalq *output_headers;
|
||||
const char *req_uri;
|
||||
char *uri;
|
||||
char *ptr;
|
||||
struct httpd_uri_parsed *parsed;
|
||||
const char *uri;
|
||||
|
||||
// Clear the proxy request flag set by evhttp if the request URI was absolute.
|
||||
// It has side-effects on Connection: keep-alive
|
||||
req->flags &= ~EVHTTP_PROXY_REQUEST;
|
||||
|
||||
// Did we get a CORS preflight request?
|
||||
input_headers = evhttp_request_get_input_headers(req);
|
||||
@ -1203,66 +1300,55 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
|
||||
return;
|
||||
}
|
||||
|
||||
req_uri = evhttp_request_get_uri(req);
|
||||
if (!req_uri || strcmp(req_uri, "/") == 0)
|
||||
uri = evhttp_request_get_uri(req);
|
||||
if (!uri)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "No URI in request\n");
|
||||
redirect_to_admin(req);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uri = strdup(req_uri);
|
||||
ptr = strchr(uri, '?');
|
||||
if (ptr)
|
||||
parsed = httpd_uri_parse(uri);
|
||||
if (!parsed || !parsed->path || (strcmp(parsed->path, "/") == 0))
|
||||
{
|
||||
DPRINTF(E_SPAM, L_HTTPD, "Found query string\n");
|
||||
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
ptr = uri;
|
||||
uri = evhttp_decode_uri(uri);
|
||||
free(ptr);
|
||||
|
||||
/* Dispatch protocol-specific URIs */
|
||||
if (rsp_is_request(req, uri))
|
||||
{
|
||||
rsp_request(req);
|
||||
|
||||
goto out;
|
||||
}
|
||||
else if (daap_is_request(req, uri))
|
||||
{
|
||||
daap_request(req);
|
||||
|
||||
goto out;
|
||||
}
|
||||
else if (dacp_is_request(req, uri))
|
||||
{
|
||||
dacp_request(req);
|
||||
|
||||
goto out;
|
||||
}
|
||||
else if (jsonapi_is_request(req, uri))
|
||||
{
|
||||
jsonapi_request(req);
|
||||
|
||||
goto out;
|
||||
}
|
||||
else if (streaming_is_request(req, uri))
|
||||
{
|
||||
streaming_request(req);
|
||||
|
||||
redirect_to_admin(req);
|
||||
goto out;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_HTTPD, "HTTP request: %s\n", uri);
|
||||
/* Dispatch protocol-specific handlers */
|
||||
if (dacp_is_request(parsed->path))
|
||||
{
|
||||
dacp_request(req, parsed);
|
||||
goto out;
|
||||
}
|
||||
else if (daap_is_request(parsed->path))
|
||||
{
|
||||
daap_request(req, parsed);
|
||||
goto out;
|
||||
}
|
||||
else if (jsonapi_is_request(parsed->path))
|
||||
{
|
||||
jsonapi_request(req, parsed);
|
||||
goto out;
|
||||
}
|
||||
else if (streaming_is_request(parsed->path))
|
||||
{
|
||||
streaming_request(req, parsed);
|
||||
goto out;
|
||||
}
|
||||
else if (rsp_is_request(parsed->path))
|
||||
{
|
||||
rsp_request(req, parsed);
|
||||
goto out;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri);
|
||||
|
||||
/* Serve web interface files */
|
||||
serve_file(req, uri);
|
||||
serve_file(req, parsed->path);
|
||||
|
||||
out:
|
||||
free(uri);
|
||||
httpd_uri_free(parsed);
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
@ -1298,7 +1384,7 @@ exit_cb(int fd, short event, void *arg)
|
||||
httpd_exit = 1;
|
||||
}
|
||||
|
||||
char *
|
||||
/*static char *
|
||||
httpd_fixup_uri(struct evhttp_request *req)
|
||||
{
|
||||
struct evkeyvalq *headers;
|
||||
@ -1314,7 +1400,7 @@ httpd_fixup_uri(struct evhttp_request *req)
|
||||
if (!uri)
|
||||
return NULL;
|
||||
|
||||
/* No query string, nothing to do */
|
||||
// No query string, nothing to do
|
||||
q = strchr(uri, '?');
|
||||
if (!q)
|
||||
return strdup(uri);
|
||||
@ -1329,8 +1415,8 @@ httpd_fixup_uri(struct evhttp_request *req)
|
||||
&& (strncmp(ua, "Roku", strlen("Roku")) != 0))
|
||||
return strdup(uri);
|
||||
|
||||
/* Reencode + as %2B and space as + in the query,
|
||||
which iTunes and Roku devices don't do */
|
||||
// Reencode + as %2B and space as + in the query,
|
||||
// which iTunes and Roku devices don't do
|
||||
len = strlen(uri);
|
||||
|
||||
u = q;
|
||||
@ -1377,7 +1463,7 @@ httpd_fixup_uri(struct evhttp_request *req)
|
||||
*f = '\0';
|
||||
|
||||
return fixed;
|
||||
}
|
||||
}*/
|
||||
|
||||
static const char *http_reply_401 = "<html><head><title>401 Unauthorized</title></head><body>Authorization required</body></html>";
|
||||
|
||||
|
41
src/httpd.h
41
src/httpd.h
@ -4,6 +4,7 @@
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum httpd_send_flags
|
||||
@ -11,6 +12,43 @@ enum httpd_send_flags
|
||||
HTTPD_SEND_NO_GZIP = (1 << 0),
|
||||
};
|
||||
|
||||
/*
|
||||
* Contains a parsed version of the URI httpd got. The URI may have been
|
||||
* complete:
|
||||
* scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
|
||||
* or relative:
|
||||
* [/path][?query][#fragment]
|
||||
*
|
||||
* We are interested in the path and the query, so they are disassembled to
|
||||
* path_parts and ev_query. If the request is http://x:3689/foo/bar?key1=val1,
|
||||
* then part_parts[1] is "foo", [2] is "bar" and the rest is null (the first
|
||||
* element points to the copy of the path so it can be freed).
|
||||
*
|
||||
* The allocated strings are URI decoded.
|
||||
*/
|
||||
struct httpd_uri_parsed
|
||||
{
|
||||
const char *uri;
|
||||
struct evhttp_uri *ev_uri;
|
||||
struct evkeyvalq ev_query;
|
||||
char *uri_decoded;
|
||||
char *path;
|
||||
char *path_parts[7];
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper to free the parsed uri struct
|
||||
*/
|
||||
void
|
||||
httpd_uri_free(struct httpd_uri_parsed *parsed);
|
||||
|
||||
/*
|
||||
* Parse an URI into the struct
|
||||
*/
|
||||
struct httpd_uri_parsed *
|
||||
httpd_uri_parse(const char *uri);
|
||||
|
||||
|
||||
void
|
||||
httpd_stream_file(struct evhttp_request *req, int id);
|
||||
|
||||
@ -53,9 +91,6 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
|
||||
void
|
||||
httpd_send_error(struct evhttp_request *req, int error, const char *reason);
|
||||
|
||||
char *
|
||||
httpd_fixup_uri(struct evhttp_request *req);
|
||||
|
||||
int
|
||||
httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm);
|
||||
|
||||
|
353
src/httpd_daap.c
353
src/httpd_daap.c
@ -42,22 +42,19 @@
|
||||
#include <uninorm.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#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 <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/http_struct.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
/* httpd event base, from httpd.c */
|
||||
extern struct event_base *evbase_httpd;
|
||||
@ -105,20 +102,16 @@ struct daap_session {
|
||||
|
||||
// Will be filled out by daap_request_parse()
|
||||
struct daap_request {
|
||||
// The request URI
|
||||
const char *source_uri;
|
||||
// http request struct - null when the request is from daap_reply_build(), i.e. the cache
|
||||
struct evhttp_request *req;
|
||||
// If the request is http://x:3689/foo/bar?key1=val1, then path is "foo/bar"
|
||||
const char *path;
|
||||
// If the request is http://x:3689/foo/bar?key1=val1, then part_part[1] is "foo", [2] is "bar" and the rest is null
|
||||
char *path_parts[7];
|
||||
// A the request query was ?key1=val1&key2=val2 then this will be parsed into this struct
|
||||
struct evkeyvalq query;
|
||||
// User-agent
|
||||
const char *user_agent;
|
||||
// Was the request made by a remote, e.g. Apple Remote
|
||||
bool is_remote;
|
||||
// The parsed request URI given to us by httpd.c
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
// Shortcut to &uri_parsed->ev_query
|
||||
struct evkeyvalq *query;
|
||||
// http request struct - null when the request is from daap_reply_build(), i.e. the cache
|
||||
struct evhttp_request *req;
|
||||
// Pointer to the session matching the request
|
||||
struct daap_session *session;
|
||||
// A pointer to the handler that will process the request
|
||||
@ -507,112 +500,32 @@ daap_sort_finalize(struct sort_ctx *ctx)
|
||||
|
||||
/* ----------------------------- OTHER HELPERS ------------------------------ */
|
||||
|
||||
/* Remotes are clients that will issue DACP commands. For these clients we will
|
||||
* do the playback, and we will not stream to them. This is a crude function to
|
||||
* identify them, so we can give them appropriate treatment.
|
||||
*/
|
||||
static bool
|
||||
is_remote(const char *user_agent)
|
||||
{
|
||||
if (!user_agent)
|
||||
return false;
|
||||
if (strcasestr(user_agent, "remote"))
|
||||
return true;
|
||||
if (strstr(user_agent, "Retune"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *
|
||||
uri_relative(char *uri, const char *protocol)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
if (strncmp(uri, protocol, strlen(protocol)) != 0)
|
||||
return NULL;
|
||||
|
||||
ret = strchr(uri + strlen(protocol), '/');
|
||||
if (!ret)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Malformed DAAP Request URI '%s'\n", uri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *
|
||||
daap_fix_request_uri(struct evhttp_request *req, char *uri)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
/* iTunes 9 gives us an absolute request-uri like
|
||||
* daap://10.1.1.20:3689/server-info
|
||||
* iTunes 12.1 gives us an absolute request-uri for streaming like
|
||||
* http://10.1.1.20:3689/databases/1/items/1.mp3
|
||||
*/
|
||||
|
||||
if ( (ret = uri_relative(uri, "daap://")) || (ret = uri_relative(uri, "http://")) )
|
||||
{
|
||||
/* Clear the proxy request flag set by evhttp
|
||||
* due to the request URI being absolute.
|
||||
* It has side-effects on Connection: keep-alive
|
||||
*/
|
||||
req->flags &= ~EVHTTP_PROXY_REQUEST;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/* Returns eg /databases/1/containers from /databases/1/containers?meta=dmap.item... */
|
||||
static char *
|
||||
extract_uri(char *full_uri)
|
||||
{
|
||||
char *uri;
|
||||
char *ptr;
|
||||
|
||||
ptr = strchr(full_uri, '?');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
uri = strdup(full_uri);
|
||||
|
||||
if (ptr)
|
||||
*ptr = '?';
|
||||
|
||||
if (!uri)
|
||||
return NULL;
|
||||
|
||||
ptr = uri;
|
||||
uri = evhttp_decode_uri(uri);
|
||||
free(ptr);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/* We try not to return items that the client cannot play (like Spotify and
|
||||
* internet streams in iTunes), or which are inappropriate (like internet streams
|
||||
* in the album tab of remotes)
|
||||
* in the album tab of remotes). Note that the function must never append a
|
||||
* filter if the SELECT is not from the files table.
|
||||
*/
|
||||
static void
|
||||
user_agent_filter(struct query_params *qp, struct daap_request *dreq)
|
||||
{
|
||||
char *filter;
|
||||
|
||||
if (!(qp->type == Q_ITEMS || (qp->type & Q_F_BROWSE)))
|
||||
return;
|
||||
|
||||
if (dreq->is_remote)
|
||||
{
|
||||
if (qp->filter)
|
||||
filter = safe_asprintf("%s AND (f.data_kind <> %d)", qp->filter, DATA_KIND_HTTP);
|
||||
else
|
||||
filter = safe_asprintf("(f.data_kind <> %d)", DATA_KIND_HTTP);
|
||||
// This makes sure 1) the SELECT is from files, 2) that the Remote query
|
||||
// contained extended_media_kind:1, which characterise the queries we want
|
||||
// to filter. TODO: Not a really nice way of doing this, but best I could
|
||||
// think of.
|
||||
if (!qp->filter || !strstr(qp->filter, "f.media_kind"))
|
||||
return;
|
||||
|
||||
filter = safe_asprintf("%s AND (f.data_kind <> %d)", qp->filter, DATA_KIND_HTTP);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (qp->type != Q_ITEMS)
|
||||
return;
|
||||
|
||||
if (qp->filter)
|
||||
filter = safe_asprintf("%s AND (f.data_kind = %d)", qp->filter, DATA_KIND_FILE);
|
||||
else
|
||||
@ -639,7 +552,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request
|
||||
|
||||
memset(qp, 0, sizeof(struct query_params));
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "index");
|
||||
param = evhttp_find_header(dreq->query, "index");
|
||||
if (param)
|
||||
{
|
||||
if (param[0] == '-') /* -n, last n entries */
|
||||
@ -685,7 +598,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request
|
||||
qp->idx_type = I_SUB;
|
||||
|
||||
qp->sort = S_NONE;
|
||||
param = evhttp_find_header(&dreq->query, "sort");
|
||||
param = evhttp_find_header(dreq->query, "sort");
|
||||
if (param)
|
||||
{
|
||||
if (strcmp(param, "name") == 0)
|
||||
@ -706,7 +619,7 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request
|
||||
if (sort_headers)
|
||||
{
|
||||
*sort_headers = 0;
|
||||
param = evhttp_find_header(&dreq->query, "include-sort-headers");
|
||||
param = evhttp_find_header(dreq->query, "include-sort-headers");
|
||||
if (param && (strcmp(param, "1") == 0))
|
||||
{
|
||||
*sort_headers = 1;
|
||||
@ -714,9 +627,9 @@ query_params_set(struct query_params *qp, int *sort_headers, struct daap_request
|
||||
}
|
||||
}
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "query");
|
||||
param = evhttp_find_header(dreq->query, "query");
|
||||
if (!param)
|
||||
param = evhttp_find_header(&dreq->query, "filter");
|
||||
param = evhttp_find_header(dreq->query, "filter");
|
||||
|
||||
if (param)
|
||||
{
|
||||
@ -832,14 +745,11 @@ daap_reply_send(struct evhttp_request *req, struct evbuffer *reply, enum daap_re
|
||||
}
|
||||
|
||||
static struct daap_request *
|
||||
daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp_request *req)
|
||||
daap_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent)
|
||||
{
|
||||
struct daap_request *dreq;
|
||||
struct evkeyvalq *headers;
|
||||
struct evhttp_uri *uri;
|
||||
const char *query;
|
||||
const char *param;
|
||||
char *ptr;
|
||||
int32_t id;
|
||||
int ret;
|
||||
int i;
|
||||
@ -847,32 +757,18 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp
|
||||
CHECK_NULL(L_DAAP, dreq = calloc(1, sizeof(struct daap_request)));
|
||||
|
||||
dreq->req = req;
|
||||
dreq->source_uri = source_uri;
|
||||
dreq->uri_parsed = uri_parsed;
|
||||
dreq->query = &(uri_parsed->ev_query);
|
||||
|
||||
if (user_agent)
|
||||
dreq->user_agent = user_agent;
|
||||
else if (req && (headers = evhttp_request_get_input_headers(req)))
|
||||
dreq->user_agent = evhttp_find_header(headers, "User-Agent");
|
||||
|
||||
uri = evhttp_uri_parse_with_flags(source_uri, EVHTTP_URI_NONCONFORMANT);
|
||||
if (!uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not parse URI in DAAP request: '%s'\n", source_uri);
|
||||
free(dreq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find a handler for the path (note, path includes the leading '/')
|
||||
dreq->path = evhttp_uri_get_path(uri);
|
||||
if (!dreq->path || dreq->path[0] == '\0')
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Missing path in the DAAP request: '%s'\n", source_uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Find a handler for the path
|
||||
for (i = 0; daap_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regexec(&daap_handlers[i].preg, dreq->path, 0, NULL, 0);
|
||||
ret = regexec(&daap_handlers[i].preg, uri_parsed->path, 0, NULL, 0);
|
||||
if (ret == 0)
|
||||
{
|
||||
dreq->handler = daap_handlers[i].handler;
|
||||
@ -882,27 +778,18 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp
|
||||
|
||||
if (!dreq->handler)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", dreq->path, source_uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Parse the query
|
||||
query = evhttp_uri_get_query(uri);
|
||||
ret = evhttp_parse_query_str(query, &dreq->query);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in DAAP request: '%s'\n", query, source_uri);
|
||||
DPRINTF(E_LOG, L_DAAP, "Unrecognized path '%s' in DAAP request: '%s'\n", uri_parsed->path, uri_parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Check if we have a session
|
||||
param = evhttp_find_header(&dreq->query, "session-id");
|
||||
param = evhttp_find_header(dreq->query, "session-id");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid session id in DAAP request: '%s'\n", source_uri);
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid session id in DAAP request: '%s'\n", uri_parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -910,48 +797,17 @@ daap_request_parse(const char *source_uri, const char *user_agent, struct evhttp
|
||||
}
|
||||
|
||||
// Are we making a reply for a remote?
|
||||
param = evhttp_find_header(&dreq->query, "pairing-guid");
|
||||
param = evhttp_find_header(dreq->query, "pairing-guid");
|
||||
dreq->is_remote = (param || (dreq->session && dreq->session->is_remote));
|
||||
|
||||
// Copy path and split it in parts
|
||||
CHECK_NULL(L_DAAP, dreq->path_parts[0] = strdup(dreq->path));
|
||||
|
||||
strtok_r(dreq->path_parts[0], "/", &ptr);
|
||||
for (i = 1; (i < sizeof(dreq->path_parts) / sizeof(dreq->path_parts[0])) && dreq->path_parts[i - 1]; i++)
|
||||
{
|
||||
dreq->path_parts[i] = strtok_r(NULL, "/", &ptr);
|
||||
}
|
||||
|
||||
if (!dreq->path_parts[0] || dreq->path_parts[i - 1] || (i < 2))
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP URI has too many/few components (%d): '%s'\n", (dreq->path_parts[0]) ? i : 0, dreq->path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
evhttp_uri_free(uri);
|
||||
return dreq;
|
||||
|
||||
error:
|
||||
evhttp_clear_headers(&dreq->query);
|
||||
free(dreq->path_parts[0]);
|
||||
evhttp_uri_free(uri);
|
||||
free(dreq);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
daap_request_free(struct daap_request *dreq)
|
||||
{
|
||||
if (!dreq)
|
||||
return;
|
||||
|
||||
evhttp_clear_headers(&dreq->query);
|
||||
free(dreq->path_parts[0]);
|
||||
|
||||
free(dreq);
|
||||
}
|
||||
|
||||
static int
|
||||
daap_request_authorize(struct daap_request *dreq)
|
||||
{
|
||||
@ -962,12 +818,12 @@ daap_request_authorize(struct daap_request *dreq)
|
||||
if (cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode"))
|
||||
return 0;
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "session-id");
|
||||
param = evhttp_find_header(dreq->query, "session-id");
|
||||
if (param)
|
||||
{
|
||||
if (!dreq->session)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP session not found: '%s'\n", dreq->source_uri);
|
||||
DPRINTF(E_LOG, L_DAAP, "DAAP session not found: '%s'\n", dreq->uri_parsed->uri);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -980,11 +836,11 @@ daap_request_authorize(struct daap_request *dreq)
|
||||
return 0;
|
||||
|
||||
// If no valid session then we may need to authenticate
|
||||
if ((strcmp(dreq->path, "/server-info") == 0)
|
||||
|| (strcmp(dreq->path, "/login") == 0)
|
||||
|| (strcmp(dreq->path, "/logout") == 0)
|
||||
|| (strcmp(dreq->path, "/content-codes") == 0)
|
||||
|| (strncmp(dreq->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
|
||||
if ((strcmp(dreq->uri_parsed->path, "/server-info") == 0)
|
||||
|| (strcmp(dreq->uri_parsed->path, "/login") == 0)
|
||||
|| (strcmp(dreq->uri_parsed->path, "/logout") == 0)
|
||||
|| (strcmp(dreq->uri_parsed->path, "/content-codes") == 0)
|
||||
|| (strncmp(dreq->uri_parsed->path, "/databases/1/items/", strlen("/databases/1/items/")) == 0))
|
||||
return 0; // No authentication
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Checking authentication for library\n");
|
||||
@ -1155,7 +1011,7 @@ daap_reply_login(struct evbuffer *reply, struct daap_request *dreq)
|
||||
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(reply, 32));
|
||||
|
||||
is_remote = (param = evhttp_find_header(&dreq->query, "pairing-guid"));
|
||||
is_remote = (param = evhttp_find_header(dreq->query, "pairing-guid"));
|
||||
if (param && !cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode"))
|
||||
{
|
||||
if (strlen(param) < 3)
|
||||
@ -1186,7 +1042,7 @@ daap_reply_login(struct evbuffer *reply, struct daap_request *dreq)
|
||||
DPRINTF(E_INFO, L_DAAP, "Client (unknown user-agent) logging in\n");
|
||||
}
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "request-session-id");
|
||||
param = evhttp_find_header(dreq->query, "request-session-id");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &request_session_id);
|
||||
@ -1241,7 +1097,7 @@ daap_reply_update(struct evbuffer *reply, struct daap_request *dreq)
|
||||
return DAAP_REPLY_NO_CONNECTION;
|
||||
}
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "revision-number");
|
||||
param = evhttp_find_header(dreq->query, "revision-number");
|
||||
if (!param)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "Missing revision-number in client update request\n");
|
||||
@ -1440,7 +1296,7 @@ daap_reply_songlist_generic(struct evbuffer *reply, struct daap_request *dreq, i
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(songlist, 4096));
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(song, 512));
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "meta");
|
||||
param = evhttp_find_header(dreq->query, "meta");
|
||||
if (!param)
|
||||
{
|
||||
DPRINTF(E_DBG, L_DAAP, "No meta parameter in query, using default\n");
|
||||
@ -1601,7 +1457,7 @@ daap_reply_plsonglist(struct evbuffer *reply, struct daap_request *dreq)
|
||||
int playlist;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(dreq->path_parts[3], &playlist);
|
||||
ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &playlist);
|
||||
if (ret < 0)
|
||||
{
|
||||
dmap_error_make(reply, "apso", "Invalid playlist ID");
|
||||
@ -1638,7 +1494,7 @@ daap_reply_playlists(struct evbuffer *reply, struct daap_request *dreq)
|
||||
|
||||
cfg_radiopl = cfg_getbool(cfg_getsec(cfg, "library"), "radio_playlists");
|
||||
|
||||
ret = safe_atoi32(dreq->path_parts[1], &database);
|
||||
ret = safe_atoi32(dreq->uri_parsed->path_parts[1], &database);
|
||||
if (ret < 0)
|
||||
{
|
||||
dmap_error_make(reply, "aply", "Invalid database ID");
|
||||
@ -1655,7 +1511,7 @@ daap_reply_playlists(struct evbuffer *reply, struct daap_request *dreq)
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(playlistlist, 1024));
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(playlist, 128));
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "meta");
|
||||
param = evhttp_find_header(dreq->query, "meta");
|
||||
if (!param)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n");
|
||||
@ -1853,7 +1709,7 @@ daap_reply_groups(struct evbuffer *reply, struct daap_request *dreq)
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "group-type");
|
||||
param = evhttp_find_header(dreq->query, "group-type");
|
||||
if (strcmp(param, "artists") == 0)
|
||||
{
|
||||
// Request from Remote may have the form:
|
||||
@ -1881,7 +1737,7 @@ daap_reply_groups(struct evbuffer *reply, struct daap_request *dreq)
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(grouplist, 1024));
|
||||
CHECK_ERR(L_DAAP, evbuffer_expand(group, 128));
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "meta");
|
||||
param = evhttp_find_header(dreq->query, "meta");
|
||||
if (!param)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "No meta parameter in query, using default\n");
|
||||
@ -2060,25 +1916,25 @@ daap_reply_browse(struct evbuffer *reply, struct daap_request *dreq)
|
||||
int nitems;
|
||||
int ret;
|
||||
|
||||
if (strcmp(dreq->path_parts[3], "artists") == 0)
|
||||
if (strcmp(dreq->uri_parsed->path_parts[3], "artists") == 0)
|
||||
{
|
||||
tag = "abar";
|
||||
query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_ARTISTS);
|
||||
qp.sort = S_ARTIST;
|
||||
}
|
||||
else if (strcmp(dreq->path_parts[3], "albums") == 0)
|
||||
else if (strcmp(dreq->uri_parsed->path_parts[3], "albums") == 0)
|
||||
{
|
||||
tag = "abal";
|
||||
query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_ALBUMS);
|
||||
qp.sort = S_ALBUM;
|
||||
}
|
||||
else if (strcmp(dreq->path_parts[3], "genres") == 0)
|
||||
else if (strcmp(dreq->uri_parsed->path_parts[3], "genres") == 0)
|
||||
{
|
||||
tag = "abgn";
|
||||
query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_GENRES);
|
||||
qp.sort = S_GENRE;
|
||||
}
|
||||
else if (strcmp(dreq->path_parts[3], "composers") == 0)
|
||||
else if (strcmp(dreq->uri_parsed->path_parts[3], "composers") == 0)
|
||||
{
|
||||
tag = "abcp";
|
||||
query_params_set(&qp, &sort_headers, dreq, Q_BROWSE_COMPOSERS);
|
||||
@ -2086,7 +1942,7 @@ daap_reply_browse(struct evbuffer *reply, struct daap_request *dreq)
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", dreq->path_parts[3]);
|
||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", dreq->uri_parsed->path_parts[3]);
|
||||
dmap_error_make(reply, "abro", "Invalid browse type");
|
||||
return DAAP_REPLY_ERROR;
|
||||
}
|
||||
@ -2191,16 +2047,16 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq)
|
||||
return DAAP_REPLY_NO_CONNECTION;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(dreq->path_parts[3], &id);
|
||||
ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &id);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", dreq->path_parts[3]);
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not convert id parameter to integer: '%s'\n", dreq->uri_parsed->path_parts[3]);
|
||||
return DAAP_REPLY_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if (evhttp_find_header(&dreq->query, "mw") && evhttp_find_header(&dreq->query, "mh"))
|
||||
if (evhttp_find_header(dreq->query, "mw") && evhttp_find_header(dreq->query, "mh"))
|
||||
{
|
||||
param = evhttp_find_header(&dreq->query, "mw");
|
||||
param = evhttp_find_header(dreq->query, "mw");
|
||||
ret = safe_atoi32(param, &max_w);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2208,7 +2064,7 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq)
|
||||
return DAAP_REPLY_BAD_REQUEST;
|
||||
}
|
||||
|
||||
param = evhttp_find_header(&dreq->query, "mh");
|
||||
param = evhttp_find_header(dreq->query, "mh");
|
||||
ret = safe_atoi32(param, &max_h);
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -2224,9 +2080,9 @@ daap_reply_extra_data(struct evbuffer *reply, struct daap_request *dreq)
|
||||
max_h = 0;
|
||||
}
|
||||
|
||||
if (strcmp(dreq->path_parts[2], "groups") == 0)
|
||||
if (strcmp(dreq->uri_parsed->path_parts[2], "groups") == 0)
|
||||
ret = artwork_get_group(reply, id, max_w, max_h);
|
||||
else if (strcmp(dreq->path_parts[2], "items") == 0)
|
||||
else if (strcmp(dreq->uri_parsed->path_parts[2], "items") == 0)
|
||||
ret = artwork_get_item(reply, id, max_w, max_h);
|
||||
|
||||
len = evbuffer_get_length(reply);
|
||||
@ -2272,7 +2128,7 @@ daap_stream(struct evbuffer *reply, struct daap_request *dreq)
|
||||
return DAAP_REPLY_NO_CONNECTION;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(dreq->path_parts[3], &id);
|
||||
ret = safe_atoi32(dreq->uri_parsed->path_parts[3], &id);
|
||||
if (ret < 0)
|
||||
return DAAP_REPLY_BAD_REQUEST;
|
||||
|
||||
@ -2448,33 +2304,25 @@ static struct uri_map daap_handlers[] =
|
||||
|
||||
/* ------------------------------- DAAP API --------------------------------- */
|
||||
|
||||
/* iTunes 9 gives us an absolute request-uri like
|
||||
* daap://10.1.1.20:3689/server-info
|
||||
* iTunes 12.1 gives us an absolute request-uri for streaming like
|
||||
* http://10.1.1.20:3689/databases/1/items/1.mp3
|
||||
*/
|
||||
void
|
||||
daap_request(struct evhttp_request *req)
|
||||
daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
||||
{
|
||||
struct daap_request *dreq;
|
||||
struct evkeyvalq *headers;
|
||||
struct timespec start;
|
||||
struct timespec end;
|
||||
struct evbuffer *reply;
|
||||
const char *source_uri;
|
||||
int ret;
|
||||
int msec;
|
||||
|
||||
// Clear the proxy request flag set by evhttp if the request URI was absolute.
|
||||
// It has side-effects on Connection: keep-alive
|
||||
req->flags &= ~EVHTTP_PROXY_REQUEST;
|
||||
DPRINTF(E_DBG, L_DAAP, "DAAP request: '%s'\n", uri_parsed->uri);
|
||||
|
||||
source_uri = evhttp_request_get_uri(req);
|
||||
if (!source_uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Could not get source URI from DAAP request\n");
|
||||
httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "DAAP request: %s\n", source_uri);
|
||||
|
||||
dreq = daap_request_parse(source_uri, NULL, req);
|
||||
dreq = daap_request_parse(req, uri_parsed, NULL);
|
||||
if (!dreq)
|
||||
{
|
||||
httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
@ -2485,7 +2333,7 @@ daap_request(struct evhttp_request *req)
|
||||
if (ret < 0)
|
||||
{
|
||||
httpd_send_error(req, 403, "Forbidden");
|
||||
daap_request_free(dreq);
|
||||
free(dreq);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2503,7 +2351,7 @@ daap_request(struct evhttp_request *req)
|
||||
CHECK_NULL(L_DAAP, reply = evbuffer_new());
|
||||
|
||||
// Try the cache
|
||||
ret = cache_daap_get(reply, source_uri);
|
||||
ret = cache_daap_get(reply, uri_parsed->uri);
|
||||
if (ret == 0)
|
||||
{
|
||||
// The cache will return the data gzipped, so httpd_send_reply won't need to do it
|
||||
@ -2511,7 +2359,7 @@ daap_request(struct evhttp_request *req)
|
||||
httpd_send_reply(req, HTTP_OK, "OK", reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply
|
||||
|
||||
evbuffer_free(reply);
|
||||
daap_request_free(dreq);
|
||||
free(dreq);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2525,38 +2373,34 @@ daap_request(struct evhttp_request *req)
|
||||
DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec);
|
||||
|
||||
if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold())
|
||||
cache_daap_add(source_uri, dreq->user_agent, msec);
|
||||
cache_daap_add(uri_parsed->uri, dreq->user_agent, msec);
|
||||
|
||||
evbuffer_free(reply);
|
||||
daap_request_free(dreq);
|
||||
free(dreq);
|
||||
}
|
||||
|
||||
int
|
||||
daap_is_request(struct evhttp_request *req, char *uri)
|
||||
daap_is_request(const char *path)
|
||||
{
|
||||
uri = daap_fix_request_uri(req, uri);
|
||||
if (!uri)
|
||||
return 0;
|
||||
|
||||
if (strncmp(uri, "/databases/", strlen("/databases/")) == 0)
|
||||
if (strncmp(path, "/databases/", strlen("/databases/")) == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/databases") == 0)
|
||||
if (strcmp(path, "/databases") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/server-info") == 0)
|
||||
if (strcmp(path, "/server-info") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/content-codes") == 0)
|
||||
if (strcmp(path, "/content-codes") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/login") == 0)
|
||||
if (strcmp(path, "/login") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/update") == 0)
|
||||
if (strcmp(path, "/update") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/activity") == 0)
|
||||
if (strcmp(path, "/activity") == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/logout") == 0)
|
||||
if (strcmp(path, "/logout") == 0)
|
||||
return 1;
|
||||
|
||||
#ifdef DMAP_TEST
|
||||
if (strcmp(uri, "/dmap-test") == 0)
|
||||
if (strcmp(path, "/dmap-test") == 0)
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
@ -2577,32 +2421,39 @@ daap_session_is_valid(int id)
|
||||
}
|
||||
|
||||
struct evbuffer *
|
||||
daap_reply_build(const char *source_uri, const char *user_agent)
|
||||
daap_reply_build(const char *uri, const char *user_agent)
|
||||
{
|
||||
struct daap_request *dreq;
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
struct evbuffer *reply;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: %s\n", source_uri);
|
||||
DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: '%s'\n", uri);
|
||||
|
||||
CHECK_NULL(L_DAAP, reply = evbuffer_new());
|
||||
uri_parsed = httpd_uri_parse(uri);
|
||||
if (!uri_parsed)
|
||||
return NULL;
|
||||
|
||||
dreq = daap_request_parse(source_uri, user_agent, NULL);
|
||||
dreq = daap_request_parse(NULL, uri_parsed, user_agent);
|
||||
if (!dreq)
|
||||
{
|
||||
evbuffer_free(reply);
|
||||
httpd_uri_free(uri_parsed);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_DAAP, reply = evbuffer_new());
|
||||
|
||||
ret = dreq->handler(reply, dreq);
|
||||
if (ret < 0)
|
||||
{
|
||||
evbuffer_free(reply);
|
||||
daap_request_free(dreq);
|
||||
free(dreq);
|
||||
httpd_uri_free(uri_parsed);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
daap_request_free(dreq);
|
||||
free(dreq);
|
||||
httpd_uri_free(uri_parsed);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
#ifndef __HTTPD_DAAP_H__
|
||||
#define __HTTPD_DAAP_H__
|
||||
|
||||
#include <event2/http.h>
|
||||
#include "httpd.h"
|
||||
|
||||
int
|
||||
daap_init(void);
|
||||
@ -11,15 +11,15 @@ void
|
||||
daap_deinit(void);
|
||||
|
||||
void
|
||||
daap_request(struct evhttp_request *req);
|
||||
daap_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);
|
||||
|
||||
int
|
||||
daap_is_request(struct evhttp_request *req, char *uri);
|
||||
daap_is_request(const char *path);
|
||||
|
||||
int
|
||||
daap_session_is_valid(int id);
|
||||
|
||||
struct evbuffer *
|
||||
daap_reply_build(const char *source_uri, const char *user_agent);
|
||||
daap_reply_build(const char *uri, const char *user_agent);
|
||||
|
||||
#endif /* !__HTTPD_DAAP_H__ */
|
||||
|
1333
src/httpd_dacp.c
1333
src/httpd_dacp.c
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
#ifndef __HTTPD_DACP_H__
|
||||
#define __HTTPD_DACP_H__
|
||||
|
||||
#include <event2/http.h>
|
||||
#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__ */
|
||||
|
@ -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 <config.h>
|
||||
#endif
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include <json.h>
|
||||
#include <regex.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "httpd_jsonapi.h"
|
||||
#include "conffile.h"
|
||||
#include "db.h"
|
||||
#include "httpd.h"
|
||||
#ifdef LASTFM
|
||||
# include "lastfm.h"
|
||||
#endif
|
||||
@ -54,275 +49,31 @@
|
||||
# include "spotify.h"
|
||||
#endif
|
||||
|
||||
struct json_request {
|
||||
// The parsed request URI given to us by httpd.c
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
// Shortcut to &uri_parsed->ev_query
|
||||
struct evkeyvalq *query;
|
||||
// http request struct
|
||||
struct evhttp_request *req;
|
||||
// A pointer to the handler that will process the request
|
||||
int (*handler)(struct evbuffer *reply, struct json_request *jreq);
|
||||
};
|
||||
|
||||
struct uri_map
|
||||
{
|
||||
regex_t preg;
|
||||
char *regexp;
|
||||
int (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query);
|
||||
int (*handler)(struct evbuffer *reply, struct json_request *jreq);
|
||||
};
|
||||
|
||||
/* Forward declaration of handlers */
|
||||
static struct uri_map adm_handlers[];
|
||||
|
||||
/* -------------------------------- HELPERS --------------------------------- */
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve configuration values
|
||||
*
|
||||
* Example response:
|
||||
*
|
||||
* {
|
||||
* "websocket_port": 6603,
|
||||
* "version": "25.0"
|
||||
* }
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_config(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
{
|
||||
json_object *reply;
|
||||
json_object *buildopts;
|
||||
int websocket_port;
|
||||
char **buildoptions;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
reply = json_object_new_object();
|
||||
|
||||
// Websocket port
|
||||
#ifdef HAVE_LIBWEBSOCKETS
|
||||
websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port");
|
||||
#else
|
||||
websocket_port = 0;
|
||||
#endif
|
||||
json_object_object_add(reply, "websocket_port", json_object_new_int(websocket_port));
|
||||
|
||||
// forked-daapd version
|
||||
json_object_object_add(reply, "version", json_object_new_string(VERSION));
|
||||
|
||||
// enabled build options
|
||||
buildopts = json_object_new_array();
|
||||
buildoptions = buildopts_get();
|
||||
for (i = 0; buildoptions[i]; i++)
|
||||
{
|
||||
json_object_array_add(buildopts, json_object_new_string(buildoptions[i]));
|
||||
}
|
||||
json_object_object_add(reply, "buildoptions", buildopts);
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "config: Couldn't add config data to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve informations about the library
|
||||
*
|
||||
* Example response:
|
||||
*
|
||||
* {
|
||||
* "artists": 84,
|
||||
* "albums": 151,
|
||||
* "songs": 3085,
|
||||
* "db_playtime": 687824,
|
||||
* "updating": false
|
||||
*}
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_library(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct filecount_info fci;
|
||||
int artists;
|
||||
int albums;
|
||||
bool is_scanning;
|
||||
json_object *reply;
|
||||
int ret;
|
||||
|
||||
// Fetch values for response
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
qp.type = Q_COUNT_ITEMS;
|
||||
ret = db_filecount_get(&fci, &qp);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
artists = db_files_get_artist_count();
|
||||
albums = db_files_get_album_count();
|
||||
|
||||
is_scanning = library_is_scanning();
|
||||
|
||||
|
||||
// Build json response
|
||||
|
||||
reply = json_object_new_object();
|
||||
|
||||
json_object_object_add(reply, "artists", json_object_new_int(artists));
|
||||
json_object_object_add(reply, "albums", json_object_new_int(albums));
|
||||
json_object_object_add(reply, "songs", json_object_new_int(fci.count));
|
||||
json_object_object_add(reply, "db_playtime", json_object_new_int64((fci.length / 1000)));
|
||||
json_object_object_add(reply, "updating", json_object_new_boolean(is_scanning));
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "library: Couldn't add library information data to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to trigger a library rescan
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
{
|
||||
library_rescan();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve information about the spotify integration
|
||||
*
|
||||
* Exampe response:
|
||||
*
|
||||
* {
|
||||
* "enabled": true,
|
||||
* "oauth_uri": "https://accounts.spotify.com/authorize/?client_id=...
|
||||
* }
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_spotify(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
{
|
||||
json_object *reply;
|
||||
int ret;
|
||||
|
||||
reply = json_object_new_object();
|
||||
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
int httpd_port;
|
||||
char redirect_uri[256];
|
||||
char *oauth_uri;
|
||||
struct spotify_status_info info;
|
||||
|
||||
json_object_object_add(reply, "enabled", json_object_new_boolean(true));
|
||||
|
||||
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
||||
snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port);
|
||||
|
||||
oauth_uri = spotifywebapi_oauth_uri_get(redirect_uri);
|
||||
if (!uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_object_add(reply, "oauth_uri", json_object_new_string(oauth_uri));
|
||||
free(oauth_uri);
|
||||
}
|
||||
|
||||
spotify_status_info_get(&info);
|
||||
json_object_object_add(reply, "libspotify_installed", json_object_new_boolean(info.libspotify_installed));
|
||||
json_object_object_add(reply, "libspotify_logged_in", json_object_new_boolean(info.libspotify_logged_in));
|
||||
json_object_object_add(reply, "libspotify_user", json_object_new_string(info.libspotify_user));
|
||||
json_object_object_add(reply, "webapi_token_valid", json_object_new_boolean(info.webapi_token_valid));
|
||||
json_object_object_add(reply, "webapi_user", json_object_new_string(info.webapi_user));
|
||||
|
||||
#else
|
||||
json_object_object_add(reply, "enabled", json_object_new_boolean(false));
|
||||
#endif
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "spotify: Couldn't add spotify information data to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_spotify_login(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
{
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
struct evbuffer *in_evbuf;
|
||||
json_object* request;
|
||||
const char *user;
|
||||
const char *password;
|
||||
char *errmsg = NULL;
|
||||
json_object* reply;
|
||||
json_object* errors;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_WEB, "Received spotify login request\n");
|
||||
|
||||
in_evbuf = evhttp_request_get_input_buffer(req);
|
||||
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||
if (!request)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
reply = json_object_new_object();
|
||||
|
||||
user = jparse_str_from_obj(request, "user");
|
||||
password = jparse_str_from_obj(request, "password");
|
||||
if (user && strlen(user) > 0 && password && strlen(password) > 0)
|
||||
{
|
||||
ret = spotify_login_user(user, password, &errmsg);
|
||||
if (ret < 0)
|
||||
{
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
json_object_object_add(errors, "error", json_object_new_string(errmsg));
|
||||
json_object_object_add(reply, "errors", errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(true));
|
||||
}
|
||||
free(errmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No user or password in spotify login post request\n");
|
||||
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
if (!user || strlen(user) == 0)
|
||||
json_object_object_add(errors, "user", json_object_new_string("Username is required"));
|
||||
if (!password || strlen(password) == 0)
|
||||
json_object_object_add(errors, "password", json_object_new_string("Password is required"));
|
||||
json_object_object_add(reply, "errors", errors);
|
||||
}
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "spotify: Couldn't add spotify login data to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#else
|
||||
DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to kickoff pairing of a daap/dacp client
|
||||
* Kicks off pairing of a daap/dacp client
|
||||
*
|
||||
* Expects the paring pin to be present in the post request body, e. g.:
|
||||
*
|
||||
@ -331,7 +82,7 @@ jsonapi_reply_spotify_login(struct evhttp_request *req, struct evbuffer *evbuf,
|
||||
* }
|
||||
*/
|
||||
static int
|
||||
pairing_kickoff(struct evhttp_request* req)
|
||||
pairing_kickoff(struct evhttp_request *req)
|
||||
{
|
||||
struct evbuffer *evbuf;
|
||||
json_object* request;
|
||||
@ -359,7 +110,7 @@ pairing_kickoff(struct evhttp_request* req)
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve pairing information
|
||||
* Retrieves pairing information
|
||||
*
|
||||
* Example response:
|
||||
*
|
||||
@ -372,33 +123,309 @@ static int
|
||||
pairing_get(struct evbuffer *evbuf)
|
||||
{
|
||||
char *remote_name;
|
||||
json_object *reply;
|
||||
int ret;
|
||||
json_object *jreply;
|
||||
|
||||
remote_name = remote_pairing_get_name();
|
||||
|
||||
reply = json_object_new_object();
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
if (remote_name)
|
||||
{
|
||||
json_object_object_add(reply, "active", json_object_new_boolean(true));
|
||||
json_object_object_add(reply, "remote", json_object_new_string(remote_name));
|
||||
json_object_object_add(jreply, "active", json_object_new_boolean(true));
|
||||
json_object_object_add(jreply, "remote", json_object_new_string(remote_name));
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_object_add(reply, "active", json_object_new_boolean(false));
|
||||
json_object_object_add(jreply, "active", json_object_new_boolean(false));
|
||||
}
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
free(remote_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct json_request *
|
||||
json_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
||||
{
|
||||
struct json_request *jreq;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
CHECK_NULL(L_WEB, jreq = calloc(1, sizeof(struct json_request)));
|
||||
|
||||
jreq->req = req;
|
||||
jreq->uri_parsed = uri_parsed;
|
||||
jreq->query = &(uri_parsed->ev_query);
|
||||
|
||||
// Find a handler for the path
|
||||
for (i = 0; adm_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regexec(&adm_handlers[i].preg, uri_parsed->path, 0, NULL, 0);
|
||||
if (ret == 0)
|
||||
{
|
||||
jreq->handler = adm_handlers[i].handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!jreq->handler)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in JSON api request: '%s'\n", uri_parsed->path, uri_parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return jreq;
|
||||
|
||||
error:
|
||||
free(jreq);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------- REPLY HANDLERS ------------------------------- */
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve configuration values
|
||||
*
|
||||
* Example response:
|
||||
*
|
||||
* {
|
||||
* "websocket_port": 6603,
|
||||
* "version": "25.0"
|
||||
* }
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_config(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
json_object *jreply;
|
||||
json_object *buildopts;
|
||||
int websocket_port;
|
||||
char **buildoptions;
|
||||
int i;
|
||||
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
// Websocket port
|
||||
#ifdef HAVE_LIBWEBSOCKETS
|
||||
websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port");
|
||||
#else
|
||||
websocket_port = 0;
|
||||
#endif
|
||||
json_object_object_add(jreply, "websocket_port", json_object_new_int(websocket_port));
|
||||
|
||||
// forked-daapd version
|
||||
json_object_object_add(jreply, "version", json_object_new_string(VERSION));
|
||||
|
||||
// enabled build options
|
||||
buildopts = json_object_new_array();
|
||||
buildoptions = buildopts_get();
|
||||
for (i = 0; buildoptions[i]; i++)
|
||||
{
|
||||
json_object_array_add(buildopts, json_object_new_string(buildoptions[i]));
|
||||
}
|
||||
json_object_object_add(jreply, "buildoptions", buildopts);
|
||||
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve informations about the library
|
||||
*
|
||||
* Example response:
|
||||
*
|
||||
* {
|
||||
* "artists": 84,
|
||||
* "albums": 151,
|
||||
* "songs": 3085,
|
||||
* "db_playtime": 687824,
|
||||
* "updating": false
|
||||
*}
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_library(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct filecount_info fci;
|
||||
int artists;
|
||||
int albums;
|
||||
bool is_scanning;
|
||||
json_object *jreply;
|
||||
int ret;
|
||||
|
||||
// Fetch values for response
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
qp.type = Q_COUNT_ITEMS;
|
||||
ret = db_filecount_get(&fci, &qp);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "pairing: Couldn't add pairing information data to response buffer.\n");
|
||||
DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
artists = db_files_get_artist_count();
|
||||
albums = db_files_get_album_count();
|
||||
|
||||
is_scanning = library_is_scanning();
|
||||
|
||||
|
||||
// Build json response
|
||||
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
json_object_object_add(jreply, "artists", json_object_new_int(artists));
|
||||
json_object_object_add(jreply, "albums", json_object_new_int(albums));
|
||||
json_object_object_add(jreply, "songs", json_object_new_int(fci.count));
|
||||
json_object_object_add(jreply, "db_playtime", json_object_new_int64((fci.length / 1000)));
|
||||
json_object_object_add(jreply, "updating", json_object_new_boolean(is_scanning));
|
||||
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to trigger a library rescan
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_update(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
library_rescan();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Endpoint to retrieve information about the spotify integration
|
||||
*
|
||||
* Exampe response:
|
||||
*
|
||||
* {
|
||||
* "enabled": true,
|
||||
* "oauth_uri": "https://accounts.spotify.com/authorize/?client_id=...
|
||||
* }
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_spotify(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
json_object *jreply;
|
||||
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
int httpd_port;
|
||||
char redirect_uri[256];
|
||||
char *oauth_uri;
|
||||
struct spotify_status_info info;
|
||||
|
||||
json_object_object_add(jreply, "enabled", json_object_new_boolean(true));
|
||||
|
||||
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
|
||||
snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port);
|
||||
|
||||
oauth_uri = spotifywebapi_oauth_uri_get(redirect_uri);
|
||||
if (!oauth_uri)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n");
|
||||
jparse_free(jreply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
json_object_object_add(jreply, "oauth_uri", json_object_new_string(oauth_uri));
|
||||
free(oauth_uri);
|
||||
|
||||
spotify_status_info_get(&info);
|
||||
json_object_object_add(jreply, "libspotify_installed", json_object_new_boolean(info.libspotify_installed));
|
||||
json_object_object_add(jreply, "libspotify_logged_in", json_object_new_boolean(info.libspotify_logged_in));
|
||||
json_object_object_add(jreply, "libspotify_user", json_object_new_string(info.libspotify_user));
|
||||
json_object_object_add(jreply, "webapi_token_valid", json_object_new_boolean(info.webapi_token_valid));
|
||||
json_object_object_add(jreply, "webapi_user", json_object_new_string(info.webapi_user));
|
||||
|
||||
#else
|
||||
json_object_object_add(jreply, "enabled", json_object_new_boolean(false));
|
||||
#endif
|
||||
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_spotify_login(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
struct evbuffer *in_evbuf;
|
||||
json_object* request;
|
||||
const char *user;
|
||||
const char *password;
|
||||
char *errmsg = NULL;
|
||||
json_object* jreply;
|
||||
json_object* errors;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_WEB, "Received Spotify login request\n");
|
||||
|
||||
in_evbuf = evhttp_request_get_input_buffer(jreq->req);
|
||||
|
||||
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||
if (!request)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
user = jparse_str_from_obj(request, "user");
|
||||
password = jparse_str_from_obj(request, "password");
|
||||
if (user && strlen(user) > 0 && password && strlen(password) > 0)
|
||||
{
|
||||
ret = spotify_login_user(user, password, &errmsg);
|
||||
if (ret < 0)
|
||||
{
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
json_object_object_add(errors, "error", json_object_new_string(errmsg));
|
||||
json_object_object_add(jreply, "errors", errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(true));
|
||||
}
|
||||
free(errmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No user or password in spotify login post request\n");
|
||||
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
if (!user || strlen(user) == 0)
|
||||
json_object_object_add(errors, "user", json_object_new_string("Username is required"));
|
||||
if (!password || strlen(password) == 0)
|
||||
json_object_object_add(errors, "password", json_object_new_string("Password is required"));
|
||||
json_object_object_add(jreply, "errors", errors);
|
||||
}
|
||||
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
#else
|
||||
DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -409,40 +436,36 @@ pairing_get(struct evbuffer *evbuf)
|
||||
* If request is a POST request, tries to pair the active remote with the given pin.
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_pairing(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
jsonapi_reply_pairing(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
if (evhttp_request_get_command(req) == EVHTTP_REQ_POST)
|
||||
if (evhttp_request_get_command(jreq->req) == EVHTTP_REQ_POST)
|
||||
{
|
||||
return pairing_kickoff(req);
|
||||
return pairing_kickoff(jreq->req);
|
||||
}
|
||||
|
||||
return pairing_get(evbuf);
|
||||
return pairing_get(reply);
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_lastfm(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
jsonapi_reply_lastfm(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
json_object *reply;
|
||||
json_object *jreply;
|
||||
bool enabled = false;
|
||||
bool scrobbling_enabled = false;
|
||||
int ret;
|
||||
|
||||
#ifdef LASTFM
|
||||
enabled = true;
|
||||
scrobbling_enabled = lastfm_is_enabled();
|
||||
#endif
|
||||
|
||||
reply = json_object_new_object();
|
||||
json_object_object_add(reply, "enabled", json_object_new_boolean(enabled));
|
||||
json_object_object_add(reply, "scrobbling_enabled", json_object_new_boolean(scrobbling_enabled));
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "LastFM: Couldn't add LastFM enabled to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
json_object_object_add(jreply, "enabled", json_object_new_boolean(enabled));
|
||||
json_object_object_add(jreply, "scrobbling_enabled", json_object_new_boolean(scrobbling_enabled));
|
||||
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -451,21 +474,21 @@ jsonapi_reply_lastfm(struct evhttp_request *req, struct evbuffer *evbuf, char *u
|
||||
* Endpoint to log into LastFM
|
||||
*/
|
||||
static int
|
||||
jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
jsonapi_reply_lastfm_login(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
#ifdef LASTFM
|
||||
struct evbuffer *in_evbuf;
|
||||
json_object* request;
|
||||
json_object *request;
|
||||
const char *user;
|
||||
const char *password;
|
||||
char *errmsg = NULL;
|
||||
json_object* reply;
|
||||
json_object* errors;
|
||||
json_object *jreply;
|
||||
json_object *errors;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_WEB, "Received LastFM login request\n");
|
||||
|
||||
in_evbuf = evhttp_request_get_input_buffer(req);
|
||||
in_evbuf = evhttp_request_get_input_buffer(jreq->req);
|
||||
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||
if (!request)
|
||||
{
|
||||
@ -473,7 +496,7 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c
|
||||
return -1;
|
||||
}
|
||||
|
||||
reply = json_object_new_object();
|
||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||
|
||||
user = jparse_str_from_obj(request, "user");
|
||||
password = jparse_str_from_obj(request, "password");
|
||||
@ -482,17 +505,17 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c
|
||||
ret = lastfm_login_user(user, password, &errmsg);
|
||||
if (ret < 0)
|
||||
{
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(false));
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
if (errmsg)
|
||||
json_object_object_add(errors, "error", json_object_new_string(errmsg));
|
||||
else
|
||||
json_object_object_add(errors, "error", json_object_new_string("Unknown error"));
|
||||
json_object_object_add(reply, "errors", errors);
|
||||
json_object_object_add(jreply, "errors", errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(true));
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(true));
|
||||
}
|
||||
free(errmsg);
|
||||
}
|
||||
@ -500,22 +523,18 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "No user or password in LastFM login post request\n");
|
||||
|
||||
json_object_object_add(reply, "success", json_object_new_boolean(false));
|
||||
json_object_object_add(jreply, "success", json_object_new_boolean(false));
|
||||
errors = json_object_new_object();
|
||||
if (!user || strlen(user) == 0)
|
||||
json_object_object_add(errors, "user", json_object_new_string("Username is required"));
|
||||
if (!password || strlen(password) == 0)
|
||||
json_object_object_add(errors, "password", json_object_new_string("Password is required"));
|
||||
json_object_object_add(reply, "errors", errors);
|
||||
json_object_object_add(jreply, "errors", errors);
|
||||
}
|
||||
|
||||
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
|
||||
jparse_free(reply);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "LastFM: Couldn't add LastFM login data to response buffer.\n");
|
||||
return -1;
|
||||
}
|
||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(reply, "%s", json_object_to_json_string(jreply)));
|
||||
|
||||
jparse_free(jreply);
|
||||
|
||||
#else
|
||||
DPRINTF(E_LOG, L_WEB, "Received LastFM login request but was not compiled with enable-lastfm\n");
|
||||
@ -525,7 +544,7 @@ jsonapi_reply_lastfm_login(struct evhttp_request *req, struct evbuffer *evbuf, c
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_lastfm_logout(struct evhttp_request *req, struct evbuffer *evbuf, char *uri, struct evkeyvalq *query)
|
||||
jsonapi_reply_lastfm_logout(struct evbuffer *reply, struct json_request *jreq)
|
||||
{
|
||||
#ifdef LASTFM
|
||||
lastfm_logout();
|
||||
@ -547,119 +566,59 @@ static struct uri_map adm_handlers[] =
|
||||
{ .regexp = NULL, .handler = NULL }
|
||||
};
|
||||
|
||||
void
|
||||
jsonapi_request(struct evhttp_request *req)
|
||||
{
|
||||
char *full_uri;
|
||||
char *uri;
|
||||
char *ptr;
|
||||
struct evbuffer *evbuf;
|
||||
struct evkeyvalq query;
|
||||
struct evkeyvalq *headers;
|
||||
int handler;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/* Check authentication */
|
||||
/* ------------------------------- JSON API --------------------------------- */
|
||||
|
||||
void
|
||||
jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
||||
{
|
||||
struct json_request *jreq;
|
||||
struct evbuffer *reply;
|
||||
struct evkeyvalq *headers;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri);
|
||||
|
||||
if (!httpd_admin_check_auth(req))
|
||||
{
|
||||
DPRINTF(E_DBG, L_WEB, "JSON api request denied;\n");
|
||||
DPRINTF(E_DBG, L_WEB, "JSON api request denied\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&query, 0, sizeof(struct evkeyvalq));
|
||||
|
||||
full_uri = httpd_fixup_uri(req);
|
||||
if (!full_uri)
|
||||
jreq = json_request_parse(req, uri_parsed);
|
||||
if (!jreq)
|
||||
{
|
||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
ptr = strchr(full_uri, '?');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
uri = strdup(full_uri);
|
||||
if (!uri)
|
||||
{
|
||||
free(full_uri);
|
||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr)
|
||||
*ptr = '?';
|
||||
|
||||
ptr = uri;
|
||||
uri = evhttp_decode_uri(uri);
|
||||
free(ptr);
|
||||
|
||||
DPRINTF(E_DBG, L_WEB, "Web admin request: %s\n", full_uri);
|
||||
|
||||
handler = -1;
|
||||
for (i = 0; adm_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regexec(&adm_handlers[i].preg, uri, 0, NULL, 0);
|
||||
if (ret == 0)
|
||||
{
|
||||
handler = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handler < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Unrecognized web admin request\n");
|
||||
|
||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
evbuf = evbuffer_new();
|
||||
if (!evbuf)
|
||||
{
|
||||
DPRINTF(E_LOG, L_WEB, "Could not allocate evbuffer for Web Admin reply\n");
|
||||
|
||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
evhttp_parse_query(full_uri, &query);
|
||||
|
||||
headers = evhttp_request_get_output_headers(req);
|
||||
evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
|
||||
|
||||
ret = adm_handlers[handler].handler(req, evbuf, uri, &query);
|
||||
CHECK_NULL(L_WEB, reply = evbuffer_new());
|
||||
|
||||
ret = jreq->handler(reply, jreq);
|
||||
if (ret < 0)
|
||||
{
|
||||
evhttp_send_error(req, 500, "Internal Server Error");
|
||||
}
|
||||
else
|
||||
{
|
||||
headers = evhttp_request_get_output_headers(req);
|
||||
evhttp_add_header(headers, "Content-Type", "application/json");
|
||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
|
||||
httpd_send_error(req, 500, "Internal Server Error");
|
||||
goto error;
|
||||
}
|
||||
|
||||
evbuffer_free(evbuf);
|
||||
evhttp_clear_headers(&query);
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
evhttp_add_header(headers, "Content-Type", "application/json");
|
||||
|
||||
httpd_send_reply(req, HTTP_OK, "OK", reply, 0);
|
||||
|
||||
error:
|
||||
evbuffer_free(reply);
|
||||
free(jreq);
|
||||
}
|
||||
|
||||
int
|
||||
jsonapi_is_request(struct evhttp_request *req, char *uri)
|
||||
jsonapi_is_request(const char *path)
|
||||
{
|
||||
if (strncmp(uri, "/api/", strlen("/api/")) == 0)
|
||||
if (strncmp(path, "/api/", strlen("/api/")) == 0)
|
||||
return 1;
|
||||
if (strcmp(uri, "/api") == 0)
|
||||
if (strcmp(path, "/api") == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
@ -679,7 +638,7 @@ jsonapi_init(void)
|
||||
{
|
||||
regerror(ret, &adm_handlers[i].preg, buf, sizeof(buf));
|
||||
|
||||
DPRINTF(E_FATAL, L_WEB, "Admin web interface init failed; regexp error: %s\n", buf);
|
||||
DPRINTF(E_FATAL, L_WEB, "JSON api init failed; regexp error: %s\n", buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
#ifndef __HTTPD_JSONAPI_H__
|
||||
#define __HTTPD_JSONAPI_H__
|
||||
|
||||
#include <event2/http.h>
|
||||
#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__ */
|
||||
|
383
src/httpd_rsp.c
383
src/httpd_rsp.c
@ -32,16 +32,14 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include <mxml.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
#include "httpd_rsp.h"
|
||||
#include "logger.h"
|
||||
#include "db.h"
|
||||
#include "conffile.h"
|
||||
#include "misc.h"
|
||||
#include "httpd.h"
|
||||
#include "transcode.h"
|
||||
#include "httpd_rsp.h"
|
||||
#include "rsp_query.h"
|
||||
|
||||
#define RSP_VERSION "1.0"
|
||||
@ -54,16 +52,28 @@
|
||||
#define F_DETAILED (1 << 3)
|
||||
#define F_ALWAYS (F_FULL | F_BROWSE | F_ID | F_DETAILED)
|
||||
|
||||
struct field_map {
|
||||
char *field;
|
||||
size_t offset;
|
||||
int flags;
|
||||
// Will be filled out by rsp_request_parse()
|
||||
struct rsp_request {
|
||||
// The parsed request URI given to us by httpd.c
|
||||
struct httpd_uri_parsed *uri_parsed;
|
||||
// Shortcut to &uri_parsed->ev_query
|
||||
struct evkeyvalq *query;
|
||||
// http request struct
|
||||
struct evhttp_request *req;
|
||||
// A pointer to the handler that will process the request
|
||||
void (*handler)(struct rsp_request *rreq);
|
||||
};
|
||||
|
||||
struct uri_map {
|
||||
regex_t preg;
|
||||
char *regexp;
|
||||
void (*handler)(struct evhttp_request *req, char **uri, struct evkeyvalq *query);
|
||||
void (*handler)(struct rsp_request *rreq);
|
||||
};
|
||||
|
||||
struct field_map {
|
||||
char *field;
|
||||
size_t offset;
|
||||
int flags;
|
||||
};
|
||||
|
||||
static const struct field_map pl_fields[] =
|
||||
@ -123,6 +133,11 @@ static const struct field_map rsp_fields[] =
|
||||
{ NULL, 0, 0 }
|
||||
};
|
||||
|
||||
/* Forward declaration of handlers */
|
||||
static struct uri_map rsp_handlers[];
|
||||
|
||||
|
||||
/* -------------------------------- HELPERS --------------------------------- */
|
||||
|
||||
static struct evbuffer *
|
||||
mxml_to_evbuf(mxml_node_t *tree)
|
||||
@ -161,61 +176,6 @@ mxml_to_evbuf(mxml_node_t *tree)
|
||||
return evbuf;
|
||||
}
|
||||
|
||||
/* Forward */
|
||||
static void
|
||||
rsp_send_error(struct evhttp_request *req, char *errmsg);
|
||||
|
||||
static int
|
||||
get_query_params(struct evhttp_request *req, struct evkeyvalq *query, struct query_params *qp)
|
||||
{
|
||||
const char *param;
|
||||
int ret;
|
||||
|
||||
qp->offset = 0;
|
||||
param = evhttp_find_header(query, "offset");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &qp->offset);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(req, "Invalid offset");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
qp->limit = 0;
|
||||
param = evhttp_find_header(query, "limit");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &qp->limit);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(req, "Invalid limit");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (qp->offset || qp->limit)
|
||||
qp->idx_type = I_SUB;
|
||||
else
|
||||
qp->idx_type = I_NONE;
|
||||
|
||||
qp->sort = S_NONE;
|
||||
|
||||
param = evhttp_find_header(query, "query");
|
||||
if (param)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param);
|
||||
|
||||
qp->filter = rsp_query_parse_sql(param);
|
||||
if (!qp->filter)
|
||||
DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
rsp_send_error(struct evhttp_request *req, char *errmsg)
|
||||
{
|
||||
@ -265,6 +225,56 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
|
||||
evbuffer_free(evbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
query_params_set(struct query_params *qp, struct rsp_request *rreq)
|
||||
{
|
||||
const char *param;
|
||||
int ret;
|
||||
|
||||
qp->offset = 0;
|
||||
param = evhttp_find_header(rreq->query, "offset");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &qp->offset);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(rreq->req, "Invalid offset");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
qp->limit = 0;
|
||||
param = evhttp_find_header(rreq->query, "limit");
|
||||
if (param)
|
||||
{
|
||||
ret = safe_atoi32(param, &qp->limit);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(rreq->req, "Invalid limit");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (qp->offset || qp->limit)
|
||||
qp->idx_type = I_SUB;
|
||||
else
|
||||
qp->idx_type = I_NONE;
|
||||
|
||||
qp->sort = S_NONE;
|
||||
|
||||
param = evhttp_find_header(rreq->query, "query");
|
||||
if (param)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param);
|
||||
|
||||
qp->filter = rsp_query_parse_sql(param);
|
||||
if (!qp->filter)
|
||||
DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
|
||||
{
|
||||
@ -290,9 +300,75 @@ rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
|
||||
evbuffer_free(evbuf);
|
||||
}
|
||||
|
||||
static struct rsp_request *
|
||||
rsp_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
||||
{
|
||||
struct rsp_request *rreq;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
CHECK_NULL(L_DAAP, rreq = calloc(1, sizeof(struct rsp_request)));
|
||||
|
||||
rreq->req = req;
|
||||
rreq->uri_parsed = uri_parsed;
|
||||
rreq->query = &(uri_parsed->ev_query);
|
||||
|
||||
// Find a handler for the path
|
||||
for (i = 0; rsp_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regexec(&rsp_handlers[i].preg, uri_parsed->path, 0, NULL, 0);
|
||||
if (ret == 0)
|
||||
{
|
||||
rreq->handler = rsp_handlers[i].handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rreq->handler)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Unrecognized path '%s' in RSP request: '%s'\n", uri_parsed->path, uri_parsed->uri);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return rreq;
|
||||
|
||||
error:
|
||||
free(rreq);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
rsp_request_authorize(struct rsp_request *rreq)
|
||||
{
|
||||
char *passwd;
|
||||
int ret;
|
||||
|
||||
if (cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode"))
|
||||
return 0;
|
||||
|
||||
passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password");
|
||||
if (!passwd)
|
||||
return 0;
|
||||
|
||||
DPRINTF(E_DBG, L_RSP, "Checking authentication for library\n");
|
||||
|
||||
// We don't care about the username
|
||||
ret = httpd_basic_auth(rreq->req, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Unsuccessful library authentication\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------- REPLY HANDLERS ------------------------------- */
|
||||
|
||||
static void
|
||||
rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
rsp_reply_info(struct rsp_request *rreq)
|
||||
{
|
||||
mxml_node_t *reply;
|
||||
mxml_node_t *status;
|
||||
@ -342,11 +418,11 @@ rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
node = mxmlNewElement(info, "name");
|
||||
mxmlNewText(node, 0, library);
|
||||
|
||||
rsp_send_reply(req, reply);
|
||||
rsp_send_reply(rreq->req, reply);
|
||||
}
|
||||
|
||||
static void
|
||||
rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
rsp_reply_db(struct rsp_request *rreq)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct db_playlist_info dbpli;
|
||||
@ -369,7 +445,7 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
|
||||
|
||||
rsp_send_error(req, "Could not start query");
|
||||
rsp_send_error(rreq->req, "Could not start query");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -419,7 +495,7 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
|
||||
mxmlDelete(reply);
|
||||
db_query_end(&qp);
|
||||
rsp_send_error(req, "Error fetching query results");
|
||||
rsp_send_error(rreq->req, "Error fetching query results");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -433,11 +509,11 @@ rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
|
||||
db_query_end(&qp);
|
||||
|
||||
rsp_send_reply(req, reply);
|
||||
rsp_send_reply(rreq->req, reply);
|
||||
}
|
||||
|
||||
static void
|
||||
rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
rsp_reply_playlist(struct rsp_request *rreq)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct db_media_file_info dbmfi;
|
||||
@ -460,10 +536,10 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
ret = safe_atoi32(uri[2], &qp.id);
|
||||
ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &qp.id);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(req, "Invalid playlist ID");
|
||||
rsp_send_error(rreq->req, "Invalid playlist ID");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -473,7 +549,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
qp.type = Q_PLITEMS;
|
||||
|
||||
mode = F_FULL;
|
||||
param = evhttp_find_header(query, "type");
|
||||
param = evhttp_find_header(rreq->query, "type");
|
||||
if (param)
|
||||
{
|
||||
if (strcasecmp(param, "full") == 0)
|
||||
@ -488,7 +564,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
DPRINTF(E_LOG, L_RSP, "Unknown browse mode %s\n", param);
|
||||
}
|
||||
|
||||
ret = get_query_params(req, query, &qp);
|
||||
ret = query_params_set(&qp, rreq);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
@ -497,7 +573,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
|
||||
|
||||
rsp_send_error(req, "Could not start query");
|
||||
rsp_send_error(rreq->req, "Could not start query");
|
||||
|
||||
if (qp.filter)
|
||||
free(qp.filter);
|
||||
@ -536,7 +612,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
/* Items block (all items) */
|
||||
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
|
||||
{
|
||||
headers = evhttp_request_get_input_headers(req);
|
||||
headers = evhttp_request_get_input_headers(rreq->req);
|
||||
|
||||
ua = evhttp_find_header(headers, "User-Agent");
|
||||
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
|
||||
@ -607,7 +683,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
|
||||
mxmlDelete(reply);
|
||||
db_query_end(&qp);
|
||||
rsp_send_error(req, "Error fetching query results");
|
||||
rsp_send_error(rreq->req, "Error fetching query results");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -621,11 +697,11 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
||||
|
||||
db_query_end(&qp);
|
||||
|
||||
rsp_send_reply(req, reply);
|
||||
rsp_send_reply(rreq->req, reply);
|
||||
}
|
||||
|
||||
static void
|
||||
rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
rsp_reply_browse(struct rsp_request *rreq)
|
||||
{
|
||||
struct query_params qp;
|
||||
char *browse_item;
|
||||
@ -638,30 +714,30 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
if (strcmp(uri[3], "artist") == 0)
|
||||
if (strcmp(rreq->uri_parsed->path_parts[3], "artist") == 0)
|
||||
qp.type = Q_BROWSE_ARTISTS;
|
||||
else if (strcmp(uri[3], "genre") == 0)
|
||||
else if (strcmp(rreq->uri_parsed->path_parts[3], "genre") == 0)
|
||||
qp.type = Q_BROWSE_GENRES;
|
||||
else if (strcmp(uri[3], "album") == 0)
|
||||
else if (strcmp(rreq->uri_parsed->path_parts[3], "album") == 0)
|
||||
qp.type = Q_BROWSE_ALBUMS;
|
||||
else if (strcmp(uri[3], "composer") == 0)
|
||||
else if (strcmp(rreq->uri_parsed->path_parts[3], "composer") == 0)
|
||||
qp.type = Q_BROWSE_COMPOSERS;
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", uri[3]);
|
||||
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", rreq->uri_parsed->path_parts[3]);
|
||||
|
||||
rsp_send_error(req, "Unsupported browse type");
|
||||
rsp_send_error(rreq->req, "Unsupported browse type");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = safe_atoi32(uri[2], &qp.id);
|
||||
ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &qp.id);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(req, "Invalid playlist ID");
|
||||
rsp_send_error(rreq->req, "Invalid playlist ID");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = get_query_params(req, query, &qp);
|
||||
ret = query_params_set(&qp, rreq);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
@ -670,7 +746,7 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
|
||||
|
||||
rsp_send_error(req, "Could not start query");
|
||||
rsp_send_error(rreq->req, "Could not start query");
|
||||
|
||||
if (qp.filter)
|
||||
free(qp.filter);
|
||||
@ -722,7 +798,7 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query
|
||||
|
||||
mxmlDelete(reply);
|
||||
db_query_end(&qp);
|
||||
rsp_send_error(req, "Error fetching query results");
|
||||
rsp_send_error(rreq->req, "Error fetching query results");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -736,20 +812,20 @@ rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query
|
||||
|
||||
db_query_end(&qp);
|
||||
|
||||
rsp_send_reply(req, reply);
|
||||
rsp_send_reply(rreq->req, reply);
|
||||
}
|
||||
|
||||
static void
|
||||
rsp_stream(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
|
||||
rsp_stream(struct rsp_request *rreq)
|
||||
{
|
||||
int id;
|
||||
int ret;
|
||||
|
||||
ret = safe_atoi32(uri[2], &id);
|
||||
ret = safe_atoi32(rreq->uri_parsed->path_parts[2], &id);
|
||||
if (ret < 0)
|
||||
httpd_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||
httpd_send_error(rreq->req, HTTP_BADREQUEST, "Bad Request");
|
||||
else
|
||||
httpd_stream_file(req, id);
|
||||
httpd_stream_file(rreq->req, id);
|
||||
}
|
||||
|
||||
|
||||
@ -782,127 +858,40 @@ static struct uri_map rsp_handlers[] =
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------- RSP API --------------------------------- */
|
||||
|
||||
void
|
||||
rsp_request(struct evhttp_request *req)
|
||||
rsp_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
||||
{
|
||||
char *full_uri;
|
||||
char *uri;
|
||||
char *ptr;
|
||||
char *uri_parts[5];
|
||||
struct evkeyvalq query;
|
||||
cfg_t *lib;
|
||||
char *libname;
|
||||
char *passwd;
|
||||
int handler;
|
||||
int i;
|
||||
struct rsp_request *rreq;
|
||||
int ret;
|
||||
|
||||
memset(&query, 0, sizeof(struct evkeyvalq));
|
||||
DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", uri_parsed->uri);
|
||||
|
||||
full_uri = httpd_fixup_uri(req);
|
||||
if (!full_uri)
|
||||
rreq = rsp_request_parse(req, uri_parsed);
|
||||
if (!rreq)
|
||||
{
|
||||
rsp_send_error(req, "Server error");
|
||||
return;
|
||||
}
|
||||
|
||||
ptr = strchr(full_uri, '?');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
uri = strdup(full_uri);
|
||||
if (!uri)
|
||||
ret = rsp_request_authorize(rreq);
|
||||
if (ret < 0)
|
||||
{
|
||||
rsp_send_error(req, "Server error");
|
||||
|
||||
free(full_uri);
|
||||
rsp_send_error(req, "Access denied");
|
||||
free(rreq);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr)
|
||||
*ptr = '?';
|
||||
rreq->handler(rreq);
|
||||
|
||||
ptr = uri;
|
||||
uri = evhttp_decode_uri(uri);
|
||||
free(ptr);
|
||||
|
||||
DPRINTF(E_DBG, L_RSP, "RSP request: %s\n", full_uri);
|
||||
|
||||
handler = -1;
|
||||
for (i = 0; rsp_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regexec(&rsp_handlers[i].preg, uri, 0, NULL, 0);
|
||||
if (ret == 0)
|
||||
{
|
||||
handler = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handler < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Unrecognized RSP request\n");
|
||||
|
||||
rsp_send_error(req, "Bad path");
|
||||
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check authentication */
|
||||
lib = cfg_getsec(cfg, "library");
|
||||
passwd = cfg_getstr(lib, "password");
|
||||
if (passwd && !cfg_getbool(cfg_getsec(cfg, "general"), "promiscuous_mode"))
|
||||
{
|
||||
libname = cfg_getstr(lib, "name");
|
||||
|
||||
DPRINTF(E_DBG, L_HTTPD, "Checking authentication for library '%s'\n", libname);
|
||||
|
||||
/* We don't care about the username */
|
||||
ret = httpd_basic_auth(req, NULL, passwd, libname);
|
||||
if (ret != 0)
|
||||
{
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_HTTPD, "Library authentication successful\n");
|
||||
}
|
||||
|
||||
memset(uri_parts, 0, sizeof(uri_parts));
|
||||
|
||||
uri_parts[0] = strtok_r(uri, "/", &ptr);
|
||||
for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++)
|
||||
{
|
||||
uri_parts[i] = strtok_r(NULL, "/", &ptr);
|
||||
}
|
||||
|
||||
if (!uri_parts[0] || uri_parts[i - 1] || (i < 2))
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "RSP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0);
|
||||
|
||||
rsp_send_error(req, "Bad path");
|
||||
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
evhttp_parse_query(full_uri, &query);
|
||||
|
||||
rsp_handlers[handler].handler(req, uri_parts, &query);
|
||||
|
||||
evhttp_clear_headers(&query);
|
||||
free(uri);
|
||||
free(full_uri);
|
||||
free(rreq);
|
||||
}
|
||||
|
||||
int
|
||||
rsp_is_request(struct evhttp_request *req, char *uri)
|
||||
rsp_is_request(const char *path)
|
||||
{
|
||||
if (strncmp(uri, "/rsp/", strlen("/rsp/")) == 0)
|
||||
if (strncmp(path, "/rsp/", strlen("/rsp/")) == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#ifndef __HTTPD_RSP_H__
|
||||
#define __HTTPD_RSP_H__
|
||||
|
||||
#include <event2/http.h>
|
||||
#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__ */
|
||||
|
@ -30,16 +30,13 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/http.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
#ifndef __HTTPD_STREAMING_H__
|
||||
#define __HTTPD_STREAMING_H__
|
||||
|
||||
#include <event2/http.h>
|
||||
#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);
|
||||
|
Loading…
Reference in New Issue
Block a user