mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 07:05:57 -05:00
[httpd] Make http modules agnostic to evhttp
This commit is contained in:
parent
4ae73fa9b4
commit
74f1b93b42
@ -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])
|
||||
|
@ -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 \
|
||||
|
776
src/httpd.c
776
src/httpd.c
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
|
402
src/httpd_daap.c
402
src/httpd_daap.c
File diff suppressed because it is too large
Load Diff
444
src/httpd_dacp.c
444
src/httpd_dacp.c
File diff suppressed because it is too large
Load Diff
@ -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
463
src/httpd_libevhtp.c
Normal 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
350
src/httpd_libevhttp.c
Normal 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));
|
||||
}
|
@ -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,
|
||||
|
100
src/httpd_rsp.c
100
src/httpd_rsp.c
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -52,6 +52,8 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h> // getifaddrs
|
||||
|
||||
#include <event2/http.h> // evhttp_bind
|
||||
|
||||
#include <unistr.h>
|
||||
#include <uniconv.h>
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user