summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@openwrt.org>2015-11-08 11:41:42 +0100
committerFelix Fietkau <nbd@openwrt.org>2015-11-08 11:45:29 +0100
commit25023c796a9a55cd7ec7ff364cd1229fab958679 (patch)
tree74e2f8fcd78e0a0d35a79dbd32781fb82531b9f4
parentd9513e62bf484eea8a9ca63c2bfac10f4bdb6c9f (diff)
add support for handling redirects via a script
In a json_script file you can specify rules for rewriting the URL or redirecting the browser either unconditionally, or as a fallback where it would otherwise print a 404 error Signed-off-by: Felix Fietkau <nbd@openwrt.org>
-rw-r--r--CMakeLists.txt6
-rw-r--r--file.c10
-rw-r--r--handler.c219
-rw-r--r--main.c10
-rw-r--r--uhttpd.h5
5 files changed, 245 insertions, 5 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c285dc..8514351 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,7 +21,7 @@ IF(LIBS STREQUAL "LIBS-NOTFOUND")
SET(LIBS "")
ENDIF()
-SET(SOURCES main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c plugin.c)
+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)
@@ -33,7 +33,8 @@ IF(HAVE_SHADOW)
ENDIF()
ADD_EXECUTABLE(uhttpd ${SOURCES})
-TARGET_LINK_LIBRARIES(uhttpd ubox dl ${LIBS})
+FIND_LIBRARY(libjson NAMES json-c json)
+TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
SET(PLUGINS "")
IF(LUA_SUPPORT)
@@ -69,7 +70,6 @@ IF(UBUS_SUPPORT)
SET(PLUGINS ${PLUGINS} uhttpd_ubus)
ADD_DEFINITIONS(-DHAVE_UBUS)
ADD_LIBRARY(uhttpd_ubus MODULE ubus.c)
- FIND_LIBRARY(libjson NAMES json-c json)
TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json ${libjson})
ENDIF()
diff --git a/file.c b/file.c
index 009acbd..816df85 100644
--- a/file.c
+++ b/file.c
@@ -127,7 +127,7 @@ next:
/* 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. */
-static struct path_info *
+struct path_info *
uh_path_lookup(struct client *cl, const char *url)
{
static char path_phys[PATH_MAX];
@@ -864,6 +864,10 @@ void uh_handle_request(struct client *cl)
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)
@@ -872,6 +876,10 @@ void uh_handle_request(struct client *cl)
if (__handle_file_request(cl, url))
return;
+ if (uh_handler_run(cl, &url, true) &&
+ (!url || __handle_file_request(cl, url)))
+ return;
+
req->redirect_status = 404;
if (conf.error_handler) {
error_handler = alloca(strlen(conf.error_handler) + 1);
diff --git a/handler.c b/handler.c
new file mode 100644
index 0000000..56720fb
--- /dev/null
+++ b/handler.c
@@ -0,0 +1,219 @@
+/*
+ * 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 = {
+ .type = BLOBMSG_TYPE_STRING,
+ };
+ struct blob_attr *tb;
+
+ blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
+ if (!tb)
+ return;
+
+ uh_http_header(cl, 302, "Found");
+ ustream_printf(cl->us, "Content-Length: 0\r\n");
+ ustream_printf(cl->us, "Location: %s\r\n\r\n",
+ blobmsg_get_string(tb));
+ 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_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 },
+ { "set_uri", handle_set_uri }
+ };
+ 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;
+}
diff --git a/main.c b/main.c
index 6cbceb7..fb27665 100644
--- a/main.c
+++ b/main.c
@@ -232,7 +232,7 @@ int main(int argc, char **argv)
init_defaults_pre();
signal(SIGPIPE, SIG_IGN);
- while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
+ 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':
@@ -273,6 +273,14 @@ int main(int argc, char **argv)
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",
diff --git a/uhttpd.h b/uhttpd.h
index 897d4b3..f9ea761 100644
--- a/uhttpd.h
+++ b/uhttpd.h
@@ -318,6 +318,11 @@ bool uh_create_process(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++;