diff --git a/src/httpd.c b/src/httpd.c index 738c39e2..1489cb17 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -41,7 +41,6 @@ #endif #include #include -#include #include #include @@ -137,10 +136,10 @@ static int exit_pipe[2]; #endif static int httpd_exit; static struct event *exitev; -static struct evhttp *evhttpd; +static httpd_server *httpd_serv; static pthread_t tid_httpd; -static const char *allow_origin; +static const char *httpd_allow_origin; static int httpd_port; @@ -291,61 +290,76 @@ modules_search(const char *path) /* --------------------------- REQUEST HELPERS ------------------------------ */ -static int -request_set(struct httpd_request *hreq, struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map) +static void +request_unset(struct httpd_request *hreq) { - struct httpd_uri_map *uri; - int req_method; + httpd_uri_free(hreq->uri_parsed); +} + +static void +request_set(struct httpd_request *hreq, httpd_backend *backend, const char *uri, const char *user_agent) +{ + struct httpd_uri_map *map; + struct httpd_uri_parsed *uri_parsed; + struct httpd_module *module; int ret; memset(hreq, 0, sizeof(struct httpd_request)); - // Note req is allowed to be NULL - hreq->backend = req; - hreq->uri = uri_parsed->uri; - hreq->uri_parsed = uri_parsed; - hreq->query = &(uri_parsed->query); - req_method = 0; - - if (req) + // Populate hreq by getting values from the backend (or from the caller) + hreq->backend = backend; + if (backend) { - hreq->in_body = evhttp_request_get_input_buffer(req); - hreq->in_headers = evhttp_request_get_input_headers(req); - hreq->user_agent = evhttp_find_header(hreq->in_headers, "User-Agent"); - httpd_request_peer_get(&hreq->peer_address, &hreq->peer_port, hreq); + hreq->uri = httpd_backend_uri_get(backend); + hreq->in_body = httpd_backend_input_buffer_get(backend); + hreq->in_headers = httpd_backend_input_headers_get(backend); + hreq->out_headers = httpd_backend_output_headers_get(backend); + httpd_backend_method_get(&hreq->method, backend); + httpd_backend_peer_get(&hreq->peer_address, &hreq->peer_port, backend); - req_method = evhttp_request_get_command(req); + hreq->user_agent = httpd_header_find(hreq->in_headers, "User-Agent"); + } + else + { + hreq->uri = uri; + hreq->user_agent = user_agent; } - if (user_agent) - hreq->user_agent = user_agent; + uri_parsed = httpd_uri_parse(hreq->uri); + if (!uri_parsed) + { + return; + } - // Find a handler for the path - for (uri = uri_map; uri->handler; uri++) + hreq->uri_parsed = uri_parsed; + hreq->query = &(hreq->uri_parsed->query); + + // Path with e.g. /api -> JSON module + module = modules_search(uri_parsed->path); + if (!module) + { + return; + } + + for (map = module->handlers; map->handler; map++) { // Check if handler supports the current http request method - if (uri->method && req_method && !(req_method & uri->method)) + if (map->method && hreq->method && !(map->method & hreq->method)) continue; - ret = regexec(uri->preg, uri_parsed->path, 0, NULL, 0); + ret = regexec(map->preg, uri_parsed->path, 0, NULL, 0); if (ret != 0) continue; - hreq->handler = uri->handler; - return 0; // Success + hreq->handler = map->handler; + break; } - - // Handler not found, that's an error - return -1; } void httpd_redirect_to(struct httpd_request *hreq, const char *path) { - httpd_headers *headers; - - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "Location", path); + httpd_header_add(hreq->out_headers, "Location", path); httpd_send_reply(hreq, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP); } @@ -364,7 +378,6 @@ httpd_redirect_to(struct httpd_request *hreq, const char *path) bool httpd_request_etag_matches(struct httpd_request *hreq, const char *etag) { - httpd_headers *output_headers; const char *none_match; none_match = httpd_header_find(hreq->in_headers, "If-None-Match"); @@ -374,9 +387,8 @@ httpd_request_etag_matches(struct httpd_request *hreq, const char *etag) return true; // Add cache headers to allow client side caching - output_headers = httpd_request_output_headers_get(hreq); - httpd_header_add(output_headers, "Cache-Control", "private,no-cache,max-age=0"); - httpd_header_add(output_headers, "ETag", etag); + httpd_header_add(hreq->out_headers, "Cache-Control", "private,no-cache,max-age=0"); + httpd_header_add(hreq->out_headers, "ETag", etag); return false; } @@ -395,12 +407,11 @@ httpd_request_etag_matches(struct httpd_request *hreq, const char *etag) bool httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime) { - httpd_headers *output_headers; char last_modified[1000]; const char *modified_since; struct tm timebuf; - modified_since = evhttp_find_header(hreq->in_headers, "If-Modified-Since"); + modified_since = httpd_header_find(hreq->in_headers, "If-Modified-Since"); strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", gmtime_r(&mtime, &timebuf)); @@ -409,9 +420,8 @@ httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime) return true; // Add cache headers to allow client side caching - output_headers = httpd_request_output_headers_get(hreq); - httpd_header_add(output_headers, "Cache-Control", "private,no-cache,max-age=0"); - httpd_header_add(output_headers, "Last-Modified", last_modified); + httpd_header_add(hreq->out_headers, "Cache-Control", "private,no-cache,max-age=0"); + httpd_header_add(hreq->out_headers, "Last-Modified", last_modified); return false; } @@ -419,28 +429,23 @@ httpd_request_not_modified_since(struct httpd_request *hreq, time_t mtime) void httpd_response_not_cachable(struct httpd_request *hreq) { - httpd_headers *output_headers; - - output_headers = httpd_request_output_headers_get(hreq); - // Remove potentially set cache control headers - httpd_header_remove(output_headers, "Cache-Control"); - httpd_header_remove(output_headers, "Last-Modified"); - httpd_header_remove(output_headers, "ETag"); + httpd_header_remove(hreq->out_headers, "Cache-Control"); + httpd_header_remove(hreq->out_headers, "Last-Modified"); + httpd_header_remove(hreq->out_headers, "ETag"); // Tell clients that they are not allowed to cache this response - httpd_header_add(output_headers, "Cache-Control", "no-store"); + httpd_header_add(hreq->out_headers, "Cache-Control", "no-store"); } static void -serve_file(struct httpd_request *hreq, const char *uri) +serve_file(struct httpd_request *hreq) { char *ext; char path[PATH_MAX]; char deref[PATH_MAX]; char *ctype; struct evbuffer *evbuf; - httpd_headers *output_headers; struct stat sb; int fd; int i; @@ -452,13 +457,12 @@ serve_file(struct httpd_request *hreq, const char *uri) if (!httpd_admin_check_auth(hreq)) return; - ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, uri); + ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->uri_parsed->path); if ((ret < 0) || (ret >= sizeof(path))) { - DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri); + DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", hreq->uri); httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); - return; } @@ -475,7 +479,6 @@ serve_file(struct httpd_request *hreq, const char *uri) DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path); httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); - return; } @@ -485,7 +488,6 @@ serve_file(struct httpd_request *hreq, const char *uri) DPRINTF(E_WARN, L_HTTPD, "Could not lstat() %s: %s\n", deref, strerror(errno)); httpd_send_error(hreq, HTTP_NOTFOUND, "Not Found"); - return; } @@ -584,8 +586,7 @@ serve_file(struct httpd_request *hreq, const char *uri) } } - output_headers = httpd_request_output_headers_get(hreq); - httpd_header_add(output_headers, "Content-Type", ctype); + httpd_header_add(hreq->out_headers, "Content-Type", ctype); httpd_send_reply(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); @@ -817,74 +818,69 @@ exit_cb(int fd, short event, void *arg) httpd_exit = 1; } -static void -httpd_gen_cb(struct evhttp_request *req, void *arg) +static int +handle_cors_preflight(struct httpd_request *hreq, const char *allow_origin) { - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; - struct httpd_request hreq = { .backend = req }; // TODO clean this up - struct httpd_uri_parsed *parsed; - struct httpd_module *m; - const char *uri; + bool is_cors_preflight; - // Clear the proxy request flag set by evhttp if the request URI was absolute. - // It has side-effects on Connection: keep-alive - req->flags &= ~EVHTTP_PROXY_REQUEST; + is_cors_preflight = ( hreq->method == HTTPD_METHOD_OPTIONS && hreq->in_headers && allow_origin && + httpd_header_find(hreq->in_headers, "Origin") && + httpd_header_find(hreq->in_headers, "Access-Control-Request-Method") ); + if (!is_cors_preflight) + return -1; + + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", allow_origin); + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Headers", "authorization"); + + // In this case there is no reason to go through httpd_send_reply + httpd_reply_backend_send(hreq, HTTP_OK, "OK", NULL); + return 0; +} + +static void +httpd_gen_cb(httpd_backend *backend, void *arg) +{ + struct httpd_request hreq; + + // This is to make modifications to e.g. evhttps's request object + httpd_backend_preprocess(backend); + + // Populates the hreq struct + request_set(&hreq, backend, NULL, NULL); // Did we get a CORS preflight request? - input_headers = evhttp_request_get_input_headers(req); - if ( input_headers && allow_origin && - (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) && - evhttp_find_header(input_headers, "Origin") && - evhttp_find_header(input_headers, "Access-Control-Request-Method") ) + if (handle_cors_preflight(&hreq, httpd_allow_origin) == 0) { - output_headers = evhttp_request_get_output_headers(req); - - httpd_header_add(output_headers, "Access-Control-Allow-Origin", allow_origin); - httpd_header_add(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - httpd_header_add(output_headers, "Access-Control-Allow-Headers", "authorization"); - - // In this case there is no reason to go through httpd_send_reply - evhttp_send_reply(req, HTTP_OK, "OK", NULL); - return; + goto out; } - uri = evhttp_request_get_uri(req); - if (!uri) + if (!(&hreq)->uri || !(&hreq)->uri_parsed) { - DPRINTF(E_WARN, L_HTTPD, "No URI in request\n"); + DPRINTF(E_WARN, L_HTTPD, "Invalid URI in request: '%s'\n", (&hreq)->uri); httpd_redirect_to(&hreq, "/"); - return; + goto out; } - - parsed = httpd_uri_parse(uri); - if (!parsed || !parsed->path) + else if (!(&hreq)->uri_parsed->path) { + DPRINTF(E_WARN, L_HTTPD, "Invalid path in request: '%s'\n", (&hreq)->uri); httpd_redirect_to(&hreq, "/"); goto out; } - if (strcmp(parsed->path, "/") == 0) + if ((&hreq)->handler) { - goto serve_file; + (&hreq)->handler(&hreq); } - - m = modules_search(parsed->path); - if (m) + else { - request_set(&hreq, req, parsed, NULL, m->handlers); - m->request(&hreq); - goto out; + // Serve web interface files + DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", (&hreq)->uri); + serve_file(&hreq); } - DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri); - - /* Serve web interface files */ - serve_file: - serve_file(&hreq, parsed->path); - out: - httpd_uri_free(parsed); + request_unset(&hreq); } @@ -991,10 +987,16 @@ httpd_uri_parse(const char *uri) return NULL; } -int -httpd_request_set(struct httpd_request *hreq, struct httpd_uri_parsed *uri_parsed, const char *user_agent, struct httpd_uri_map *uri_map) +void +httpd_request_unset(struct httpd_request *hreq) { - return request_set(hreq, NULL, uri_parsed, user_agent, uri_map); + request_unset(hreq); +} + +void +httpd_request_set(struct httpd_request *hreq, const char *uri, const char *user_agent) +{ + request_set(hreq, NULL, uri, user_agent); } /* Thread: httpd */ @@ -1007,7 +1009,6 @@ httpd_stream_file(struct httpd_request *hreq, int id) void (*stream_cb)(int fd, short event, void *arg); struct stat sb; struct timeval tv; - httpd_headers *output_headers; const char *param; const char *param_end; const char *client_codecs; @@ -1087,8 +1088,6 @@ httpd_stream_file(struct httpd_request *hreq, int id) transcode = transcode_needed(hreq->user_agent, client_codecs, mfi->codectype); - output_headers = httpd_request_output_headers_get(hreq); - if (transcode) { DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path); @@ -1104,8 +1103,8 @@ httpd_stream_file(struct httpd_request *hreq, int id) goto out_free_st; } - if (!evhttp_find_header(output_headers, "Content-Type")) - httpd_header_add(output_headers, "Content-Type", "audio/wav"); + if (!httpd_header_find(hreq->out_headers, "Content-Type")) + httpd_header_add(hreq->out_headers, "Content-Type", "audio/wav"); } else { @@ -1165,21 +1164,21 @@ httpd_stream_file(struct httpd_request *hreq, int id) DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n"); else { - httpd_header_remove(output_headers, "Content-Type"); - httpd_header_add(output_headers, "Content-Type", buf); + httpd_header_remove(hreq->out_headers, "Content-Type"); + httpd_header_add(hreq->out_headers, "Content-Type", buf); } } /* If no Content-Type has been set and we're streaming audio, add a proper * Content-Type for the file we're streaming. Remember DAAP streams audio * with application/x-dmap-tagged as the Content-Type (ugh!). */ - else if (!evhttp_find_header(output_headers, "Content-Type") && mfi->type) + else if (!httpd_header_find(hreq->out_headers, "Content-Type") && mfi->type) { ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type); if ((ret < 0) || (ret >= sizeof(buf))) DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n"); else - httpd_header_add(output_headers, "Content-Type", buf); + httpd_header_add(hreq->out_headers, "Content-Type", buf); } } @@ -1228,7 +1227,7 @@ httpd_stream_file(struct httpd_request *hreq, int id) if ((ret < 0) || (ret >= sizeof(buf))) DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n"); else - httpd_header_add(output_headers, "Content-Length", buf); + httpd_header_add(hreq->out_headers, "Content-Length", buf); } httpd_reply_start_send(hreq, HTTP_OK, "OK"); @@ -1247,13 +1246,13 @@ httpd_stream_file(struct httpd_request *hreq, int id) if ((ret < 0) || (ret >= sizeof(buf))) DPRINTF(E_LOG, L_HTTPD, "Content-Range too large for buffer, dropping\n"); else - httpd_header_add(output_headers, "Content-Range", buf); + httpd_header_add(hreq->out_headers, "Content-Range", buf); ret = snprintf(buf, sizeof(buf), "%" PRIi64, ((end_offset) ? end_offset + 1 : (int64_t)st->size) - offset); if ((ret < 0) || (ret >= sizeof(buf))) DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n"); else - httpd_header_add(output_headers, "Content-Length", buf); + httpd_header_add(hreq->out_headers, "Content-Length", buf); httpd_reply_start_send(hreq, 206, "Partial Content"); } @@ -1363,29 +1362,26 @@ void httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags) { struct evbuffer *gzbuf; - httpd_headers *output_headers; const char *param; int do_gzip; if (!hreq->backend) return; - output_headers = httpd_request_output_headers_get(hreq); - do_gzip = ( (!(flags & HTTPD_SEND_NO_GZIP)) && evbuf && (evbuffer_get_length(evbuf) > 512) && (param = httpd_header_find(hreq->in_headers, "Accept-Encoding")) && (strstr(param, "gzip") || strstr(param, "*")) ); - if (allow_origin) - httpd_header_add(output_headers, "Access-Control-Allow-Origin", allow_origin); + if (httpd_allow_origin) + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf))) { DPRINTF(E_DBG, L_HTTPD, "Gzipping response\n"); - httpd_header_add(output_headers, "Content-Encoding", "gzip"); + httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); httpd_reply_backend_send(hreq, code, reason, gzbuf); evbuffer_free(gzbuf); @@ -1402,17 +1398,14 @@ httpd_send_reply(struct httpd_request *hreq, int code, const char *reason, struc void httpd_send_error(struct httpd_request *hreq, int error, const char *reason) { - httpd_headers *output_headers; struct evbuffer *evbuf; - output_headers = httpd_request_output_headers_get(hreq); + httpd_headers_clear(hreq->out_headers); - httpd_headers_clear(output_headers); - - if (allow_origin) - httpd_header_add(output_headers, "Access-Control-Allow-Origin", allow_origin); - httpd_header_add(output_headers, "Content-Type", "text/html"); - httpd_header_add(output_headers, "Connection", "close"); + if (httpd_allow_origin) + httpd_header_add(hreq->out_headers, "Access-Control-Allow-Origin", httpd_allow_origin); + httpd_header_add(hreq->out_headers, "Content-Type", "text/html"); + httpd_header_add(hreq->out_headers, "Connection", "close"); evbuf = evbuffer_new(); if (!evbuf) @@ -1464,7 +1457,6 @@ int httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm) { struct evbuffer *evbuf; - httpd_headers *output_headers; char header[256]; const char *auth; char *authuser; @@ -1546,8 +1538,7 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw return -1; } - output_headers = httpd_request_output_headers_get(hreq); - httpd_header_add(output_headers, "WWW-Authenticate", header); + httpd_header_add(hreq->out_headers, "WWW-Authenticate", header); evbuffer_add_printf(evbuf, ERR_PAGE, 401, "Unauthorized", "Authorization required"); @@ -1572,19 +1563,16 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_LOG, L_HTTPD, "Could not stat() web root directory '%s': %s\n", webroot, strerror(errno)); - return -1; } if (!S_ISDIR(sb.st_mode)) { DPRINTF(E_LOG, L_HTTPD, "Web root directory '%s' is not a directory\n", webroot); - return -1; } if (!realpath(webroot, webroot_directory)) { DPRINTF(E_LOG, L_HTTPD, "Web root directory '%s' could not be dereferenced: %s\n", webroot, strerror(errno)); - return -1; } @@ -1592,7 +1580,6 @@ httpd_init(const char *webroot) if (!evbase_httpd) { DPRINTF(E_FATAL, L_HTTPD, "Could not create an event base\n"); - return -1; } @@ -1601,7 +1588,6 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Websocket init failed\n"); - goto websocket_fail; } #endif @@ -1610,7 +1596,6 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n"); - goto modules_fail; } @@ -1619,7 +1604,6 @@ httpd_init(const char *webroot) if (exit_efd < 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not create eventfd: %s\n", strerror(errno)); - goto pipe_fail; } @@ -1633,7 +1617,6 @@ httpd_init(const char *webroot) if (ret < 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno)); - goto pipe_fail; } @@ -1642,45 +1625,28 @@ httpd_init(const char *webroot) if (!exitev) { DPRINTF(E_FATAL, L_HTTPD, "Could not create exit event\n"); - goto exitev_fail; } event_add(exitev, NULL); - evhttpd = evhttp_new(evbase_httpd); - if (!evhttpd) + httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); + httpd_serv = httpd_server_new(evbase_httpd, httpd_port, httpd_gen_cb, NULL); + if (!httpd_serv) { - DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server\n"); - - goto evhttpd_fail; + DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server on port %d (server already running?)\n", httpd_port); + goto httpd_server_fail; } // For CORS headers - allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin"); - if (allow_origin) - { - if (strlen(allow_origin) != 0) - evhttp_set_allowed_methods(evhttpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS); - else - allow_origin = NULL; - } - - httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port"); - - ret = net_evhttp_bind(evhttpd, httpd_port, "httpd"); - if (ret < 0) - { - DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (server already running?)\n", httpd_port); - goto bind_fail; - } - - evhttp_set_gencb(evhttpd, httpd_gen_cb, NULL); + httpd_allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin"); + if (strlen(httpd_allow_origin) == 0) + httpd_allow_origin = NULL; + httpd_server_allow_origin_set(httpd_serv, httpd_allow_origin); ret = pthread_create(&tid_httpd, NULL, httpd, NULL); if (ret != 0) { DPRINTF(E_FATAL, L_HTTPD, "Could not spawn HTTPd thread: %s\n", strerror(errno)); - goto thread_fail; } @@ -1689,9 +1655,8 @@ httpd_init(const char *webroot) return 0; thread_fail: - bind_fail: - evhttp_free(evhttpd); - evhttpd_fail: + httpd_server_free(httpd_serv); + httpd_server_fail: event_free(exitev); exitev_fail: #ifdef HAVE_EVENTFD @@ -1759,6 +1724,6 @@ httpd_deinit(void) close(exit_pipe[1]); #endif event_free(exitev); - evhttp_free(evhttpd); + httpd_server_free(httpd_serv); event_base_free(evbase_httpd); } diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c index c6b8b427..91556723 100644 --- a/src/httpd_artworkapi.c +++ b/src/httpd_artworkapi.c @@ -62,14 +62,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) { - httpd_headers *headers; - - headers = httpd_request_output_headers_get(hreq); - if (format == ART_FMT_PNG) - httpd_header_add(headers, "Content-Type", "image/png"); + httpd_header_add(hreq->out_headers, "Content-Type", "image/png"); else if (format == ART_FMT_JPEG) - httpd_header_add(headers, "Content-Type", "image/jpeg"); + httpd_header_add(hreq->out_headers, "Content-Type", "image/jpeg"); else return HTTP_NOCONTENT; diff --git a/src/httpd_daap.c b/src/httpd_daap.c index c8ff4ef6..9d429e70 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -1935,7 +1935,6 @@ daap_reply_browse(struct httpd_request *hreq) static enum daap_reply_result daap_reply_extra_data(struct httpd_request *hreq) { - httpd_headers *headers; char clen[32]; const char *param; char *ctype; @@ -2008,11 +2007,10 @@ daap_reply_extra_data(struct httpd_request *hreq) goto no_artwork; } - headers = httpd_request_output_headers_get(hreq); - httpd_header_remove(headers, "Content-Type"); - httpd_header_add(headers, "Content-Type", ctype); + httpd_header_remove(hreq->out_headers, "Content-Type"); + httpd_header_add(hreq->out_headers, "Content-Type", ctype); snprintf(clen, sizeof(clen), "%ld", (long)len); - httpd_header_add(headers, "Content-Length", clen); + httpd_header_add(hreq->out_headers, "Content-Length", clen); return DAAP_REPLY_OK_NO_GZIP; @@ -2216,7 +2214,6 @@ static struct httpd_uri_map daap_handlers[] = static void daap_request(struct httpd_request *hreq) { - httpd_headers *headers; struct timespec start; struct timespec end; struct daap_session session; @@ -2261,13 +2258,12 @@ daap_request(struct httpd_request *hreq) } // Set reply headers - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "Accept-Ranges", "bytes"); - httpd_header_add(headers, "DAAP-Server", PACKAGE_NAME "/" VERSION); + httpd_header_add(hreq->out_headers, "Accept-Ranges", "bytes"); + httpd_header_add(hreq->out_headers, "DAAP-Server", PACKAGE_NAME "/" VERSION); // Content-Type for all replies, even the actual audio streaming. Note that // video streaming will override this Content-Type with a more appropriate // video/ Content-Type as expected by clients like Front Row. - httpd_header_add(headers, "Content-Type", "application/x-dmap-tagged"); + httpd_header_add(hreq->out_headers, "Content-Type", "application/x-dmap-tagged"); // Now we create the actual reply CHECK_NULL(L_DAAP, hreq->reply = evbuffer_new()); @@ -2277,7 +2273,7 @@ daap_request(struct httpd_request *hreq) if (ret == 0) { // The cache will return the data gzipped, so httpd_send_reply won't need to do it - httpd_header_add(headers, "Content-Encoding", "gzip"); + httpd_header_add(hreq->out_headers, "Content-Encoding", "gzip"); httpd_send_reply(hreq, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); // TODO not all want this reply evbuffer_free(hreq->reply); @@ -2320,7 +2316,6 @@ struct evbuffer * daap_reply_build(const char *uri, const char *user_agent, int is_remote) { struct httpd_request hreq; - struct httpd_uri_parsed *uri_parsed; struct evbuffer *reply; struct daap_session session; int ret; @@ -2329,14 +2324,10 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) reply = NULL; - uri_parsed = httpd_uri_parse(uri); - if (!uri_parsed) - return NULL; - - ret = httpd_request_set(&hreq, uri_parsed, user_agent, daap_handlers); - if (ret < 0) + httpd_request_set(&hreq, uri, user_agent); + if (!(&hreq)->handler) { - DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path '%s' in request: '%s'\n", uri_parsed->path, uri_parsed->uri); + DPRINTF(E_LOG, L_DAAP, "Cannot build reply, unrecognized path in request: '%s'\n", uri); goto out; } @@ -2357,7 +2348,7 @@ daap_reply_build(const char *uri, const char *user_agent, int is_remote) reply = hreq.reply; out: - httpd_uri_free(uri_parsed); + httpd_request_unset(&hreq); return reply; } diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 8e2aeb12..9fdd1dc2 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -2316,7 +2316,6 @@ static int dacp_reply_nowplayingartwork(struct httpd_request *hreq) { char clen[32]; - httpd_headers *headers; const char *param; char *ctype; size_t len; @@ -2381,11 +2380,10 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq) goto no_artwork; } - headers = httpd_request_output_headers_get(hreq); - httpd_header_remove(headers, "Content-Type"); - httpd_header_add(headers, "Content-Type", ctype); + httpd_header_remove(hreq->out_headers, "Content-Type"); + httpd_header_add(hreq->out_headers, "Content-Type", ctype); snprintf(clen, sizeof(clen), "%ld", (long)len); - httpd_header_add(headers, "Content-Length", clen); + httpd_header_add(hreq->out_headers, "Content-Length", clen); httpd_send_reply(hreq, HTTP_OK, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); return 0; @@ -2855,8 +2853,6 @@ static struct httpd_uri_map dacp_handlers[] = static void dacp_request(struct httpd_request *hreq) { - httpd_headers *headers; - DPRINTF(E_DBG, L_DACP, "DACP request: '%s'\n", hreq->uri); if (!hreq->handler) @@ -2867,10 +2863,9 @@ dacp_request(struct httpd_request *hreq) return; } - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "DAAP-Server", PACKAGE_NAME "/" VERSION); + httpd_header_add(hreq->out_headers, "DAAP-Server", PACKAGE_NAME "/" VERSION); /* Content-Type for all DACP replies; can be overriden as needed */ - httpd_header_add(headers, "Content-Type", "application/x-dmap-tagged"); + httpd_header_add(hreq->out_headers, "Content-Type", "application/x-dmap-tagged"); CHECK_NULL(L_DACP, hreq->reply = evbuffer_new()); diff --git a/src/httpd_internal.h b/src/httpd_internal.h index 63d034d3..8ca06d1a 100644 --- a/src/httpd_internal.h +++ b/src/httpd_internal.h @@ -4,17 +4,16 @@ #include #include +#include #include #include +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 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, @@ -62,16 +61,18 @@ struct httpd_uri_parsed * evbuffer. */ struct httpd_request { + // Request method + enum httpd_methods method; + // The request URI + const char *uri; // User-agent (if available) const char *user_agent; // The parsed request URI given to us by httpd_uri_parse struct httpd_uri_parsed *uri_parsed; // Shortcut to &uri_parsed->query httpd_query *query; - // Shortcut to &uri_parsed->uri - const char *uri; // Backend private request object - void *backend; + httpd_backend *backend; // Source IP address (ipv4 or ipv6) and port of the request (if available) char *peer_address; unsigned short peer_port; @@ -82,6 +83,8 @@ struct httpd_request { httpd_headers *in_headers; // Request body struct evbuffer *in_body; + // Response headers + httpd_headers *out_headers; // Reply evbuffer struct evbuffer *reply; @@ -149,8 +152,11 @@ httpd_uri_parse(const char *uri); void httpd_stream_file(struct httpd_request *hreq, int id); -int -httpd_request_set(struct httpd_request *hreq, 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 httpd_request *hreq, time_t mtime); @@ -206,6 +212,11 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw /*-------------------------- WRAPPERS FOR EVHTTP -----------------------------*/ +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); + const char * httpd_query_value_find(httpd_query *query, const char *key); @@ -227,12 +238,6 @@ httpd_header_add(httpd_headers *headers, const char *key, const char *val); void httpd_headers_clear(httpd_headers *headers); -httpd_headers * -httpd_request_input_headers_get(struct httpd_request *hreq); - -httpd_headers * -httpd_request_output_headers_get(struct httpd_request *hreq); - int httpd_connection_closecb_set(httpd_connection *conn, httpd_connection_closecb cb, void *arg); @@ -244,15 +249,6 @@ httpd_connection_free(httpd_connection *conn); httpd_connection * httpd_request_connection_get(struct httpd_request *hreq); -/* -const char * -httpd_request_uri_get(struct httpd_request *hreq); -*/ -int -httpd_request_peer_get(char **addr, uint16_t *port, struct httpd_request *hreq); - -int -httpd_request_method_get(enum httpd_methods *method, struct httpd_request *hreq); void httpd_request_backend_free(struct httpd_request *hreq); @@ -272,4 +268,40 @@ httpd_reply_chunk_send(struct httpd_request *hreq, struct evbuffer *evbuf, httpd void httpd_reply_end_send(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 populate struct httpd_request ---------*/ + +httpd_connection * +httpd_backend_connection_get(httpd_backend *backend); + +const char * +httpd_backend_uri_get(httpd_backend *backend); + +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(char **addr, uint16_t *port, httpd_backend *backend); + +int +httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend); + +void +httpd_backend_preprocess(httpd_backend *backend); + #endif /* !__HTTPD_INTERNAL_H__ */ diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index d5755c7f..9b490a06 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4705,8 +4705,6 @@ static struct httpd_uri_map adm_handlers[] = static void jsonapi_request(struct httpd_request *hreq) { - ; - httpd_headers *headers; int status_code; DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", hreq->uri); @@ -4733,8 +4731,7 @@ jsonapi_request(struct httpd_request *hreq) switch (status_code) { case HTTP_OK: /* 200 OK */ - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "Content-Type", "application/json"); + httpd_header_add(hreq->out_headers, "Content-Type", "application/json"); httpd_send_reply(hreq, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); break; case HTTP_NOCONTENT: /* 204 No Content */ diff --git a/src/httpd_libevhttp.c b/src/httpd_libevhttp.c index aa8c0da3..fcaf50a4 100644 --- a/src/httpd_libevhttp.c +++ b/src/httpd_libevhttp.c @@ -1,8 +1,10 @@ #include #include +#include #include +#include "misc.h" // For net_evhttp_bind #include "httpd_internal.h" int @@ -28,45 +30,7 @@ httpd_connection_free(httpd_connection *conn) httpd_connection * httpd_request_connection_get(struct httpd_request *hreq) { - return evhttp_request_get_connection(hreq->backend); -} -/* -const char * -httpd_request_uri_get(httpd_request *req) -{ - return evhttp_request_get_uri(req); -} -*/ -int -httpd_request_peer_get(char **addr, uint16_t *port, struct httpd_request *hreq) -{ - httpd_connection *conn = httpd_request_connection_get(hreq->backend); - if (!conn) - return -1; - - return httpd_connection_peer_get(addr, port, conn); -} - -int -httpd_request_method_get(enum httpd_methods *method, struct httpd_request *hreq) -{ - enum evhttp_cmd_type cmd = evhttp_request_get_command(hreq->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; + return httpd_backend_connection_get(hreq->backend); } void @@ -132,12 +96,6 @@ httpd_headers_clear(httpd_headers *headers) evhttp_clear_headers(headers); } -httpd_headers * -httpd_request_input_headers_get(struct httpd_request *hreq) -{ - return evhttp_request_get_input_headers(hreq->backend); -} - httpd_headers * httpd_request_output_headers_get(struct httpd_request *hreq) { @@ -167,3 +125,110 @@ httpd_reply_end_send(struct httpd_request *hreq) { evhttp_send_reply_end(hreq->backend); } + +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); +} + +httpd_connection * +httpd_backend_connection_get(httpd_backend *backend) +{ + return evhttp_request_get_connection(backend); +} + +const char * +httpd_backend_uri_get(httpd_backend *backend) +{ + 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); +} + +int +httpd_backend_peer_get(char **addr, uint16_t *port, httpd_backend *backend) +{ + httpd_connection *conn = httpd_backend_connection_get(backend); + if (!conn) + return -1; + + return httpd_connection_peer_get(addr, port, conn); +} + +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; +} diff --git a/src/httpd_rsp.c b/src/httpd_rsp.c index 09a7d25c..ec87f0ce 100644 --- a/src/httpd_rsp.c +++ b/src/httpd_rsp.c @@ -160,7 +160,6 @@ static void rsp_send_error(struct httpd_request *hreq, char *errmsg) { struct evbuffer *evbuf; - httpd_headers *headers; mxml_node_t *reply; mxml_node_t *status; mxml_node_t *node; @@ -196,9 +195,8 @@ rsp_send_error(struct httpd_request *hreq, char *errmsg) return; } - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "Content-Type", "text/xml; charset=utf-8"); - httpd_header_add(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(hreq, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP); @@ -280,7 +278,6 @@ static void rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply) { struct evbuffer *evbuf; - httpd_headers *headers; evbuf = mxml_to_evbuf(reply); mxmlDelete(reply); @@ -292,9 +289,8 @@ rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply) return; } - headers = httpd_request_output_headers_get(hreq); - httpd_header_add(headers, "Content-Type", "text/xml; charset=utf-8"); - httpd_header_add(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(hreq, HTTP_OK, "OK", evbuf, 0); diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 56ad2c6d..64e78179 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -537,7 +537,6 @@ static void streaming_request(struct httpd_request *hreq) { struct streaming_session *session; - httpd_headers *output_headers; cfg_t *lib; const char *name; const char *param; @@ -561,21 +560,20 @@ streaming_request(struct httpd_request *hreq) lib = cfg_getsec(cfg, "library"); name = cfg_getstr(lib, "name"); - output_headers = httpd_request_output_headers_get(hreq); - httpd_header_add(output_headers, "Content-Type", "audio/mpeg"); - httpd_header_add(output_headers, "Server", PACKAGE_NAME "/" VERSION); - httpd_header_add(output_headers, "Cache-Control", "no-cache"); - httpd_header_add(output_headers, "Pragma", "no-cache"); - httpd_header_add(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; - httpd_header_add(output_headers, "icy-name", name); + httpd_header_add(hreq->out_headers, "icy-name", name); snprintf(buf, sizeof(buf)-1, "%d", streaming_icy_metaint); - httpd_header_add(output_headers, "icy-metaint", buf); + httpd_header_add(hreq->out_headers, "icy-metaint", buf); } - httpd_header_add(output_headers, "Access-Control-Allow-Origin", "*"); - httpd_header_add(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"); httpd_reply_start_send(hreq, HTTP_OK, "OK");