Bump uhttpd version 2016-10-25

This commit is contained in:
jbnadal
2017-03-20 18:32:22 +01:00
parent bd8184c724
commit 6e84d9aea6
23 changed files with 5604 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
config BR2_PACKAGE_LIBUBOX
bool "libubox"
depends on !BR2_STATIC_LIBS
select BR2_PACKAGE_JSON_C
help
This library originates from the OpenWrt project to
handle the configuration file infrastructure, but can
also be used for the same purposes in projects other
than OpenWrt.
http://nbd.name/gitweb.cgi?p=luci2/libubox.git;a=summary
comment "libubox needs a toolchain w/ dynamic library"
depends on BR2_STATIC_LIBS

View File

@@ -0,0 +1,24 @@
################################################################################
#
# LIB UBOX
#
################################################################################
UHTTPD_VERSION:= 2016-10-25
UHTTPD_SITE = $(TOPDIR)/../../src/3P/libubox
UHTTPD_SITE_METHOD = local
UHTTPD_LICENSE = LGPLv2.1, GPLv2, BSD-3c, MIT
UHTTPD_INSTALL_STAGING = YES
UHTTPD_DEPENDENCIES = json-c
UHTTPD_CONF = SRC_DIR=$(TOPDIR)/../..
UHTTPD_CONF_OPTS +=-DBUILD_LUA=OFF
UHTTPD_CONF_ENV = $(UHTTPD_CONF)
UHTTPD_MAKE_ENV = $(UHTTPD_CONF)
UHTTPD_CONF_OPTS += -DMODULE_PATH=$(TOPDIR)/../../bsp/cmake-modules -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
$(eval $(cmake-package))

View File

@@ -0,0 +1,88 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(uhttpd C)
INCLUDE (CheckFunctionExists)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations --std=gnu99 -g3)
OPTION(TLS_SUPPORT "TLS support" ON)
OPTION(LUA_SUPPORT "Lua support" ON)
OPTION(UBUS_SUPPORT "ubus support" ON)
IF(APPLE)
INCLUDE_DIRECTORIES(/opt/local/include)
LINK_DIRECTORIES(/opt/local/lib)
ENDIF()
FIND_LIBRARY(LIBS crypt)
IF(LIBS STREQUAL "LIBS-NOTFOUND")
SET(LIBS "")
ENDIF()
FIND_PATH(ubox_include_dir libubox/usock.h)
INCLUDE_DIRECTORIES(${ubox_include_dir})
SET(SOURCES main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c plugin.c handler.c)
IF(TLS_SUPPORT)
SET(SOURCES ${SOURCES} tls.c)
ADD_DEFINITIONS(-DHAVE_TLS)
ENDIF()
CHECK_FUNCTION_EXISTS(getspnam HAVE_SHADOW)
IF(HAVE_SHADOW)
ADD_DEFINITIONS(-DHAVE_SHADOW)
ENDIF()
ADD_EXECUTABLE(uhttpd ${SOURCES})
FIND_LIBRARY(libjson NAMES json-c json)
TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
SET(PLUGINS "")
IF(LUA_SUPPORT)
FIND_PROGRAM(PKG_CONFIG pkg-config)
IF(NOT LUA_CFLAGS AND PKG_CONFIG)
EXECUTE_PROCESS(
COMMAND pkg-config --silence-errors --cflags lua5.1
OUTPUT_VARIABLE LUA_CFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE
)
ENDIF()
IF(NOT LUA_LIBS AND PKG_CONFIG)
EXECUTE_PROCESS(
COMMAND pkg-config --silence-errors --libs lua5.1
OUTPUT_VARIABLE LUA_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE
)
ENDIF()
IF(NOT LUA_LIBS)
SET(LUA_LIBS "lua")
ENDIF()
SET(PLUGINS ${PLUGINS} uhttpd_lua)
ADD_DEFINITIONS(-DHAVE_LUA ${LUA_CFLAGS})
ADD_LIBRARY(uhttpd_lua MODULE lua.c)
TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl)
ENDIF()
IF(UBUS_SUPPORT)
SET(PLUGINS ${PLUGINS} uhttpd_ubus)
ADD_DEFINITIONS(-DHAVE_UBUS)
ADD_LIBRARY(uhttpd_ubus MODULE ubus.c)
TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json ${libjson})
ENDIF()
IF(PLUGINS)
SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES
PREFIX ""
)
ENDIF()
INSTALL(TARGETS uhttpd ${PLUGINS}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
)

135
src/3P/uhttpd/auth.c Normal file
View File

@@ -0,0 +1,135 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _GNU_SOURCE
#define _XOPEN_SOURCE 700
#include <strings.h>
#ifdef HAVE_SHADOW
#include <shadow.h>
#endif
#include "uhttpd.h"
static LIST_HEAD(auth_realms);
void uh_auth_add(const char *path, const char *user, const char *pass)
{
struct auth_realm *new = NULL;
struct passwd *pwd;
const char *new_pass = NULL;
char *dest_path, *dest_user, *dest_pass;
#ifdef HAVE_SHADOW
struct spwd *spwd;
#endif
/* given password refers to a passwd entry */
if ((strlen(pass) > 3) && !strncmp(pass, "$p$", 3)) {
#ifdef HAVE_SHADOW
/* try to resolve shadow entry */
spwd = getspnam(&pass[3]);
if (spwd)
new_pass = spwd->sp_pwdp;
#endif
if (!new_pass) {
pwd = getpwnam(&pass[3]);
if (pwd && pwd->pw_passwd && pwd->pw_passwd[0] &&
pwd->pw_passwd[0] != '!')
new_pass = pwd->pw_passwd;
}
} else {
new_pass = pass;
}
if (!new_pass || !new_pass[0])
return;
new = calloc_a(sizeof(*new),
&dest_path, strlen(path) + 1,
&dest_user, strlen(user) + 1,
&dest_pass, strlen(new_pass) + 1);
if (!new)
return;
new->path = strcpy(dest_path, path);
new->user = strcpy(dest_user, user);
new->pass = strcpy(dest_pass, new_pass);
list_add(&new->list, &auth_realms);
}
bool uh_auth_check(struct client *cl, struct path_info *pi)
{
struct http_request *req = &cl->request;
struct auth_realm *realm;
bool user_match = false;
char *user = NULL;
char *pass = NULL;
int plen;
if (pi->auth && !strncasecmp(pi->auth, "Basic ", 6)) {
const char *auth = pi->auth + 6;
uh_b64decode(uh_buf, sizeof(uh_buf), auth, strlen(auth));
pass = strchr(uh_buf, ':');
if (pass) {
user = uh_buf;
*pass++ = 0;
}
}
req->realm = NULL;
plen = strlen(pi->name);
list_for_each_entry(realm, &auth_realms, list) {
int rlen = strlen(realm->path);
if (plen < rlen)
continue;
if (strncasecmp(pi->name, realm->path, rlen) != 0)
continue;
req->realm = realm;
if (!user)
break;
if (strcmp(user, realm->user) != 0)
continue;
user_match = true;
break;
}
if (!req->realm)
return true;
if (user_match &&
(!strcmp(pass, realm->pass) ||
!strcmp(crypt(pass, realm->pass), realm->pass)))
return true;
uh_http_header(cl, 401, "Authorization Required");
ustream_printf(cl->us,
"WWW-Authenticate: Basic realm=\"%s\"\r\n"
"Content-Type: text/plain\r\n\r\n",
conf.realm);
uh_chunk_printf(cl, "Authorization Required\n");
uh_request_done(cl);
return false;
}

118
src/3P/uhttpd/cgi.c Normal file
View File

@@ -0,0 +1,118 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _GNU_SOURCE
#include <libubox/blobmsg.h>
#include "uhttpd.h"
static LIST_HEAD(interpreters);
void uh_interpreter_add(const char *ext, const char *path)
{
struct interpreter *in;
char *new_ext, *new_path;
in = calloc_a(sizeof(*in),
&new_ext, strlen(ext) + 1,
&new_path, strlen(path) + 1);
in->ext = strcpy(new_ext, ext);
in->path = strcpy(new_path, path);
list_add_tail(&in->list, &interpreters);
}
static void cgi_main(struct client *cl, struct path_info *pi, char *url)
{
const struct interpreter *ip = pi->ip;
struct env_var *var;
clearenv();
setenv("PATH", conf.cgi_path, 1);
for (var = uh_get_process_vars(cl, pi); var->name; var++) {
if (!var->value)
continue;
setenv(var->name, var->value, 1);
}
if (!chdir(pi->root)) {
if (ip)
execl(ip->path, ip->path, pi->phys, NULL);
else
execl(pi->phys, pi->phys, NULL);
}
printf("Status: 500 Internal Server Error\r\n\r\n"
"Unable to launch the requested CGI program:\n"
" %s: %s\n", ip ? ip->path : pi->phys, strerror(errno));
}
static void cgi_handle_request(struct client *cl, char *url, struct path_info *pi)
{
unsigned int mode = S_IFREG | S_IXOTH;
if (!pi->ip && !((pi->stat.st_mode & mode) == mode)) {
uh_client_error(cl, 403, "Forbidden",
"You don't have permission to access %s on this server.",
url);
return;
}
if (!uh_create_process(cl, pi, url, cgi_main)) {
uh_client_error(cl, 500, "Internal Server Error",
"Failed to create CGI process: %s", strerror(errno));
return;
}
return;
}
static bool check_cgi_path(struct path_info *pi, const char *url)
{
struct interpreter *ip;
const char *path = pi->phys;
int path_len = strlen(path);
list_for_each_entry(ip, &interpreters, list) {
int len = strlen(ip->ext);
if (len >= path_len)
continue;
if (strcmp(path + path_len - len, ip->ext) != 0)
continue;
pi->ip = ip;
return true;
}
pi->ip = NULL;
if (conf.cgi_docroot_path)
return uh_path_match(conf.cgi_docroot_path, pi->phys);
return false;
}
struct dispatch_handler cgi_dispatch = {
.script = true,
.check_path = check_cgi_path,
.handle_request = cgi_handle_request,
};

656
src/3P/uhttpd/client.c Normal file
View File

@@ -0,0 +1,656 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <libubox/blobmsg.h>
#include <ctype.h>
#include "uhttpd.h"
#include "tls.h"
static LIST_HEAD(clients);
static bool client_done = false;
int n_clients = 0;
struct config conf = {};
const char * const http_versions[] = {
[UH_HTTP_VER_0_9] = "HTTP/0.9",
[UH_HTTP_VER_1_0] = "HTTP/1.0",
[UH_HTTP_VER_1_1] = "HTTP/1.1",
};
const char * const http_methods[] = {
[UH_HTTP_MSG_GET] = "GET",
[UH_HTTP_MSG_POST] = "POST",
[UH_HTTP_MSG_HEAD] = "HEAD",
[UH_HTTP_MSG_OPTIONS] = "OPTIONS",
};
void uh_http_header(struct client *cl, int code, const char *summary)
{
struct http_request *r = &cl->request;
struct blob_attr *cur;
const char *enc = "Transfer-Encoding: chunked\r\n";
const char *conn;
int rem;
cl->http_code = code;
if (!uh_use_chunked(cl))
enc = "";
if (r->connection_close)
conn = "Connection: close";
else
conn = "Connection: Keep-Alive";
ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s",
http_versions[cl->request.version],
code, summary, conn, enc);
if (!r->connection_close)
ustream_printf(cl->us, "Keep-Alive: timeout=%d\r\n", conf.http_keepalive);
blobmsg_for_each_attr(cur, cl->hdr_response.head, rem)
ustream_printf(cl->us, "%s: %s\r\n", blobmsg_name(cur),
blobmsg_get_string(cur));
}
static void uh_connection_close(struct client *cl)
{
cl->state = CLIENT_STATE_CLOSE;
cl->us->eof = true;
ustream_state_change(cl->us);
}
static void uh_dispatch_done(struct client *cl)
{
if (cl->dispatch.free)
cl->dispatch.free(cl);
if (cl->dispatch.req_free)
cl->dispatch.req_free(cl);
}
static void client_timeout(struct uloop_timeout *timeout)
{
struct client *cl = container_of(timeout, struct client, timeout);
cl->state = CLIENT_STATE_CLOSE;
uh_connection_close(cl);
}
static void uh_set_client_timeout(struct client *cl, int timeout)
{
cl->timeout.cb = client_timeout;
uloop_timeout_set(&cl->timeout, timeout * 1000);
}
static void uh_keepalive_poll_cb(struct uloop_timeout *timeout)
{
struct client *cl = container_of(timeout, struct client, timeout);
int sec = cl->requests > 0 ? conf.http_keepalive : conf.network_timeout;
uh_set_client_timeout(cl, sec);
cl->us->notify_read(cl->us, 0);
}
static void uh_poll_connection(struct client *cl)
{
cl->timeout.cb = uh_keepalive_poll_cb;
uloop_timeout_set(&cl->timeout, 1);
}
void uh_request_done(struct client *cl)
{
uh_chunk_eof(cl);
uh_dispatch_done(cl);
blob_buf_init(&cl->hdr_response, 0);
memset(&cl->dispatch, 0, sizeof(cl->dispatch));
if (!conf.http_keepalive || cl->request.connection_close)
return uh_connection_close(cl);
cl->state = CLIENT_STATE_INIT;
cl->requests++;
uh_poll_connection(cl);
}
void __printf(4, 5)
uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...)
{
va_list arg;
uh_http_header(cl, code, summary);
ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
uh_chunk_printf(cl, "<h1>%s</h1>", summary);
if (fmt) {
va_start(arg, fmt);
uh_chunk_vprintf(cl, fmt, arg);
va_end(arg);
}
uh_request_done(cl);
}
static void uh_header_error(struct client *cl, int code, const char *summary)
{
uh_client_error(cl, code, summary, NULL);
uh_connection_close(cl);
}
static int find_idx(const char * const *list, int max, const char *str)
{
int i;
for (i = 0; i < max; i++)
if (!strcmp(list[i], str))
return i;
return -1;
}
static int client_parse_request(struct client *cl, char *data)
{
struct http_request *req = &cl->request;
char *type, *path, *version;
int h_method, h_version;
type = strtok(data, " ");
path = strtok(NULL, " ");
version = strtok(NULL, " ");
if (!type || !path || !version)
return CLIENT_STATE_DONE;
blobmsg_add_string(&cl->hdr, "URL", path);
memset(&cl->request, 0, sizeof(cl->request));
h_method = find_idx(http_methods, ARRAY_SIZE(http_methods), type);
h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version);
if (h_method < 0 || h_version < 0) {
req->version = UH_HTTP_VER_1_0;
return CLIENT_STATE_DONE;
}
req->method = h_method;
req->version = h_version;
if (req->version < UH_HTTP_VER_1_1 || req->method == UH_HTTP_MSG_POST ||
!conf.http_keepalive)
req->connection_close = true;
return CLIENT_STATE_HEADER;
}
static bool client_init_cb(struct client *cl, char *buf, int len)
{
char *newline;
newline = strstr(buf, "\r\n");
if (!newline)
return false;
if (newline == buf) {
ustream_consume(cl->us, 2);
return true;
}
*newline = 0;
blob_buf_init(&cl->hdr, 0);
cl->state = client_parse_request(cl, buf);
ustream_consume(cl->us, newline + 2 - buf);
if (cl->state == CLIENT_STATE_DONE)
uh_header_error(cl, 400, "Bad Request");
return true;
}
static bool rfc1918_filter_check(struct client *cl)
{
if (!conf.rfc1918_filter)
return true;
if (!uh_addr_rfc1918(&cl->peer_addr) || uh_addr_rfc1918(&cl->srv_addr))
return true;
uh_client_error(cl, 403, "Forbidden",
"Rejected request from RFC1918 IP "
"to public server address");
return false;
}
static bool tls_redirect_check(struct client *cl)
{
int rem, port;
struct blob_attr *cur;
char *ptr, *url = NULL, *host = NULL;
if (cl->tls || !conf.tls_redirect)
return true;
if ((port = uh_first_tls_port(cl->srv_addr.family)) == -1)
return true;
blob_for_each_attr(cur, cl->hdr.head, rem) {
if (!strcmp(blobmsg_name(cur), "host"))
host = blobmsg_get_string(cur);
if (!strcmp(blobmsg_name(cur), "URL"))
url = blobmsg_get_string(cur);
if (url && host)
break;
}
if (!url || !host)
return true;
if ((ptr = strchr(host, ']')) != NULL)
*(ptr+1) = 0;
else if ((ptr = strchr(host, ':')) != NULL)
*ptr = 0;
cl->request.disable_chunked = true;
cl->request.connection_close = true;
uh_http_header(cl, 307, "Temporary Redirect");
if (port != 443)
ustream_printf(cl->us, "Location: https://%s:%d%s\r\n\r\n", host, port, url);
else
ustream_printf(cl->us, "Location: https://%s%s\r\n\r\n", host, url);
uh_request_done(cl);
return false;
}
static void client_header_complete(struct client *cl)
{
struct http_request *r = &cl->request;
if (!rfc1918_filter_check(cl))
return;
if (!tls_redirect_check(cl))
return;
if (r->expect_cont)
ustream_printf(cl->us, "HTTP/1.1 100 Continue\r\n\r\n");
switch(r->ua) {
case UH_UA_MSIE_OLD:
if (r->method != UH_HTTP_MSG_POST)
break;
/* fall through */
case UH_UA_SAFARI:
r->connection_close = true;
break;
default:
break;
}
uh_handle_request(cl);
}
static void client_parse_header(struct client *cl, char *data)
{
struct http_request *r = &cl->request;
char *err;
char *name;
char *val;
if (!*data) {
uloop_timeout_cancel(&cl->timeout);
cl->state = CLIENT_STATE_DATA;
client_header_complete(cl);
return;
}
val = uh_split_header(data);
if (!val) {
cl->state = CLIENT_STATE_DONE;
return;
}
for (name = data; *name; name++)
if (isupper(*name))
*name = tolower(*name);
if (!strcmp(data, "expect")) {
if (!strcasecmp(val, "100-continue"))
r->expect_cont = true;
else {
uh_header_error(cl, 412, "Precondition Failed");
return;
}
} else if (!strcmp(data, "content-length")) {
r->content_length = strtoul(val, &err, 0);
if (err && *err) {
uh_header_error(cl, 400, "Bad Request");
return;
}
} else if (!strcmp(data, "transfer-encoding")) {
if (!strcmp(val, "chunked"))
r->transfer_chunked = true;
} else if (!strcmp(data, "connection")) {
if (!strcasecmp(val, "close"))
r->connection_close = true;
} else if (!strcmp(data, "user-agent")) {
char *str;
if (strstr(val, "Opera"))
r->ua = UH_UA_OPERA;
else if ((str = strstr(val, "MSIE ")) != NULL) {
r->ua = UH_UA_MSIE_NEW;
if (str[5] && str[6] == '.') {
switch (str[5]) {
case '6':
if (strstr(str, "SV1"))
break;
/* fall through */
case '5':
case '4':
r->ua = UH_UA_MSIE_OLD;
break;
}
}
}
else if (strstr(val, "Chrome/"))
r->ua = UH_UA_CHROME;
else if (strstr(val, "Safari/") && strstr(val, "Mac OS X"))
r->ua = UH_UA_SAFARI;
else if (strstr(val, "Gecko/"))
r->ua = UH_UA_GECKO;
else if (strstr(val, "Konqueror"))
r->ua = UH_UA_KONQUEROR;
}
blobmsg_add_string(&cl->hdr, data, val);
cl->state = CLIENT_STATE_HEADER;
}
void client_poll_post_data(struct client *cl)
{
struct dispatch *d = &cl->dispatch;
struct http_request *r = &cl->request;
char *buf;
int len;
if (cl->state == CLIENT_STATE_DONE)
return;
while (1) {
char *sep;
int offset = 0;
int cur_len;
buf = ustream_get_read_buf(cl->us, &len);
if (!buf || !len)
break;
if (!d->data_send)
return;
cur_len = min(r->content_length, len);
if (cur_len) {
if (d->data_blocked)
break;
if (d->data_send)
cur_len = d->data_send(cl, buf, cur_len);
r->content_length -= cur_len;
ustream_consume(cl->us, cur_len);
continue;
}
if (!r->transfer_chunked)
break;
if (r->transfer_chunked > 1)
offset = 2;
sep = strstr(buf + offset, "\r\n");
if (!sep)
break;
*sep = 0;
r->content_length = strtoul(buf + offset, &sep, 16);
r->transfer_chunked++;
ustream_consume(cl->us, sep + 2 - buf);
/* invalid chunk length */
if (sep && *sep) {
r->content_length = 0;
r->transfer_chunked = 0;
break;
}
/* empty chunk == eof */
if (!r->content_length) {
r->transfer_chunked = false;
break;
}
}
buf = ustream_get_read_buf(cl->us, &len);
if (!r->content_length && !r->transfer_chunked &&
cl->state != CLIENT_STATE_DONE) {
if (cl->dispatch.data_done)
cl->dispatch.data_done(cl);
cl->state = CLIENT_STATE_DONE;
}
}
static bool client_data_cb(struct client *cl, char *buf, int len)
{
client_poll_post_data(cl);
return false;
}
static bool client_header_cb(struct client *cl, char *buf, int len)
{
char *newline;
int line_len;
newline = strstr(buf, "\r\n");
if (!newline)
return false;
*newline = 0;
client_parse_header(cl, buf);
line_len = newline + 2 - buf;
ustream_consume(cl->us, line_len);
if (cl->state == CLIENT_STATE_DATA)
return client_data_cb(cl, newline + 2, len - line_len);
return true;
}
typedef bool (*read_cb_t)(struct client *cl, char *buf, int len);
static read_cb_t read_cbs[] = {
[CLIENT_STATE_INIT] = client_init_cb,
[CLIENT_STATE_HEADER] = client_header_cb,
[CLIENT_STATE_DATA] = client_data_cb,
};
void uh_client_read_cb(struct client *cl)
{
struct ustream *us = cl->us;
char *str;
int len;
client_done = false;
do {
str = ustream_get_read_buf(us, &len);
if (!str || !len)
break;
if (cl->state >= array_size(read_cbs) || !read_cbs[cl->state])
break;
if (!read_cbs[cl->state](cl, str, len)) {
if (len == us->r.buffer_len &&
cl->state != CLIENT_STATE_DATA)
uh_header_error(cl, 413, "Request Entity Too Large");
break;
}
} while (!client_done);
}
static void client_close(struct client *cl)
{
if (cl->refcount) {
cl->state = CLIENT_STATE_CLEANUP;
return;
}
client_done = true;
n_clients--;
uh_dispatch_done(cl);
uloop_timeout_cancel(&cl->timeout);
if (cl->tls)
uh_tls_client_detach(cl);
ustream_free(&cl->sfd.stream);
close(cl->sfd.fd.fd);
list_del(&cl->list);
blob_buf_free(&cl->hdr);
blob_buf_free(&cl->hdr_response);
free(cl);
uh_unblock_listeners();
}
void uh_client_notify_state(struct client *cl)
{
struct ustream *s = cl->us;
if (!s->write_error && cl->state != CLIENT_STATE_CLEANUP) {
if (cl->state == CLIENT_STATE_DATA)
return;
if (!s->eof || s->w.data_bytes)
return;
}
return client_close(cl);
}
static void client_ustream_read_cb(struct ustream *s, int bytes)
{
struct client *cl = container_of(s, struct client, sfd.stream);
uh_client_read_cb(cl);
}
static void client_ustream_write_cb(struct ustream *s, int bytes)
{
struct client *cl = container_of(s, struct client, sfd.stream);
if (cl->dispatch.write_cb)
cl->dispatch.write_cb(cl);
}
static void client_notify_state(struct ustream *s)
{
struct client *cl = container_of(s, struct client, sfd.stream);
uh_client_notify_state(cl);
}
static void set_addr(struct uh_addr *addr, void *src)
{
struct sockaddr_in *sin = src;
struct sockaddr_in6 *sin6 = src;
addr->family = sin->sin_family;
if (addr->family == AF_INET) {
addr->port = ntohs(sin->sin_port);
memcpy(&addr->in, &sin->sin_addr, sizeof(addr->in));
} else {
addr->port = ntohs(sin6->sin6_port);
memcpy(&addr->in6, &sin6->sin6_addr, sizeof(addr->in6));
}
}
bool uh_accept_client(int fd, bool tls)
{
static struct client *next_client;
struct client *cl;
unsigned int sl;
int sfd;
static int client_id = 0;
struct sockaddr_in6 addr;
if (!next_client)
next_client = calloc(1, sizeof(*next_client));
cl = next_client;
sl = sizeof(addr);
sfd = accept(fd, (struct sockaddr *) &addr, &sl);
if (sfd < 0)
return false;
set_addr(&cl->peer_addr, &addr);
sl = sizeof(addr);
getsockname(sfd, (struct sockaddr *) &addr, &sl);
set_addr(&cl->srv_addr, &addr);
cl->us = &cl->sfd.stream;
if (tls) {
uh_tls_client_attach(cl);
} else {
cl->us->notify_read = client_ustream_read_cb;
cl->us->notify_write = client_ustream_write_cb;
cl->us->notify_state = client_notify_state;
}
cl->us->string_data = true;
ustream_fd_init(&cl->sfd, sfd);
uh_poll_connection(cl);
list_add_tail(&cl->list, &clients);
next_client = NULL;
n_clients++;
cl->id = client_id++;
cl->tls = tls;
return true;
}
void uh_close_fds(void)
{
struct client *cl;
uloop_done();
uh_close_listen_fds();
list_for_each_entry(cl, &clients, list) {
close(cl->sfd.fd.fd);
if (cl->dispatch.close_fds)
cl->dispatch.close_fds(cl);
}
}

900
src/3P/uhttpd/file.c Normal file
View File

@@ -0,0 +1,900 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _BSD_SOURCE
#define _DARWIN_C_SOURCE
#define _XOPEN_SOURCE 700
#include <sys/types.h>
#include <sys/dir.h>
#include <time.h>
#include <strings.h>
#include <dirent.h>
#include <inttypes.h>
#include <libubox/blobmsg.h>
#include "uhttpd.h"
#include "mimetypes.h"
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
static LIST_HEAD(index_files);
static LIST_HEAD(dispatch_handlers);
static LIST_HEAD(pending_requests);
static int n_requests;
struct deferred_request {
struct list_head list;
struct dispatch_handler *d;
struct client *cl;
struct path_info pi;
bool called, path;
};
struct index_file {
struct list_head list;
const char *name;
};
enum file_hdr {
HDR_AUTHORIZATION,
HDR_IF_MODIFIED_SINCE,
HDR_IF_UNMODIFIED_SINCE,
HDR_IF_MATCH,
HDR_IF_NONE_MATCH,
HDR_IF_RANGE,
__HDR_MAX
};
void uh_index_add(const char *filename)
{
struct index_file *idx;
idx = calloc(1, sizeof(*idx));
idx->name = filename;
list_add_tail(&idx->list, &index_files);
}
static char * canonpath(const char *path, char *path_resolved)
{
const char *path_cpy = path;
char *path_res = path_resolved;
if (conf.no_symlinks)
return realpath(path, path_resolved);
/* normalize */
while ((*path_cpy != '\0') && (path_cpy < (path + PATH_MAX - 2))) {
if (*path_cpy != '/')
goto next;
/* skip repeating / */
if (path_cpy[1] == '/') {
path_cpy++;
continue;
}
/* /./ or /../ */
if (path_cpy[1] == '.') {
/* skip /./ */
if ((path_cpy[2] == '/') || (path_cpy[2] == '\0')) {
path_cpy += 2;
continue;
}
/* collapse /x/../ */
if ((path_cpy[2] == '.') &&
((path_cpy[3] == '/') || (path_cpy[3] == '\0'))) {
while ((path_res > path_resolved) && (*--path_res != '/'));
path_cpy += 3;
continue;
}
}
next:
*path_res++ = *path_cpy++;
}
/* remove trailing slash if not root / */
if ((path_res > (path_resolved+1)) && (path_res[-1] == '/'))
path_res--;
else if (path_res == path_resolved)
*path_res++ = '/';
*path_res = '\0';
return path_resolved;
}
/* Returns NULL on error.
** NB: improperly encoded URL should give client 400 [Bad Syntax]; returning
** NULL here causes 404 [Not Found], but that's not too unreasonable. */
struct path_info *
uh_path_lookup(struct client *cl, const char *url)
{
static char path_phys[PATH_MAX];
static char path_info[PATH_MAX];
static struct path_info p;
const char *docroot = conf.docroot;
int docroot_len = strlen(docroot);
char *pathptr = NULL;
bool slash;
int i = 0;
int len;
struct stat s;
struct index_file *idx;
/* back out early if url is undefined */
if (url == NULL)
return NULL;
memset(&p, 0, sizeof(p));
path_phys[0] = 0;
path_info[0] = 0;
strcpy(uh_buf, docroot);
/* separate query string from url */
if ((pathptr = strchr(url, '?')) != NULL) {
p.query = pathptr[1] ? pathptr + 1 : NULL;
/* urldecode component w/o query */
if (pathptr > url) {
if (uh_urldecode(&uh_buf[docroot_len],
sizeof(uh_buf) - docroot_len - 1,
url, pathptr - url ) < 0)
return NULL;
}
}
/* no query string, decode all of url */
else if (uh_urldecode(&uh_buf[docroot_len],
sizeof(uh_buf) - docroot_len - 1,
url, strlen(url) ) < 0)
return NULL;
/* create canon path */
len = strlen(uh_buf);
slash = len && uh_buf[len - 1] == '/';
len = min(len, sizeof(path_phys) - 1);
for (i = len; i >= 0; i--) {
char ch = uh_buf[i];
bool exists;
if (ch != 0 && ch != '/')
continue;
uh_buf[i] = 0;
exists = !!canonpath(uh_buf, path_phys);
uh_buf[i] = ch;
if (!exists)
continue;
/* test current path */
if (stat(path_phys, &p.stat))
continue;
snprintf(path_info, sizeof(path_info), "%s", uh_buf + i);
break;
}
/* check whether found path is within docroot */
if (strncmp(path_phys, docroot, docroot_len) != 0 ||
(path_phys[docroot_len] != 0 &&
path_phys[docroot_len] != '/'))
return NULL;
/* is a regular file */
if (p.stat.st_mode & S_IFREG) {
p.root = docroot;
p.phys = path_phys;
p.name = &path_phys[docroot_len];
p.info = path_info[0] ? path_info : NULL;
return &p;
}
if (!(p.stat.st_mode & S_IFDIR))
return NULL;
if (path_info[0])
return NULL;
pathptr = path_phys + strlen(path_phys);
/* ensure trailing slash */
if (pathptr[-1] != '/') {
pathptr[0] = '/';
pathptr[1] = 0;
pathptr++;
}
/* if requested url resolves to a directory and a trailing slash
is missing in the request url, redirect the client to the same
url with trailing slash appended */
if (!slash) {
uh_http_header(cl, 302, "Found");
if (!uh_use_chunked(cl))
ustream_printf(cl->us, "Content-Length: 0\r\n");
ustream_printf(cl->us, "Location: %s%s%s\r\n\r\n",
&path_phys[docroot_len],
p.query ? "?" : "",
p.query ? p.query : "");
uh_request_done(cl);
p.redirected = 1;
return &p;
}
/* try to locate index file */
len = path_phys + sizeof(path_phys) - pathptr - 1;
list_for_each_entry(idx, &index_files, list) {
if (strlen(idx->name) > len)
continue;
strcpy(pathptr, idx->name);
if (!stat(path_phys, &s) && (s.st_mode & S_IFREG)) {
memcpy(&p.stat, &s, sizeof(p.stat));
break;
}
*pathptr = 0;
}
p.root = docroot;
p.phys = path_phys;
p.name = &path_phys[docroot_len];
return p.phys ? &p : NULL;
}
static const char * uh_file_mime_lookup(const char *path)
{
const struct mimetype *m = &uh_mime_types[0];
const char *e;
while (m->extn) {
e = &path[strlen(path)-1];
while (e >= path) {
if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
return m->mime;
e--;
}
m++;
}
return "application/octet-stream";
}
static const char * uh_file_mktag(struct stat *s, char *buf, int len)
{
snprintf(buf, len, "\"%" PRIx64 "-%" PRIx64 "-%" PRIx64 "\"",
s->st_ino, s->st_size, (uint64_t)s->st_mtime);
return buf;
}
static time_t uh_file_date2unix(const char *date)
{
struct tm t;
memset(&t, 0, sizeof(t));
if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL)
return timegm(&t);
return 0;
}
static char * uh_file_unix2date(time_t ts, char *buf, int len)
{
struct tm *t = gmtime(&ts);
strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT", t);
return buf;
}
static char *uh_file_header(struct client *cl, int idx)
{
if (!cl->dispatch.file.hdr[idx])
return NULL;
return (char *) blobmsg_data(cl->dispatch.file.hdr[idx]);
}
static void uh_file_response_ok_hdrs(struct client *cl, struct stat *s)
{
char buf[128];
if (s) {
ustream_printf(cl->us, "ETag: %s\r\n", uh_file_mktag(s, buf, sizeof(buf)));
ustream_printf(cl->us, "Last-Modified: %s\r\n",
uh_file_unix2date(s->st_mtime, buf, sizeof(buf)));
}
ustream_printf(cl->us, "Date: %s\r\n",
uh_file_unix2date(time(NULL), buf, sizeof(buf)));
}
static void uh_file_response_200(struct client *cl, struct stat *s)
{
uh_http_header(cl, 200, "OK");
return uh_file_response_ok_hdrs(cl, s);
}
static void uh_file_response_304(struct client *cl, struct stat *s)
{
uh_http_header(cl, 304, "Not Modified");
return uh_file_response_ok_hdrs(cl, s);
}
static void uh_file_response_412(struct client *cl)
{
uh_http_header(cl, 412, "Precondition Failed");
}
static bool uh_file_if_match(struct client *cl, struct stat *s)
{
char buf[128];
const char *tag = uh_file_mktag(s, buf, sizeof(buf));
char *hdr = uh_file_header(cl, HDR_IF_MATCH);
char *p;
int i;
if (!hdr)
return true;
p = &hdr[0];
for (i = 0; i < strlen(hdr); i++)
{
if ((hdr[i] == ' ') || (hdr[i] == ',')) {
hdr[i++] = 0;
p = &hdr[i];
} else if (!strcmp(p, "*") || !strcmp(p, tag)) {
return true;
}
}
uh_file_response_412(cl);
return false;
}
static int uh_file_if_modified_since(struct client *cl, struct stat *s)
{
char *hdr = uh_file_header(cl, HDR_IF_MODIFIED_SINCE);
if (!hdr)
return true;
if (uh_file_date2unix(hdr) >= s->st_mtime) {
uh_file_response_304(cl, s);
return false;
}
return true;
}
static int uh_file_if_none_match(struct client *cl, struct stat *s)
{
char buf[128];
const char *tag = uh_file_mktag(s, buf, sizeof(buf));
char *hdr = uh_file_header(cl, HDR_IF_NONE_MATCH);
char *p;
int i;
if (!hdr)
return true;
p = &hdr[0];
for (i = 0; i < strlen(hdr); i++) {
if ((hdr[i] == ' ') || (hdr[i] == ',')) {
hdr[i++] = 0;
p = &hdr[i];
} else if (!strcmp(p, "*") || !strcmp(p, tag)) {
if ((cl->request.method == UH_HTTP_MSG_GET) ||
(cl->request.method == UH_HTTP_MSG_HEAD))
uh_file_response_304(cl, s);
else
uh_file_response_412(cl);
return false;
}
}
return true;
}
static int uh_file_if_range(struct client *cl, struct stat *s)
{
char *hdr = uh_file_header(cl, HDR_IF_RANGE);
if (hdr) {
uh_file_response_412(cl);
return false;
}
return true;
}
static int uh_file_if_unmodified_since(struct client *cl, struct stat *s)
{
char *hdr = uh_file_header(cl, HDR_IF_UNMODIFIED_SINCE);
if (hdr && uh_file_date2unix(hdr) <= s->st_mtime) {
uh_file_response_412(cl);
return false;
}
return true;
}
static int dirent_cmp(const struct dirent **a, const struct dirent **b)
{
bool dir_a = !!((*a)->d_type & DT_DIR);
bool dir_b = !!((*b)->d_type & DT_DIR);
/* directories first */
if (dir_a != dir_b)
return dir_b - dir_a;
return alphasort(a, b);
}
static void list_entries(struct client *cl, struct dirent **files, int count,
const char *path, char *local_path)
{
const char *suffix = "/";
const char *type = "directory";
unsigned int mode = S_IXOTH;
struct stat s;
char *file;
char buf[128];
int i;
file = local_path + strlen(local_path);
for (i = 0; i < count; i++) {
const char *name = files[i]->d_name;
bool dir = !!(files[i]->d_type & DT_DIR);
if (name[0] == '.' && name[1] == 0)
goto next;
sprintf(file, "%s", name);
if (stat(local_path, &s))
goto next;
if (!dir) {
suffix = "";
mode = S_IROTH;
type = uh_file_mime_lookup(local_path);
}
if (!(s.st_mode & mode))
goto next;
uh_chunk_printf(cl,
"<li><strong><a href='%s%s%s'>%s</a>%s"
"</strong><br /><small>modified: %s"
"<br />%s - %.02f kbyte<br />"
"<br /></small></li>",
path, name, suffix,
name, suffix,
uh_file_unix2date(s.st_mtime, buf, sizeof(buf)),
type, s.st_size / 1024.0);
*file = 0;
next:
free(files[i]);
}
}
static void uh_file_dirlist(struct client *cl, struct path_info *pi)
{
struct dirent **files = NULL;
int count = 0;
uh_file_response_200(cl, NULL);
ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
uh_chunk_printf(cl,
"<html><head><title>Index of %s</title></head>"
"<body><h1>Index of %s</h1><hr /><ol>",
pi->name, pi->name);
count = scandir(pi->phys, &files, NULL, dirent_cmp);
if (count > 0) {
strcpy(uh_buf, pi->phys);
list_entries(cl, files, count, pi->name, uh_buf);
}
free(files);
uh_chunk_printf(cl, "</ol><hr /></body></html>");
uh_request_done(cl);
}
static void file_write_cb(struct client *cl)
{
int fd = cl->dispatch.file.fd;
int r;
while (cl->us->w.data_bytes < 256) {
r = read(fd, uh_buf, sizeof(uh_buf));
if (r < 0) {
if (errno == EINTR)
continue;
}
if (!r) {
uh_request_done(cl);
return;
}
uh_chunk_write(cl, uh_buf, r);
}
}
static void uh_file_free(struct client *cl)
{
close(cl->dispatch.file.fd);
}
static void uh_file_data(struct client *cl, struct path_info *pi, int fd)
{
/* test preconditions */
if (!cl->dispatch.no_cache &&
(!uh_file_if_modified_since(cl, &pi->stat) ||
!uh_file_if_match(cl, &pi->stat) ||
!uh_file_if_range(cl, &pi->stat) ||
!uh_file_if_unmodified_since(cl, &pi->stat) ||
!uh_file_if_none_match(cl, &pi->stat))) {
ustream_printf(cl->us, "\r\n");
uh_request_done(cl);
close(fd);
return;
}
/* write status */
uh_file_response_200(cl, &pi->stat);
ustream_printf(cl->us, "Content-Type: %s\r\n",
uh_file_mime_lookup(pi->name));
ustream_printf(cl->us, "Content-Length: %" PRIu64 "\r\n\r\n",
pi->stat.st_size);
/* send body */
if (cl->request.method == UH_HTTP_MSG_HEAD) {
uh_request_done(cl);
close(fd);
return;
}
cl->dispatch.file.fd = fd;
cl->dispatch.write_cb = file_write_cb;
cl->dispatch.free = uh_file_free;
cl->dispatch.close_fds = uh_file_free;
file_write_cb(cl);
}
static bool __handle_file_request(struct client *cl, char *url);
static void uh_file_request(struct client *cl, const char *url,
struct path_info *pi, struct blob_attr **tb)
{
int fd;
struct http_request *req = &cl->request;
char *error_handler;
if (!(pi->stat.st_mode & S_IROTH))
goto error;
if (pi->stat.st_mode & S_IFREG) {
fd = open(pi->phys, O_RDONLY);
if (fd < 0)
goto error;
req->disable_chunked = true;
cl->dispatch.file.hdr = tb;
uh_file_data(cl, pi, fd);
cl->dispatch.file.hdr = NULL;
return;
}
if ((pi->stat.st_mode & S_IFDIR)) {
if (conf.no_dirlists)
goto error;
uh_file_dirlist(cl, pi);
return;
}
error:
/* check for a previously set 403 redirect status to prevent infinite
recursion when the error page itself lacks sufficient permissions */
if (conf.error_handler && req->redirect_status != 403) {
req->redirect_status = 403;
error_handler = alloca(strlen(conf.error_handler) + 1);
strcpy(error_handler, conf.error_handler);
if (__handle_file_request(cl, error_handler))
return;
}
uh_client_error(cl, 403, "Forbidden",
"You don't have permission to access %s on this server.",
url);
}
void uh_dispatch_add(struct dispatch_handler *d)
{
list_add_tail(&d->list, &dispatch_handlers);
}
static struct dispatch_handler *
dispatch_find(const char *url, struct path_info *pi)
{
struct dispatch_handler *d;
list_for_each_entry(d, &dispatch_handlers, list) {
if (pi) {
if (d->check_url)
continue;
if (d->check_path(pi, url))
return d;
} else {
if (d->check_path)
continue;
if (d->check_url(url))
return d;
}
}
return NULL;
}
static void
uh_invoke_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
{
char *url = blobmsg_data(blob_data(cl->hdr.head));
n_requests++;
d->handle_request(cl, url, pi);
}
static void uh_complete_request(struct client *cl)
{
struct deferred_request *dr;
n_requests--;
while (!list_empty(&pending_requests)) {
if (n_requests >= conf.max_script_requests)
return;
dr = list_first_entry(&pending_requests, struct deferred_request, list);
list_del(&dr->list);
cl = dr->cl;
dr->called = true;
cl->dispatch.data_blocked = false;
uh_invoke_script(cl, dr->d, dr->path ? &dr->pi : NULL);
client_poll_post_data(cl);
}
}
static void
uh_free_pending_request(struct client *cl)
{
struct deferred_request *dr = cl->dispatch.req_data;
if (dr->called)
uh_complete_request(cl);
else
list_del(&dr->list);
free(dr);
}
static int field_len(const char *ptr)
{
if (!ptr)
return 0;
return strlen(ptr) + 1;
}
#define path_info_fields \
_field(root) \
_field(phys) \
_field(name) \
_field(info) \
_field(query) \
_field(auth)
static void
uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
{
struct deferred_request *dr;
char *_root, *_phys, *_name, *_info, *_query, *_auth;
cl->dispatch.req_free = uh_free_pending_request;
if (pi) {
/* allocate enough memory to duplicate all path_info strings in one block */
#undef _field
#define _field(_name) &_##_name, field_len(pi->_name),
dr = calloc_a(sizeof(*dr), path_info_fields NULL);
memcpy(&dr->pi, pi, sizeof(*pi));
dr->path = true;
/* copy all path_info strings */
#undef _field
#define _field(_name) if (pi->_name) dr->pi._name = strcpy(_##_name, pi->_name);
path_info_fields
} else {
dr = calloc(1, sizeof(*dr));
}
cl->dispatch.req_data = dr;
cl->dispatch.data_blocked = true;
dr->cl = cl;
dr->d = d;
list_add(&dr->list, &pending_requests);
}
static void
uh_invoke_handler(struct client *cl, struct dispatch_handler *d, char *url, struct path_info *pi)
{
if (!d->script)
return d->handle_request(cl, url, pi);
if (n_requests >= conf.max_script_requests)
return uh_defer_script(cl, d, pi);
cl->dispatch.req_free = uh_complete_request;
uh_invoke_script(cl, d, pi);
}
static bool __handle_file_request(struct client *cl, char *url)
{
static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
[HDR_AUTHORIZATION] = { "authorization", BLOBMSG_TYPE_STRING },
[HDR_IF_MODIFIED_SINCE] = { "if-modified-since", BLOBMSG_TYPE_STRING },
[HDR_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", BLOBMSG_TYPE_STRING },
[HDR_IF_MATCH] = { "if-match", BLOBMSG_TYPE_STRING },
[HDR_IF_NONE_MATCH] = { "if-none-match", BLOBMSG_TYPE_STRING },
[HDR_IF_RANGE] = { "if-range", BLOBMSG_TYPE_STRING },
};
struct dispatch_handler *d;
struct blob_attr *tb[__HDR_MAX];
struct path_info *pi;
pi = uh_path_lookup(cl, url);
if (!pi)
return false;
if (pi->redirected)
return true;
blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
if (tb[HDR_AUTHORIZATION])
pi->auth = blobmsg_data(tb[HDR_AUTHORIZATION]);
if (!uh_auth_check(cl, pi))
return true;
d = dispatch_find(url, pi);
if (d)
uh_invoke_handler(cl, d, url, pi);
else
uh_file_request(cl, url, pi, tb);
return true;
}
static char *uh_handle_alias(char *old_url)
{
struct alias *alias;
static char *new_url;
static int url_len;
if (!list_empty(&conf.cgi_alias)) list_for_each_entry(alias, &conf.cgi_alias, list) {
int old_len;
int new_len;
int path_len = 0;
if (!uh_path_match(alias->alias, old_url))
continue;
if (alias->path)
path_len = strlen(alias->path);
old_len = strlen(old_url) + 1;
new_len = old_len + MAX(conf.cgi_prefix_len, path_len);
if (new_len > url_len) {
new_url = realloc(new_url, new_len);
url_len = new_len;
}
*new_url = '\0';
if (alias->path)
strcpy(new_url, alias->path);
else if (conf.cgi_prefix)
strcpy(new_url, conf.cgi_prefix);
strcat(new_url, old_url);
return new_url;
}
return old_url;
}
void uh_handle_request(struct client *cl)
{
struct http_request *req = &cl->request;
struct dispatch_handler *d;
char *url = blobmsg_data(blob_data(cl->hdr.head));
char *error_handler;
blob_buf_init(&cl->hdr_response, 0);
url = uh_handle_alias(url);
uh_handler_run(cl, &url, false);
if (!url)
return;
req->redirect_status = 200;
d = dispatch_find(url, NULL);
if (d)
return uh_invoke_handler(cl, d, url, NULL);
if (__handle_file_request(cl, url))
return;
if (uh_handler_run(cl, &url, true)) {
if (!url)
return;
uh_handler_run(cl, &url, false);
if (__handle_file_request(cl, url))
return;
}
req->redirect_status = 404;
if (conf.error_handler) {
error_handler = alloca(strlen(conf.error_handler) + 1);
strcpy(error_handler, conf.error_handler);
if (__handle_file_request(cl, error_handler))
return;
}
uh_client_error(cl, 404, "Not Found", "The requested URL %s was not found on this server.", url);
}

258
src/3P/uhttpd/handler.c Normal file
View File

@@ -0,0 +1,258 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2015 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/json_script.h>
#include "uhttpd.h"
struct handler {
struct list_head list;
struct json_script_file *request;
struct json_script_file *fallback;
};
static LIST_HEAD(handlers);
static struct json_script_ctx handler_ctx;
static struct env_var *cur_vars;
static struct blob_buf b;
static int handler_ret;
static struct client *cur_client;
static char **cur_url;
static void
handle_redirect(struct json_script_ctx *ctx, struct blob_attr *data)
{
struct client *cl = cur_client;
static struct blobmsg_policy policy[3] = {
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_INT32 },
{ .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[3];
const char *status = "Found";
int code = 302;
blobmsg_parse_array(policy, ARRAY_SIZE(policy), tb, blobmsg_data(data), blobmsg_data_len(data));
if (!tb[0])
return;
if (tb[1]) {
code = blobmsg_get_u32(tb[1]);
if (tb[2])
status = blobmsg_get_string(tb[2]);
}
uh_http_header(cl, code, status);
if (!uh_use_chunked(cl))
ustream_printf(cl->us, "Content-Length: 0\r\n");
ustream_printf(cl->us, "Location: %s\r\n\r\n",
blobmsg_get_string(tb[0]));
uh_request_done(cl);
*cur_url = NULL;
handler_ret = 1;
json_script_abort(ctx);
}
static void
handle_set_uri(struct json_script_ctx *ctx, struct blob_attr *data)
{
struct client *cl = cur_client;
static struct blobmsg_policy policy = {
.type = BLOBMSG_TYPE_STRING,
};
struct blob_attr *tb;
struct blob_attr *old_url = blob_data(cl->hdr.head);
blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
if (!tb)
return;
blob_buf_init(&b, 0);
blob_put_raw(&b, blob_next(old_url), blob_len(cl->hdr.head) - blob_pad_len(old_url));
/* replace URL in client header cache */
blob_buf_init(&cl->hdr, 0);
blobmsg_add_string(&cl->hdr, "URL", blobmsg_get_string(tb));
blob_put_raw(&cl->hdr, blob_data(b.head), blob_len(b.head));
*cur_url = blobmsg_data(blob_data(cl->hdr.head));
cur_vars = NULL;
blob_buf_init(&b, 0);
handler_ret = 1;
json_script_abort(ctx);
}
static void
handle_add_header(struct json_script_ctx *ctx, struct blob_attr *data)
{
struct client *cl = cur_client;
static struct blobmsg_policy policy[2] = {
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[2];
blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb, blobmsg_data(data), blobmsg_data_len(data));
if (!tb[0] || !tb[1])
return;
blobmsg_add_string(&cl->hdr_response, blobmsg_get_string(tb[0]),
blobmsg_get_string(tb[1]));
}
static void
handle_no_cache(struct json_script_ctx *ctx, struct blob_attr *data)
{
struct client *cl = cur_client;
cl->dispatch.no_cache = true;
}
static void
handle_command(struct json_script_ctx *ctx, const char *name,
struct blob_attr *data, struct blob_attr *vars)
{
static const struct {
const char *name;
void (*func)(struct json_script_ctx *ctx, struct blob_attr *data);
} cmds[] = {
{ "redirect", handle_redirect },
{ "rewrite", handle_set_uri },
{ "add-header", handle_add_header },
{ "no-cache", handle_no_cache },
};
int i;
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
if (!strcmp(cmds[i].name, name)) {
cmds[i].func(ctx, data);
return;
}
}
}
static const char *
handle_var(struct json_script_ctx *ctx, const char *name,
struct blob_attr *vars)
{
struct client *cl = cur_client;
struct env_var *cur;
static struct path_info empty_path;
if (!cur_vars) {
struct path_info *p = uh_path_lookup(cl, *cur_url);
if (!p)
p = &empty_path;
cur_vars = uh_get_process_vars(cl, p);
}
for (cur = cur_vars; cur->name; cur++) {
if (!strcmp(cur->name, name))
return cur->value;
}
return NULL;
}
static void
handler_init(void)
{
if (handler_ctx.handle_command)
return;
json_script_init(&handler_ctx);
handler_ctx.handle_command = handle_command;
handler_ctx.handle_var = handle_var;
}
static bool set_handler(struct json_script_file **dest, struct blob_attr *data)
{
if (!data)
return true;
*dest = json_script_file_from_blobmsg(NULL, blobmsg_data(data), blobmsg_data_len(data));
return *dest;
}
int uh_handler_add(const char *file)
{
enum {
H_REQUEST,
H_FALLBACK,
__H_MAX,
};
struct blobmsg_policy policy[__H_MAX] = {
[H_REQUEST] = { "request", BLOBMSG_TYPE_ARRAY },
[H_FALLBACK] = { "fallback", BLOBMSG_TYPE_ARRAY },
};
struct blob_attr *tb[__H_MAX];
struct handler *h;
handler_init();
blob_buf_init(&b, 0);
if (!blobmsg_add_json_from_file(&b, file))
return -1;
blobmsg_parse(policy, __H_MAX, tb, blob_data(b.head), blob_len(b.head));
if (!tb[H_REQUEST] && !tb[H_FALLBACK])
return -1;
h = calloc(1, sizeof(*h));
if (!set_handler(&h->request, tb[H_REQUEST]) ||
!set_handler(&h->fallback, tb[H_FALLBACK])) {
free(h->request);
free(h->fallback);
free(h);
return -1;
}
list_add_tail(&h->list, &handlers);
return 0;
}
int uh_handler_run(struct client *cl, char **url, bool fallback)
{
struct json_script_file *f;
struct handler *h;
cur_client = cl;
cur_url = url;
cur_vars = NULL;
handler_ret = 0;
list_for_each_entry(h, &handlers, list) {
f = fallback ? h->fallback : h->request;
if (!f)
continue;
blob_buf_init(&b, 0);
json_script_run_file(&handler_ctx, f, b.head);
if (handler_ctx.abort)
break;
}
return handler_ret;
}

222
src/3P/uhttpd/listen.c Normal file
View File

@@ -0,0 +1,222 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include "uhttpd.h"
struct listener {
struct list_head list;
struct uloop_fd fd;
int socket;
int n_clients;
struct sockaddr_in6 addr;
bool tls;
bool blocked;
};
static LIST_HEAD(listeners);
static int n_blocked;
void uh_close_listen_fds(void)
{
struct listener *l;
list_for_each_entry(l, &listeners, list)
close(l->fd.fd);
}
static void uh_block_listener(struct listener *l)
{
uloop_fd_delete(&l->fd);
n_blocked++;
l->blocked = true;
}
static void uh_poll_listeners(struct uloop_timeout *timeout)
{
struct listener *l;
if ((!n_blocked && conf.max_connections) ||
n_clients >= conf.max_connections)
return;
list_for_each_entry(l, &listeners, list) {
if (!l->blocked)
continue;
l->fd.cb(&l->fd, ULOOP_READ);
if (n_clients >= conf.max_connections)
break;
n_blocked--;
l->blocked = false;
uloop_fd_add(&l->fd, ULOOP_READ);
}
}
void uh_unblock_listeners(void)
{
static struct uloop_timeout poll_timer = {
.cb = uh_poll_listeners
};
uloop_timeout_set(&poll_timer, 1);
}
static void listener_cb(struct uloop_fd *fd, unsigned int events)
{
struct listener *l = container_of(fd, struct listener, fd);
while (1) {
if (!uh_accept_client(fd->fd, l->tls))
break;
}
if (conf.max_connections && n_clients >= conf.max_connections)
uh_block_listener(l);
}
void uh_setup_listeners(void)
{
struct listener *l;
int yes = 1;
list_for_each_entry(l, &listeners, list) {
int sock = l->fd.fd;
/* TCP keep-alive */
if (conf.tcp_keepalive > 0) {
#ifdef linux
int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt;
tcp_ka_idl = 1;
tcp_ka_cnt = 3;
tcp_ka_int = conf.tcp_keepalive;
setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &tcp_ka_idl, sizeof(tcp_ka_idl));
setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int));
setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &tcp_ka_cnt, sizeof(tcp_ka_cnt));
#endif
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
}
l->fd.cb = listener_cb;
uloop_fd_add(&l->fd, ULOOP_READ);
}
}
int uh_socket_bind(const char *host, const char *port, bool tls)
{
int sock = -1;
int yes = 1;
int status;
int bound = 0;
struct listener *l = NULL;
struct addrinfo *addrs = NULL, *p = NULL;
static struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE,
};
if ((status = getaddrinfo(host, port, &hints, &addrs)) != 0) {
fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
return 0;
}
/* try to bind a new socket to each found address */
for (p = addrs; p; p = p->ai_next) {
/* get the socket */
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sock < 0) {
perror("socket()");
goto error;
}
/* "address already in use" */
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) {
perror("setsockopt()");
goto error;
}
/* required to get parallel v4 + v6 working */
if (p->ai_family == AF_INET6 &&
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
perror("setsockopt()");
goto error;
}
/* bind */
if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) {
perror("bind()");
goto error;
}
/* listen */
if (listen(sock, UH_LIMIT_CLIENTS) < 0) {
perror("listen()");
goto error;
}
fd_cloexec(sock);
l = calloc(1, sizeof(*l));
if (!l)
goto error;
l->fd.fd = sock;
l->tls = tls;
l->addr = *(struct sockaddr_in6 *)p->ai_addr;
list_add_tail(&l->list, &listeners);
bound++;
continue;
error:
if (sock > -1)
close(sock);
}
freeaddrinfo(addrs);
return bound;
}
int uh_first_tls_port(int family)
{
struct listener *l;
int tls_port = -1;
list_for_each_entry(l, &listeners, list) {
if (!l->tls || l->addr.sin6_family != family)
continue;
if (tls_port != -1 && ntohs(l->addr.sin6_port) != 443)
continue;
tls_port = ntohs(l->addr.sin6_port);
}
return tls_port;
}

300
src/3P/uhttpd/lua.c Normal file
View File

@@ -0,0 +1,300 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <libubox/blobmsg.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <poll.h>
#include "uhttpd.h"
#include "plugin.h"
#define UH_LUA_CB "handle_request"
static const struct uhttpd_ops *ops;
static struct config *_conf;
#define conf (*_conf)
static lua_State *_L;
static int uh_lua_recv(lua_State *L)
{
static struct pollfd pfd = {
.fd = STDIN_FILENO,
.events = POLLIN,
};
luaL_Buffer B;
int data_len = 0;
int len;
int r;
len = luaL_checknumber(L, 1);
luaL_buffinit(L, &B);
while(len > 0) {
char *buf;
buf = luaL_prepbuffer(&B);
r = read(STDIN_FILENO, buf, LUAL_BUFFERSIZE);
if (r < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
pfd.revents = 0;
poll(&pfd, 1, 1000);
if (pfd.revents & POLLIN)
continue;
}
if (errno == EINTR)
continue;
if (!data_len)
data_len = -1;
break;
}
if (!r)
break;
luaL_addsize(&B, r);
data_len += r;
if (r != LUAL_BUFFERSIZE)
break;
}
luaL_pushresult(&B);
lua_pushnumber(L, data_len);
if (data_len > 0) {
lua_pushvalue(L, -2);
lua_remove(L, -3);
return 2;
} else {
lua_remove(L, -2);
return 1;
}
}
static int uh_lua_send(lua_State *L)
{
const char *buf;
size_t len;
buf = luaL_checklstring(L, 1, &len);
if (len > 0)
len = write(STDOUT_FILENO, buf, len);
lua_pushnumber(L, len);
return 1;
}
static int
uh_lua_strconvert(lua_State *L, int (*convert)(char *, int, const char *, int))
{
const char *in_buf;
static char out_buf[4096];
size_t in_len;
int out_len;
in_buf = luaL_checklstring(L, 1, &in_len);
out_len = convert(out_buf, sizeof(out_buf), in_buf, in_len);
if (out_len < 0) {
const char *error;
if (out_len == -1)
error = "buffer overflow";
else
error = "malformed string";
luaL_error(L, "%s on URL conversion\n", error);
}
lua_pushlstring(L, out_buf, out_len);
return 1;
}
static int uh_lua_urldecode(lua_State *L)
{
return uh_lua_strconvert(L, ops->urldecode);
}
static int uh_lua_urlencode(lua_State *L)
{
return uh_lua_strconvert(L, ops->urlencode);
}
static lua_State *uh_lua_state_init(void)
{
const char *msg = "(unknown error)";
const char *status;
lua_State *L;
int ret;
L = luaL_newstate();
luaL_openlibs(L);
/* build uhttpd api table */
lua_newtable(L);
lua_pushcfunction(L, uh_lua_send);
lua_setfield(L, -2, "send");
lua_pushcfunction(L, uh_lua_send);
lua_setfield(L, -2, "sendc");
lua_pushcfunction(L, uh_lua_recv);
lua_setfield(L, -2, "recv");
lua_pushcfunction(L, uh_lua_urldecode);
lua_setfield(L, -2, "urldecode");
lua_pushcfunction(L, uh_lua_urlencode);
lua_setfield(L, -2, "urlencode");
lua_pushstring(L, conf.docroot);
lua_setfield(L, -2, "docroot");
lua_setglobal(L, "uhttpd");
ret = luaL_loadfile(L, conf.lua_handler);
if (ret) {
status = "loading";
goto error;
}
ret = lua_pcall(L, 0, 0, 0);
if (ret) {
status = "initializing";
goto error;
}
lua_getglobal(L, UH_LUA_CB);
if (!lua_isfunction(L, -1)) {
fprintf(stderr, "Error: Lua handler provides no " UH_LUA_CB "() callback.\n");
exit(1);
}
return L;
error:
if (!lua_isnil(L, -1))
msg = lua_tostring(L, -1);
fprintf(stderr, "Error %s Lua handler: %s\n", status, msg);
exit(1);
return NULL;
}
static void lua_main(struct client *cl, struct path_info *pi, char *url)
{
struct blob_attr *cur;
const char *error;
struct env_var *var;
lua_State *L = _L;
int path_len, prefix_len;
char *str;
int rem;
lua_getglobal(L, UH_LUA_CB);
/* new env table for this request */
lua_newtable(L);
prefix_len = strlen(conf.lua_prefix);
path_len = strlen(url);
str = strchr(url, '?');
if (str) {
if (*(str + 1))
pi->query = str + 1;
path_len = str - url;
}
if (path_len > prefix_len) {
lua_pushlstring(L, url + prefix_len,
path_len - prefix_len);
lua_setfield(L, -2, "PATH_INFO");
}
for (var = ops->get_process_vars(cl, pi); var->name; var++) {
if (!var->value)
continue;
lua_pushstring(L, var->value);
lua_setfield(L, -2, var->name);
}
lua_pushnumber(L, 0.9 + (cl->request.version / 10.0));
lua_setfield(L, -2, "HTTP_VERSION");
lua_newtable(L);
blob_for_each_attr(cur, cl->hdr.head, rem) {
lua_pushstring(L, blobmsg_data(cur));
lua_setfield(L, -2, blobmsg_name(cur));
}
lua_setfield(L, -2, "headers");
switch(lua_pcall(L, 1, 0, 0)) {
case LUA_ERRMEM:
case LUA_ERRRUN:
error = luaL_checkstring(L, -1);
if (!error)
error = "(unknown error)";
printf("Status: 500 Internal Server Error\r\n\r\n"
"Unable to launch the requested Lua program:\n"
" %s: %s\n", pi->phys, error);
}
exit(0);
}
static void lua_handle_request(struct client *cl, char *url, struct path_info *pi)
{
static struct path_info _pi;
pi = &_pi;
pi->name = conf.lua_prefix;
pi->phys = conf.lua_handler;
if (!ops->create_process(cl, pi, url, lua_main)) {
ops->client_error(cl, 500, "Internal Server Error",
"Failed to create CGI process: %s", strerror(errno));
}
}
static bool check_lua_url(const char *url)
{
return ops->path_match(conf.lua_prefix, url);
}
static struct dispatch_handler lua_dispatch = {
.script = true,
.check_url = check_lua_url,
.handle_request = lua_handle_request,
};
static int lua_plugin_init(const struct uhttpd_ops *o, struct config *c)
{
ops = o;
_conf = c;
_L = uh_lua_state_init();
ops->dispatch_add(&lua_dispatch);
return 0;
}
struct uhttpd_plugin uhttpd_plugin = {
.init = lua_plugin_init,
};

527
src/3P/uhttpd/main.c Normal file
View File

@@ -0,0 +1,527 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define _BSD_SOURCE
#define _GNU_SOURCE
#define _XOPEN_SOURCE 700
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <getopt.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <libubox/usock.h>
#include "uhttpd.h"
#include "tls.h"
char uh_buf[4096];
static int run_server(void)
{
uloop_init();
uh_setup_listeners();
uh_plugin_post_init();
uloop_run();
return 0;
}
static void uh_config_parse(void)
{
const char *path = conf.file;
FILE *c;
char line[512];
char *col1;
char *col2;
char *eol;
if (!path)
path = "/etc/httpd.conf";
c = fopen(path, "r");
if (!c)
return;
memset(line, 0, sizeof(line));
while (fgets(line, sizeof(line) - 1, c)) {
if ((line[0] == '/') && (strchr(line, ':') != NULL)) {
if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
!(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
!(eol = strchr(col2, '\n')) || (*eol++ = 0))
continue;
uh_auth_add(line, col1, col2);
} else if (!strncmp(line, "I:", 2)) {
if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
!(eol = strchr(col1, '\n')) || (*eol++ = 0))
continue;
uh_index_add(strdup(col1));
} else if (!strncmp(line, "E404:", 5)) {
if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
!(eol = strchr(col1, '\n')) || (*eol++ = 0))
continue;
conf.error_handler = strdup(col1);
}
else if ((line[0] == '*') && (strchr(line, ':') != NULL)) {
if (!(col1 = strchr(line, '*')) || (*col1++ = 0) ||
!(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
!(eol = strchr(col2, '\n')) || (*eol++ = 0))
continue;
uh_interpreter_add(col1, col2);
}
}
fclose(c);
}
static int add_listener_arg(char *arg, bool tls)
{
char *host = NULL;
char *port = arg;
char *s;
int l;
s = strrchr(arg, ':');
if (s) {
host = arg;
port = s + 1;
*s = 0;
}
if (host && *host == '[') {
l = strlen(host);
if (l >= 2) {
host[l-1] = 0;
host++;
}
}
return uh_socket_bind(host, port, tls);
}
static int usage(const char *name)
{
fprintf(stderr,
"Usage: %s -p [addr:]port -h docroot\n"
" -f Do not fork to background\n"
" -c file Configuration file, default is '/etc/httpd.conf'\n"
" -p [addr:]port Bind to specified address and port, multiple allowed\n"
#ifdef HAVE_TLS
" -s [addr:]port Like -p but provide HTTPS on this port\n"
" -C file ASN.1 server certificate file\n"
" -K file ASN.1 server private key file\n"
" -q Redirect all HTTP requests to HTTPS\n"
#endif
" -h directory Specify the document root, default is '.'\n"
" -E string Use given virtual URL as 404 error handler\n"
" -I string Use given filename as index for directories, multiple allowed\n"
" -S Do not follow symbolic links outside of the docroot\n"
" -D Do not allow directory listings, send 403 instead\n"
" -R Enable RFC1918 filter\n"
" -n count Maximum allowed number of concurrent script requests\n"
" -N count Maximum allowed number of concurrent connections\n"
#ifdef HAVE_LUA
" -l string URL prefix for Lua handler, default is '/lua'\n"
" -L file Lua handler script, omit to disable Lua\n"
#endif
#ifdef HAVE_UBUS
" -u string URL prefix for UBUS via JSON-RPC handler\n"
" -U file Override ubus socket path\n"
" -a Do not authenticate JSON-RPC requests against UBUS session api\n"
" -X Enable CORS HTTP headers on JSON-RPC api\n"
#endif
" -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
" -y alias[=path] URL alias handle\n"
" -i .ext=path Use interpreter at path for files with the given extension\n"
" -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n"
" -T seconds Network timeout in seconds, default is 30\n"
" -k seconds HTTP keepalive timeout\n"
" -d string URL decode given string\n"
" -r string Specify basic auth realm\n"
" -m string MD5 crypt given string\n"
"\n", name
);
return 1;
}
static void init_defaults_pre(void)
{
conf.script_timeout = 60;
conf.network_timeout = 30;
conf.http_keepalive = 20;
conf.max_script_requests = 3;
conf.max_connections = 100;
conf.realm = "Protected Area";
conf.cgi_prefix = "/cgi-bin";
conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin";
INIT_LIST_HEAD(&conf.cgi_alias);
}
static void init_defaults_post(void)
{
uh_index_add("index.html");
uh_index_add("index.htm");
uh_index_add("default.html");
uh_index_add("default.htm");
if (conf.cgi_prefix) {
char *str = malloc(strlen(conf.docroot) + strlen(conf.cgi_prefix) + 1);
strcpy(str, conf.docroot);
strcat(str, conf.cgi_prefix);
conf.cgi_docroot_path = str;
conf.cgi_prefix_len = strlen(conf.cgi_prefix);
};
}
static void fixup_prefix(char *str)
{
int len;
if (!str || !str[0])
return;
len = strlen(str) - 1;
while (len >= 0 && str[len] == '/')
len--;
str[len + 1] = 0;
}
int main(int argc, char **argv)
{
struct alias *alias;
bool nofork = false;
char *port;
int opt, ch;
int cur_fd;
int bound = 0;
#ifdef HAVE_TLS
int n_tls = 0;
const char *tls_key = NULL, *tls_crt = NULL;
#endif
BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
uh_dispatch_add(&cgi_dispatch);
init_defaults_pre();
signal(SIGPIPE, SIG_IGN);
while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
switch(ch) {
#ifdef HAVE_TLS
case 'C':
tls_crt = optarg;
break;
case 'K':
tls_key = optarg;
break;
case 'q':
conf.tls_redirect = 1;
break;
case 's':
n_tls++;
/* fall through */
#else
case 'C':
case 'K':
case 'q':
case 's':
fprintf(stderr, "uhttpd: TLS support not compiled, "
"ignoring -%c\n", ch);
break;
#endif
case 'p':
optarg = strdup(optarg);
bound += add_listener_arg(optarg, (ch == 's'));
break;
case 'h':
if (!realpath(optarg, uh_buf)) {
fprintf(stderr, "Error: Invalid directory %s: %s\n",
optarg, strerror(errno));
exit(1);
}
conf.docroot = strdup(uh_buf);
break;
case 'H':
if (uh_handler_add(optarg)) {
fprintf(stderr, "Error: Failed to load handler script %s\n",
optarg);
exit(1);
}
break;
case 'E':
if (optarg[0] != '/') {
fprintf(stderr, "Error: Invalid error handler: %s\n",
optarg);
exit(1);
}
conf.error_handler = optarg;
break;
case 'I':
if (optarg[0] == '/') {
fprintf(stderr, "Error: Invalid index page: %s\n",
optarg);
exit(1);
}
uh_index_add(optarg);
break;
case 'S':
conf.no_symlinks = 1;
break;
case 'D':
conf.no_dirlists = 1;
break;
case 'R':
conf.rfc1918_filter = 1;
break;
case 'n':
conf.max_script_requests = atoi(optarg);
break;
case 'N':
conf.max_connections = atoi(optarg);
break;
case 'x':
fixup_prefix(optarg);
conf.cgi_prefix = optarg;
break;
case 'y':
alias = calloc(1, sizeof(*alias));
if (!alias) {
fprintf(stderr, "Error: failed to allocate alias\n");
exit(1);
}
alias->alias = strdup(optarg);
alias->path = strchr(alias->alias, '=');
if (alias->path)
*alias->path++ = 0;
list_add(&alias->list, &conf.cgi_alias);
break;
case 'i':
optarg = strdup(optarg);
port = strchr(optarg, '=');
if (optarg[0] != '.' || !port) {
fprintf(stderr, "Error: Invalid interpreter: %s\n",
optarg);
exit(1);
}
*port++ = 0;
uh_interpreter_add(optarg, port);
break;
case 't':
conf.script_timeout = atoi(optarg);
break;
case 'T':
conf.network_timeout = atoi(optarg);
break;
case 'k':
conf.http_keepalive = atoi(optarg);
break;
case 'A':
conf.tcp_keepalive = atoi(optarg);
break;
case 'f':
nofork = 1;
break;
case 'd':
optarg = strdup(optarg);
port = alloca(strlen(optarg) + 1);
if (!port)
return -1;
/* "decode" plus to space to retain compat */
for (opt = 0; optarg[opt]; opt++)
if (optarg[opt] == '+')
optarg[opt] = ' ';
/* opt now contains strlen(optarg) -- no need to re-scan */
if (uh_urldecode(port, opt, optarg, opt) < 0) {
fprintf(stderr, "uhttpd: invalid encoding\n");
return -1;
}
printf("%s", port);
return 0;
break;
/* basic auth realm */
case 'r':
conf.realm = optarg;
break;
/* md5 crypt */
case 'm':
printf("%s\n", crypt(optarg, "$1$"));
return 0;
break;
/* config file */
case 'c':
conf.file = optarg;
break;
#ifdef HAVE_LUA
case 'l':
conf.lua_prefix = optarg;
break;
case 'L':
conf.lua_handler = optarg;
break;
#else
case 'l':
case 'L':
fprintf(stderr, "uhttpd: Lua support not compiled, "
"ignoring -%c\n", ch);
break;
#endif
#ifdef HAVE_UBUS
case 'a':
conf.ubus_noauth = 1;
break;
case 'u':
conf.ubus_prefix = optarg;
break;
case 'U':
conf.ubus_socket = optarg;
break;
case 'X':
conf.ubus_cors = 1;
break;
#else
case 'a':
case 'u':
case 'U':
case 'X':
fprintf(stderr, "uhttpd: UBUS support not compiled, "
"ignoring -%c\n", ch);
break;
#endif
default:
return usage(argv[0]);
}
}
uh_config_parse();
if (!conf.docroot) {
if (!realpath(".", uh_buf)) {
fprintf(stderr, "Error: Unable to determine work dir\n");
return 1;
}
conf.docroot = strdup(uh_buf);
}
init_defaults_post();
if (!bound) {
fprintf(stderr, "Error: No sockets bound, unable to continue\n");
return 1;
}
#ifdef HAVE_TLS
if (n_tls) {
if (!tls_crt || !tls_key) {
fprintf(stderr, "Please specify a certificate and "
"a key file to enable SSL support\n");
return 1;
}
if (uh_tls_init(tls_key, tls_crt))
return 1;
}
#endif
#ifdef HAVE_LUA
if (conf.lua_handler || conf.lua_prefix) {
if (!conf.lua_handler || !conf.lua_prefix) {
fprintf(stderr, "Need handler and prefix to enable Lua support\n");
return 1;
}
if (uh_plugin_init("uhttpd_lua.so"))
return 1;
}
#endif
#ifdef HAVE_UBUS
if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so"))
return 1;
#endif
/* fork (if not disabled) */
if (!nofork) {
switch (fork()) {
case -1:
perror("fork()");
exit(1);
case 0:
/* daemon setup */
if (chdir("/"))
perror("chdir()");
cur_fd = open("/dev/null", O_WRONLY);
if (cur_fd > 0) {
dup2(cur_fd, 0);
dup2(cur_fd, 1);
dup2(cur_fd, 2);
}
break;
default:
exit(0);
}
}
return run_server();
}

94
src/3P/uhttpd/mimetypes.h Normal file
View File

@@ -0,0 +1,94 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _UHTTPD_MIMETYPES_
struct mimetype {
const char *extn;
const char *mime;
};
static const struct mimetype uh_mime_types[] = {
{ "txt", "text/plain" },
{ "log", "text/plain" },
{ "js", "text/javascript" },
{ "css", "text/css" },
{ "htm", "text/html" },
{ "html", "text/html" },
{ "diff", "text/x-patch" },
{ "patch", "text/x-patch" },
{ "c", "text/x-csrc" },
{ "h", "text/x-chdr" },
{ "o", "text/x-object" },
{ "ko", "text/x-object" },
{ "bmp", "image/bmp" },
{ "gif", "image/gif" },
{ "png", "image/png" },
{ "jpg", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "svg", "image/svg+xml" },
{ "json", "application/json" },
{ "jsonp", "application/javascript" },
{ "zip", "application/zip" },
{ "pdf", "application/pdf" },
{ "xml", "application/xml" },
{ "xsl", "application/xml" },
{ "doc", "application/msword" },
{ "ppt", "application/vnd.ms-powerpoint" },
{ "xls", "application/vnd.ms-excel" },
{ "odt", "application/vnd.oasis.opendocument.text" },
{ "odp", "application/vnd.oasis.opendocument.presentation" },
{ "pl", "application/x-perl" },
{ "sh", "application/x-shellscript" },
{ "php", "application/x-php" },
{ "deb", "application/x-deb" },
{ "iso", "application/x-cd-image" },
{ "tar.gz", "application/x-compressed-tar" },
{ "tgz", "application/x-compressed-tar" },
{ "gz", "application/x-gzip" },
{ "tar.bz2", "application/x-bzip-compressed-tar" },
{ "tbz", "application/x-bzip-compressed-tar" },
{ "bz2", "application/x-bzip" },
{ "tar", "application/x-tar" },
{ "rar", "application/x-rar-compressed" },
{ "mp3", "audio/mpeg" },
{ "ogg", "audio/x-vorbis+ogg" },
{ "wav", "audio/x-wav" },
{ "mpg", "video/mpeg" },
{ "mpeg", "video/mpeg" },
{ "avi", "video/x-msvideo" },
{ "README", "text/plain" },
{ "log", "text/plain" },
{ "cfg", "text/plain" },
{ "conf", "text/plain" },
{ "pac", "application/x-ns-proxy-autoconfig" },
{ "wpad.dat", "application/x-ns-proxy-autoconfig" },
{ NULL, NULL }
};
#endif

70
src/3P/uhttpd/plugin.c Normal file
View File

@@ -0,0 +1,70 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <dlfcn.h>
#include "uhttpd.h"
#include "plugin.h"
static LIST_HEAD(plugins);
static const struct uhttpd_ops ops = {
.dispatch_add = uh_dispatch_add,
.path_match = uh_path_match,
.create_process = uh_create_process,
.get_process_vars = uh_get_process_vars,
.http_header = uh_http_header,
.client_error = uh_client_error,
.request_done = uh_request_done,
.chunk_write = uh_chunk_write,
.chunk_printf = uh_chunk_printf,
.urlencode = uh_urlencode,
.urldecode = uh_urldecode,
};
int uh_plugin_init(const char *name)
{
struct uhttpd_plugin *p;
const char *sym;
void *dlh;
dlh = dlopen(name, RTLD_LAZY | RTLD_GLOBAL);
if (!dlh) {
fprintf(stderr, "Could not open plugin %s: %s\n", name, dlerror());
return -ENOENT;
}
sym = "uhttpd_plugin";
p = dlsym(dlh, sym);
if (!p) {
fprintf(stderr, "Could not find symbol '%s' in plugin '%s'\n", sym, name);
return -ENOENT;
}
list_add(&p->list, &plugins);
return p->init(&ops, &conf);
}
void uh_plugin_post_init(void)
{
struct uhttpd_plugin *p;
list_for_each_entry(p, &plugins, list)
if (p->post_init)
p->post_init();
}

45
src/3P/uhttpd/plugin.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "uhttpd.h"
struct uhttpd_ops {
void (*dispatch_add)(struct dispatch_handler *d);
bool (*path_match)(const char *prefix, const char *url);
bool (*create_process)(struct client *cl, struct path_info *pi, char *url,
void (*cb)(struct client *cl, struct path_info *pi, char *url));
struct env_var *(*get_process_vars)(struct client *cl, struct path_info *pi);
void (*http_header)(struct client *cl, int code, const char *summary);
void (*client_error)(struct client *cl, int code, const char *summary, const char *fmt, ...);
void (*request_done)(struct client *cl);
void (*chunk_write)(struct client *cl, const void *data, int len);
void (*chunk_printf)(struct client *cl, const char *format, ...);
int (*urlencode)(char *buf, int blen, const char *src, int slen);
int (*urldecode)(char *buf, int blen, const char *src, int slen);
};
struct uhttpd_plugin {
struct list_head list;
int (*init)(const struct uhttpd_ops *ops, struct config *conf);
void (*post_init)(void);
};

389
src/3P/uhttpd/proc.c Normal file
View File

@@ -0,0 +1,389 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <arpa/inet.h>
#include <libubox/blobmsg.h>
#include "uhttpd.h"
#define __headers \
__header(accept, accept) \
__header(accept_charset, accept-charset) \
__header(accept_encoding, accept-encoding) \
__header(accept_language, accept-language) \
__header(authorization, authorization) \
__header(connection, connection) \
__header(cookie, cookie) \
__header(host, host) \
__header(referer, referer) \
__header(user_agent, user-agent) \
__header(content_type, content-type) \
__header(content_length, content-length) \
__header(x_http_method_override, x-http-method-override)
#undef __header
#define __header __enum_header
enum client_hdr {
__headers
__HDR_MAX,
};
#undef __header
#define __header __blobmsg_header
static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
__headers
};
static const struct {
const char *name;
int idx;
} proc_header_env[] = {
{ "HTTP_ACCEPT", HDR_accept },
{ "HTTP_ACCEPT_CHARSET", HDR_accept_charset },
{ "HTTP_ACCEPT_ENCODING", HDR_accept_encoding },
{ "HTTP_ACCEPT_LANGUAGE", HDR_accept_language },
{ "HTTP_AUTHORIZATION", HDR_authorization },
{ "HTTP_CONNECTION", HDR_connection },
{ "HTTP_COOKIE", HDR_cookie },
{ "HTTP_HOST", HDR_host },
{ "HTTP_REFERER", HDR_referer },
{ "HTTP_USER_AGENT", HDR_user_agent },
{ "HTTP_X_HTTP_METHOD_OVERRIDE", HDR_x_http_method_override },
{ "CONTENT_TYPE", HDR_content_type },
{ "CONTENT_LENGTH", HDR_content_length },
};
enum extra_vars {
/* no update needed */
_VAR_GW,
_VAR_SOFTWARE,
/* updated by uh_get_process_vars */
VAR_SCRIPT_NAME,
VAR_SCRIPT_FILE,
VAR_DOCROOT,
VAR_QUERY,
VAR_REQUEST,
VAR_PROTO,
VAR_METHOD,
VAR_PATH_INFO,
VAR_USER,
VAR_HTTPS,
VAR_REDIRECT,
VAR_SERVER_NAME,
VAR_SERVER_ADDR,
VAR_SERVER_PORT,
VAR_REMOTE_NAME,
VAR_REMOTE_ADDR,
VAR_REMOTE_PORT,
__VAR_MAX,
};
static char local_addr[INET6_ADDRSTRLEN], remote_addr[INET6_ADDRSTRLEN];
static char local_port[6], remote_port[6];
static char redirect_status[4];
static struct env_var extra_vars[] = {
[_VAR_GW] = { "GATEWAY_INTERFACE", "CGI/1.1" },
[_VAR_SOFTWARE] = { "SERVER_SOFTWARE", "uhttpd" },
[VAR_SCRIPT_NAME] = { "SCRIPT_NAME" },
[VAR_SCRIPT_FILE] = { "SCRIPT_FILENAME" },
[VAR_DOCROOT] = { "DOCUMENT_ROOT" },
[VAR_QUERY] = { "QUERY_STRING" },
[VAR_REQUEST] = { "REQUEST_URI" },
[VAR_PROTO] = { "SERVER_PROTOCOL" },
[VAR_METHOD] = { "REQUEST_METHOD" },
[VAR_PATH_INFO] = { "PATH_INFO" },
[VAR_USER] = { "REMOTE_USER" },
[VAR_HTTPS] = { "HTTPS" },
[VAR_REDIRECT] = { "REDIRECT_STATUS", redirect_status },
[VAR_SERVER_NAME] = { "SERVER_NAME", local_addr },
[VAR_SERVER_ADDR] = { "SERVER_ADDR", local_addr },
[VAR_SERVER_PORT] = { "SERVER_PORT", local_port },
[VAR_REMOTE_NAME] = { "REMOTE_HOST", remote_addr },
[VAR_REMOTE_ADDR] = { "REMOTE_ADDR", remote_addr },
[VAR_REMOTE_PORT] = { "REMOTE_PORT", remote_port },
};
struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi)
{
struct http_request *req = &cl->request;
struct blob_attr *data = cl->hdr.head;
struct env_var *vars = (void *) uh_buf;
struct blob_attr *tb[__HDR_MAX];
const char *url;
int len;
int i;
url = blobmsg_data(blob_data(cl->hdr.head));
len = ARRAY_SIZE(proc_header_env);
len += ARRAY_SIZE(extra_vars);
len *= sizeof(struct env_var);
BUILD_BUG_ON(sizeof(uh_buf) < len);
extra_vars[VAR_SCRIPT_NAME].value = pi->name;
extra_vars[VAR_SCRIPT_FILE].value = pi->phys;
extra_vars[VAR_DOCROOT].value = pi->root;
extra_vars[VAR_QUERY].value = pi->query ? pi->query : "";
extra_vars[VAR_REQUEST].value = url;
extra_vars[VAR_PROTO].value = http_versions[req->version];
extra_vars[VAR_METHOD].value = http_methods[req->method];
extra_vars[VAR_PATH_INFO].value = pi->info;
extra_vars[VAR_USER].value = req->realm ? req->realm->user : NULL;
extra_vars[VAR_HTTPS].value = cl->tls ? "on" : NULL;
snprintf(redirect_status, sizeof(redirect_status),
"%d", req->redirect_status);
inet_ntop(cl->srv_addr.family, &cl->srv_addr.in, local_addr, sizeof(local_addr));
snprintf(local_port, sizeof(local_port), "%d", cl->srv_addr.port);
inet_ntop(cl->peer_addr.family, &cl->peer_addr.in, remote_addr, sizeof(remote_addr));
snprintf(remote_port, sizeof(remote_port), "%d", cl->peer_addr.port);
blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(data), blob_len(data));
for (i = 0; i < ARRAY_SIZE(proc_header_env); i++) {
struct blob_attr *cur;
cur = tb[proc_header_env[i].idx];
vars[i].name = proc_header_env[i].name;
vars[i].value = cur ? blobmsg_data(cur) : "";
}
memcpy(&vars[i], extra_vars, sizeof(extra_vars));
i += ARRAY_SIZE(extra_vars);
vars[i].name = NULL;
vars[i].value = NULL;
return vars;
}
static void proc_close_fds(struct client *cl)
{
struct dispatch_proc *p = &cl->dispatch.proc;
close(p->r.sfd.fd.fd);
if (p->wrfd.fd >= 0)
close(p->wrfd.fd);
}
static void proc_handle_close(struct relay *r, int ret)
{
if (r->header_cb) {
uh_client_error(r->cl, 502, "Bad Gateway",
"The process did not produce any response");
return;
}
uh_request_done(r->cl);
}
static void proc_handle_header(struct relay *r, const char *name, const char *val)
{
static char status_buf[64];
struct client *cl = r->cl;
char *sep;
char buf[4];
if (!strcmp(name, "Status")) {
sep = strchr(val, ' ');
if (sep != val + 3)
return;
memcpy(buf, val, 3);
buf[3] = 0;
snprintf(status_buf, sizeof(status_buf), "%s", sep + 1);
cl->dispatch.proc.status_msg = status_buf;
cl->dispatch.proc.status_code = atoi(buf);
return;
}
blobmsg_add_string(&cl->dispatch.proc.hdr, name, val);
}
static void proc_handle_header_end(struct relay *r)
{
struct client *cl = r->cl;
struct dispatch_proc *p = &cl->dispatch.proc;
struct blob_attr *cur;
int rem;
uloop_timeout_cancel(&p->timeout);
uh_http_header(cl, cl->dispatch.proc.status_code, cl->dispatch.proc.status_msg);
blob_for_each_attr(cur, cl->dispatch.proc.hdr.head, rem)
ustream_printf(cl->us, "%s: %s\r\n", blobmsg_name(cur), blobmsg_data(cur));
ustream_printf(cl->us, "\r\n");
if (cl->request.method == UH_HTTP_MSG_HEAD)
r->skip_data = true;
}
static void proc_write_close(struct client *cl)
{
struct dispatch_proc *p = &cl->dispatch.proc;
if (p->wrfd.fd < 0)
return;
uloop_fd_delete(&p->wrfd);
close(p->wrfd.fd);
p->wrfd.fd = -1;
}
static void proc_free(struct client *cl)
{
struct dispatch_proc *p = &cl->dispatch.proc;
uloop_timeout_cancel(&p->timeout);
blob_buf_free(&p->hdr);
proc_write_close(cl);
uh_relay_free(&p->r);
}
static void proc_write_cb(struct uloop_fd *fd, unsigned int events)
{
struct client *cl = container_of(fd, struct client, dispatch.proc.wrfd);
client_poll_post_data(cl);
}
static void proc_relay_write_cb(struct client *cl)
{
struct dispatch_proc *p = &cl->dispatch.proc;
if (ustream_pending_data(cl->us, true))
return;
ustream_set_read_blocked(&p->r.sfd.stream, false);
p->r.sfd.stream.notify_read(&p->r.sfd.stream, 0);
}
static int proc_data_send(struct client *cl, const char *data, int len)
{
struct dispatch_proc *p = &cl->dispatch.proc;
int retlen = 0;
int ret;
while (len) {
ret = write(p->wrfd.fd, data, len);
if (ret < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
/* consume all data */
ret = len;
}
if (!ret)
break;
retlen += ret;
len -= ret;
data += ret;
}
if (len)
uloop_fd_add(&p->wrfd, ULOOP_WRITE);
else
uloop_fd_delete(&p->wrfd);
return retlen;
}
static void proc_timeout_cb(struct uloop_timeout *timeout)
{
struct dispatch_proc *proc = container_of(timeout, struct dispatch_proc, timeout);
struct client *cl = container_of(proc, struct client, dispatch.proc);
uh_relay_kill(cl, &proc->r);
}
bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
void (*cb)(struct client *cl, struct path_info *pi, char *url))
{
struct dispatch *d = &cl->dispatch;
struct dispatch_proc *proc = &d->proc;
int rfd[2], wfd[2];
int pid;
blob_buf_init(&proc->hdr, 0);
proc->status_code = 200;
proc->status_msg = "OK";
if (pipe(rfd))
return false;
if (pipe(wfd))
goto close_rfd;
pid = fork();
if (pid < 0)
goto close_wfd;
if (!pid) {
close(0);
close(1);
dup2(rfd[1], 1);
dup2(wfd[0], 0);
close(rfd[0]);
close(rfd[1]);
close(wfd[0]);
close(wfd[1]);
uh_close_fds();
cb(cl, pi, url);
exit(0);
}
close(rfd[1]);
close(wfd[0]);
proc->wrfd.fd = wfd[1];
uh_relay_open(cl, &proc->r, rfd[0], pid);
d->free = proc_free;
d->close_fds = proc_close_fds;
d->data_send = proc_data_send;
d->data_done = proc_write_close;
d->write_cb = proc_relay_write_cb;
proc->r.header_cb = proc_handle_header;
proc->r.header_end = proc_handle_header_end;
proc->r.close = proc_handle_close;
proc->wrfd.cb = proc_write_cb;
proc->timeout.cb = proc_timeout_cb;
if (conf.script_timeout > 0)
uloop_timeout_set(&proc->timeout, conf.script_timeout * 1000);
return true;
close_wfd:
close(wfd[0]);
close(wfd[1]);
close_rfd:
close(rfd[0]);
close(rfd[1]);
return false;
}

205
src/3P/uhttpd/relay.c Normal file
View File

@@ -0,0 +1,205 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <signal.h>
#include "uhttpd.h"
void uh_relay_free(struct relay *r)
{
if (!r->cl)
return;
if (r->proc.pending)
kill(r->proc.pid, SIGKILL);
uloop_timeout_cancel(&r->timeout);
uloop_process_delete(&r->proc);
ustream_free(&r->sfd.stream);
close(r->sfd.fd.fd);
r->cl = NULL;
}
void uh_relay_close(struct relay *r, int ret)
{
struct ustream *us = &r->sfd.stream;
if (!us->notify_read)
return;
us->notify_read = NULL;
us->notify_write = NULL;
us->notify_state = NULL;
if (r->close)
r->close(r, ret);
}
static void relay_error(struct relay *r)
{
struct ustream *s = &r->sfd.stream;
int len;
r->error = true;
s->eof = true;
ustream_get_read_buf(s, &len);
if (len)
ustream_consume(s, len);
ustream_state_change(s);
}
static void relay_process_headers(struct relay *r)
{
struct ustream *s = &r->sfd.stream;
char *buf, *newline;
int len;
if (!r->header_cb)
return;
while (r->header_cb) {
int line_len;
char *val;
buf = ustream_get_read_buf(s, &len);
if (!buf || !len)
break;
newline = strchr(buf, '\n');
if (!newline)
break;
line_len = newline + 1 - buf;
if (newline > buf && newline[-1] == '\r')
newline--;
*newline = 0;
if (newline == buf) {
r->header_cb = NULL;
if (r->header_end)
r->header_end(r);
ustream_consume(s, line_len);
break;
}
val = uh_split_header(buf);
if (!val) {
relay_error(r);
return;
}
r->header_cb(r, buf, val);
ustream_consume(s, line_len);
}
}
static void relay_read_cb(struct ustream *s, int bytes)
{
struct relay *r = container_of(s, struct relay, sfd.stream);
struct client *cl = r->cl;
struct ustream *us = cl->us;
char *buf;
int len;
if (r->process_done)
uloop_timeout_set(&r->timeout, 1);
if (!r->error)
relay_process_headers(r);
if (r->header_cb) {
/*
* if eof, ensure that remaining data is discarded, so the
* state change cb will tear down the stream
*/
if (s->eof)
relay_error(r);
return;
}
if (!s->eof && ustream_pending_data(us, true)) {
ustream_set_read_blocked(s, true);
return;
}
buf = ustream_get_read_buf(s, &len);
if (!buf || !len)
return;
if (!r->skip_data)
uh_chunk_write(cl, buf, len);
ustream_consume(s, len);
}
static void relay_close_if_done(struct uloop_timeout *timeout)
{
struct relay *r = container_of(timeout, struct relay, timeout);
struct ustream *s = &r->sfd.stream;
while (ustream_poll(&r->sfd.stream));
if (!(r->process_done || s->eof) || ustream_pending_data(s, false))
return;
uh_relay_close(r, r->ret);
}
static void relay_state_cb(struct ustream *s)
{
struct relay *r = container_of(s, struct relay, sfd.stream);
if (r->process_done)
uloop_timeout_set(&r->timeout, 1);
}
static void relay_proc_cb(struct uloop_process *proc, int ret)
{
struct relay *r = container_of(proc, struct relay, proc);
r->process_done = true;
r->ret = ret;
uloop_timeout_set(&r->timeout, 1);
}
void uh_relay_kill(struct client *cl, struct relay *r)
{
struct ustream *us = &r->sfd.stream;
kill(r->proc.pid, SIGKILL);
us->eof = true;
ustream_state_change(us);
}
void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid)
{
struct ustream *us = &r->sfd.stream;
r->cl = cl;
us->notify_read = relay_read_cb;
us->notify_state = relay_state_cb;
us->string_data = true;
ustream_fd_init(&r->sfd, fd);
r->proc.pid = pid;
r->proc.cb = relay_proc_cb;
uloop_process_add(&r->proc);
r->timeout.cb = relay_close_if_done;
}

55
src/3P/uhttpd/session-test.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
. /usr/share/libubox/jshn.sh
json_load "$(ubus call session create)"
json_get_var sid ubus_rpc_session
json_init
json_add_string ubus_rpc_session "$sid"
json_add_array "objects"
json_add_array ""
json_add_string "" "session"
json_add_string "" "list"
json_close_array
json_close_array
ubus call session grant "$(json_dump)"
echo "Session: $sid"
echo "Request 1"
wget -q -O- \
--post-data='{
"jsonrpc": "2.0",
"method" : "call",
"params" : [
"'$sid'",
"session",
"test",
{},
]
}' "http://localhost:8080/ubus"
echo "Request 2"
wget -q -O- \
--post-data='[
{
"jsonrpc": "2.0",
"method" : "call",
"params" : [
"'$sid'",
"session",
"list",
{},
]
},
{
"jsonrpc": "2.0",
"method" : "call",
"params" : [
"'$sid'",
"session",
"test",
{},
]
},
]' "http://localhost:8080/ubus"

103
src/3P/uhttpd/tls.c Normal file
View File

@@ -0,0 +1,103 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <dlfcn.h>
#include "uhttpd.h"
#include "tls.h"
#ifdef __APPLE__
#define LIB_EXT "dylib"
#else
#define LIB_EXT "so"
#endif
static struct ustream_ssl_ops *ops;
static void *dlh;
static void *ctx;
int uh_tls_init(const char *key, const char *crt)
{
static bool _init = false;
if (_init)
return 0;
_init = true;
dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
if (!dlh) {
fprintf(stderr, "Failed to load ustream-ssl library: %s\n", dlerror());
return -ENOENT;
}
ops = dlsym(dlh, "ustream_ssl_ops");
if (!ops) {
fprintf(stderr, "Could not find required symbol 'ustream_ssl_ops' in ustream-ssl library\n");
return -ENOENT;
}
ctx = ops->context_new(true);
if (!ctx) {
fprintf(stderr, "Failed to initialize ustream-ssl\n");
return -EINVAL;
}
if (ops->context_set_crt_file(ctx, crt) ||
ops->context_set_key_file(ctx, key)) {
fprintf(stderr, "Failed to load certificate/key files\n");
return -EINVAL;
}
return 0;
}
static void tls_ustream_read_cb(struct ustream *s, int bytes)
{
struct client *cl = container_of(s, struct client, ssl.stream);
uh_client_read_cb(cl);
}
static void tls_ustream_write_cb(struct ustream *s, int bytes)
{
struct client *cl = container_of(s, struct client, ssl.stream);
if (cl->dispatch.write_cb)
cl->dispatch.write_cb(cl);
}
static void tls_notify_state(struct ustream *s)
{
struct client *cl = container_of(s, struct client, ssl.stream);
uh_client_notify_state(cl);
}
void uh_tls_client_attach(struct client *cl)
{
cl->us = &cl->ssl.stream;
ops->init(&cl->ssl, &cl->sfd.stream, ctx, true);
cl->us->notify_read = tls_ustream_read_cb;
cl->us->notify_write = tls_ustream_write_cb;
cl->us->notify_state = tls_notify_state;
}
void uh_tls_client_detach(struct client *cl)
{
ustream_free(&cl->ssl.stream);
}

46
src/3P/uhttpd/tls.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __UHTTPD_TLS_H
#define __UHTTPD_TLS_H
#ifdef HAVE_TLS
int uh_tls_init(const char *key, const char *crt);
void uh_tls_client_attach(struct client *cl);
void uh_tls_client_detach(struct client *cl);
#else
static inline int uh_tls_init(const char *key, const char *crt)
{
return -1;
}
static inline void uh_tls_client_attach(struct client *cl)
{
}
static inline void uh_tls_client_detach(struct client *cl)
{
}
#endif
#endif

685
src/3P/uhttpd/ubus.c Normal file
View File

@@ -0,0 +1,685 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/avl.h>
#include <libubox/avl-cmp.h>
#include <stdio.h>
#include <poll.h>
#include "uhttpd.h"
#include "plugin.h"
static const struct uhttpd_ops *ops;
static struct config *_conf;
#define conf (*_conf)
static struct ubus_context *ctx;
static struct blob_buf buf;
#define UH_UBUS_MAX_POST_SIZE 4096
#define UH_UBUS_DEFAULT_SID "00000000000000000000000000000000"
enum {
RPC_JSONRPC,
RPC_METHOD,
RPC_PARAMS,
RPC_ID,
__RPC_MAX,
};
static const struct blobmsg_policy rpc_policy[__RPC_MAX] = {
[RPC_JSONRPC] = { .name = "jsonrpc", .type = BLOBMSG_TYPE_STRING },
[RPC_METHOD] = { .name = "method", .type = BLOBMSG_TYPE_STRING },
[RPC_PARAMS] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY },
[RPC_ID] = { .name = "id", .type = BLOBMSG_TYPE_UNSPEC },
};
enum {
SES_ACCESS,
__SES_MAX,
};
static const struct blobmsg_policy ses_policy[__SES_MAX] = {
[SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
};
struct rpc_data {
struct blob_attr *id;
const char *sid;
const char *method;
const char *object;
const char *function;
struct blob_attr *data;
struct blob_attr *params;
};
struct list_data {
bool verbose;
struct blob_buf *buf;
};
enum rpc_error {
ERROR_PARSE,
ERROR_REQUEST,
ERROR_METHOD,
ERROR_PARAMS,
ERROR_INTERNAL,
ERROR_OBJECT,
ERROR_SESSION,
ERROR_ACCESS,
ERROR_TIMEOUT,
__ERROR_MAX
};
static const struct {
int code;
const char *msg;
} json_errors[__ERROR_MAX] = {
[ERROR_PARSE] = { -32700, "Parse error" },
[ERROR_REQUEST] = { -32600, "Invalid request" },
[ERROR_METHOD] = { -32601, "Method not found" },
[ERROR_PARAMS] = { -32602, "Invalid parameters" },
[ERROR_INTERNAL] = { -32603, "Internal error" },
[ERROR_OBJECT] = { -32000, "Object not found" },
[ERROR_SESSION] = { -32001, "Session not found" },
[ERROR_ACCESS] = { -32002, "Access denied" },
[ERROR_TIMEOUT] = { -32003, "ubus request timed out" },
};
enum cors_hdr {
HDR_ORIGIN,
HDR_ACCESS_CONTROL_REQUEST_METHOD,
HDR_ACCESS_CONTROL_REQUEST_HEADERS,
__HDR_MAX
};
static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout);
static void uh_ubus_next_batched_request(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
du->timeout.cb = __uh_ubus_next_batched_request;
uloop_timeout_set(&du->timeout, 1);
}
static void uh_ubus_add_cors_headers(struct client *cl)
{
struct blob_attr *tb[__HDR_MAX];
static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
[HDR_ORIGIN] = { "origin", BLOBMSG_TYPE_STRING },
[HDR_ACCESS_CONTROL_REQUEST_METHOD] = { "access-control-request-method", BLOBMSG_TYPE_STRING },
[HDR_ACCESS_CONTROL_REQUEST_HEADERS] = { "access-control-request-headers", BLOBMSG_TYPE_STRING },
};
blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
if (!tb[HDR_ORIGIN])
return;
if (tb[HDR_ACCESS_CONTROL_REQUEST_METHOD])
{
char *hdr = (char *) blobmsg_data(tb[HDR_ACCESS_CONTROL_REQUEST_METHOD]);
if (strcmp(hdr, "POST") && strcmp(hdr, "OPTIONS"))
return;
}
ustream_printf(cl->us, "Access-Control-Allow-Origin: %s\r\n",
blobmsg_data(tb[HDR_ORIGIN]));
if (tb[HDR_ACCESS_CONTROL_REQUEST_HEADERS])
ustream_printf(cl->us, "Access-Control-Allow-Headers: %s\r\n",
blobmsg_data(tb[HDR_ACCESS_CONTROL_REQUEST_HEADERS]));
ustream_printf(cl->us, "Access-Control-Allow-Methods: POST, OPTIONS\r\n");
ustream_printf(cl->us, "Access-Control-Allow-Credentials: true\r\n");
}
static void uh_ubus_send_header(struct client *cl)
{
ops->http_header(cl, 200, "OK");
if (conf.ubus_cors)
uh_ubus_add_cors_headers(cl);
ustream_printf(cl->us, "Content-Type: application/json\r\n");
if (cl->request.method == UH_HTTP_MSG_OPTIONS)
ustream_printf(cl->us, "Content-Length: 0\r\n");
ustream_printf(cl->us, "\r\n");
}
static void uh_ubus_send_response(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
const char *sep = "";
char *str;
if (du->array && du->array_idx > 1)
sep = ",";
str = blobmsg_format_json(buf.head, true);
ops->chunk_printf(cl, "%s%s", sep, str);
free(str);
du->jsobj_cur = NULL;
if (du->array)
uh_ubus_next_batched_request(cl);
else
return ops->request_done(cl);
}
static void uh_ubus_init_response(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
struct json_object *obj = du->jsobj_cur, *obj2 = NULL;
blob_buf_init(&buf, 0);
blobmsg_add_string(&buf, "jsonrpc", "2.0");
if (obj)
json_object_object_get_ex(obj, "id", &obj2);
if (obj2)
blobmsg_add_json_element(&buf, "id", obj2);
else
blobmsg_add_field(&buf, BLOBMSG_TYPE_UNSPEC, "id", NULL, 0);
}
static void uh_ubus_json_error(struct client *cl, enum rpc_error type)
{
void *c;
uh_ubus_init_response(cl);
c = blobmsg_open_table(&buf, "error");
blobmsg_add_u32(&buf, "code", json_errors[type].code);
blobmsg_add_string(&buf, "message", json_errors[type].msg);
blobmsg_close_table(&buf, c);
uh_ubus_send_response(cl);
}
static void
uh_ubus_request_data_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
blobmsg_add_field(&du->buf, BLOBMSG_TYPE_TABLE, "", blob_data(msg), blob_len(msg));
}
static void
uh_ubus_request_cb(struct ubus_request *req, int ret)
{
struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
struct client *cl = container_of(du, struct client, dispatch.ubus);
struct blob_attr *cur;
void *r;
int rem;
uloop_timeout_cancel(&du->timeout);
uh_ubus_init_response(cl);
r = blobmsg_open_array(&buf, "result");
blobmsg_add_u32(&buf, "", ret);
blob_for_each_attr(cur, du->buf.head, rem)
blobmsg_add_blob(&buf, cur);
blobmsg_close_array(&buf, r);
uh_ubus_send_response(cl);
}
static void
uh_ubus_timeout_cb(struct uloop_timeout *timeout)
{
struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout);
struct client *cl = container_of(du, struct client, dispatch.ubus);
ubus_abort_request(ctx, &du->req);
uh_ubus_json_error(cl, ERROR_TIMEOUT);
}
static void uh_ubus_close_fds(struct client *cl)
{
if (ctx->sock.fd < 0)
return;
close(ctx->sock.fd);
ctx->sock.fd = -1;
}
static void uh_ubus_request_free(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
blob_buf_free(&du->buf);
uloop_timeout_cancel(&du->timeout);
if (du->jsobj)
json_object_put(du->jsobj);
if (du->jstok)
json_tokener_free(du->jstok);
if (du->req_pending)
ubus_abort_request(ctx, &du->req);
}
static void uh_ubus_single_error(struct client *cl, enum rpc_error type)
{
uh_ubus_send_header(cl);
uh_ubus_json_error(cl, type);
ops->request_done(cl);
}
static void uh_ubus_send_request(struct client *cl, json_object *obj, const char *sid, struct blob_attr *args)
{
struct dispatch *d = &cl->dispatch;
struct dispatch_ubus *du = &d->ubus;
struct blob_attr *cur;
static struct blob_buf req;
int ret, rem;
blob_buf_init(&req, 0);
blobmsg_for_each_attr(cur, args, rem) {
if (!strcmp(blobmsg_name(cur), "ubus_rpc_session"))
return uh_ubus_json_error(cl, ERROR_PARAMS);
blobmsg_add_blob(&req, cur);
}
blobmsg_add_string(&req, "ubus_rpc_session", sid);
blob_buf_init(&du->buf, 0);
memset(&du->req, 0, sizeof(du->req));
ret = ubus_invoke_async(ctx, du->obj, du->func, req.head, &du->req);
if (ret)
return uh_ubus_json_error(cl, ERROR_INTERNAL);
du->req.data_cb = uh_ubus_request_data_cb;
du->req.complete_cb = uh_ubus_request_cb;
ubus_complete_request_async(ctx, &du->req);
du->timeout.cb = uh_ubus_timeout_cb;
uloop_timeout_set(&du->timeout, conf.script_timeout * 1000);
du->req_pending = true;
}
static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv)
{
struct blob_attr *sig, *attr;
struct list_data *data = priv;
int rem, rem2;
void *t, *o;
if (!data->verbose) {
blobmsg_add_string(data->buf, NULL, obj->path);
return;
}
if (!obj->signature)
return;
o = blobmsg_open_table(data->buf, obj->path);
blob_for_each_attr(sig, obj->signature, rem) {
t = blobmsg_open_table(data->buf, blobmsg_name(sig));
rem2 = blobmsg_data_len(sig);
__blob_for_each_attr(attr, blobmsg_data(sig), rem2) {
if (blob_id(attr) != BLOBMSG_TYPE_INT32)
continue;
switch (blobmsg_get_u32(attr)) {
case BLOBMSG_TYPE_INT8:
blobmsg_add_string(data->buf, blobmsg_name(attr), "boolean");
break;
case BLOBMSG_TYPE_INT32:
blobmsg_add_string(data->buf, blobmsg_name(attr), "number");
break;
case BLOBMSG_TYPE_STRING:
blobmsg_add_string(data->buf, blobmsg_name(attr), "string");
break;
case BLOBMSG_TYPE_ARRAY:
blobmsg_add_string(data->buf, blobmsg_name(attr), "array");
break;
case BLOBMSG_TYPE_TABLE:
blobmsg_add_string(data->buf, blobmsg_name(attr), "object");
break;
default:
blobmsg_add_string(data->buf, blobmsg_name(attr), "unknown");
break;
}
}
blobmsg_close_table(data->buf, t);
}
blobmsg_close_table(data->buf, o);
}
static void uh_ubus_send_list(struct client *cl, json_object *obj, struct blob_attr *params)
{
struct blob_attr *cur, *dup;
struct list_data data = { .buf = &cl->dispatch.ubus.buf, .verbose = false };
void *r;
int rem;
blob_buf_init(data.buf, 0);
uh_client_ref(cl);
if (!params || blob_id(params) != BLOBMSG_TYPE_ARRAY) {
r = blobmsg_open_array(data.buf, "result");
ubus_lookup(ctx, NULL, uh_ubus_list_cb, &data);
blobmsg_close_array(data.buf, r);
}
else {
r = blobmsg_open_table(data.buf, "result");
dup = blob_memdup(params);
if (dup)
{
rem = blobmsg_data_len(dup);
data.verbose = true;
__blob_for_each_attr(cur, blobmsg_data(dup), rem)
ubus_lookup(ctx, blobmsg_data(cur), uh_ubus_list_cb, &data);
free(dup);
}
blobmsg_close_table(data.buf, r);
}
uh_client_unref(cl);
uh_ubus_init_response(cl);
blobmsg_add_blob(&buf, blob_data(data.buf->head));
uh_ubus_send_response(cl);
}
static bool parse_json_rpc(struct rpc_data *d, struct blob_attr *data)
{
const struct blobmsg_policy data_policy[] = {
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_TABLE },
};
struct blob_attr *tb[__RPC_MAX];
struct blob_attr *tb2[4];
struct blob_attr *cur;
blobmsg_parse(rpc_policy, __RPC_MAX, tb, blob_data(data), blob_len(data));
cur = tb[RPC_JSONRPC];
if (!cur || strcmp(blobmsg_data(cur), "2.0") != 0)
return false;
cur = tb[RPC_METHOD];
if (!cur)
return false;
d->id = tb[RPC_ID];
d->method = blobmsg_data(cur);
cur = tb[RPC_PARAMS];
if (!cur)
return true;
d->params = blob_memdup(cur);
if (!d->params)
return false;
blobmsg_parse_array(data_policy, ARRAY_SIZE(data_policy), tb2,
blobmsg_data(d->params), blobmsg_data_len(d->params));
if (tb2[0])
d->sid = blobmsg_data(tb2[0]);
if (conf.ubus_noauth && (!d->sid || !*d->sid))
d->sid = UH_UBUS_DEFAULT_SID;
if (tb2[1])
d->object = blobmsg_data(tb2[1]);
if (tb2[2])
d->function = blobmsg_data(tb2[2]);
d->data = tb2[3];
return true;
}
static void uh_ubus_init_batch(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
du->array = true;
uh_ubus_send_header(cl);
ops->chunk_printf(cl, "[");
}
static void uh_ubus_complete_batch(struct client *cl)
{
ops->chunk_printf(cl, "]");
ops->request_done(cl);
}
static void uh_ubus_allowed_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct blob_attr *tb[__SES_MAX];
bool *allow = (bool *)req->priv;
if (!msg)
return;
blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
if (tb[SES_ACCESS])
*allow = blobmsg_get_bool(tb[SES_ACCESS]);
}
static bool uh_ubus_allowed(const char *sid, const char *obj, const char *fun)
{
uint32_t id;
bool allow = false;
static struct blob_buf req;
if (ubus_lookup_id(ctx, "session", &id))
return false;
blob_buf_init(&req, 0);
blobmsg_add_string(&req, "ubus_rpc_session", sid);
blobmsg_add_string(&req, "object", obj);
blobmsg_add_string(&req, "function", fun);
ubus_invoke(ctx, id, "access", req.head, uh_ubus_allowed_cb, &allow, conf.script_timeout * 500);
return allow;
}
static void uh_ubus_handle_request_object(struct client *cl, struct json_object *obj)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
struct rpc_data data = {};
enum rpc_error err = ERROR_PARSE;
uh_client_ref(cl);
if (json_object_get_type(obj) != json_type_object)
goto error;
du->jsobj_cur = obj;
blob_buf_init(&buf, 0);
if (!blobmsg_add_object(&buf, obj))
goto error;
if (!parse_json_rpc(&data, buf.head))
goto error;
if (!strcmp(data.method, "call")) {
if (!data.sid || !data.object || !data.function || !data.data)
goto error;
du->func = data.function;
if (ubus_lookup_id(ctx, data.object, &du->obj)) {
err = ERROR_OBJECT;
goto error;
}
if (!conf.ubus_noauth && !uh_ubus_allowed(data.sid, data.object, data.function)) {
err = ERROR_ACCESS;
goto error;
}
uh_ubus_send_request(cl, obj, data.sid, data.data);
goto out;
}
else if (!strcmp(data.method, "list")) {
uh_ubus_send_list(cl, obj, data.params);
goto out;
}
else {
err = ERROR_METHOD;
goto error;
}
error:
uh_ubus_json_error(cl, err);
out:
if (data.params)
free(data.params);
uh_client_unref(cl);
}
static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout)
{
struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout);
struct client *cl = container_of(du, struct client, dispatch.ubus);
struct json_object *obj = du->jsobj;
int len;
len = json_object_array_length(obj);
if (du->array_idx >= len)
return uh_ubus_complete_batch(cl);
obj = json_object_array_get_idx(obj, du->array_idx++);
uh_ubus_handle_request_object(cl, obj);
}
static void uh_ubus_data_done(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
struct json_object *obj = du->jsobj;
switch (obj ? json_object_get_type(obj) : json_type_null) {
case json_type_object:
uh_ubus_send_header(cl);
return uh_ubus_handle_request_object(cl, obj);
case json_type_array:
uh_ubus_init_batch(cl);
return uh_ubus_next_batched_request(cl);
default:
return uh_ubus_single_error(cl, ERROR_PARSE);
}
}
static int uh_ubus_data_send(struct client *cl, const char *data, int len)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
if (du->jsobj || !du->jstok)
goto error;
du->post_len += len;
if (du->post_len > UH_UBUS_MAX_POST_SIZE)
goto error;
du->jsobj = json_tokener_parse_ex(du->jstok, data, len);
return len;
error:
uh_ubus_single_error(cl, ERROR_PARSE);
return 0;
}
static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi)
{
struct dispatch *d = &cl->dispatch;
blob_buf_init(&buf, 0);
switch (cl->request.method)
{
case UH_HTTP_MSG_POST:
d->data_send = uh_ubus_data_send;
d->data_done = uh_ubus_data_done;
d->close_fds = uh_ubus_close_fds;
d->free = uh_ubus_request_free;
d->ubus.jstok = json_tokener_new();
break;
case UH_HTTP_MSG_OPTIONS:
uh_ubus_send_header(cl);
ops->request_done(cl);
break;
default:
ops->client_error(cl, 400, "Bad Request", "Invalid Request");
}
}
static bool
uh_ubus_check_url(const char *url)
{
return ops->path_match(conf.ubus_prefix, url);
}
static int
uh_ubus_init(void)
{
static struct dispatch_handler ubus_dispatch = {
.check_url = uh_ubus_check_url,
.handle_request = uh_ubus_handle_request,
};
ctx = ubus_connect(conf.ubus_socket);
if (!ctx) {
fprintf(stderr, "Unable to connect to ubus socket\n");
exit(1);
}
ops->dispatch_add(&ubus_dispatch);
uloop_done();
return 0;
}
static int uh_ubus_plugin_init(const struct uhttpd_ops *o, struct config *c)
{
ops = o;
_conf = c;
return uh_ubus_init();
}
static void uh_ubus_post_init(void)
{
ubus_add_uloop(ctx);
}
struct uhttpd_plugin uhttpd_plugin = {
.init = uh_ubus_plugin_init,
.post_init = uh_ubus_post_init,
};

342
src/3P/uhttpd/uhttpd.h Normal file
View File

@@ -0,0 +1,342 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __UHTTPD_H
#define __UHTTPD_H
#include <netinet/in.h>
#include <limits.h>
#include <dirent.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/blob.h>
#include <libubox/utils.h>
#ifdef HAVE_UBUS
#include <libubus.h>
#include <json-c/json.h>
#endif
#ifdef HAVE_TLS
#include <libubox/ustream-ssl.h>
#endif
#include "utils.h"
#define UH_LIMIT_CLIENTS 64
#define __enum_header(_name, _val) HDR_##_name,
#define __blobmsg_header(_name, _val) [HDR_##_name] = { .name = #_val, .type = BLOBMSG_TYPE_STRING },
struct client;
struct alias {
struct list_head list;
char *alias;
char *path;
};
struct config {
const char *docroot;
const char *realm;
const char *file;
const char *error_handler;
const char *cgi_prefix;
const char *cgi_docroot_path;
const char *cgi_path;
const char *lua_handler;
const char *lua_prefix;
const char *ubus_prefix;
const char *ubus_socket;
int no_symlinks;
int no_dirlists;
int network_timeout;
int rfc1918_filter;
int tls_redirect;
int tcp_keepalive;
int max_script_requests;
int max_connections;
int http_keepalive;
int script_timeout;
int ubus_noauth;
int ubus_cors;
int cgi_prefix_len;
struct list_head cgi_alias;
};
struct auth_realm {
struct list_head list;
const char *path;
const char *user;
const char *pass;
};
enum http_method {
UH_HTTP_MSG_GET,
UH_HTTP_MSG_POST,
UH_HTTP_MSG_HEAD,
UH_HTTP_MSG_OPTIONS,
};
enum http_version {
UH_HTTP_VER_0_9,
UH_HTTP_VER_1_0,
UH_HTTP_VER_1_1,
};
enum http_user_agent {
UH_UA_UNKNOWN,
UH_UA_GECKO,
UH_UA_CHROME,
UH_UA_SAFARI,
UH_UA_MSIE,
UH_UA_KONQUEROR,
UH_UA_OPERA,
UH_UA_MSIE_OLD,
UH_UA_MSIE_NEW,
};
struct http_request {
enum http_method method;
enum http_version version;
enum http_user_agent ua;
int redirect_status;
int content_length;
bool expect_cont;
bool connection_close;
bool disable_chunked;
uint8_t transfer_chunked;
const struct auth_realm *realm;
};
enum client_state {
CLIENT_STATE_INIT,
CLIENT_STATE_HEADER,
CLIENT_STATE_DATA,
CLIENT_STATE_DONE,
CLIENT_STATE_CLOSE,
CLIENT_STATE_CLEANUP,
};
struct interpreter {
struct list_head list;
const char *path;
const char *ext;
};
struct path_info {
const char *root;
const char *phys;
const char *name;
const char *info;
const char *query;
const char *auth;
bool redirected;
struct stat stat;
const struct interpreter *ip;
};
struct env_var {
const char *name;
const char *value;
};
struct relay {
struct ustream_fd sfd;
struct uloop_process proc;
struct uloop_timeout timeout;
struct client *cl;
bool process_done;
bool error;
bool skip_data;
int ret;
int header_ofs;
void (*header_cb)(struct relay *r, const char *name, const char *value);
void (*header_end)(struct relay *r);
void (*close)(struct relay *r, int ret);
};
struct dispatch_proc {
struct uloop_timeout timeout;
struct blob_buf hdr;
struct uloop_fd wrfd;
struct relay r;
int status_code;
char *status_msg;
};
struct dispatch_handler {
struct list_head list;
bool script;
bool (*check_url)(const char *url);
bool (*check_path)(struct path_info *pi, const char *url);
void (*handle_request)(struct client *cl, char *url, struct path_info *pi);
};
#ifdef HAVE_UBUS
struct dispatch_ubus {
struct ubus_request req;
struct uloop_timeout timeout;
struct json_tokener *jstok;
struct json_object *jsobj;
struct json_object *jsobj_cur;
int post_len;
uint32_t obj;
const char *func;
struct blob_buf buf;
bool req_pending;
bool array;
int array_idx;
};
#endif
struct dispatch {
int (*data_send)(struct client *cl, const char *data, int len);
void (*data_done)(struct client *cl);
void (*write_cb)(struct client *cl);
void (*close_fds)(struct client *cl);
void (*free)(struct client *cl);
void *req_data;
void (*req_free)(struct client *cl);
bool data_blocked;
bool no_cache;
union {
struct {
struct blob_attr **hdr;
int fd;
} file;
struct dispatch_proc proc;
#ifdef HAVE_UBUS
struct dispatch_ubus ubus;
#endif
};
};
struct client {
struct list_head list;
int refcount;
int id;
struct ustream *us;
struct ustream_fd sfd;
#ifdef HAVE_TLS
struct ustream_ssl ssl;
#endif
struct uloop_timeout timeout;
int requests;
enum client_state state;
bool tls;
int http_code;
struct http_request request;
struct uh_addr srv_addr, peer_addr;
struct blob_buf hdr;
struct blob_buf hdr_response;
struct dispatch dispatch;
};
extern char uh_buf[4096];
extern int n_clients;
extern struct config conf;
extern const char * const http_versions[];
extern const char * const http_methods[];
extern struct dispatch_handler cgi_dispatch;
void uh_index_add(const char *filename);
bool uh_accept_client(int fd, bool tls);
void uh_unblock_listeners(void);
void uh_setup_listeners(void);
int uh_socket_bind(const char *host, const char *port, bool tls);
int uh_first_tls_port(int family);
bool uh_use_chunked(struct client *cl);
void uh_chunk_write(struct client *cl, const void *data, int len);
void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg);
void __printf(2, 3)
uh_chunk_printf(struct client *cl, const char *format, ...);
void uh_chunk_eof(struct client *cl);
void uh_request_done(struct client *cl);
void uh_http_header(struct client *cl, int code, const char *summary);
void __printf(4, 5)
uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...);
void uh_handle_request(struct client *cl);
void client_poll_post_data(struct client *cl);
void uh_client_read_cb(struct client *cl);
void uh_client_notify_state(struct client *cl);
void uh_auth_add(const char *path, const char *user, const char *pass);
bool uh_auth_check(struct client *cl, struct path_info *pi);
void uh_close_listen_fds(void);
void uh_close_fds(void);
void uh_interpreter_add(const char *ext, const char *path);
void uh_dispatch_add(struct dispatch_handler *d);
void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid);
void uh_relay_close(struct relay *r, int ret);
void uh_relay_free(struct relay *r);
void uh_relay_kill(struct client *cl, struct relay *r);
struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi);
bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
void (*cb)(struct client *cl, struct path_info *pi, char *url));
int uh_plugin_init(const char *name);
void uh_plugin_post_init(void);
int uh_handler_add(const char *file);
int uh_handler_run(struct client *cl, char **url, bool fallback);
struct path_info *uh_path_lookup(struct client *cl, const char *url);
static inline void uh_client_ref(struct client *cl)
{
cl->refcount++;
}
static inline void uh_client_unref(struct client *cl)
{
if (--cl->refcount)
return;
if (cl->state == CLIENT_STATE_CLEANUP)
ustream_state_change(cl->us);
}
#endif

251
src/3P/uhttpd/utils.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <ctype.h>
#include "uhttpd.h"
bool uh_use_chunked(struct client *cl)
{
if (cl->request.version != UH_HTTP_VER_1_1)
return false;
if (cl->request.method == UH_HTTP_MSG_HEAD || cl->request.method == UH_HTTP_MSG_OPTIONS)
return false;
/* RFC2616 10.2.5, 10.3.5 */
if (cl->http_code == 204 || cl->http_code == 304)
return false;
return !cl->request.disable_chunked;
}
void uh_chunk_write(struct client *cl, const void *data, int len)
{
bool chunked = uh_use_chunked(cl);
if (cl->state == CLIENT_STATE_CLEANUP)
return;
uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
if (chunked)
ustream_printf(cl->us, "%X\r\n", len);
ustream_write(cl->us, data, len, true);
if (chunked)
ustream_printf(cl->us, "\r\n", len);
}
void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
{
char buf[256];
va_list arg2;
int len;
if (cl->state == CLIENT_STATE_CLEANUP)
return;
uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
if (!uh_use_chunked(cl)) {
ustream_vprintf(cl->us, format, arg);
return;
}
va_copy(arg2, arg);
len = vsnprintf(buf, sizeof(buf), format, arg2);
va_end(arg2);
ustream_printf(cl->us, "%X\r\n", len);
if (len < sizeof(buf))
ustream_write(cl->us, buf, len, true);
else
ustream_vprintf(cl->us, format, arg);
ustream_printf(cl->us, "\r\n", len);
}
void uh_chunk_printf(struct client *cl, const char *format, ...)
{
va_list arg;
va_start(arg, format);
uh_chunk_vprintf(cl, format, arg);
va_end(arg);
}
void uh_chunk_eof(struct client *cl)
{
if (!uh_use_chunked(cl))
return;
if (cl->state == CLIENT_STATE_CLEANUP)
return;
ustream_printf(cl->us, "0\r\n\r\n");
}
/* blen is the size of buf; slen is the length of src. The input-string need
** not be, and the output string will not be, null-terminated. Returns the
** length of the decoded string, -1 on buffer overflow, -2 on malformed string. */
int uh_urldecode(char *buf, int blen, const char *src, int slen)
{
int i;
int len = 0;
#define hex(x) \
(((x) <= '9') ? ((x) - '0') : \
(((x) <= 'F') ? ((x) - 'A' + 10) : \
((x) - 'a' + 10)))
for (i = 0; (i < slen) && (len < blen); i++)
{
if (src[i] != '%') {
buf[len++] = src[i];
continue;
}
if (i + 2 >= slen || !isxdigit(src[i + 1]) || !isxdigit(src[i + 2]))
return -2;
buf[len++] = (char)(16 * hex(src[i+1]) + hex(src[i+2]));
i += 2;
}
buf[len] = 0;
return (i == slen) ? len : -1;
}
/* blen is the size of buf; slen is the length of src. The input-string need
** not be, and the output string will not be, null-terminated. Returns the
** length of the encoded string, or -1 on error (buffer overflow) */
int uh_urlencode(char *buf, int blen, const char *src, int slen)
{
int i;
int len = 0;
static const char hex[] = "0123456789abcdef";
for (i = 0; (i < slen) && (len < blen); i++)
{
if( isalnum(src[i]) || (src[i] == '-') || (src[i] == '_') ||
(src[i] == '.') || (src[i] == '~') )
{
buf[len++] = src[i];
}
else if ((len+3) <= blen)
{
buf[len++] = '%';
buf[len++] = hex[(src[i] >> 4) & 15];
buf[len++] = hex[ src[i] & 15];
}
else
{
len = -1;
break;
}
}
return (i == slen) ? len : -1;
}
int uh_b64decode(char *buf, int blen, const void *src, int slen)
{
const unsigned char *str = src;
unsigned int cout = 0;
unsigned int cin = 0;
int len = 0;
int i = 0;
for (i = 0; (i <= slen) && (str[i] != 0); i++)
{
cin = str[i];
if ((cin >= '0') && (cin <= '9'))
cin = cin - '0' + 52;
else if ((cin >= 'A') && (cin <= 'Z'))
cin = cin - 'A';
else if ((cin >= 'a') && (cin <= 'z'))
cin = cin - 'a' + 26;
else if (cin == '+')
cin = 62;
else if (cin == '/')
cin = 63;
else if (cin == '=')
cin = 0;
else
continue;
cout = (cout << 6) | cin;
if ((i % 4) != 3)
continue;
if ((len + 3) >= blen)
break;
buf[len++] = (char)(cout >> 16);
buf[len++] = (char)(cout >> 8);
buf[len++] = (char)(cout);
}
buf[len++] = 0;
return len;
}
bool uh_path_match(const char *prefix, const char *url)
{
int len = strlen(prefix);
/* A prefix of "/" will - by definition - match any url */
if (prefix[0] == '/' && len == 1)
return true;
if (strncmp(url, prefix, len) != 0)
return false;
return url[len] == '/' || url[len] == 0;
}
char *uh_split_header(char *str)
{
char *val;
val = strchr(str, ':');
if (!val)
return NULL;
*val = 0;
val++;
while (isspace(*val))
val++;
return val;
}
bool uh_addr_rfc1918(struct uh_addr *addr)
{
uint32_t a;
if (addr->family != AF_INET)
return false;
a = htonl(addr->in.s_addr);
return ((a >= 0x0A000000) && (a <= 0x0AFFFFFF)) ||
((a >= 0xAC100000) && (a <= 0xAC1FFFFF)) ||
((a >= 0xC0A80000) && (a <= 0xC0A8FFFF));
return 0;
}

77
src/3P/uhttpd/utils.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* uhttpd - Tiny single-threaded httpd
*
* Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
* Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _UHTTPD_UTILS_
#include <sys/stat.h>
#include <stdarg.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <alloca.h>
#include <time.h>
#include <unistd.h>
struct uh_addr {
uint8_t family;
uint16_t port;
union {
struct in_addr in;
struct in6_addr in6;
};
};
#define min(x, y) (((x) < (y)) ? (x) : (y))
#define max(x, y) (((x) > (y)) ? (x) : (y))
#define array_size(x) \
(sizeof(x) / sizeof(x[0]))
#define fd_cloexec(fd) \
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)
#ifdef __APPLE__
static inline void clearenv(void)
{
extern char **environ;
*environ = NULL;
}
time_t timegm (struct tm *tm);
#endif
#ifdef __GNUC__
#define __printf(a, b) __attribute__((format(printf, a, b)))
#else
#define __printf(a, b)
#endif
int uh_urldecode(char *buf, int blen, const char *src, int slen);
int uh_urlencode(char *buf, int blen, const char *src, int slen);
int uh_b64decode(char *buf, int blen, const void *src, int slen);
bool uh_path_match(const char *prefix, const char *url);
char *uh_split_header(char *str);
bool uh_addr_rfc1918(struct uh_addr *addr);
#endif