From 25023c796a9a55cd7ec7ff364cd1229fab958679 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sun, 8 Nov 2015 11:41:42 +0100 Subject: [PATCH] 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 --- CMakeLists.txt | 6 +- file.c | 10 ++- handler.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 10 ++- uhttpd.h | 5 ++ 5 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 handler.c 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 + * + * 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 +#include +#include + +#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++; -- 2.30.2