Bump uhttpd version 2016-10-25
This commit is contained in:
14
bsp/buildroot_external/package/uhttpd/Config.in
Normal file
14
bsp/buildroot_external/package/uhttpd/Config.in
Normal 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
|
||||||
24
bsp/buildroot_external/package/uhttpd/uhttpd.mk
Normal file
24
bsp/buildroot_external/package/uhttpd/uhttpd.mk
Normal 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))
|
||||||
88
src/3P/uhttpd/CMakeLists.txt
Normal file
88
src/3P/uhttpd/CMakeLists.txt
Normal 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
135
src/3P/uhttpd/auth.c
Normal 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
118
src/3P/uhttpd/cgi.c
Normal 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
656
src/3P/uhttpd/client.c
Normal 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
900
src/3P/uhttpd/file.c
Normal 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
258
src/3P/uhttpd/handler.c
Normal 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
222
src/3P/uhttpd/listen.c
Normal 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
300
src/3P/uhttpd/lua.c
Normal 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
527
src/3P/uhttpd/main.c
Normal 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
94
src/3P/uhttpd/mimetypes.h
Normal 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
70
src/3P/uhttpd/plugin.c
Normal 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
45
src/3P/uhttpd/plugin.h
Normal 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
389
src/3P/uhttpd/proc.c
Normal 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
205
src/3P/uhttpd/relay.c
Normal 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
55
src/3P/uhttpd/session-test.sh
Executable 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
103
src/3P/uhttpd/tls.c
Normal 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
46
src/3P/uhttpd/tls.h
Normal 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
685
src/3P/uhttpd/ubus.c
Normal 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
342
src/3P/uhttpd/uhttpd.h
Normal 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
251
src/3P/uhttpd/utils.c
Normal 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
77
src/3P/uhttpd/utils.h
Normal 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
|
||||||
Reference in New Issue
Block a user