[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], [libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
[evthread_use_pthreads], [event2/thread.h]) [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) dnl Build with Avahi (or Bonjour if not)
OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [Avahi mDNS], [avahi], [AVAHI], OWNTONE_ARG_WITH_CHECK([OWNTONE_OPTS], [Avahi mDNS], [avahi], [AVAHI],
[avahi-client >= 0.6.24], [avahi_client_new], [avahi-client/client.h]) [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 LIBWEBSOCKETS_SRC=websocket.c websocket.h
endif endif
if COND_LIBEVHTP
HTTPDBACKEND_SRC=httpd_libevhtp.c
else
HTTPDBACKEND_SRC=httpd_libevhttp.c
endif
GPERF_FILES = \ GPERF_FILES = \
daap_query.gperf \ daap_query.gperf \
dacp_prop.gperf \ dacp_prop.gperf \
@ -92,6 +98,7 @@ owntone_SOURCES = main.c \
library.c library.h \ library.c library.h \
$(MDNS_SRC) mdns.h \ $(MDNS_SRC) mdns.h \
remote_pairing.c remote_pairing.h \ remote_pairing.c remote_pairing.h \
$(HTTPDBACKEND_SRC) \
httpd.c httpd.h httpd_internal.h \ httpd.c httpd.h httpd_internal.h \
httpd_rsp.c \ httpd_rsp.c \
httpd_daap.c httpd_daap.h \ 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> # include <config.h>
#endif #endif
#include <regex.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <string.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_w = 0;
*max_h = 0; *max_h = 0;
param = evhttp_find_header(hreq->query, "maxwidth"); param = httpd_query_value_find(hreq->query, "maxwidth");
if (param) if (param)
{ {
ret = safe_atou32(param, max_w); ret = safe_atou32(param, max_w);
if (ret < 0) 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) if (param)
{ {
ret = safe_atou32(param, max_h); ret = safe_atou32(param, max_h);
if (ret < 0) 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; return 0;
@ -62,14 +61,10 @@ request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h)
static int static int
response_process(struct httpd_request *hreq, int format) response_process(struct httpd_request *hreq, int format)
{ {
struct evkeyvalq *headers;
headers = evhttp_request_get_output_headers(hreq->req);
if (format == ART_FMT_PNG) 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) 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 else
return HTTP_NOCONTENT; return HTTP_NOCONTENT;
@ -92,7 +87,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq)
if (ret != 0) if (ret != 0)
return HTTP_NOTFOUND; 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); return response_process(hreq, ret);
} }
@ -109,11 +104,11 @@ artworkapi_reply_item(struct httpd_request *hreq)
if (ret != 0) if (ret != 0)
return ret; return ret;
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id); ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0) if (ret != 0)
return HTTP_BADREQUEST; 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); return response_process(hreq, ret);
} }
@ -130,20 +125,20 @@ artworkapi_reply_group(struct httpd_request *hreq)
if (ret != 0) if (ret != 0)
return ret; return ret;
ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id); ret = safe_atou32(hreq->path_parts[2], &id);
if (ret != 0) if (ret != 0)
return HTTP_BADREQUEST; 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); return response_process(hreq, ret);
} }
static struct httpd_uri_map artworkapi_handlers[] = static struct httpd_uri_map artworkapi_handlers[] =
{ {
{ EVHTTP_REQ_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying }, { HTTPD_METHOD_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying },
{ EVHTTP_REQ_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item }, { HTTPD_METHOD_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item },
{ EVHTTP_REQ_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group }, { HTTPD_METHOD_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group },
{ 0, NULL, NULL } { 0, NULL, NULL }
}; };
@ -155,52 +150,47 @@ artworkapi_request(struct httpd_request *hreq)
{ {
int status_code; int status_code;
DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", hreq->uri); if (!httpd_admin_check_auth(hreq))
if (!httpd_admin_check_auth(hreq->req))
return; return;
if (!hreq->handler) if (!hreq->handler)
{ {
DPRINTF(E_LOG, L_WEB, "Unrecognized path in artwork api request: '%s'\n", hreq->uri); 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; return;
} }
CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
status_code = hreq->handler(hreq); status_code = hreq->handler(hreq);
switch (status_code) switch (status_code)
{ {
case HTTP_OK: /* 200 OK */ 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; break;
case HTTP_NOCONTENT: /* 204 No Content */ 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; break;
case HTTP_NOTMODIFIED: /* 304 Not Modified */ 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; break;
case HTTP_BADREQUEST: /* 400 Bad Request */ case HTTP_BADREQUEST: /* 400 Bad Request */
httpd_send_error(hreq->req, status_code, "Bad Request"); httpd_send_error(hreq, status_code, "Bad Request");
break; break;
case HTTP_NOTFOUND: /* 404 Not Found */ case HTTP_NOTFOUND: /* 404 Not Found */
httpd_send_error(hreq->req, status_code, "Not Found"); httpd_send_error(hreq, status_code, "Not Found");
break; break;
case HTTP_INTERNAL: /* 500 Internal Server Error */ case HTTP_INTERNAL: /* 500 Internal Server Error */
default: 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 = struct httpd_module httpd_artworkapi =
{ {
.name = "Artwork API", .name = "Artwork API",
.type = MODULE_ARTWORKAPI, .type = MODULE_ARTWORKAPI,
.logdomain = L_WEB,
.subpaths = { "/artwork/", NULL }, .subpaths = { "/artwork/", NULL },
.handlers = artworkapi_handlers, .handlers = artworkapi_handlers,
.request = artworkapi_request, .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 <stdbool.h>
#include <time.h> #include <time.h>
#include <event2/http.h> #include <event2/event.h>
#include <event2/keyvalq_struct.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 enum httpd_send_flags
{ {
HTTPD_SEND_NO_GZIP = (1 << 0), 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 ---------------------------------*/ /*---------------------------------- MODULES ---------------------------------*/
@ -85,6 +115,7 @@ struct httpd_module
const char *name; const char *name;
enum httpd_modules type; enum httpd_modules type;
char initialized; char initialized;
int logdomain;
// Null-terminated list of URL subpath that the module accepts e.g., /subpath/morepath/file.mp3 // Null-terminated list of URL subpath that the module accepts e.g., /subpath/morepath/file.mp3
const char *subpaths[16]; const char *subpaths[16];
@ -93,9 +124,9 @@ struct httpd_module
// Pointer to the module's handler definitions // Pointer to the module's handler definitions
struct httpd_uri_map *handlers; struct httpd_uri_map *handlers;
int (*init)(void); int (*init)(struct event_base *);
void (*deinit)(void); 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 struct httpd_uri_map
{ {
int method; enum httpd_methods method;
char *regexp; char *regexp;
int (*handler)(struct httpd_request *hreq); int (*handler)(struct httpd_request *hreq);
void *preg; void *preg;
}; };
/* /*------------------------------- HTTPD STRUCTS ------------------------------*/
* Helper to free the parsed uri struct
*/
void
httpd_uri_free(struct httpd_uri_parsed *parsed);
/* /*
* 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 * struct httpd_request {
httpd_uri_parse(const char *uri); // 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 void
httpd_stream_file(struct evhttp_request *req, int id); httpd_stream_file(struct httpd_request *hreq, int id);
/* void
* Parse a request into the httpd_request struct. Nothing is copied, so the httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent);
* 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 void
* request headers, except if provided as an argument to this function. httpd_request_unset(struct httpd_request *hreq);
*/
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);
bool 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 bool
httpd_request_etag_matches(struct evhttp_request *req, const char *etag); httpd_request_etag_matches(struct httpd_request *hreq, const char *etag);
void 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 * 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 * may direct it not to. It will set CORS headers as appropriate. Should be
* thread safe. * thread safe.
* *
* @in req The evhttp request struct * @in req The http request struct
* @in code HTTP code, e.g. 200 * @in code HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning * @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used of the error code will be used
@ -157,7 +230,16 @@ httpd_response_not_cachable(struct evhttp_request *req);
* @in flags See flags above * @in flags See flags above
*/ */
void 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 * 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. * which is not possible with evhttp_send_error, because it clears the headers.
* Should be thread safe. * Should be thread safe.
* *
* @in req The evhttp request struct * @in req The http request struct
* @in error HTTP code, e.g. 200 * @in error HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning * @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used of the error code will be used
*/ */
void 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 * Redirects to the given path
*/ */
void void
httpd_redirect_to(struct evhttp_request *req, const char *path); httpd_redirect_to(struct httpd_request *hreq, const char *path);
bool bool
httpd_admin_check_auth(struct evhttp_request *req); httpd_admin_check_auth(struct httpd_request *hreq);
int 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__ */ #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); ret = spotifywebapi_oauth_callback(hreq->query, redirect_uri, &errmsg);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri_parsed->uri, errmsg); DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri, errmsg);
httpd_send_error(hreq->req, HTTP_INTERNAL, errmsg); httpd_send_error(hreq, HTTP_INTERNAL, errmsg);
return -1; return -1;
} }
httpd_redirect_to(hreq->req, "/#/settings/online-services"); httpd_redirect_to(hreq, "/#/settings/online-services");
return 0; 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"); 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; return -1;
} }
@ -93,13 +93,11 @@ static struct httpd_uri_map oauth_handlers[] =
static void static void
oauth_request(struct httpd_request *hreq) oauth_request(struct httpd_request *hreq)
{ {
DPRINTF(E_LOG, L_WEB, "OAuth request: '%s'\n", hreq->uri);
if (!hreq->handler) if (!hreq->handler)
{ {
DPRINTF(E_LOG, L_WEB, "Unrecognized path in OAuth request: '%s'\n", hreq->uri); 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; return;
} }
@ -110,6 +108,7 @@ struct httpd_module httpd_oauth =
{ {
.name = "OAuth", .name = "OAuth",
.type = MODULE_OAUTH, .type = MODULE_OAUTH,
.logdomain = L_WEB,
.subpaths = { "/oauth/", NULL }, .subpaths = { "/oauth/", NULL },
.fullpaths = { "/oauth", NULL }, .fullpaths = { "/oauth", NULL },
.handlers = oauth_handlers, .handlers = oauth_handlers,

View File

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

View File

@ -41,9 +41,6 @@
#include "listener.h" #include "listener.h"
#include "db.h" #include "db.h"
/* httpd event base, from httpd.c */
extern struct event_base *evbase_httpd;
// Seconds between sending silence when player is idle // Seconds between sending silence when player is idle
// (to prevent client from hanging up) // (to prevent client from hanging up)
#define STREAMING_SILENCE_INTERVAL 1 #define STREAMING_SILENCE_INTERVAL 1
@ -58,7 +55,7 @@ extern struct event_base *evbase_httpd;
// Linked list of mp3 streaming requests // Linked list of mp3 streaming requests
struct streaming_session { struct streaming_session {
struct evhttp_request *req; struct httpd_request *hreq;
struct streaming_session *next; struct streaming_session *next;
bool require_icy; // Client requested icy meta bool require_icy; // Client requested icy meta
@ -102,18 +99,15 @@ static char streaming_icy_title[STREAMING_ICY_METATITLELEN_MAX];
static void 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 *this;
struct streaming_session *session; struct streaming_session *session;
struct streaming_session *prev; struct streaming_session *prev;
const char *address;
ev_uint16_t port;
this = (struct streaming_session *)arg; this = (struct streaming_session *)arg;
httpd_peer_get(&address, &port, evcon); DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", this->hreq->peer_address, (int)this->hreq->peer_port);
DPRINTF(E_INFO, L_STREAMING, "Stopping mp3 streaming to %s:%d\n", address, (int)port);
pthread_mutex_lock(&streaming_sessions_lck); pthread_mutex_lock(&streaming_sessions_lck);
if (!streaming_sessions) if (!streaming_sessions)
@ -127,7 +121,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg)
prev = NULL; prev = NULL;
for (session = streaming_sessions; session; session = session->next) for (session = streaming_sessions; session; session = session->next)
{ {
if (session->req == this->req) if (session->hreq == this->hreq)
break; break;
prev = session; prev = session;
@ -135,7 +129,7 @@ streaming_close_cb(struct evhttp_connection *evcon, void *arg)
if (!session) 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); free(this);
pthread_mutex_unlock(&streaming_sessions_lck); pthread_mutex_unlock(&streaming_sessions_lck);
return; 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?), // 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 // so we do it with a reply end
evhttp_send_reply_end(session->req); httpd_send_reply_end(session->hreq);
free(session); free(session);
if (!streaming_sessions) if (!streaming_sessions)
@ -168,13 +162,17 @@ static void
streaming_end(void) streaming_end(void)
{ {
struct streaming_session *session; struct streaming_session *session;
<<<<<<< HEAD
struct evhttp_connection *evcon; struct evhttp_connection *evcon;
const char *address; const char *address;
ev_uint16_t port; ev_uint16_t port;
=======
>>>>>>> [httpd] Remove all traces of evhttp from httpd modules
pthread_mutex_lock(&streaming_sessions_lck); pthread_mutex_lock(&streaming_sessions_lck);
for (session = streaming_sessions; streaming_sessions; session = streaming_sessions) for (session = streaming_sessions; streaming_sessions; session = streaming_sessions)
{ {
<<<<<<< HEAD
evcon = evhttp_request_get_connection(session->req); evcon = evhttp_request_get_connection(session->req);
if (evcon) if (evcon)
{ {
@ -183,6 +181,16 @@ streaming_end(void)
DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", address, (int)port); DPRINTF(E_INFO, L_STREAMING, "Force close stream to %s:%d\n", address, (int)port);
} }
evhttp_send_reply_end(session->req); 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; streaming_sessions = session->next;
free(session); free(session);
@ -450,7 +458,7 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
free(splice_buf); free(splice_buf);
splice_buf = NULL; splice_buf = NULL;
evhttp_send_reply_chunk(session->req, evbuf); httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL);
if (session->next == 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); buf = evbuffer_pullup(streaming_encoded_data, -1);
evbuffer_add(evbuf, buf, len); evbuffer_add(evbuf, buf, len);
evhttp_send_reply_chunk(session->req, evbuf); httpd_send_reply_chunk(session->hreq, evbuf, NULL, NULL);
} }
else 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; session->bytes_sent += len;
} }
@ -547,12 +555,8 @@ static void
streaming_request(struct httpd_request *hreq) streaming_request(struct httpd_request *hreq)
{ {
struct streaming_session *session; struct streaming_session *session;
struct evhttp_connection *evcon;
struct evkeyvalq *output_headers;
cfg_t *lib; cfg_t *lib;
const char *name; const char *name;
const char *address;
ev_uint16_t port;
const char *param; const char *param;
bool require_icy = false; bool require_icy = false;
char buf[9]; 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"); 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; return;
} }
evcon = evhttp_request_get_connection(hreq->req); param = httpd_header_find(hreq->in_headers, "Icy-MetaData");
evhttp_connection_get_peer(evcon, &address, &port);
param = evhttp_find_header( evhttp_request_get_input_headers(hreq->req), "Icy-MetaData");
if (param && strcmp(param, "1") == 0) if (param && strcmp(param, "1") == 0)
require_icy = true; 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"); lib = cfg_getsec(cfg, "library");
name = cfg_getstr(lib, "name"); name = cfg_getstr(lib, "name");
output_headers = evhttp_request_get_output_headers(hreq->req); httpd_header_add(hreq->out_headers, "Content-Type", "audio/mpeg");
evhttp_add_header(output_headers, "Content-Type", "audio/mpeg"); httpd_header_add(hreq->out_headers, "Server", PACKAGE_NAME "/" VERSION);
evhttp_add_header(output_headers, "Server", PACKAGE_NAME "/" VERSION); httpd_header_add(hreq->out_headers, "Cache-Control", "no-cache");
evhttp_add_header(output_headers, "Cache-Control", "no-cache"); httpd_header_add(hreq->out_headers, "Pragma", "no-cache");
evhttp_add_header(output_headers, "Pragma", "no-cache"); httpd_header_add(hreq->out_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
evhttp_add_header(output_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
if (require_icy) if (require_icy)
{ {
++streaming_icy_clients; ++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); 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", "*"); httpd_header_add(hreq->out_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-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)); session = calloc(1, sizeof(struct streaming_session));
if (!session) if (!session)
{ {
DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n"); 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; return;
} }
@ -611,7 +612,7 @@ streaming_request(struct httpd_request *hreq)
event_add(metaev, NULL); event_add(metaev, NULL);
} }
session->req = hreq->req; session->hreq = hreq;
session->next = streaming_sessions; session->next = streaming_sessions;
session->require_icy = require_icy; session->require_icy = require_icy;
session->bytes_sent = 0; session->bytes_sent = 0;
@ -619,11 +620,11 @@ streaming_request(struct httpd_request *hreq)
pthread_mutex_unlock(&streaming_sessions_lck); 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 static int
streaming_init(void) streaming_init(struct event_base *evbase)
{ {
int ret; int ret;
cfg_t *cfgsec; cfg_t *cfgsec;
@ -713,8 +714,8 @@ streaming_init(void)
// Initialize buffer for encoded mp3 audio and event for pipe reading // Initialize buffer for encoded mp3 audio and event for pipe reading
CHECK_NULL(L_STREAMING, streaming_encoded_data = evbuffer_new()); 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, 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_httpd, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_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; streaming_icy_clients = 0;
@ -755,6 +756,7 @@ struct httpd_module httpd_streaming =
{ {
.name = "Streaming", .name = "Streaming",
.type = MODULE_STREAMING, .type = MODULE_STREAMING,
.logdomain = L_STREAMING,
.fullpaths = { "/stream.mp3", NULL }, .fullpaths = { "/stream.mp3", NULL },
.handlers = streaming_handlers, .handlers = streaming_handlers,
.init = streaming_init, .init = streaming_init,

View File

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

View File

@ -14,7 +14,6 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <event2/http.h>
#ifndef SOCK_NONBLOCK #ifndef SOCK_NONBLOCK
#include <fcntl.h> #include <fcntl.h>
@ -54,6 +53,9 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
int int
net_bind(short unsigned *port, int type, const char *log_service_name); 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 int
net_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name); net_evhttp_bind(struct evhttp *evhttp, unsigned short port, const char *log_service_name);