Merge branch 'refactor_daap1'

This commit is contained in:
ejurgensen 2017-11-12 21:39:34 +01:00
commit f3f24e520c
24 changed files with 2972 additions and 3409 deletions

View File

@ -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

View File

@ -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) \

View File

@ -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);

View File

@ -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);

View File

@ -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()
};

127
src/db.c
View File

@ -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
*

View File

@ -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);

View File

@ -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);

View File

@ -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);

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,97 @@
#ifndef __HTTPD_H__
#define __HTTPD_H__
#include <stdbool.h>
#include <regex.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <stdbool.h>
#include <event2/keyvalq_struct.h>
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);

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
#ifndef __HTTPD_DAAP_H__
#define __HTTPD_DAAP_H__
#include <event2/http.h>
#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__ */

File diff suppressed because it is too large Load Diff

View File

@ -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__ */

View File

@ -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,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);
}

View File

@ -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__ */

155
src/httpd_oauth.c Normal file
View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2017 Espen Jürgensen <espenjurgensen@gmail.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#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);
}

18
src/httpd_oauth.h Normal file
View File

@ -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__ */

View File

@ -28,20 +28,17 @@
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <regex.h>
#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"
@ -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;

View File

@ -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__ */

View File

@ -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)
{

View File

@ -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);

View File

@ -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;
}