mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
Introduce new evhttp-based HTTP server
This commit is contained in:
parent
13c06803f8
commit
095350e6eb
@ -20,6 +20,7 @@ mt_daapd_SOURCES = main.c daapd.h webserver.c \
|
||||
filescanner.c filescanner.h \
|
||||
filescanner_ffmpeg.c filescanner_urlfile.c filescanner_m3u.c \
|
||||
mdns_avahi.c mdns_avahi.h \
|
||||
httpd.c httpd.h \
|
||||
db-generic.c db-generic.h ff-plugins.c ff-plugins.h \
|
||||
scan-wma.c \
|
||||
smart-parser.c smart-parser.h xml-rpc.c xml-rpc.h \
|
||||
|
401
src/httpd.c
Normal file
401
src/httpd.c
Normal file
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 <event.h>
|
||||
#include <evhttp.h>
|
||||
|
||||
#include "daapd.h"
|
||||
#include "err.h"
|
||||
#include "conffile.h"
|
||||
#include "httpd.h"
|
||||
|
||||
|
||||
#define WEBFACE_ROOT "/usr/share/mt-daapd/admin-root/"
|
||||
|
||||
struct content_type_map {
|
||||
char *ext;
|
||||
char *ctype;
|
||||
};
|
||||
|
||||
|
||||
static 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 int exit_pipe[2];
|
||||
static int httpd_exit;
|
||||
static struct event_base *evbase_httpd;
|
||||
static struct event exitev;
|
||||
static struct evhttp *evhttpd;
|
||||
static pthread_t tid_httpd;
|
||||
|
||||
|
||||
/* Thread: httpd */
|
||||
static int
|
||||
path_is_legal(char *path)
|
||||
{
|
||||
return strncmp(WEBFACE_ROOT, path, strlen(WEBFACE_ROOT));
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
redirect_to_index(struct evhttp_request *req, char *uri)
|
||||
{
|
||||
char buf[256];
|
||||
int slashed;
|
||||
int ret;
|
||||
|
||||
slashed = (uri[strlen(uri) - 1] == '/');
|
||||
|
||||
ret = snprintf(buf, sizeof(buf), "%s%sindex.html", uri, (slashed) ? "" : "/");
|
||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Redirection URL exceeds buffer length\n");
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
evhttp_add_header(req->output_headers, "Location", buf);
|
||||
evhttp_send_reply(req, HTTP_MOVETEMP, "Moved", NULL);
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
serve_file(struct evhttp_request *req, char *uri)
|
||||
{
|
||||
char *ext;
|
||||
char path[PATH_MAX];
|
||||
char *deref;
|
||||
char *ctype;
|
||||
struct evbuffer *evbuf;
|
||||
struct stat sb;
|
||||
int fd;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */
|
||||
if ((ret < 0) || (ret >= sizeof(path)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Request exceeds PATH_MAX: %s\n", uri);
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ret = lstat(path, &sb);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not lstat() %s: %s\n", path, strerror(errno));
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (S_ISDIR(sb.st_mode))
|
||||
{
|
||||
redirect_to_index(req, uri);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (S_ISLNK(sb.st_mode))
|
||||
{
|
||||
deref = realpath(path, NULL);
|
||||
if (!deref)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(deref) + 1 > PATH_MAX)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Dereferenced path exceeds PATH_MAX: %s\n", path);
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
|
||||
free(deref);
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(path, deref);
|
||||
free(deref);
|
||||
|
||||
ret = stat(path, &sb);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno));
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (S_ISDIR(sb.st_mode))
|
||||
{
|
||||
redirect_to_index(req, uri);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (path_is_legal(path) != 0)
|
||||
{
|
||||
evhttp_send_error(req, 403, "Forbidden");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
evbuf = evbuffer_new();
|
||||
if (!evbuf)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not create evbuffer\n");
|
||||
|
||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
|
||||
return;
|
||||
}
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", path, strerror(errno));
|
||||
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = evbuffer_read(evbuf, fd, sb.st_size);
|
||||
close(fd);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not read file into evbuffer\n");
|
||||
|
||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal error");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evhttp_add_header(req->output_headers, "Content-Type", ctype);
|
||||
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||
|
||||
evbuffer_free(evbuf);
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
webface_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
const char *req_uri;
|
||||
char *uri;
|
||||
char *ptr;
|
||||
|
||||
req_uri = evhttp_request_uri(req);
|
||||
if (!req_uri)
|
||||
{
|
||||
redirect_to_index(req, "/");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uri = strdup(req_uri);
|
||||
ptr = strchr(uri, '?');
|
||||
if (ptr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_HTTPD, "Found query string\n");
|
||||
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
ptr = uri;
|
||||
uri = evhttp_decode_uri(uri);
|
||||
free(ptr);
|
||||
|
||||
serve_file(req, uri);
|
||||
|
||||
free(uri);
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
static void *
|
||||
httpd(void *arg)
|
||||
{
|
||||
event_base_dispatch(evbase_httpd);
|
||||
|
||||
if (!httpd_exit)
|
||||
DPRINTF(E_FATAL, L_HTTPD, "HTTPd event loop terminated ahead of time!\n");
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
/* Thread: httpd */
|
||||
static void
|
||||
exit_cb(int fd, short event, void *arg)
|
||||
{
|
||||
event_base_loopbreak(evbase_httpd);
|
||||
|
||||
httpd_exit = 1;
|
||||
}
|
||||
|
||||
/* Thread: main */
|
||||
int
|
||||
httpd_init(void)
|
||||
{
|
||||
unsigned short port;
|
||||
int bindv6;
|
||||
int ret;
|
||||
|
||||
httpd_exit = 0;
|
||||
|
||||
ret = pipe2(exit_pipe, O_CLOEXEC);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_HTTPD, "Could not create pipe: %s\n", strerror(errno));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
evbase_httpd = event_base_new();
|
||||
if (!evbase_httpd)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_HTTPD, "Could not create an event base\n");
|
||||
|
||||
goto evbase_fail;
|
||||
}
|
||||
|
||||
event_set(&exitev, exit_pipe[0], EV_READ, exit_cb, NULL);
|
||||
event_base_set(evbase_httpd, &exitev);
|
||||
event_add(&exitev, NULL);
|
||||
|
||||
evhttpd = evhttp_new(evbase_httpd);
|
||||
if (!evhttpd)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_HTTPD, "Could not create HTTP server\n");
|
||||
|
||||
goto evhttp_fail;
|
||||
}
|
||||
|
||||
port = cfg_getint(cfg_getnsec(cfg, "library", 0), "port") /* tmp */ + 1;
|
||||
|
||||
/* evhttp doesn't support IPv6 yet, so this is expected to fail */
|
||||
bindv6 = evhttp_bind_socket(evhttpd, "::", port);
|
||||
if (bindv6 < 0)
|
||||
DPRINTF(E_INF, L_HTTPD, "Could not bind IN6ADDR_ANY:%d (that's OK)\n", port);
|
||||
|
||||
ret = evhttp_bind_socket(evhttpd, "0.0.0.0", port);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (bindv6 < 0)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_HTTPD, "Could not bind INADDR_ANY:%d\n", port);
|
||||
|
||||
goto bind_fail;
|
||||
}
|
||||
}
|
||||
|
||||
evhttp_set_gencb(evhttpd, webface_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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
thread_fail:
|
||||
bind_fail:
|
||||
evhttp_free(evhttpd);
|
||||
evhttp_fail:
|
||||
event_base_free(evbase_httpd);
|
||||
evbase_fail:
|
||||
close(exit_pipe[0]);
|
||||
close(exit_pipe[1]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Thread: main */
|
||||
void
|
||||
httpd_deinit(void)
|
||||
{
|
||||
int dummy = 42;
|
||||
int ret;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = pthread_join(tid_httpd, NULL);
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_HTTPD, "Could not join HTTPd thread: %s\n", strerror(errno));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
close(exit_pipe[0]);
|
||||
close(exit_pipe[1]);
|
||||
evhttp_free(evhttpd);
|
||||
event_base_free(evbase_httpd);
|
||||
}
|
11
src/httpd.h
Normal file
11
src/httpd.h
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
#ifndef __HTTPD_H__
|
||||
#define __HTTPD_H__
|
||||
|
||||
int
|
||||
httpd_init(void);
|
||||
|
||||
void
|
||||
httpd_deinit(void);
|
||||
|
||||
#endif /* !__HTTPD_H__ */
|
Loading…
Reference in New Issue
Block a user