owntone-server/src/httpd.c

1824 lines
44 KiB
C

/*
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <inttypes.h>
#ifdef HAVE_EVENTFD
# include <sys/eventfd.h>
#endif
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <regex.h>
#include <zlib.h>
#include "logger.h"
#include "db.h"
#include "conffile.h"
#include "misc.h"
#include "worker.h"
#include "httpd.h"
#include "httpd_internal.h"
#include "transcode.h"
#ifdef LASTFM
# include "lastfm.h"
#endif
#ifdef HAVE_LIBWEBSOCKETS
# include "websocket.h"
#endif
#define STREAM_CHUNK_SIZE (64 * 1024)
#define ERR_PAGE "<html>\n<head>\n" \
"<title>%d %s</title>\n" \
"</head>\n<body>\n" \
"<h1>%s</h1>\n" \
"</body>\n</html>\n"
#define HTTPD_STREAM_SAMPLE_RATE 44100
#define HTTPD_STREAM_BPS 16
#define HTTPD_STREAM_CHANNELS 2
extern struct httpd_module httpd_dacp;
extern struct httpd_module httpd_daap;
extern struct httpd_module httpd_jsonapi;
extern struct httpd_module httpd_artworkapi;
extern struct httpd_module httpd_streaming;
extern struct httpd_module httpd_oauth;
extern struct httpd_module httpd_rsp;
// Must be in sync with enum httpd_modules
static struct httpd_module *httpd_modules[] = {
&httpd_dacp,
&httpd_daap,
&httpd_jsonapi,
&httpd_artworkapi,
&httpd_streaming,
&httpd_oauth,
&httpd_rsp,
NULL
};
struct content_type_map {
char *ext;
char *ctype;
};
struct stream_ctx {
struct evhttp_request *req;
uint8_t *buf;
struct evbuffer *evbuf;
struct event *ev;
int id;
int fd;
off_t size;
off_t stream_size;
off_t offset;
off_t start_offset;
off_t end_offset;
int marked;
struct transcode_ctx *xcode;
};
static const struct content_type_map ext2ctype[] =
{
{ ".html", "text/html; charset=utf-8" },
{ ".xml", "text/xml; charset=utf-8" },
{ ".css", "text/css; charset=utf-8" },
{ ".txt", "text/plain; charset=utf-8" },
{ ".js", "application/javascript; charset=utf-8" },
{ ".gif", "image/gif" },
{ ".ico", "image/x-ico" },
{ ".png", "image/png" },
{ NULL, NULL }
};
static char webroot_directory[PATH_MAX];
struct event_base *evbase_httpd;
#ifdef HAVE_EVENTFD
static int exit_efd;
#else
static int exit_pipe[2];
#endif
static int httpd_exit;
static struct event *exitev;
static struct evhttp *evhttpd;
static pthread_t tid_httpd;
static const char *allow_origin;
static int httpd_port;
/* -------------------------------- HELPERS --------------------------------- */
static int
path_is_legal(const char *path)
{
return strncmp(webroot_directory, path, strlen(webroot_directory));
}
/* Callback from the worker thread (async operation as it may block) */
static void
playcount_inc_cb(void *arg)
{
int *id = arg;
db_file_inc_playcount(*id);
}
#ifdef LASTFM
/* Callback from the worker thread (async operation as it may block) */
static void
scrobble_cb(void *arg)
{
int *id = arg;
lastfm_scrobble(*id);
}
#endif
/* --------------------------- MODULES INTERFACE ---------------------------- */
static void
modules_handlers_unset(struct httpd_uri_map *uri_map)
{
struct httpd_uri_map *uri;
for (uri = uri_map; uri->preg; uri++)
{
regfree(uri->preg); // Frees allocation by regcomp
free(uri->preg); // Frees our own calloc
}
}
static int
modules_handlers_set(struct httpd_uri_map *uri_map)
{
struct httpd_uri_map *uri;
char buf[64];
int ret;
for (uri = uri_map; uri->handler; uri++)
{
uri->preg = calloc(1, sizeof(regex_t));
if (!uri->preg)
{
DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, out of memory");
goto error;
}
ret = regcomp(uri->preg, uri->regexp, REG_EXTENDED | REG_NOSUB);
if (ret != 0)
{
regerror(ret, uri->preg, buf, sizeof(buf));
DPRINTF(E_LOG, L_HTTPD, "Error setting URI handler, regexp error: %s\n", buf);
goto error;
}
}
return 0;
error:
modules_handlers_unset(uri_map);
return -1;
}
static int
modules_init(void)
{
struct httpd_module **ptr;
struct httpd_module *m;
for (ptr = httpd_modules; *ptr; ptr++)
{
m = *ptr;
m->initialized = (!m->init || m->init() == 0);
if (!m->initialized)
{
DPRINTF(E_FATAL, L_HTTPD, "%s init failed\n", m->name);
return -1;
}
if (modules_handlers_set(m->handlers) != 0)
{
DPRINTF(E_FATAL, L_HTTPD, "%s handler configuration failed\n", m->name);
return -1;
}
}
return 0;
}
static void
modules_deinit(void)
{
struct httpd_module **ptr;
struct httpd_module *m;
for (ptr = httpd_modules; *ptr; ptr++)
{
m = *ptr;
if (m->initialized && m->deinit)
m->deinit();
modules_handlers_unset(m->handlers);
}
}
static struct httpd_module *
modules_search(const char *path)
{
struct httpd_module **ptr;
struct httpd_module *m;
const char **test;
bool is_found = false;
for (ptr = httpd_modules; *ptr; ptr++)
{
m = *ptr;
if (!m->subpaths || !m->request)
continue;
for (test = m->subpaths; *test && !is_found; test++)
is_found = (strncmp(path, *test, strlen(*test)) == 0);
for (test = m->fullpaths; *test && !is_found; test++)
is_found = (strcmp(path, *test) == 0);
if (is_found)
return m;
}
return NULL;
}
/* --------------------------- REQUEST HELPERS ------------------------------ */
void
httpd_redirect_to(struct evhttp_request *req, const char *path)
{
struct evkeyvalq *headers;
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Location", path);
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
}
/*
* Checks if the given ETag matches the "If-None-Match" request header
*
* If the request does not contains a "If-None-Match" header value or the value
* does not match the given ETag, it returns false (modified) and adds the
* "Cache-Control" and "ETag" headers to the response header.
*
* @param req The request with request and response headers
* @param etag The valid ETag for the requested resource
* @return True if the given ETag matches the request-header-value "If-None-Match", otherwise false
*/
bool
httpd_request_etag_matches(struct evhttp_request *req, const char *etag)
{
struct evkeyvalq *input_headers;
struct evkeyvalq *output_headers;
const char *none_match;
input_headers = evhttp_request_get_input_headers(req);
none_match = evhttp_find_header(input_headers, "If-None-Match");
// Return not modified, if given timestamp matches "If-Modified-Since" request header
if (none_match && (strcasecmp(etag, none_match) == 0))
return true;
// Add cache headers to allow client side caching
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Cache-Control", "private,no-cache,max-age=0");
evhttp_add_header(output_headers, "ETag", etag);
return false;
}
/*
* Checks if the given timestamp matches the "If-Modified-Since" request header
*
* If the request does not contains a "If-Modified-Since" header value or the value
* does not match the given timestamp, it returns false (modified) and adds the
* "Cache-Control" and "Last-Modified" headers to the response header.
*
* @param req The request with request and response headers
* @param mtime The last modified timestamp for the requested resource
* @return True if the given timestamp matches the request-header-value "If-Modified-Since", otherwise false
*/
bool
httpd_request_not_modified_since(struct evhttp_request *req, time_t mtime)
{
struct evkeyvalq *input_headers;
struct evkeyvalq *output_headers;
char last_modified[1000];
const char *modified_since;
struct tm timebuf;
input_headers = evhttp_request_get_input_headers(req);
modified_since = evhttp_find_header(input_headers, "If-Modified-Since");
strftime(last_modified, sizeof(last_modified), "%a, %d %b %Y %H:%M:%S %Z", gmtime_r(&mtime, &timebuf));
// Return not modified, if given timestamp matches "If-Modified-Since" request header
if (modified_since && (strcasecmp(last_modified, modified_since) == 0))
return true;
// Add cache headers to allow client side caching
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Cache-Control", "private,no-cache,max-age=0");
evhttp_add_header(output_headers, "Last-Modified", last_modified);
return false;
}
void
httpd_response_not_cachable(struct evhttp_request *req)
{
struct evkeyvalq *output_headers;
output_headers = evhttp_request_get_output_headers(req);
// Remove potentially set cache control headers
evhttp_remove_header(output_headers, "Cache-Control");
evhttp_remove_header(output_headers, "Last-Modified");
evhttp_remove_header(output_headers, "ETag");
// Tell clients that they are not allowed to cache this response
evhttp_add_header(output_headers, "Cache-Control", "no-store");
}
static void
serve_file(struct evhttp_request *req, const char *uri)
{
char *ext;
char path[PATH_MAX];
char deref[PATH_MAX];
char *ctype;
struct evbuffer *evbuf;
struct evkeyvalq *output_headers;
struct stat sb;
int fd;
int i;
uint8_t buf[4096];
bool slashed;
int ret;
/* Check authentication */
if (!httpd_admin_check_auth(req))
return;
ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, uri);
if ((ret < 0) || (ret >= sizeof(path)))
{
DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri);
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
if (!realpath(path, deref))
{
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
if (strlen(deref) >= PATH_MAX)
{
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
ret = lstat(deref, &sb);
if (ret < 0)
{
DPRINTF(E_WARN, L_HTTPD, "Could not lstat() %s: %s\n", deref, strerror(errno));
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
if (S_ISDIR(sb.st_mode))
{
slashed = (path[strlen(path) - 1] == '/');
strncat(path, ((slashed) ? "index.html" : "/index.html"), sizeof(path) - strlen(path) - 1);
if (!realpath(path, deref))
{
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
if (strlen(deref) >= PATH_MAX)
{
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
ret = stat(deref, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno));
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
}
if (path_is_legal(deref) != 0)
{
DPRINTF(E_WARN, L_HTTPD, "Access to file outside the web root dir forbidden: %s\n", deref);
httpd_send_error(req, 403, "Forbidden");
return;
}
if (httpd_request_not_modified_since(req, sb.st_mtime))
{
httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP);
return;
}
evbuf = evbuffer_new();
if (!evbuf)
{
DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n");
httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
return;
}
fd = open(deref, O_RDONLY);
if (fd < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", deref, strerror(errno));
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
evbuffer_free(evbuf);
return;
}
ret = evbuffer_expand(evbuf, sb.st_size);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Out of memory for htdocs-file\n");
goto out_fail;
}
while ((ret = read(fd, buf, sizeof(buf))) > 0)
evbuffer_add(evbuf, buf, ret);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n");
goto out_fail;
}
ctype = "application/octet-stream";
ext = strrchr(path, '.');
if (ext)
{
for (i = 0; ext2ctype[i].ext; i++)
{
if (strcmp(ext, ext2ctype[i].ext) == 0)
{
ctype = ext2ctype[i].ctype;
break;
}
}
}
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Content-Type", ctype);
httpd_send_reply(req, HTTP_OK, "OK", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
close(fd);
return;
out_fail:
httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
evbuffer_free(evbuf);
close(fd);
}
/* ---------------------------- STREAM HANDLING ----------------------------- */
static void
stream_end(struct stream_ctx *st, int failed)
{
struct evhttp_connection *evcon;
evcon = evhttp_request_get_connection(st->req);
if (evcon)
evhttp_connection_set_closecb(evcon, NULL, NULL);
if (!failed)
evhttp_send_reply_end(st->req);
evbuffer_free(st->evbuf);
event_free(st->ev);
if (st->xcode)
transcode_cleanup(&st->xcode);
else
{
free(st->buf);
close(st->fd);
}
free(st);
}
static void
stream_end_register(struct stream_ctx *st)
{
if (!st->marked
&& (st->stream_size > ((st->size * 50) / 100))
&& (st->offset > ((st->size * 80) / 100)))
{
st->marked = 1;
worker_execute(playcount_inc_cb, &st->id, sizeof(int), 0);
#ifdef LASTFM
worker_execute(scrobble_cb, &st->id, sizeof(int), 1);
#endif
}
}
static void
stream_chunk_resched_cb(struct evhttp_connection *evcon, void *arg)
{
struct stream_ctx *st;
struct timeval tv;
int ret;
st = (struct stream_ctx *)arg;
evutil_timerclear(&tv);
ret = event_add(st->ev, &tv);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n");
stream_end(st, 0);
}
}
static void
stream_chunk_xcode_cb(int fd, short event, void *arg)
{
struct stream_ctx *st;
struct timeval tv;
int xcoded;
int ret;
st = (struct stream_ctx *)arg;
xcoded = transcode(st->evbuf, NULL, st->xcode, STREAM_CHUNK_SIZE);
if (xcoded <= 0)
{
if (xcoded == 0)
DPRINTF(E_INFO, L_HTTPD, "Done streaming transcoded file id %d\n", st->id);
else
DPRINTF(E_LOG, L_HTTPD, "Transcoding error, file id %d\n", st->id);
stream_end(st, 0);
return;
}
DPRINTF(E_DBG, L_HTTPD, "Got %d bytes from transcode; streaming file id %d\n", xcoded, st->id);
/* Consume transcoded data until we meet start_offset */
if (st->start_offset > st->offset)
{
ret = st->start_offset - st->offset;
if (ret < xcoded)
{
evbuffer_drain(st->evbuf, ret);
st->offset += ret;
ret = xcoded - ret;
}
else
{
evbuffer_drain(st->evbuf, xcoded);
st->offset += xcoded;
goto consume;
}
}
else
ret = xcoded;
evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
st->offset += ret;
stream_end_register(st);
return;
consume: /* reschedule immediately - consume up to start_offset */
evutil_timerclear(&tv);
ret = event_add(st->ev, &tv);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming (xcode)\n");
stream_end(st, 0);
return;
}
}
static void
stream_chunk_raw_cb(int fd, short event, void *arg)
{
struct stream_ctx *st;
size_t chunk_size;
int ret;
st = (struct stream_ctx *)arg;
if (st->end_offset && (st->offset > st->end_offset))
{
stream_end(st, 0);
return;
}
if (st->end_offset && ((st->offset + STREAM_CHUNK_SIZE) > (st->end_offset + 1)))
chunk_size = st->end_offset + 1 - st->offset;
else
chunk_size = STREAM_CHUNK_SIZE;
ret = read(st->fd, st->buf, chunk_size);
if (ret <= 0)
{
if (ret == 0)
DPRINTF(E_INFO, L_HTTPD, "Done streaming file id %d\n", st->id);
else
DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id);
stream_end(st, 0);
return;
}
DPRINTF(E_DBG, L_HTTPD, "Read %d bytes; streaming file id %d\n", ret, st->id);
evbuffer_add(st->evbuf, st->buf, ret);
evhttp_send_reply_chunk_with_cb(st->req, st->evbuf, stream_chunk_resched_cb, st);
st->offset += ret;
stream_end_register(st);
}
static void
stream_fail_cb(struct evhttp_connection *evcon, void *arg)
{
struct stream_ctx *st;
st = (struct stream_ctx *)arg;
DPRINTF(E_WARN, L_HTTPD, "Connection failed; stopping streaming of file ID %d\n", st->id);
/* Stop streaming */
event_del(st->ev);
stream_end(st, 1);
}
/* ---------------------------- MAIN HTTPD THREAD --------------------------- */
static void *
httpd(void *arg)
{
int ret;
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Error: DB init failed\n");
pthread_exit(NULL);
}
event_base_dispatch(evbase_httpd);
if (!httpd_exit)
DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n");
db_perthread_deinit();
pthread_exit(NULL);
}
static void
exit_cb(int fd, short event, void *arg)
{
event_base_loopbreak(evbase_httpd);
httpd_exit = 1;
}
static void
httpd_gen_cb(struct evhttp_request *req, void *arg)
{
struct evkeyvalq *input_headers;
struct evkeyvalq *output_headers;
struct httpd_request hreq;
struct httpd_uri_parsed *parsed;
struct httpd_module *m;
const char *uri;
// 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;
// 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") )
{
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
evhttp_add_header(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
evhttp_add_header(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;
}
uri = evhttp_request_get_uri(req);
if (!uri)
{
DPRINTF(E_WARN, L_HTTPD, "No URI in request\n");
httpd_redirect_to(req, "/");
return;
}
parsed = httpd_uri_parse(uri);
if (!parsed || !parsed->path)
{
httpd_redirect_to(req, "/");
goto out;
}
if (strcmp(parsed->path, "/") == 0)
{
goto serve_file;
}
m = modules_search(parsed->path);
if (m)
{
httpd_request_parse(&hreq, req, parsed, NULL, m->handlers);
m->request(&hreq);
goto out;
}
DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri);
/* Serve web interface files */
serve_file:
serve_file(req, parsed->path);
out:
httpd_uri_free(parsed);
}
/* ------------------------------- HTTPD API -------------------------------- */
void
httpd_uri_free(struct httpd_uri_parsed *parsed)
{
int i;
if (!parsed)
return;
free(parsed->uri_decoded);
free(parsed->path);
for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
{
free(parsed->path_parts[i]);
}
evhttp_clear_headers(&(parsed->ev_query));
if (parsed->ev_uri)
evhttp_uri_free(parsed->ev_uri);
free(parsed);
}
struct httpd_uri_parsed *
httpd_uri_parse(const char *uri)
{
struct httpd_uri_parsed *parsed;
char *path = NULL;
const char *query;
char *path_part;
char *ptr;
int i;
int ret;
CHECK_NULL(L_HTTPD, parsed = calloc(1, sizeof(struct httpd_uri_parsed)));
parsed->uri = uri;
parsed->ev_uri = evhttp_uri_parse_with_flags(parsed->uri, EVHTTP_URI_NONCONFORMANT);
if (!parsed->ev_uri)
{
DPRINTF(E_LOG, L_HTTPD, "Could not parse request: '%s'\n", parsed->uri);
goto error;
}
parsed->uri_decoded = evhttp_uridecode(parsed->uri, 0, NULL);
if (!parsed->uri_decoded)
{
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode request: '%s'\n", parsed->uri);
goto error;
}
query = evhttp_uri_get_query(parsed->ev_uri);
if (query && strchr(query, '='))
{
ret = evhttp_parse_query_str(query, &(parsed->ev_query));
if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Invalid query '%s' in request: '%s'\n", query, parsed->uri);
goto error;
}
}
path = strdup(evhttp_uri_get_path(parsed->ev_uri));
if (!path)
{
DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri);
return parsed;
}
parsed->path = evhttp_uridecode(path, 0, NULL);
if (!parsed->path)
{
DPRINTF(E_LOG, L_HTTPD, "Could not URI decode path: '%s'\n", path);
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)
{
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
DPRINTF(E_LOG, L_HTTPD, "URI path has too many components (%d): '%s'\n", i, parsed->path);
goto error;
}
free(path);
return parsed;
error:
free(path);
httpd_uri_free(parsed);
return NULL;
}
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)
{
;
struct evhttp_connection *evcon;
struct evkeyvalq *headers;
struct httpd_uri_map *uri;
int req_method;
int ret;
memset(hreq, 0, sizeof(struct httpd_request));
// Note req is allowed to be NULL
hreq->req = req;
hreq->uri = uri_parsed->uri;
hreq->uri_parsed = uri_parsed;
hreq->query = &(uri_parsed->ev_query);
req_method = 0;
if (req)
{
headers = evhttp_request_get_input_headers(req);
hreq->user_agent = evhttp_find_header(headers, "User-Agent");
evcon = evhttp_request_get_connection(req);
if (evcon)
httpd_peer_get(&hreq->peer_address, &hreq->peer_port, evcon);
else
DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
req_method = evhttp_request_get_command(req);
}
if (user_agent)
hreq->user_agent = user_agent;
// Find a handler for the path
for (uri = uri_map; uri->handler; uri++)
{
// Check if handler supports the current http request method
if (uri->method && req_method && !(req_method & uri->method))
continue;
ret = regexec(uri->preg, uri_parsed->path, 0, NULL, 0);
if (ret != 0)
continue;
hreq->handler = uri->handler;
return 0; // Success
}
// Handler not found, that's an error
return -1;
}
/* Thread: httpd */
void
httpd_stream_file(struct evhttp_request *req, int id)
{
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 };
struct media_file_info *mfi;
struct stream_ctx *st;
void (*stream_cb)(int fd, short event, void *arg);
struct stat sb;
struct timeval tv;
struct evhttp_connection *evcon;
struct evkeyvalq *input_headers;
struct evkeyvalq *output_headers;
const char *param;
const char *param_end;
const char *ua;
const char *client_codecs;
char buf[64];
int64_t offset;
int64_t end_offset;
off_t pos;
int transcode;
int ret;
offset = 0;
end_offset = 0;
input_headers = evhttp_request_get_input_headers(req);
param = evhttp_find_header(input_headers, "Range");
if (param)
{
DPRINTF(E_DBG, L_HTTPD, "Found Range header: %s\n", param);
/* Start offset */
ret = safe_atoi64(param + strlen("bytes="), &offset);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Invalid start offset, will stream whole file (%s)\n", param);
offset = 0;
}
/* End offset, if any */
else
{
param_end = strchr(param, '-');
if (param_end && (strlen(param_end) > 1))
{
ret = safe_atoi64(param_end + 1, &end_offset);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Invalid end offset, will stream to end of file (%s)\n", param);
end_offset = 0;
}
if (end_offset < offset)
{
DPRINTF(E_LOG, L_HTTPD, "End offset < start offset, will stream to end of file (%" PRIi64 " < %" PRIi64 ")\n", end_offset, offset);
end_offset = 0;
}
}
}
}
mfi = db_file_fetch_byid(id);
if (!mfi)
{
DPRINTF(E_LOG, L_HTTPD, "Item %d not found\n", id);
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
return;
}
if (mfi->data_kind != DATA_KIND_FILE)
{
DPRINTF(E_LOG, L_HTTPD, "Could not serve '%s' to client, not a file\n", mfi->path);
evhttp_send_error(req, 500, "Cannot stream non-file content");
goto out_free_mfi;
}
st = (struct stream_ctx *)malloc(sizeof(struct stream_ctx));
if (!st)
{
DPRINTF(E_LOG, L_HTTPD, "Out of memory for struct stream_ctx\n");
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_free_mfi;
}
memset(st, 0, sizeof(struct stream_ctx));
st->fd = -1;
ua = evhttp_find_header(input_headers, "User-Agent");
client_codecs = evhttp_find_header(input_headers, "Accept-Codecs");
transcode = transcode_needed(ua, client_codecs, mfi->codectype);
output_headers = evhttp_request_get_output_headers(req);
if (transcode)
{
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
stream_cb = stream_chunk_xcode_cb;
st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
if (!st->xcode)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_free_st;
}
if (!evhttp_find_header(output_headers, "Content-Type"))
evhttp_add_header(output_headers, "Content-Type", "audio/wav");
}
else
{
/* Stream the raw file */
DPRINTF(E_INFO, L_HTTPD, "Preparing to stream %s\n", mfi->path);
st->buf = (uint8_t *)malloc(STREAM_CHUNK_SIZE);
if (!st->buf)
{
DPRINTF(E_LOG, L_HTTPD, "Out of memory for raw streaming buffer\n");
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_free_st;
}
stream_cb = stream_chunk_raw_cb;
st->fd = open(mfi->path, O_RDONLY);
if (st->fd < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
goto out_cleanup;
}
ret = stat(mfi->path, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
goto out_cleanup;
}
st->size = sb.st_size;
pos = lseek(st->fd, offset, SEEK_SET);
if (pos == (off_t) -1)
{
DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
goto out_cleanup;
}
st->offset = offset;
st->end_offset = end_offset;
/* Content-Type for video files is different than for audio files
* and overrides whatever may have been set previously, like
* application/x-dmap-tagged when we're speaking DAAP.
*/
if (mfi->has_video)
{
/* Front Row and others expect video/<type> */
ret = snprintf(buf, sizeof(buf), "video/%s", mfi->type);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
else
{
evhttp_remove_header(output_headers, "Content-Type");
evhttp_add_header(output_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)
{
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
evhttp_add_header(output_headers, "Content-Type", buf);
}
}
st->evbuf = evbuffer_new();
if (!st->evbuf)
{
DPRINTF(E_LOG, L_HTTPD, "Could not allocate an evbuffer for streaming\n");
evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
}
ret = evbuffer_expand(st->evbuf, STREAM_CHUNK_SIZE);
if (ret != 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not expand evbuffer for streaming\n");
evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
}
st->ev = event_new(evbase_httpd, -1, EV_TIMEOUT, stream_cb, st);
evutil_timerclear(&tv);
if (!st->ev || (event_add(st->ev, &tv) < 0))
{
DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");
evhttp_clear_headers(output_headers);
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
goto out_cleanup;
}
st->id = mfi->id;
st->start_offset = offset;
st->stream_size = st->size;
st->req = req;
if ((offset == 0) && (end_offset == 0))
{
/* If we are not decoding, send the Content-Length. We don't do
* that if we are decoding because we can only guesstimate the
* size in this case and the error margin is unknown and variable.
*/
if (!transcode)
{
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
else
evhttp_add_header(output_headers, "Content-Length", buf);
}
evhttp_send_reply_start(req, HTTP_OK, "OK");
}
else
{
if (offset > 0)
st->stream_size -= offset;
if (end_offset > 0)
st->stream_size -= (st->size - end_offset);
DPRINTF(E_DBG, L_HTTPD, "Stream request with range %" PRIi64 "-%" PRIi64 "\n", offset, end_offset);
ret = snprintf(buf, sizeof(buf), "bytes %" PRIi64 "-%" PRIi64 "/%" PRIi64,
offset, (end_offset) ? end_offset : (int64_t)st->size, (int64_t)st->size);
if ((ret < 0) || (ret >= sizeof(buf)))
DPRINTF(E_LOG, L_HTTPD, "Content-Range too large for buffer, dropping\n");
else
evhttp_add_header(output_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
evhttp_add_header(output_headers, "Content-Length", buf);
evhttp_send_reply_start(req, 206, "Partial Content");
}
#ifdef HAVE_POSIX_FADVISE
if (!transcode)
{
/* Hint the OS */
if ( (ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED)) != 0 ||
(ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_SEQUENTIAL)) != 0 ||
(ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_NOREUSE)) != 0 )
DPRINTF(E_DBG, L_HTTPD, "posix_fadvise() failed with error %d\n", ret);
}
#endif
evcon = evhttp_request_get_connection(req);
evhttp_connection_set_closecb(evcon, stream_fail_cb, st);
DPRINTF(E_INFO, L_HTTPD, "Kicking off streaming for %s\n", mfi->path);
free_mfi(mfi, 0);
return;
out_cleanup:
if (st->evbuf)
evbuffer_free(st->evbuf);
if (st->xcode)
transcode_cleanup(&st->xcode);
if (st->buf)
free(st->buf);
if (st->fd > 0)
close(st->fd);
out_free_st:
free(st);
out_free_mfi:
free_mfi(mfi, 0);
}
struct evbuffer *
httpd_gzip_deflate(struct evbuffer *in)
{
struct evbuffer *out;
struct evbuffer_iovec iovec[1];
z_stream strm;
int ret;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// Just to keep Coverity from complaining about uninitialized values
strm.total_in = 0;
strm.total_out = 0;
// Set up a gzip stream (the "+ 16" in 15 + 16), instead of a zlib stream (default)
ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
{
DPRINTF(E_LOG, L_HTTPD, "zlib setup failed: %s\n", zError(ret));
return NULL;
}
strm.next_in = evbuffer_pullup(in, -1);
strm.avail_in = evbuffer_get_length(in);
out = evbuffer_new();
if (!out)
{
DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for gzipped reply\n");
goto out_deflate_end;
}
// We use this to avoid a memcpy. The 512 is an arbitrary padding to make sure
// there is enough space, even if the compressed output should be slightly
// larger than input (could happen with small inputs).
ret = evbuffer_reserve_space(out, strm.avail_in + 512, iovec, 1);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Could not reserve memory for gzipped reply\n");
goto out_evbuf_free;
}
strm.next_out = iovec[0].iov_base;
strm.avail_out = iovec[0].iov_len;
ret = deflate(&strm, Z_FINISH);
if (ret != Z_STREAM_END)
goto out_evbuf_free;
iovec[0].iov_len -= strm.avail_out;
evbuffer_commit_space(out, iovec, 1);
deflateEnd(&strm);
return out;
out_evbuf_free:
evbuffer_free(out);
out_deflate_end:
deflateEnd(&strm);
return NULL;
}
void
httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *evbuf, enum httpd_send_flags flags)
{
struct evbuffer *gzbuf;
struct evkeyvalq *input_headers;
struct evkeyvalq *output_headers;
const char *param;
int do_gzip;
if (!req)
return;
input_headers = evhttp_request_get_input_headers(req);
output_headers = evhttp_request_get_output_headers(req);
do_gzip = ( (!(flags & HTTPD_SEND_NO_GZIP)) &&
evbuf && (evbuffer_get_length(evbuf) > 512) &&
(param = evhttp_find_header(input_headers, "Accept-Encoding")) &&
(strstr(param, "gzip") || strstr(param, "*"))
);
if (allow_origin)
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
if (do_gzip && (gzbuf = httpd_gzip_deflate(evbuf)))
{
DPRINTF(E_DBG, L_HTTPD, "Gzipping response\n");
evhttp_add_header(output_headers, "Content-Encoding", "gzip");
evhttp_send_reply(req, code, reason, gzbuf);
evbuffer_free(gzbuf);
// Drain original buffer, as would be after evhttp_send_reply()
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
}
else
{
evhttp_send_reply(req, code, reason, evbuf);
}
}
// This is a modified version of evhttp_send_error (credit libevent)
void
httpd_send_error(struct evhttp_request* req, int error, const char* reason)
{
struct evkeyvalq *output_headers;
struct evbuffer *evbuf;
if (!allow_origin)
{
evhttp_send_error(req, error, reason);
return;
}
output_headers = evhttp_request_get_output_headers(req);
evhttp_clear_headers(output_headers);
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
evhttp_add_header(output_headers, "Content-Type", "text/html");
evhttp_add_header(output_headers, "Connection", "close");
evbuf = evbuffer_new();
if (!evbuf)
DPRINTF(E_LOG, L_HTTPD, "Could not allocate evbuffer for error page\n");
else
evbuffer_add_printf(evbuf, ERR_PAGE, error, reason, reason);
evhttp_send_reply(req, error, reason, evbuf);
if (evbuf)
evbuffer_free(evbuf);
}
bool
httpd_admin_check_auth(struct evhttp_request *req)
{
struct evhttp_connection *evcon;
const char *addr;
uint16_t port;
const char *passwd;
int ret;
evcon = evhttp_request_get_connection(req);
if (!evcon)
{
DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
return false;
}
httpd_peer_get(&addr, &port, evcon);
if (net_peer_address_is_trusted(addr))
return true;
passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password");
if (!passwd)
{
DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: No password set in the config\n", evhttp_request_get_uri(req));
httpd_send_error(req, 403, "Forbidden");
return false;
}
DPRINTF(E_DBG, L_HTTPD, "Checking web interface authentication\n");
ret = httpd_basic_auth(req, "admin", passwd, PACKAGE " web interface");
if (ret != 0)
{
DPRINTF(E_LOG, L_HTTPD, "Web interface request to '%s' denied: Incorrect password\n", evhttp_request_get_uri(req));
// httpd_basic_auth has sent a reply
return false;
}
DPRINTF(E_DBG, L_HTTPD, "Authentication successful\n");
return true;
}
int
httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm)
{
struct evbuffer *evbuf;
struct evkeyvalq *headers;
char header[256];
const char *auth;
char *authuser;
char *authpwd;
int ret;
headers = evhttp_request_get_input_headers(req);
auth = evhttp_find_header(headers, "Authorization");
if (!auth)
{
DPRINTF(E_DBG, L_HTTPD, "No Authorization header\n");
goto need_auth;
}
if (strncmp(auth, "Basic ", strlen("Basic ")) != 0)
{
DPRINTF(E_LOG, L_HTTPD, "Bad Authentication header\n");
goto need_auth;
}
auth += strlen("Basic ");
authuser = (char *)b64_decode(NULL, auth);
if (!authuser)
{
DPRINTF(E_LOG, L_HTTPD, "Could not decode Authentication header\n");
goto need_auth;
}
authpwd = strchr(authuser, ':');
if (!authpwd)
{
DPRINTF(E_LOG, L_HTTPD, "Malformed Authentication header\n");
free(authuser);
goto need_auth;
}
*authpwd = '\0';
authpwd++;
if (user)
{
if (strcmp(user, authuser) != 0)
{
DPRINTF(E_LOG, L_HTTPD, "Username mismatch\n");
free(authuser);
goto need_auth;
}
}
if (strcmp(passwd, authpwd) != 0)
{
DPRINTF(E_LOG, L_HTTPD, "Bad password\n");
free(authuser);
goto need_auth;
}
free(authuser);
return 0;
need_auth:
ret = snprintf(header, sizeof(header), "Basic realm=\"%s\"", realm);
if ((ret < 0) || (ret >= sizeof(header)))
{
httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
evbuf = evbuffer_new();
if (!evbuf)
{
httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "WWW-Authenticate", header);
evbuffer_add_printf(evbuf, ERR_PAGE, 401, "Unauthorized", "Authorization required");
httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP);
evbuffer_free(evbuf);
return -1;
}
void
httpd_peer_get(const char **address, ev_uint16_t *port, struct evhttp_connection *evcon)
{
#ifdef HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR
evhttp_connection_get_peer(evcon, address, port);
#else
evhttp_connection_get_peer(evcon, (char **)address, port);
#endif
}
/* Thread: main */
int
httpd_init(const char *webroot)
{
struct stat sb;
int ret;
httpd_exit = 0;
DPRINTF(E_DBG, L_HTTPD, "Starting web server with root directory '%s'\n", webroot);
ret = stat(webroot, &sb);
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;
}
evbase_httpd = event_base_new();
if (!evbase_httpd)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not create an event base\n");
return -1;
}
#ifdef HAVE_LIBWEBSOCKETS
ret = websocket_init();
if (ret < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Websocket init failed\n");
goto websocket_fail;
}
#endif
ret = modules_init();
if (ret < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Modules init failed\n");
goto modules_fail;
}
#ifdef HAVE_EVENTFD
exit_efd = eventfd(0, EFD_CLOEXEC);
if (exit_efd < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not create eventfd: %s\n", strerror(errno));
goto pipe_fail;
}
exitev = event_new(evbase_httpd, exit_efd, EV_READ, exit_cb, NULL);
#else
# ifdef HAVE_PIPE2
ret = pipe2(exit_pipe, O_CLOEXEC);
# else
ret = pipe(exit_pipe);
# endif
if (ret < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno));
goto pipe_fail;
}
exitev = event_new(evbase_httpd, exit_pipe[0], EV_READ, exit_cb, NULL);
#endif /* HAVE_EVENTFD */
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)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server\n");
goto evhttpd_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);
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;
}
thread_setname(tid_httpd, "httpd");
return 0;
thread_fail:
bind_fail:
evhttp_free(evhttpd);
evhttpd_fail:
event_free(exitev);
exitev_fail:
#ifdef HAVE_EVENTFD
close(exit_efd);
#else
close(exit_pipe[0]);
close(exit_pipe[1]);
#endif
pipe_fail:
modules_fail:
modules_deinit();
#ifdef HAVE_LIBWEBSOCKETS
websocket_deinit();
websocket_fail:
#endif
event_base_free(evbase_httpd);
return -1;
}
/* Thread: main */
void
httpd_deinit(void)
{
int ret;
#ifdef HAVE_EVENTFD
ret = eventfd_write(exit_efd, 1);
if (ret < 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not send exit event: %s\n", strerror(errno));
return;
}
#else
int dummy = 42;
ret = write(exit_pipe[1], &dummy, sizeof(dummy));
if (ret != sizeof(dummy))
{
DPRINTF(E_FATAL, L_HTTPD, "Could not write to exit fd: %s\n", strerror(errno));
return;
}
#endif
ret = pthread_join(tid_httpd, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_HTTPD, "Could not join HTTPd thread: %s\n", strerror(errno));
return;
}
modules_deinit();
#ifdef HAVE_LIBWEBSOCKETS
websocket_deinit();
#endif
#ifdef HAVE_EVENTFD
close(exit_efd);
#else
close(exit_pipe[0]);
close(exit_pipe[1]);
#endif
event_free(exitev);
evhttp_free(evhttpd);
event_base_free(evbase_httpd);
}