From 8bdf4c778a8a27de51d762254c6bccc92ec3c4d5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 27 Dec 2022 11:54:24 +0100 Subject: [PATCH] [httpd] Start adding libevhtp backend --- configure.ac | 5 + src/Makefile.am | 8 +- src/httpd.c | 9 +- src/httpd_daap.c | 7 +- src/httpd_dacp.c | 7 +- src/httpd_internal.h | 49 ++++-- src/httpd_libevhtp.c | 372 ++++++++++++++++++++++++++++++++++++++++++ src/httpd_libevhttp.c | 17 +- 8 files changed, 434 insertions(+), 40 deletions(-) create mode 100644 src/httpd_libevhtp.c diff --git a/configure.ac b/configure.ac index 45573da6..e6bef8dd 100644 --- a/configure.ac +++ b/configure.ac @@ -258,6 +258,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]) diff --git a/src/Makefile.am b/src/Makefile.am index 07ade9a3..d468d831 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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,8 +98,8 @@ 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_libevhttp.c \ httpd_rsp.c \ httpd_daap.c httpd_daap.h \ httpd_dacp.c \ diff --git a/src/httpd.c b/src/httpd.c index e8bd4250..ec4ca9d8 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -41,6 +41,7 @@ #endif #include +#include #include #include "logger.h" @@ -295,11 +296,13 @@ request_unset(struct httpd_request *hreq) evbuffer_free(hreq->out_body); httpd_uri_parsed_free(hreq->uri_parsed); + httpd_backend_data_free(hreq->backend_data); } static void request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, const char *user_agent) { + httpd_backend_data *backend_data; struct httpd_uri_map *map; int ret; @@ -309,12 +312,14 @@ request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, hreq->backend = backend; if (backend) { - hreq->uri = httpd_backend_uri_get(backend); + backend_data = httpd_backend_data_create(backend); + hreq->backend_data = backend_data; + hreq->uri = httpd_backend_uri_get(backend, backend_data); hreq->in_headers = httpd_backend_input_headers_get(backend); hreq->out_headers = httpd_backend_output_headers_get(backend); hreq->in_body = httpd_backend_input_buffer_get(backend); httpd_backend_method_get(&hreq->method, backend); - httpd_backend_peer_get(&hreq->peer_address, &hreq->peer_port, backend); + httpd_backend_peer_get(&hreq->peer_address, &hreq->peer_port, backend, backend_data); hreq->user_agent = httpd_header_find(hreq->in_headers, "User-Agent"); } diff --git a/src/httpd_daap.c b/src/httpd_daap.c index 48c2bcd2..32a3d511 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -2372,12 +2372,9 @@ daap_deinit(void) { update_requests = ur->next; + httpd_request_closecb_set(ur->hreq, NULL, NULL); conn = httpd_request_connection_get(ur->hreq); - if (conn) - { - httpd_connection_closecb_set(conn, NULL, NULL); - httpd_connection_free(conn); - } + httpd_connection_free(conn); // TODO necessary? update_free(ur); } diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 4b6b02fc..f3e58e4c 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -2938,12 +2938,9 @@ dacp_deinit(void) { update_requests = ur->next; + httpd_request_closecb_set(ur->hreq, NULL, NULL); conn = httpd_request_connection_get(ur->hreq); - if (conn) - { - httpd_connection_closecb_set(conn, NULL, NULL); - httpd_connection_free(conn); - } + httpd_connection_free(conn); // TODO necessary? free(ur); } diff --git a/src/httpd_internal.h b/src/httpd_internal.h index b567e3cb..87b3d535 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -12,9 +12,27 @@ struct httpd_request; -#include -#include -#include // evhtp conflicts with regex since it brings it own +#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; @@ -22,6 +40,8 @@ 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); @@ -104,13 +124,14 @@ struct httpd_request { 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; - // A pointer to extra data that the module handling the request might need - void *extra_data; // The original, request URI. The URI may have been complete: // scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] @@ -141,6 +162,8 @@ struct httpd_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; }; @@ -239,12 +262,6 @@ httpd_header_add(httpd_headers *headers, const char *key, const char *val); void httpd_headers_clear(httpd_headers *headers); -int -httpd_connection_closecb_set(httpd_connection *conn, httpd_connection_closecb cb, void *arg); - -int -httpd_connection_peer_get(const char **addr, uint16_t *port, httpd_connection *conn); - void httpd_connection_free(httpd_connection *conn); @@ -284,11 +301,17 @@ 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_uri_get(httpd_backend *backend, httpd_backend_data *backend_data); httpd_headers * httpd_backend_input_headers_get(httpd_backend *backend); @@ -300,7 +323,7 @@ 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_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); diff --git a/src/httpd_libevhtp.c b/src/httpd_libevhtp.c new file mode 100644 index 00000000..9fb243a0 --- /dev/null +++ b/src/httpd_libevhtp.c @@ -0,0 +1,372 @@ +#include +#include + +#include "misc.h" +#include "httpd_internal.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; + 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_kvs_free(query); +} + +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_headers_add_header(headers, evhtp_header_new(key, val, 1, 1)); // Copy key/val +} + +void +httpd_headers_clear(httpd_headers *headers) +{ + evhtp_headers_free(headers); +} + +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); +} + +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, "httpd"); + if (fd < 0) + goto error; + + if (evhtp_accept_socket(server, fd, 0) != 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) +{ + evhtp_send_reply_start(backend, code); + evhtp_send_reply_body(backend, evbuf); + evhtp_send_reply_end(backend); +} + +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) +{ +} + +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); + backend_data->uri = safe_asprintf("%s?%s", uri->path->full, (char *)uri->query_raw); + + 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; + + *addr = NULL; + *port = 0; + + conn = evhtp_request_get_connection(backend); + if (!conn) + return -1; + + naddr.sa = *conn->saddr; + 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) +{ +} + +httpd_uri_parsed * +httpd_uri_parsed_create(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->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)); +} diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index ad9972d0..44e705eb 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -65,23 +65,12 @@ httpd_headers_clear(httpd_headers *headers) evhttp_clear_headers(headers); } -int -httpd_connection_closecb_set(httpd_connection *conn, httpd_connection_closecb cb, void *arg) -{ - evhttp_connection_set_closecb(conn, cb, arg); - return 0; -} - -int -httpd_connection_peer_get(const char **addr, uint16_t *port, httpd_connection *conn) -{ - evhttp_connection_get_peer(conn, (char **)addr, port); - return 0; -} - void httpd_connection_free(httpd_connection *conn) { + if (!conn) + return; + evhttp_connection_free(conn); }