[httpd] Make http modules agnostic to evhttp

This commit is contained in:
ejurgensen 2022-12-21 19:11:03 +01:00
parent 4ae73fa9b4
commit 74f1b93b42
15 changed files with 2152 additions and 1399 deletions

View File

@ -268,6 +268,11 @@ OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevent_pthreads support],
[libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
[evthread_use_pthreads], [event2/thread.h])
dnl Build with libevhtp
OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [libevhtp support], [libevhtp], [LIBEVHTP],
[evhtp])
AM_CONDITIONAL([COND_LIBEVHTP], [[test "x$with_libevhtp" = "xyes"]])
dnl Build with Avahi (or Bonjour if not)
OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [Avahi mDNS], [avahi], [AVAHI],
[avahi-client >= 0.6.24], [avahi_client_new], [avahi-client/client.h])

View File

@ -46,6 +46,12 @@ if COND_LIBWEBSOCKETS
LIBWEBSOCKETS_SRC=websocket.c websocket.h
endif
if COND_LIBEVHTP
HTTPDBACKEND_SRC=httpd_libevhtp.c
else
HTTPDBACKEND_SRC=httpd_libevhttp.c
endif
GPERF_FILES = \
daap_query.gperf \
dacp_prop.gperf \
@ -92,6 +98,7 @@ owntone_SOURCES = main.c \
library.c library.h \
$(MDNS_SRC) mdns.h \
remote_pairing.c remote_pairing.h \
$(HTTPDBACKEND_SRC) \
httpd.c httpd.h httpd_internal.h \
httpd_rsp.c \
httpd_daap.c httpd_daap.h \

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,6 @@
# include <config.h>
#endif
#include <regex.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
@ -40,20 +39,20 @@ request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h)
*max_w = 0;
*max_h = 0;
param = evhttp_find_header(hreq->query, "maxwidth");
param = httpd_query_value_find(hreq->query, "maxwidth");
if (param)
{
ret = safe_atou32(param, max_w);
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "Invalid width in request: '%s'\n", hreq->uri_parsed->uri);
DPRINTF(E_LOG, L_WEB, "Invalid width in request: '%s'\n", hreq->uri);
}
param = evhttp_find_header(hreq->query, "maxheight");
param = httpd_query_value_find(hreq->query, "maxheight");
if (param)
{
ret = safe_atou32(param, max_h);
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "Invalid height in request: '%s'\n", hreq->uri_parsed->uri);
DPRINTF(E_LOG, L_WEB, "Invalid height in request: '%s'\n", hreq->uri);
}
return 0;
@ -62,14 +61,10 @@ request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h)
static int
response_process(struct httpd_request *hreq, int format)
{
struct evkeyvalq *headers;
headers = evhttp_request_get_output_headers(hreq->req);
if (format == ART_FMT_PNG)
evhttp_add_header(headers, "Content-Type", "image/png");
httpd_header_add(hreq->out_headers, "Content-Type", "image/png");
else if (format == ART_FMT_JPEG)
evhttp_add_header(headers, "Content-Type", "image/jpeg");
httpd_header_add(hreq->out_headers, "Content-Type", "image/jpeg");
else
return HTTP_NOCONTENT;
@ -92,7 +87,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq)
if (ret != 0)
return HTTP_NOTFOUND;
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -109,11 +104,11 @@ artworkapi_reply_item(struct httpd_request *hreq)
if (ret != 0)
return ret;
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -130,20 +125,20 @@ artworkapi_reply_group(struct httpd_request *hreq)
if (ret != 0)
return ret;
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id);
ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_group(hreq->reply, id, max_w, max_h, 0);
ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
static struct httpd_uri_map artworkapi_handlers[] =
{
{ EVHTTP_REQ_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying },
{ EVHTTP_REQ_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item },
{ EVHTTP_REQ_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group },
{ HTTPD_METHOD_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying },
{ HTTPD_METHOD_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item },
{ HTTPD_METHOD_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group },
{ 0, NULL, NULL }
};
@ -155,52 +150,47 @@ artworkapi_request(struct httpd_request *hreq)
{
int status_code;
DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", hreq->uri);
if (!httpd_admin_check_auth(hreq->req))
if (!httpd_admin_check_auth(hreq))
return;
if (!hreq->handler)
{
DPRINTF(E_LOG, L_WEB, "Unrecognized path in artwork api request: '%s'\n", hreq->uri);
httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return;
}
CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
status_code = hreq->handler(hreq);
switch (status_code)
{
case HTTP_OK: /* 200 OK */
httpd_send_reply(hreq->req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP);
httpd_send_reply(hreq, status_code, "OK", hreq->out_body, HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOCONTENT: /* 204 No Content */
httpd_send_reply(hreq->req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP);
httpd_send_reply(hreq, status_code, "No Content", hreq->out_body, HTTPD_SEND_NO_GZIP);
break;
case HTTP_NOTMODIFIED: /* 304 Not Modified */
httpd_send_reply(hreq->req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
httpd_send_reply(hreq, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
break;
case HTTP_BADREQUEST: /* 400 Bad Request */
httpd_send_error(hreq->req, status_code, "Bad Request");
httpd_send_error(hreq, status_code, "Bad Request");
break;
case HTTP_NOTFOUND: /* 404 Not Found */
httpd_send_error(hreq->req, status_code, "Not Found");
httpd_send_error(hreq, status_code, "Not Found");
break;
case HTTP_INTERNAL: /* 500 Internal Server Error */
default:
httpd_send_error(hreq->req, HTTP_INTERNAL, "Internal Server Error");
httpd_send_error(hreq, HTTP_INTERNAL, "Internal Server Error");
}
evbuffer_free(hreq->reply);
}
struct httpd_module httpd_artworkapi =
{
.name = "Artwork API",
.type = MODULE_ARTWORKAPI,
.logdomain = L_WEB,
.subpaths = { "/artwork/", NULL },
.handlers = artworkapi_handlers,
.request = artworkapi_request,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,67 +4,97 @@
#include <stdbool.h>
#include <time.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>
#include <event2/event.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* Response codes from event2/http.h */
#define HTTP_CONTINUE 100 /**< client should proceed to send */
#define HTTP_SWITCH_PROTOCOLS 101 /**< switching to another protocol */
#define HTTP_PROCESSING 102 /**< processing the request, but no response is available yet */
#define HTTP_EARLYHINTS 103 /**< return some response headers */
#define HTTP_OK 200 /**< request completed ok */
#define HTTP_CREATED 201 /**< new resource is created */
#define HTTP_ACCEPTED 202 /**< accepted for processing */
#define HTTP_NONAUTHORITATIVE 203 /**< returning a modified version of the origin's response */
#define HTTP_NOCONTENT 204 /**< request does not have content */
#define HTTP_MOVEPERM 301 /**< the uri moved permanently */
#define HTTP_MOVETEMP 302 /**< the uri moved temporarily */
#define HTTP_NOTMODIFIED 304 /**< page was not modified from last */
#define HTTP_BADREQUEST 400 /**< invalid http request was made */
#define HTTP_UNAUTHORIZED 401 /**< authentication is required */
#define HTTP_PAYMENTREQUIRED 402 /**< user exceeded limit on requests */
#define HTTP_FORBIDDEN 403 /**< user not having the necessary permissions */
#define HTTP_NOTFOUND 404 /**< could not find content for uri */
#define HTTP_BADMETHOD 405 /**< method not allowed for this uri */
#define HTTP_ENTITYTOOLARGE 413 /**< request is larger than the server is able to process */
#define HTTP_EXPECTATIONFAILED 417 /**< we can't handle this expectation */
#define HTTP_INTERNAL 500 /**< internal error */
#define HTTP_NOTIMPLEMENTED 501 /**< not implemented */
#define HTTP_BADGATEWAY 502 /**< received an invalid response from the upstream */
#define HTTP_SERVUNAVAIL 503 /**< the server is not available */
struct httpd_request;
#ifdef HAVE_LIBEVHTP
struct evhtp_s;
struct evhtp_connection_s;
struct evhtp_request_s;
struct evhtp_kvs_s;
struct httpd_uri_parsed;
struct httpd_backend_data;
typedef struct evhtp_s httpd_server;
typedef struct evhtp_connection_s httpd_connection;
typedef struct evhtp_request_s httpd_backend;
typedef struct evhtp_kvs_s httpd_headers;
typedef struct evhtp_kvs_s httpd_query;
typedef struct httpd_uri_parsed httpd_uri_parsed;
typedef struct httpd_backend_data httpd_backend_data;
#else
struct evhttp;
struct evhttp_connection;
struct evhttp_request;
struct evkeyvalq;
struct httpd_uri_parsed;
typedef struct evhttp httpd_server;
typedef struct evhttp_connection httpd_connection;
typedef struct evhttp_request httpd_backend;
typedef struct evkeyvalq httpd_headers;
typedef struct evkeyvalq httpd_query;
typedef struct httpd_uri_parsed httpd_uri_parsed;
typedef void httpd_backend_data; // Not used for evhttp
#endif
typedef char *httpd_uri_path_parts[31];
typedef void (*httpd_general_cb)(httpd_backend *backend, void *arg);
typedef void (*httpd_connection_closecb)(httpd_connection *conn, void *arg);
typedef void (*httpd_connection_chunkcb)(httpd_connection *conn, void *arg);
typedef void (*httpd_query_iteratecb)(const char *key, const char *val, void *arg);
enum httpd_methods
{
HTTPD_METHOD_GET = 1 << 0,
HTTPD_METHOD_POST = 1 << 1,
HTTPD_METHOD_HEAD = 1 << 2,
HTTPD_METHOD_PUT = 1 << 3,
HTTPD_METHOD_DELETE = 1 << 4,
HTTPD_METHOD_OPTIONS = 1 << 5,
HTTPD_METHOD_TRACE = 1 << 6,
HTTPD_METHOD_CONNECT = 1 << 7,
HTTPD_METHOD_PATCH = 1 << 8,
};
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[0] is "foo", [1] is "bar" and the rest is null.
*
* Each path_part is an allocated URI decoded string.
*/
struct httpd_uri_parsed
{
const char *uri;
struct evhttp_uri *ev_uri;
struct evkeyvalq ev_query;
char *uri_decoded;
char *path;
char *path_parts[31];
};
/*
* 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;
// Shortcut to &uri_parsed->uri
const char *uri;
// 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;
// Source IP address (ipv4 or ipv6) and port of the request (if available)
char *peer_address;
unsigned short peer_port;
// 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);
};
/*---------------------------------- MODULES ---------------------------------*/
@ -85,6 +115,7 @@ struct httpd_module
const char *name;
enum httpd_modules type;
char initialized;
int logdomain;
// Null-terminated list of URL subpath that the module accepts e.g., /subpath/morepath/file.mp3
const char *subpaths[16];
@ -93,9 +124,9 @@ struct httpd_module
// Pointer to the module's handler definitions
struct httpd_uri_map *handlers;
int (*init)(void);
int (*init)(struct event_base *);
void (*deinit)(void);
void (*request)(struct httpd_request *hreq);
void (*request)(struct httpd_request *);
};
/*
@ -103,45 +134,87 @@ struct httpd_module
*/
struct httpd_uri_map
{
int method;
enum httpd_methods method;
char *regexp;
int (*handler)(struct httpd_request *hreq);
void *preg;
};
/*
* Helper to free the parsed uri struct
*/
void
httpd_uri_free(struct httpd_uri_parsed *parsed);
/*------------------------------- HTTPD STRUCTS ------------------------------*/
/*
* Parse an URI into the struct
* 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_uri_parsed *
httpd_uri_parse(const char *uri);
struct httpd_request {
// Request method
enum httpd_methods method;
// Backend private request object
httpd_backend *backend;
// For storing data that the actual backend doesn't have readily available
// e.g. peer address string for libevhtp
httpd_backend_data *backend_data;
// User-agent (if available)
const char *user_agent;
// Source IP address (ipv4 or ipv6) and port of the request (if available)
const char *peer_address;
unsigned short peer_port;
// The original, request URI. The URI may have been complete:
// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
// or relative:
// [/path][?query][#fragment]
const char *uri;
// URI decoded path from the request URI
const char *path;
// If the request is http://x:3689/foo/bar?key1=val1, then part_parts[0] is
// "foo", [1] is "bar" and the rest is null. Each path_part is an allocated
// URI decoded string.
httpd_uri_path_parts path_parts;
// Struct with the query, used with httpd_query_ functions
httpd_query *query;
// Backend private parser URI object
httpd_uri_parsed *uri_parsed;
// Request headers
httpd_headers *in_headers;
// Request body
struct evbuffer *in_body;
// Response headers
httpd_headers *out_headers;
// Response body
struct evbuffer *out_body;
// Our httpd module that will process this request
struct httpd_module *module;
// A pointer to the handler that will process the request
int (*handler)(struct httpd_request *hreq);
// A pointer to extra data that the module handling the request might need
void *extra_data;
};
/*------------------------------ HTTPD FUNCTIONS -----------------------------*/
void
httpd_stream_file(struct evhttp_request *req, int id);
httpd_stream_file(struct httpd_request *hreq, int id);
/*
* Parse a request into the httpd_request struct. Nothing is copied, so 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.
*/
int
httpd_request_parse(struct httpd_request *hreq, struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map);
void
httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent);
void
httpd_request_unset(struct httpd_request *hreq);
bool
httpd_request_not_modified_since(struct evhttp_request *req, time_t mtime);
httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime);
bool
httpd_request_etag_matches(struct evhttp_request *req, const char *etag);
httpd_request_etag_matches(struct httpd_request *hreq, const char *etag);
void
httpd_response_not_cachable(struct evhttp_request *req);
httpd_response_not_cachable(struct httpd_request *hreq);
/*
* This wrapper around evhttp_send_reply should be used whenever a request may
@ -149,7 +222,7 @@ httpd_response_not_cachable(struct evhttp_request *req);
* may direct it not to. It will set CORS headers as appropriate. Should be
* thread safe.
*
* @in req The evhttp request struct
* @in req The http request struct
* @in code HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used
@ -157,7 +230,16 @@ httpd_response_not_cachable(struct evhttp_request *req);
* @in flags See flags above
*/
void
httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags);
httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags);
void
httpd_send_reply_start(struct httpd_request *hreq, int code, const char *reason);
void
httpd_send_reply_chunk(struct httpd_request *hreq, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg);
void
httpd_send_reply_end(struct httpd_request *hreq);
/*
* This is a substitute for evhttp_send_error that should be used whenever an
@ -165,24 +247,138 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
* which is not possible with evhttp_send_error, because it clears the headers.
* Should be thread safe.
*
* @in req The evhttp request struct
* @in req The http request struct
* @in error HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used
*/
void
httpd_send_error(struct evhttp_request *req, int error, const char *reason);
httpd_send_error(struct httpd_request *hreq, int error, const char *reason);
/*
* Redirects to the given path
*/
void
httpd_redirect_to(struct evhttp_request *req, const char *path);
httpd_redirect_to(struct httpd_request *hreq, const char *path);
bool
httpd_admin_check_auth(struct evhttp_request *req);
httpd_admin_check_auth(struct httpd_request *hreq);
int
httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm);
httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm);
/*-------------------------- WRAPPERS FOR EVHTTP -----------------------------*/
const char *
httpd_query_value_find(httpd_query *query, const char *key);
void
httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg);
void
httpd_query_clear(httpd_query *query);
const char *
httpd_header_find(httpd_headers *headers, const char *key);
void
httpd_header_remove(httpd_headers *headers, const char *key);
void
httpd_header_add(httpd_headers *headers, const char *key, const char *val);
void
httpd_headers_clear(httpd_headers *headers);
void
httpd_connection_free(httpd_connection *conn);
httpd_connection *
httpd_request_connection_get(struct httpd_request *hreq);
void
httpd_request_backend_free(struct httpd_request *hreq);
int
httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg);
struct event_base *
httpd_request_evbase_get(struct httpd_request *hreq);
void
httpd_server_free(httpd_server *server);
httpd_server *
httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg);
void
httpd_server_allow_origin_set(httpd_server *server, bool allow);
/*----------------- Only called by httpd.c to send raw replies ---------------*/
void
httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf);
void
httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason);
void
httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg);
void
httpd_backend_reply_end_send(httpd_backend *backend);
/*---------- Only called by httpd.c to populate struct httpd_request ---------*/
httpd_backend_data *
httpd_backend_data_create(httpd_backend *backend);
void
httpd_backend_data_free(httpd_backend_data *backend_data);
httpd_connection *
httpd_backend_connection_get(httpd_backend *backend);
const char *
httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data);
httpd_headers *
httpd_backend_input_headers_get(httpd_backend *backend);
httpd_headers *
httpd_backend_output_headers_get(httpd_backend *backend);
struct evbuffer *
httpd_backend_input_buffer_get(httpd_backend *backend);
int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data);
int
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend);
void
httpd_backend_preprocess(httpd_backend *backend);
httpd_uri_parsed *
httpd_uri_parsed_create(httpd_backend *backend);
httpd_uri_parsed *
httpd_uri_parsed_create_fromuri(const char *uri);
void
httpd_uri_parsed_free(httpd_uri_parsed *uri_parsed);
httpd_query *
httpd_uri_query_get(httpd_uri_parsed *parsed);
const char *
httpd_uri_path_get(httpd_uri_parsed *parsed);
void
httpd_uri_path_parts_get(httpd_uri_path_parts *part_parts, httpd_uri_parsed *parsed);
#endif /* !__HTTPD_INTERNAL_H__ */

File diff suppressed because it is too large Load Diff

463
src/httpd_libevhtp.c Normal file
View File

@ -0,0 +1,463 @@
#include <string.h>
#include <evhtp.h>
#include "misc.h"
#include "httpd_internal.h"
#include "logger.h"
struct httpd_backend_data
{
char peer_address[32];
uint16_t peer_port;
httpd_connection_closecb closecb;
void *closecb_arg;
char *uri;
};
struct httpd_uri_parsed
{
evhtp_uri_t *ev_uri;
bool ev_uri_is_standalone; // true if ev_uri was allocated without a request, but via _fromuri
unsigned char *path_parts_buffer; // Allocated to hold the path parts in one buffer
httpd_uri_path_parts path_parts;
};
const char *
httpd_query_value_find(httpd_query *query, const char *key)
{
return evhtp_kv_find(query, key);
}
void
httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg)
{
evhtp_kv_t *param;
TAILQ_FOREACH(param, query, next)
{
cb(param->key, param->val, arg);
}
}
void
httpd_query_clear(httpd_query *query)
{
evhtp_kv_t *param;
TAILQ_FOREACH(param, query, next)
{
evhtp_kv_rm_and_free(query, param);
}
}
const char *
httpd_header_find(httpd_headers *headers, const char *key)
{
return evhtp_header_find(headers, key);
}
void
httpd_header_remove(httpd_headers *headers, const char *key)
{
evhtp_header_rm_and_free(headers, evhtp_headers_find_header(headers, key));
}
void
httpd_header_add(httpd_headers *headers, const char *key, const char *val)
{
evhtp_header_t *header = evhtp_header_new(key, val, 1, 1); // 1, 1 = Copy key/val
evhtp_headers_add_header(headers, header);
}
void
httpd_headers_clear(httpd_headers *headers)
{
evhtp_kv_t *param;
TAILQ_FOREACH(param, headers, next)
{
evhtp_kv_rm_and_free(headers, param);
}
}
void
httpd_connection_free(httpd_connection *conn)
{
if (!conn)
return;
evhtp_connection_free(conn);
}
httpd_connection *
httpd_request_connection_get(struct httpd_request *hreq)
{
return evhtp_request_get_connection(hreq->backend);
}
void
httpd_request_backend_free(struct httpd_request *hreq)
{
evhtp_request_free(hreq->backend);
}
static short unsigned
closecb_wrapper(httpd_connection *conn, void *arg)
{
httpd_backend_data *backend_data = arg;
backend_data->closecb(conn, backend_data->closecb_arg);
return 0;
}
int
httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg)
{
httpd_connection *conn;
hreq->backend_data->closecb = cb;
hreq->backend_data->closecb_arg = arg;
conn = httpd_request_connection_get(hreq);
if (conn)
return -1;
if (!cb)
return evhtp_connection_unset_hook(conn, evhtp_hook_on_connection_fini);
return evhtp_connection_set_hook(conn, evhtp_hook_on_connection_fini, closecb_wrapper, hreq->backend_data);
}
struct event_base *
httpd_request_evbase_get(struct httpd_request *hreq)
{
httpd_connection *conn = httpd_request_connection_get(hreq);
if (conn)
return NULL;
return conn->evbase;
}
void
httpd_server_free(httpd_server *server)
{
if (!server)
return;
evhtp_free(server);
}
httpd_server *
httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg)
{
evhtp_t *server;
int fd;
server = evhtp_new(evbase, NULL);
if (!server)
goto error;
fd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd");
if (fd < 0)
goto error;
if (evhtp_accept_socket(server, fd, -1) != 0)
goto error;
evhtp_set_gencb(server, cb, arg);
return server;
error:
httpd_server_free(server);
return NULL;
}
void
httpd_server_allow_origin_set(httpd_server *server, bool allow)
{
}
httpd_backend_data *
httpd_backend_data_create(httpd_backend *backend)
{
httpd_backend_data *backend_data;
backend_data = calloc(1, sizeof(httpd_backend_data));
if (!backend_data)
return NULL;
return backend_data;
}
void
httpd_backend_data_free(httpd_backend_data *backend_data)
{
free(backend_data->uri);
free(backend_data);
}
void
httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf)
{
if (evbuf)
evbuffer_add_buffer(backend->buffer_out, evbuf);
evhtp_send_reply(backend, code);
}
void
httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason)
{
evhtp_send_reply_chunk_start(backend, code);
}
void
httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg)
{
// TODO
}
void
httpd_backend_reply_end_send(httpd_backend *backend)
{
evhtp_send_reply_chunk_end(backend);
}
httpd_connection *
httpd_backend_connection_get(httpd_backend *backend)
{
return evhtp_request_get_connection(backend);
}
const char *
httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data)
{
evhtp_uri_t *uri = backend->uri;
if (!uri || !uri->path)
return NULL;
free(backend_data->uri);
if (backend->uri->query_raw)
backend_data->uri = safe_asprintf("%s?%s", uri->path->full, backend->uri->query_raw);
else
backend_data->uri = safe_asprintf("%s", uri->path->full);
return (const char *)backend_data->uri;
}
httpd_headers *
httpd_backend_input_headers_get(httpd_backend *backend)
{
return backend->headers_in;
}
httpd_headers *
httpd_backend_output_headers_get(httpd_backend *backend)
{
return backend->headers_out;
}
struct evbuffer *
httpd_backend_input_buffer_get(httpd_backend *backend)
{
return backend->buffer_in;
}
int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data)
{
httpd_connection *conn;
union net_sockaddr naddr;
socklen_t sa_len = sizeof(naddr);
*addr = NULL;
*port = 0;
conn = evhtp_request_get_connection(backend);
if (!conn)
return -1;
// We cannot use conn->saddr as we don't have the size, so it won't work for ipv6
getpeername(conn->sock, &naddr.sa, &sa_len);
net_address_get(backend_data->peer_address, sizeof(backend_data->peer_address), &naddr);
net_port_get(&backend_data->peer_port, &naddr);
*addr = backend_data->peer_address;
*port = backend_data->peer_port;
return 0;
}
int
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend)
{
htp_method cmd = evhtp_request_get_method(backend);
switch (cmd)
{
case htp_method_GET: *method = HTTPD_METHOD_GET; break;
case htp_method_POST: *method = HTTPD_METHOD_POST; break;
case htp_method_HEAD: *method = HTTPD_METHOD_HEAD; break;
case htp_method_PUT: *method = HTTPD_METHOD_PUT; break;
case htp_method_DELETE: *method = HTTPD_METHOD_DELETE; break;
case htp_method_OPTIONS: *method = HTTPD_METHOD_OPTIONS; break;
case htp_method_TRACE: *method = HTTPD_METHOD_TRACE; break;
case htp_method_CONNECT: *method = HTTPD_METHOD_CONNECT; break;
case htp_method_PATCH: *method = HTTPD_METHOD_PATCH; break;
default: *method = HTTPD_METHOD_GET; return -1;
}
return 0;
}
void
httpd_backend_preprocess(httpd_backend *backend)
{
// Nothing to do here
}
static int
query_decode(evhtp_kvs_t **query)
{
evhtp_kvs_t *query_decoded;
evhtp_kv_t *encoded;
evhtp_kv_t *decoded;
char buf[2048];
unsigned char *out;
size_t val_size;
query_decoded = evhtp_kvs_new();
if (!query_decoded)
return -1;
TAILQ_FOREACH(encoded, *query, next)
{
// Must include zero terminator in length or output won't be terminated
// (not very clear from evhtp docs)
val_size = strlen(encoded->val) + 1;
if (val_size > sizeof(buf))
continue;
// Isn't done by evhtp_unescape_string :-(
safe_snreplace(encoded->val, val_size, "+", " ");
out = (unsigned char *)buf;
evhtp_unescape_string(&out, (unsigned char *)encoded->val, val_size);
decoded = evhtp_kv_new(encoded->key, buf, 1, 1); // 1, 1 = Copy key/val
evhtp_kvs_add_kv(query_decoded, decoded);
}
evhtp_kvs_free(*query);
*query = query_decoded;
return 0;
}
httpd_uri_parsed *
httpd_uri_parsed_create(httpd_backend *backend)
{
httpd_uri_parsed *parsed = NULL;
char *path = NULL;
size_t path_len;
char *path_part;
off_t path_part_offset;
char *ptr;
unsigned char *unescaped_part;
int i;
if (!backend->uri->path->path) // Not sure if this can happen
goto error;
path_len = strlen(backend->uri->path->path);
if (path_len == 0)
goto error;
path = strdup(backend->uri->path->path);
if (!path)
goto error;
parsed = calloc(1, sizeof(struct httpd_uri_parsed));
if (!parsed)
goto error;
// Pointers of parsed->path_parts will point into this buffer, so it will hold
// the uri decoded path parts separated by zeroes
parsed->path_parts_buffer = calloc(1, path_len + 1);
if (!parsed->path_parts_buffer)
goto error;
parsed->ev_uri = backend->uri;
path_part = strtok_r(path, "/", &ptr);
path_part_offset = path_part - path;
for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
{
// libevhtp's evhtp_unescape_string() is wonky (and feels unsafe...), for
// some reason it wants a double pointer to a user allocated buffer.
unescaped_part = parsed->path_parts_buffer + (path_part - path) - path_part_offset;
parsed->path_parts[i] = (char *)unescaped_part;
evhtp_unescape_string(&unescaped_part, (unsigned char *)path_part, strlen(path_part));
path_part = strtok_r(NULL, "/", &ptr);
}
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
if (path_part)
goto error;
// uri->query isn't uri decoded, so we replace it with one that is
if (backend->uri->query)
query_decode(&backend->uri->query);
free(path);
return parsed;
error:
httpd_uri_parsed_free(parsed);
free(path);
return NULL;
}
httpd_uri_parsed *
httpd_uri_parsed_create_fromuri(const char *uri)
{
// TODO
return NULL;
}
void
httpd_uri_parsed_free(httpd_uri_parsed *parsed)
{
if (!parsed)
return;
// TODO
// if (parsed->ev_uri_is_standalone)
// free ev_uri;
free(parsed->path_parts_buffer);
free(parsed);
}
httpd_query *
httpd_uri_query_get(httpd_uri_parsed *parsed)
{
return parsed->ev_uri->query;
}
const char *
httpd_uri_path_get(httpd_uri_parsed *parsed)
{
if (!parsed->ev_uri->path)
return NULL;
return parsed->ev_uri->path->full;
}
void
httpd_uri_path_parts_get(httpd_uri_path_parts *path_parts, httpd_uri_parsed *parsed)
{
memcpy(path_parts, parsed->path_parts, sizeof(httpd_uri_path_parts));
}

350
src/httpd_libevhttp.c Normal file
View File

@ -0,0 +1,350 @@
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>
#include "misc.h" // For net_evhttp_bind
#include "httpd_internal.h"
struct httpd_uri_parsed
{
struct evhttp_uri *ev_uri;
struct evkeyvalq query;
char *path;
httpd_uri_path_parts path_parts;
};
const char *
httpd_query_value_find(httpd_query *query, const char *key)
{
return evhttp_find_header(query, key);
}
void
httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg)
{
struct evkeyval *param;
TAILQ_FOREACH(param, query, next)
{
cb(param->key, param->value, arg);
}
}
void
httpd_query_clear(httpd_query *query)
{
evhttp_clear_headers(query);
}
const char *
httpd_header_find(httpd_headers *headers, const char *key)
{
return evhttp_find_header(headers, key);
}
void
httpd_header_remove(httpd_headers *headers, const char *key)
{
evhttp_remove_header(headers, key);
}
void
httpd_header_add(httpd_headers *headers, const char *key, const char *val)
{
evhttp_add_header(headers, key, val);
}
void
httpd_headers_clear(httpd_headers *headers)
{
evhttp_clear_headers(headers);
}
void
httpd_connection_free(httpd_connection *conn)
{
if (!conn)
return;
evhttp_connection_free(conn);
}
httpd_connection *
httpd_request_connection_get(struct httpd_request *hreq)
{
return httpd_backend_connection_get(hreq->backend);
}
void
httpd_request_backend_free(struct httpd_request *hreq)
{
evhttp_request_free(hreq->backend);
}
int
httpd_request_closecb_set(struct httpd_request *hreq, httpd_connection_closecb cb, void *arg)
{
httpd_connection *conn = httpd_request_connection_get(hreq);
if (!conn)
return -1;
evhttp_connection_set_closecb(conn, cb, arg);
return 0;
}
struct event_base *
httpd_request_evbase_get(struct httpd_request *hreq)
{
httpd_connection *conn = httpd_request_connection_get(hreq);
if (conn)
return NULL;
return evhttp_connection_get_base(conn);
}
void
httpd_server_free(httpd_server *server)
{
if (!server)
return;
evhttp_free(server);
}
httpd_server *
httpd_server_new(struct event_base *evbase, unsigned short port, httpd_general_cb cb, void *arg)
{
int ret;
struct evhttp *server = evhttp_new(evbase);
if (!server)
goto error;
ret = net_evhttp_bind(server, port, "httpd");
if (ret < 0)
goto error;
evhttp_set_gencb(server, cb, arg);
return server;
error:
httpd_server_free(server);
return NULL;
}
void
httpd_server_allow_origin_set(httpd_server *server, bool allow)
{
evhttp_set_allowed_methods(server, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
}
void
httpd_backend_reply_send(httpd_backend *backend, int code, const char *reason, struct evbuffer *evbuf)
{
evhttp_send_reply(backend, code, reason, evbuf);
}
void
httpd_backend_reply_start_send(httpd_backend *backend, int code, const char *reason)
{
evhttp_send_reply_start(backend, code, reason);
}
void
httpd_backend_reply_chunk_send(httpd_backend *backend, struct evbuffer *evbuf, httpd_connection_chunkcb cb, void *arg)
{
evhttp_send_reply_chunk_with_cb(backend, evbuf, cb, arg);
}
void
httpd_backend_reply_end_send(httpd_backend *backend)
{
evhttp_send_reply_end(backend);
}
httpd_backend_data *
httpd_backend_data_create(httpd_backend *backend)
{
return "dummy";
}
void
httpd_backend_data_free(httpd_backend_data *backend_data)
{
// Nothing to do
}
httpd_connection *
httpd_backend_connection_get(httpd_backend *backend)
{
return evhttp_request_get_connection(backend);
}
const char *
httpd_backend_uri_get(httpd_backend *backend, httpd_backend_data *backend_data)
{
return evhttp_request_get_uri(backend);
}
httpd_headers *
httpd_backend_input_headers_get(httpd_backend *backend)
{
return evhttp_request_get_input_headers(backend);
}
httpd_headers *
httpd_backend_output_headers_get(httpd_backend *backend)
{
return evhttp_request_get_output_headers(backend);
}
struct evbuffer *
httpd_backend_input_buffer_get(httpd_backend *backend)
{
return evhttp_request_get_input_buffer(backend);
}
struct evbuffer *
httpd_backend_output_buffer_get(httpd_backend *backend)
{
return evhttp_request_get_output_buffer(backend);
}
int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data)
{
httpd_connection *conn = httpd_backend_connection_get(backend);
if (!conn)
return -1;
evhttp_connection_get_peer(conn, (char **)addr, port);
return 0;
}
int
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend)
{
enum evhttp_cmd_type cmd = evhttp_request_get_command(backend);
switch (cmd)
{
case EVHTTP_REQ_GET: *method = HTTPD_METHOD_GET; break;
case EVHTTP_REQ_POST: *method = HTTPD_METHOD_POST; break;
case EVHTTP_REQ_HEAD: *method = HTTPD_METHOD_HEAD; break;
case EVHTTP_REQ_PUT: *method = HTTPD_METHOD_PUT; break;
case EVHTTP_REQ_DELETE: *method = HTTPD_METHOD_DELETE; break;
case EVHTTP_REQ_OPTIONS: *method = HTTPD_METHOD_OPTIONS; break;
case EVHTTP_REQ_TRACE: *method = HTTPD_METHOD_TRACE; break;
case EVHTTP_REQ_CONNECT: *method = HTTPD_METHOD_CONNECT; break;
case EVHTTP_REQ_PATCH: *method = HTTPD_METHOD_PATCH; break;
default: *method = HTTPD_METHOD_GET; return -1;
}
return 0;
}
void
httpd_backend_preprocess(httpd_backend *backend)
{
// Clear the proxy request flag set by evhttp if the request URI was absolute.
// It has side-effects on Connection: keep-alive
backend->flags &= ~EVHTTP_PROXY_REQUEST;
}
httpd_uri_parsed *
httpd_uri_parsed_create(httpd_backend *backend)
{
const char *uri = evhttp_request_get_uri(backend);
return httpd_uri_parsed_create_fromuri(uri);
}
httpd_uri_parsed *
httpd_uri_parsed_create_fromuri(const char *uri)
{
struct httpd_uri_parsed *parsed;
const char *query;
char *path = NULL;
char *path_part;
char *ptr;
int i;
parsed = calloc(1, sizeof(struct httpd_uri_parsed));
if (!parsed)
goto error;
parsed->ev_uri = evhttp_uri_parse_with_flags(uri, EVHTTP_URI_NONCONFORMANT);
if (!parsed->ev_uri)
goto error;
query = evhttp_uri_get_query(parsed->ev_uri);
if (query && strchr(query, '=') && evhttp_parse_query_str(query, &(parsed->query)) < 0)
goto error;
path = strdup(evhttp_uri_get_path(parsed->ev_uri));
if (!path || !(parsed->path = evhttp_uridecode(path, 0, NULL)))
goto error;
path_part = strtok_r(path, "/", &ptr);
for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
{
parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
path_part = strtok_r(NULL, "/", &ptr);
}
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
if (path_part)
goto error;
free(path);
return parsed;
error:
free(path);
httpd_uri_parsed_free(parsed);
return NULL;
}
void
httpd_uri_parsed_free(httpd_uri_parsed *parsed)
{
int i;
if (!parsed)
return;
free(parsed->path);
for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
free(parsed->path_parts[i]);
httpd_query_clear(&(parsed->query));
if (parsed->ev_uri)
evhttp_uri_free(parsed->ev_uri);
free(parsed);
}
httpd_query *
httpd_uri_query_get(httpd_uri_parsed *parsed)
{
return &parsed->query;
}
const char *
httpd_uri_path_get(httpd_uri_parsed *parsed)
{
return parsed->path;
}
void
httpd_uri_path_parts_get(httpd_uri_path_parts *path_parts, httpd_uri_parsed *parsed)
{
memcpy(path_parts, parsed->path_parts, sizeof(httpd_uri_path_parts));
}

View File

@ -54,12 +54,12 @@ oauth_reply_spotify(struct httpd_request *hreq)
ret = spotifywebapi_oauth_callback(hreq->query, redirect_uri, &errmsg);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri_parsed->uri, errmsg);
httpd_send_error(hreq->req, HTTP_INTERNAL, errmsg);
DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri, errmsg);
httpd_send_error(hreq, HTTP_INTERNAL, errmsg);
return -1;
}
httpd_redirect_to(hreq->req, "/#/settings/online-services");
httpd_redirect_to(hreq, "/#/settings/online-services");
return 0;
}
@ -69,7 +69,7 @@ oauth_reply_spotify(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_WEB, "This version was built without support for Spotify\n");
httpd_send_error(hreq->req, HTTP_NOTFOUND, "This version was built without support for Spotify");
httpd_send_error(hreq, HTTP_NOTFOUND, "This version was built without support for Spotify");
return -1;
}
@ -93,13 +93,11 @@ static struct httpd_uri_map oauth_handlers[] =
static void
oauth_request(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_WEB, "OAuth request: '%s'\n", hreq->uri);
if (!hreq->handler)
{
DPRINTF(E_LOG, L_WEB, "Unrecognized path in OAuth request: '%s'\n", hreq->uri);
httpd_send_error(hreq->req, HTTP_NOTFOUND, NULL);
httpd_send_error(hreq, HTTP_NOTFOUND, NULL);
return;
}
@ -110,6 +108,7 @@ struct httpd_module httpd_oauth =
{
.name = "OAuth",
.type = MODULE_OAUTH,
.logdomain = L_WEB,
.subpaths = { "/oauth/", NULL },
.fullpaths = { "/oauth", NULL },
.handlers = oauth_handlers,

View File

@ -157,10 +157,9 @@ mxml_to_evbuf(mxml_node_t *tree)
}
static void
rsp_send_error(struct evhttp_request *req, char *errmsg)
rsp_send_error(struct httpd_request *hreq, char *errmsg)
{
struct evbuffer *evbuf;
struct evkeyvalq *headers;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *node;
@ -191,16 +190,15 @@ rsp_send_error(struct evhttp_request *req, char *errmsg)
if (!evbuf)
{
httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
evhttp_add_header(headers, "Connection", "close");
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
httpd_header_add(hreq->out_headers, "Connection", "close");
httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
}
@ -214,25 +212,25 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
int ret;
qp->offset = 0;
param = evhttp_find_header(hreq->query, "offset");
param = httpd_query_value_find(hreq->query, "offset");
if (param)
{
ret = safe_atoi32(param, &qp->offset);
if (ret < 0)
{
rsp_send_error(hreq->req, "Invalid offset");
rsp_send_error(hreq, "Invalid offset");
return -1;
}
}
qp->limit = 0;
param = evhttp_find_header(hreq->query, "limit");
param = httpd_query_value_find(hreq->query, "limit");
if (param)
{
ret = safe_atoi32(param, &qp->limit);
if (ret < 0)
{
rsp_send_error(hreq->req, "Invalid limit");
rsp_send_error(hreq, "Invalid limit");
return -1;
}
}
@ -243,7 +241,7 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
qp->idx_type = I_NONE;
qp->filter = NULL;
param = evhttp_find_header(hreq->query, "query");
param = httpd_query_value_find(hreq->query, "query");
if (param)
{
ret = snprintf(query, sizeof(query), "%s", param);
@ -277,26 +275,24 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
}
static void
rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply)
{
struct evbuffer *evbuf;
struct evkeyvalq *headers;
evbuf = mxml_to_evbuf(reply);
mxmlDelete(reply);
if (!evbuf)
{
rsp_send_error(req, "Could not finalize reply");
rsp_send_error(hreq, "Could not finalize reply");
return;
}
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Content-Type", "text/xml; charset=utf-8");
evhttp_add_header(headers, "Connection", "close");
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
httpd_header_add(hreq->out_headers, "Connection", "close");
httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, 0);
evbuffer_free(evbuf);
}
@ -317,7 +313,7 @@ rsp_request_authorize(struct httpd_request *hreq)
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"));
ret = httpd_basic_auth(hreq, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
if (ret != 0)
{
DPRINTF(E_LOG, L_RSP, "Unsuccessful library authorization attempt from '%s'\n", hreq->peer_address);
@ -381,7 +377,7 @@ rsp_reply_info(struct httpd_request *hreq)
node = mxmlNewElement(info, "name");
mxmlNewText(node, 0, library);
rsp_send_reply(hreq->req, reply);
rsp_send_reply(hreq, reply);
return 0;
}
@ -410,7 +406,7 @@ rsp_reply_db(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
rsp_send_error(hreq->req, "Could not start query");
rsp_send_error(hreq, "Could not start query");
return -1;
}
@ -464,7 +460,7 @@ rsp_reply_db(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
rsp_send_error(hreq->req, "Error fetching query results");
rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@ -478,7 +474,7 @@ rsp_reply_db(struct httpd_request *hreq)
db_query_end(&qp);
rsp_send_reply(hreq->req, reply);
rsp_send_reply(hreq, reply);
return 0;
}
@ -488,7 +484,6 @@ rsp_reply_playlist(struct httpd_request *hreq)
{
struct query_params qp;
struct db_media_file_info dbmfi;
struct evkeyvalq *headers;
const char *param;
const char *ua;
const char *client_codecs;
@ -507,10 +502,10 @@ rsp_reply_playlist(struct httpd_request *hreq)
memset(&qp, 0, sizeof(struct query_params));
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0)
{
rsp_send_error(hreq->req, "Invalid playlist ID");
rsp_send_error(hreq, "Invalid playlist ID");
return -1;
}
@ -522,7 +517,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
qp.sort = S_NAME;
mode = F_FULL;
param = evhttp_find_header(hreq->query, "type");
param = httpd_query_value_find(hreq->query, "type");
if (param)
{
if (strcasecmp(param, "full") == 0)
@ -546,7 +541,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
rsp_send_error(hreq->req, "Could not start query");
rsp_send_error(hreq, "Could not start query");
if (qp.filter)
free(qp.filter);
@ -586,10 +581,8 @@ rsp_reply_playlist(struct httpd_request *hreq)
/* Items block (all items) */
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
headers = evhttp_request_get_input_headers(hreq->req);
ua = evhttp_find_header(headers, "User-Agent");
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
ua = httpd_header_find(hreq->in_headers, "User-Agent");
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
@ -657,7 +650,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
rsp_send_error(hreq->req, "Error fetching query results");
rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@ -671,7 +664,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
db_query_end(&qp);
rsp_send_reply(hreq->req, reply);
rsp_send_reply(hreq, reply);
return 0;
}
@ -690,34 +683,34 @@ rsp_reply_browse(struct httpd_request *hreq)
memset(&qp, 0, sizeof(struct query_params));
if (strcmp(hreq->uri_parsed->path_parts[3], "artist") == 0)
if (strcmp(hreq->path_parts[3], "artist") == 0)
{
qp.type = Q_BROWSE_ARTISTS;
}
else if (strcmp(hreq->uri_parsed->path_parts[3], "genre") == 0)
else if (strcmp(hreq->path_parts[3], "genre") == 0)
{
qp.type = Q_BROWSE_GENRES;
}
else if (strcmp(hreq->uri_parsed->path_parts[3], "album") == 0)
else if (strcmp(hreq->path_parts[3], "album") == 0)
{
qp.type = Q_BROWSE_ALBUMS;
}
else if (strcmp(hreq->uri_parsed->path_parts[3], "composer") == 0)
else if (strcmp(hreq->path_parts[3], "composer") == 0)
{
qp.type = Q_BROWSE_COMPOSERS;
}
else
{
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->uri_parsed->path_parts[3]);
DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->path_parts[3]);
rsp_send_error(hreq->req, "Unsupported browse type");
rsp_send_error(hreq, "Unsupported browse type");
return -1;
}
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &qp.id);
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0)
{
rsp_send_error(hreq->req, "Invalid playlist ID");
rsp_send_error(hreq, "Invalid playlist ID");
return -1;
}
@ -730,7 +723,7 @@ rsp_reply_browse(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Could not start query\n");
rsp_send_error(hreq->req, "Could not start query");
rsp_send_error(hreq, "Could not start query");
if (qp.filter)
free(qp.filter);
@ -783,7 +776,7 @@ rsp_reply_browse(struct httpd_request *hreq)
mxmlDelete(reply);
db_query_end(&qp);
rsp_send_error(hreq->req, "Error fetching query results");
rsp_send_error(hreq, "Error fetching query results");
return -1;
}
@ -797,7 +790,7 @@ rsp_reply_browse(struct httpd_request *hreq)
db_query_end(&qp);
rsp_send_reply(hreq->req, reply);
rsp_send_reply(hreq, reply);
return 0;
}
@ -808,14 +801,14 @@ rsp_stream(struct httpd_request *hreq)
int id;
int ret;
ret = safe_atoi32(hreq->uri_parsed->path_parts[2], &id);
ret = safe_atoi32(hreq->path_parts[2], &id);
if (ret < 0)
{
httpd_send_error(hreq->req, HTTP_BADREQUEST, "Bad Request");
httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
return -1;
}
httpd_stream_file(hreq->req, id);
httpd_stream_file(hreq, id);
return 0;
}
@ -867,20 +860,18 @@ rsp_request(struct httpd_request *hreq)
{
int ret;
DPRINTF(E_DBG, L_RSP, "RSP request: '%s'\n", hreq->uri);
if (!hreq->handler)
{
DPRINTF(E_LOG, L_RSP, "Unrecognized path in RSP request: '%s'\n", hreq->uri);
rsp_send_error(hreq->req, "Server error");
rsp_send_error(hreq, "Server error");
return;
}
ret = rsp_request_authorize(hreq);
if (ret < 0)
{
rsp_send_error(hreq->req, "Access denied");
rsp_send_error(hreq, "Access denied");
free(hreq);
return;
}
@ -889,7 +880,7 @@ rsp_request(struct httpd_request *hreq)
}
static int
rsp_init(void)
rsp_init(struct event_base *evbase)
{
snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE);
@ -900,6 +891,7 @@ struct httpd_module httpd_rsp =
{
.name = "RSP",
.type = MODULE_RSP,
.logdomain = L_RSP,
.subpaths = { "/rsp/", NULL },
.handlers = rsp_handlers,
.init = rsp_init,

View File

@ -41,9 +41,6 @@
#include "listener.h"
#include "db.h"
/* httpd event base, from httpd.c */
extern struct event_base *evbase_httpd;
// Seconds between sending silence when player is idle
// (to prevent client from hanging up)
#define STREAMING_SILENCE_INTERVAL 1
@ -58,7 +55,7 @@ extern struct event_base *evbase_httpd;
// Linked list of mp3 streaming requests
struct streaming_session {
struct evhttp_request *req;
struct httpd_request *hreq;
struct streaming_session *next;
bool require_icy; // Client requested icy meta
@ -102,18 +99,15 @@ static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX];
static void
streaming_close_cb(struct evhttp_connection *evcon, void *arg)
streaming_close_cb(httpd_connection *conn, void *arg)
{
struct streaming_session *this;
struct streaming_session *session;
struct streaming_session *prev;
const char *address;
ev_uint16_t port;
this = (struct streaming_session *)arg;
httpd_peer_get(&address, &port, evcon);
DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", address, (int)port);
DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", this->hreq->peer_address, (int)this->hreq->peer_port);
pthread_mutex_lock(&streaming_sessions_lck);
if (!streaming_sessions)
@ -127,7 +121,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg)
prev = NULL;
for (session = streaming_sessions; session; session = session->next)
{
if (session->req == this->req)
if (session->hreq == this->hreq)
break;
prev = session;
@ -135,7 +129,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg)
if (!session)
{
DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", address, (int)port);
DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream (%s:%d)\n", this->hreq->peer_address, (int)this->hreq->peer_port);
free(this);
pthread_mutex_unlock(&streaming_sessions_lck);
return;
@ -151,7 +145,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg)
// Valgrind says libevent doesn't free the request on disconnect (even though it owns it - libevent bug?),
// so we do it with a reply end
evhttp_send_reply_end(session->req);
httpd_send_reply_end(session->hreq);
free(session);
if (!streaming_sessions)
@ -168,13 +162,17 @@ static void
streaming_end(void)
{
struct streaming_session *session;
<<<<<<< HEAD
struct evhttp_connection *evcon;
const char *address;
ev_uint16_t port;
=======
>>>>>>> [httpd] Remove all traces of evhttp from httpd modules
pthread_mutex_lock(&streaming_sessions_lck);
for (session = streaming_sessions; streaming_sessions; session = streaming_sessions)
{
<<<<<<< HEAD
evcon = evhttp_request_get_connection(session->req);
if (evcon)
{
@ -183,6 +181,16 @@ streaming_end(void)
DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", address, (int)port);
}
evhttp_send_reply_end(session->req);
=======
DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", session->hreq->peer_address, (int)session->hreq->peer_port);
httpd_request_closecb_set(session->hreq, NULL, NULL);
<<<<<<< HEAD
httpd_reply_end_send(session->hreq);
>>>>>>> [httpd] Remove all traces of evhttp from httpd modules
=======
httpd_send_reply_end(session->hreq);
>>>>>>> [httpd] Changes to httpd_send_reply_chunk et al
streaming_sessions = session->next;
free(session);
@ -450,7 +458,7 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
free(splice_buf);
splice_buf = NULL;
evhttp_send_reply_chunk(session->req, evbuf);
httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL);
if (session->next == NULL)
{
@ -465,11 +473,11 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
{
buf = evbuffer_pullup(streaming_encoded_data, -1);
evbuffer_add(evbuf, buf, len);
evhttp_send_reply_chunk(session->req, evbuf);
httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL);
}
else
{
evhttp_send_reply_chunk(session->req, streaming_encoded_data);
httpd_send_reply_chunk(session->hreq, streaming_encoded_data, NULL, NULL);
}
session->bytes_sent += len;
}
@ -547,12 +555,8 @@ static void
streaming_request(struct httpd_request *hreq)
{
struct streaming_session *session;
struct evhttp_connection *evcon;
struct evkeyvalq *output_headers;
cfg_t *lib;
const char *name;
const char *address;
ev_uint16_t port;
const char *param;
bool require_icy = false;
char buf[9];
@ -561,45 +565,42 @@ streaming_request(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_STREAMING, "Got MP3 streaming request, but cannot encode to MP3\n");
evhttp_send_error(hreq->req, HTTP_NOTFOUND, "Not Found");
httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found");
return;
}
evcon = evhttp_request_get_connection(hreq->req);
evhttp_connection_get_peer(evcon, &address, &port);
param = evhttp_find_header( evhttp_request_get_input_headers(hreq->req), "Icy-MetaData");
param = httpd_header_find(hreq->in_headers, "Icy-MetaData");
if (param && strcmp(param, "1") == 0)
require_icy = true;
DPRINTF(E_INFO, L_STREAMING, "Beginning mp3 streaming (with icy=%d, icy_metaint=%d) to %s:%d\n", require_icy, streaming_icy_metaint, address, (int)port);
DPRINTF(E_INFO, L_STREAMING, "Beginning mp3 streaming (with icy=%d, icy_metaint=%d) to %s:%d\n", require_icy, streaming_icy_metaint, hreq->peer_address, (int)hreq->peer_port);
lib = cfg_getsec(cfg, "library");
name = cfg_getstr(lib, "name");
output_headers = evhttp_request_get_output_headers(hreq->req);
evhttp_add_header(output_headers, "Content-Type", "audio/mpeg");
evhttp_add_header(output_headers, "Server", PACKAGE_NAME "/" VERSION);
evhttp_add_header(output_headers, "Cache-Control", "no-cache");
evhttp_add_header(output_headers, "Pragma", "no-cache");
evhttp_add_header(output_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
httpd_header_add(hreq->out_headers, "Content-Type", "audio/mpeg");
httpd_header_add(hreq->out_headers, "Server", PACKAGE_NAME "/" VERSION);
httpd_header_add(hreq->out_headers, "Cache-Control", "no-cache");
httpd_header_add(hreq->out_headers, "Pragma", "no-cache");
httpd_header_add(hreq->out_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
if (require_icy)
{
++streaming_icy_clients;
evhttp_add_header(output_headers, "icy-name", name);
httpd_header_add(hreq->out_headers, "icy-name", name);
snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint);
evhttp_add_header(output_headers, "icy-metaint", buf);
httpd_header_add(hreq->out_headers, "icy-metaint", buf);
}
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", "*");
evhttp_add_header(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", "*");
httpd_header_add(hreq->out_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
evhttp_send_reply_start(hreq->req, HTTP_OK, "OK");
httpd_send_reply_start(hreq, HTTP_OK, "OK");
session = calloc(1, sizeof(struct streaming_session));
if (!session)
{
DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n");
evhttp_send_error(hreq->req, HTTP_SERVUNAVAIL, "Internal Server Error");
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
@ -611,7 +612,7 @@ streaming_request(struct httpd_request *hreq)
event_add(metaev, NULL);
}
session->req = hreq->req;
session->hreq = hreq;
session->next = streaming_sessions;
session->require_icy = require_icy;
session->bytes_sent = 0;
@ -619,11 +620,11 @@ streaming_request(struct httpd_request *hreq)
pthread_mutex_unlock(&streaming_sessions_lck);
evhttp_connection_set_closecb(evcon, streaming_close_cb, session);
httpd_request_closecb_set(hreq, streaming_close_cb, session);
}
static int
streaming_init(void)
streaming_init(struct event_base *evbase)
{
int ret;
cfg_t *cfgsec;
@ -713,8 +714,8 @@ streaming_init(void)
// Initialize buffer for encoded mp3 audio and event for pipe reading
CHECK_NULL(L_STREAMING, streaming_encoded_data = evbuffer_new());
CHECK_NULL(L_STREAMING, streamingev = event_new(evbase_httpd, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL));
CHECK_NULL(L_STREAMING, metaev = event_new(evbase_httpd, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL));
CHECK_NULL(L_STREAMING, streamingev = event_new(evbase, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL));
CHECK_NULL(L_STREAMING, metaev = event_new(evbase, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL));
streaming_icy_clients = 0;
@ -755,6 +756,7 @@ struct httpd_module httpd_streaming =
{
.name = "Streaming",
.type = MODULE_STREAMING,
.logdomain = L_STREAMING,
.fullpaths = { "/stream.mp3", NULL },
.handlers = streaming_handlers,
.init = streaming_init,

View File

@ -52,6 +52,8 @@
#include <arpa/inet.h>
#include <ifaddrs.h> // getifaddrs
#include <event2/http.h> // evhttp_bind
#include <unistr.h>
#include <uniconv.h>

View File

@ -14,7 +14,6 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <event2/http.h>
#ifndef SOCK_NONBLOCK
#include <fcntl.h>
@ -54,6 +53,9 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
int
net_bind(short unsigned *port, int type, const char *log_service_name);
// To avoid polluting namespace too much we don't include event2/http.h here
struct evhttp;
int
net_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name);