add support for handling redirects via a script
authorFelix Fietkau <nbd@openwrt.org>
Sun, 8 Nov 2015 10:41:42 +0000 (11:41 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Sun, 8 Nov 2015 10:45:29 +0000 (11:45 +0100)
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>
CMakeLists.txt
file.c
handler.c [new file with mode: 0644]
main.c
uhttpd.h

index 8c285dcb8c8c1471628fb2fe0288ac256d924dd3..8514351b98a3c1540ac454c22dbd2e8230ef2f24 100644 (file)
@@ -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 009acbd9aca217ed6c0f6810344567432c0f55e4..816df85256214756223628297a1cea04b71f9e3b 100644 (file)
--- 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 (file)
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 6cbceb7c142c327b1382a3c084d055b5f569c85d..fb2766576e8f84890dac44a9208cfaad3add0769 100644 (file)
--- 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",
index 897d4b34ee5b302aa58a95955703516d30873888..f9ea76196ec1706553fab7a1adc9741ca45cb2ff 100644 (file)
--- 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++;