ucode: add ucode plugin support
authorJo-Philipp Wich <jo@mein.io>
Tue, 23 Nov 2021 10:47:08 +0000 (11:47 +0100)
committerJo-Philipp Wich <jo@mein.io>
Tue, 23 Nov 2021 18:26:43 +0000 (19:26 +0100)
The ucode plugin mirrors the functionality of the Lua module, but using
the ucode script interpreter instead.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
CMakeLists.txt
main.c
ucode.c [new file with mode: 0644]
uhttpd.h

index 7ae8ba4d13d64df719c2593e38f22991cf87139f..564842d8844be8ef9d5be49c380ad5bb1a75b15e 100644 (file)
@@ -10,6 +10,7 @@ ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations
 OPTION(TLS_SUPPORT "TLS support" ON)
 OPTION(LUA_SUPPORT "Lua support" ON)
 OPTION(UBUS_SUPPORT "ubus support" ON)
+OPTION(UCODE_SUPPORT "ucode support" ON)
 
 IF(APPLE)
   INCLUDE_DIRECTORIES(/opt/local/include)
@@ -69,6 +70,13 @@ IF(LUA_SUPPORT)
        TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl)
 ENDIF()
 
+IF(UCODE_SUPPORT)
+       SET(PLUGINS ${PLUGINS} uhttpd_ucode)
+       ADD_DEFINITIONS(-DHAVE_UCODE)
+       ADD_LIBRARY(uhttpd_ucode MODULE ucode.c)
+       TARGET_LINK_LIBRARIES(uhttpd_ucode ucode ${libjson})
+ENDIF()
+
 IF(UBUS_SUPPORT)
        SET(PLUGINS ${PLUGINS} uhttpd_ubus)
        ADD_DEFINITIONS(-DHAVE_UBUS)
diff --git a/main.c b/main.c
index 73e3d42607d69a49f7681e277a8f8e6210a4ecf9..13d02a4f101512c01be6a3141f8cb186f18487ff 100644 (file)
--- a/main.c
+++ b/main.c
@@ -188,6 +188,9 @@ static void init_defaults_pre(void)
        conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin";
        INIT_LIST_HEAD(&conf.cgi_alias);
        INIT_LIST_HEAD(&conf.lua_prefix);
+#if HAVE_UCODE
+       INIT_LIST_HEAD(&conf.ucode_prefix);
+#endif
 }
 
 static void init_defaults_post(void)
@@ -241,6 +244,25 @@ static void add_lua_prefix(const char *prefix, const char *handler) {
 }
 #endif
 
+#ifdef HAVE_UCODE
+static void add_ucode_prefix(const char *prefix, const char *handler) {
+       struct ucode_prefix *p;
+       char *pprefix, *phandler;
+
+       p = calloc_a(sizeof(*p),
+                    &pprefix, strlen(prefix) + 1,
+                    &phandler, strlen(handler) + 1);
+
+       if (!p)
+               return;
+
+       p->prefix = strcpy(pprefix, prefix);
+       p->handler = strcpy(phandler, handler);
+
+       list_add_tail(&p->list, &conf.ucode_prefix);
+}
+#endif
+
 int main(int argc, char **argv)
 {
        struct alias *alias;
@@ -256,6 +278,9 @@ int main(int argc, char **argv)
 #ifdef HAVE_LUA
        const char *lua_prefix = NULL, *lua_handler = NULL;
 #endif
+#ifdef HAVE_UCODE
+       const char *ucode_prefix = NULL, *ucode_handler = NULL;
+#endif
 
        BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
 
@@ -263,7 +288,7 @@ int main(int argc, char **argv)
        init_defaults_pre();
        signal(SIGPIPE, SIG_IGN);
 
-       while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:e:fh:H:I:i:K:k:L:l:m:N:n:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
+       while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:e:fh:H:I:i:K:k:L:l:m:N:n:O:o:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
                switch(ch) {
 #ifdef HAVE_TLS
                case 'C':
@@ -475,6 +500,38 @@ int main(int argc, char **argv)
                                        "ignoring -%c\n", ch);
                        break;
 #endif
+#ifdef HAVE_UCODE
+               case 'o':
+               case 'O':
+                       if (ch == 'o') {
+                               if (ucode_prefix)
+                                       fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n",
+                                               ch, ucode_prefix);
+
+                               ucode_prefix = optarg;
+                       }
+                       else {
+                               if (ucode_handler)
+                                       fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n",
+                                               ch, ucode_handler);
+
+                               ucode_handler = optarg;
+                       }
+
+                       if (ucode_prefix && ucode_handler) {
+                               add_ucode_prefix(ucode_prefix, ucode_handler);
+                               ucode_prefix = NULL;
+                               ucode_handler = NULL;
+                       }
+
+                       break;
+#else
+               case 'o':
+               case 'O':
+                       fprintf(stderr, "uhttpd: ucode support not compiled, "
+                                       "ignoring -%c\n", ch);
+                       break;
+#endif
 #ifdef HAVE_UBUS
                case 'a':
                        conf.ubus_noauth = 1;
@@ -549,6 +606,15 @@ int main(int argc, char **argv)
        if (!list_empty(&conf.lua_prefix) && uh_plugin_init("uhttpd_lua.so"))
                return 1;
 #endif
+#ifdef HAVE_UCODE
+       if (ucode_handler || ucode_prefix) {
+               fprintf(stderr, "Need handler and prefix to enable ucode support\n");
+               return 1;
+       }
+
+       if (!list_empty(&conf.ucode_prefix) && uh_plugin_init("uhttpd_ucode.so"))
+               return 1;
+#endif
 #ifdef HAVE_UBUS
        if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so"))
                return 1;
diff --git a/ucode.c b/ucode.c
new file mode 100644 (file)
index 0000000..f408576
--- /dev/null
+++ b/ucode.c
@@ -0,0 +1,435 @@
+/*
+ * 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 <ucode/compiler.h>
+#include <ucode/lib.h>
+#include <ucode/vm.h>
+#include <stdio.h>
+#include <poll.h>
+
+#include "uhttpd.h"
+#include "plugin.h"
+
+#define UH_UCODE_CB    "handle_request"
+
+static const struct uhttpd_ops *ops;
+static struct config *_conf;
+#define conf (*_conf)
+
+static struct ucode_prefix *current_prefix;
+
+static uc_value_t *
+uh_ucode_recv(uc_vm_t *vm, size_t nargs)
+{
+       static struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN };
+       int data_len = 0, len = BUFSIZ, rlen, r;
+       uc_value_t *v = uc_fn_arg(0);
+       uc_stringbuf_t *buf;
+
+       if (ucv_type(v) == UC_INTEGER) {
+               len = ucv_int64_get(v);
+       }
+       else if (v != NULL) {
+               uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument not an integer");
+
+               return NULL;
+       }
+
+       buf = ucv_stringbuf_new();
+
+       while (len > 0) {
+               rlen = (len < BUFSIZ) ? len : BUFSIZ;
+
+               if (printbuf_memset(buf, -1, 0, rlen)) {
+                       uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory");
+                       printbuf_free(buf);
+
+                       return NULL;
+               }
+
+               buf->bpos -= rlen;
+               r = read(STDIN_FILENO, buf->buf + buf->bpos, rlen);
+
+               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;
+               }
+
+               buf->bpos += r;
+               data_len += r;
+               len -= r;
+
+               if (r != rlen)
+                       break;
+       }
+
+       if (data_len > 0) {
+               /* add final guard \0 but do not count it */
+               if (printbuf_memset(buf, -1, 0, 1)) {
+                       uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory");
+                       printbuf_free(buf);
+
+                       return NULL;
+               }
+
+               buf->bpos--;
+
+               return ucv_stringbuf_finish(buf);
+       }
+
+       printbuf_free(buf);
+
+       return NULL;
+}
+
+static uc_value_t *
+uh_ucode_send(uc_vm_t *vm, size_t nargs)
+{
+       uc_value_t *val = uc_fn_arg(0);
+       ssize_t len;
+       char *p;
+
+       if (ucv_type(val) == UC_STRING) {
+               len = write(STDOUT_FILENO, ucv_string_get(val), ucv_string_length(val));
+       }
+       else if (val != NULL) {
+               p = ucv_to_string(vm, val);
+               len = p ? write(STDOUT_FILENO, p, strlen(p)) : 0;
+               free(p);
+       }
+       else {
+               len = 0;
+       }
+
+       return ucv_int64_new(len);
+}
+
+static uc_value_t *
+uh_ucode_strconvert(uc_vm_t *vm, size_t nargs, int (*convert)(char *, int, const char *, int))
+{
+       uc_value_t *val = uc_fn_arg(0);
+       static char out_buf[4096];
+       int out_len;
+       char *p;
+
+       if (ucv_type(val) == UC_STRING) {
+               out_len = convert(out_buf, sizeof(out_buf),
+                       ucv_string_get(val), ucv_string_length(val));
+       }
+       else if (val != NULL) {
+               p = ucv_to_string(vm, val);
+               out_len = p ? convert(out_buf, sizeof(out_buf), p, strlen(p)) : 0;
+               free(p);
+       }
+       else {
+               out_len = 0;
+       }
+
+       if (out_len < 0) {
+               const char *error;
+
+               if (out_len == -1)
+                       error = "buffer overflow";
+               else
+                       error = "malformed string";
+
+               uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+                       "%s on URL conversion\n", error);
+
+               return NULL;
+       }
+
+       return ucv_string_new_length(out_buf, out_len);
+}
+
+static uc_value_t *
+uh_ucode_urldecode(uc_vm_t *vm, size_t nargs)
+{
+       return uh_ucode_strconvert(vm, nargs, ops->urldecode);
+}
+
+static uc_value_t *
+uh_ucode_urlencode(uc_vm_t *vm, size_t nargs)
+{
+       return uh_ucode_strconvert(vm, nargs, ops->urlencode);
+}
+
+static uc_parse_config_t config = {
+       .strict_declarations = false,
+       .lstrip_blocks = true,
+       .trim_blocks = true
+};
+
+static void
+uh_ucode_exception(uc_vm_t *vm, uc_exception_t *ex)
+{
+       uc_value_t *ctx;
+
+       printf("Status: 500 Internal Server Error\r\n\r\n"
+       "Exception while executing ucode program %s:\n",
+       current_prefix->handler);
+
+       switch (ex->type) {
+       case EXCEPTION_SYNTAX:    printf("Syntax error");    break;
+       case EXCEPTION_RUNTIME:   printf("Runtime error");   break;
+       case EXCEPTION_TYPE:      printf("Type error");      break;
+       case EXCEPTION_REFERENCE: printf("Reference error"); break;
+       default:                  printf("Error");
+       }
+
+       printf(": %s\n", ex->message);
+
+       ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL);
+
+       if (ctx)
+               printf("%s\n", ucv_string_get(ctx));
+}
+
+static void
+uh_ucode_state_init(struct ucode_prefix *ucode)
+{
+       char *syntax_error = NULL;
+       uc_vm_t *vm = &ucode->ctx;
+       uc_function_t *handler;
+       uc_vm_status_t status;
+       uc_source_t *src;
+       uc_value_t *v;
+       int exitcode;
+
+       uc_vm_init(vm, &config);
+       uc_stdlib_load(uc_vm_scope_get(vm));
+
+       /* build uhttpd api table */
+       v = ucv_object_new(vm);
+
+       ucv_object_add(v, "send", ucv_cfunction_new("send", uh_ucode_send));
+       ucv_object_add(v, "sendc", ucv_get(ucv_object_get(v, "send", NULL)));
+       ucv_object_add(v, "recv", ucv_cfunction_new("recv", uh_ucode_recv));
+       ucv_object_add(v, "urldecode", ucv_cfunction_new("urldecode", uh_ucode_urldecode));
+       ucv_object_add(v, "urlencode", ucv_cfunction_new("urlencode", uh_ucode_urlencode));
+       ucv_object_add(v, "docroot", ucv_string_new(conf.docroot));
+
+       ucv_object_add(uc_vm_scope_get(vm), "uhttpd", v);
+
+       src = uc_source_new_file(ucode->handler);
+
+       if (!src) {
+               fprintf(stderr, "Error: Unable to open ucode handler: %s\n",
+                       strerror(errno));
+
+               exit(1);
+       }
+
+       handler = uc_compile(&config, src, &syntax_error);
+
+       uc_source_put(src);
+
+       if (!handler) {
+               fprintf(stderr, "Error: Unable to compile ucode handler: %s\n",
+                       syntax_error);
+
+               exit(1);
+       }
+
+       free(syntax_error);
+
+       vm->output = fopen("/dev/null", "w");
+
+       if (!vm->output) {
+               fprintf(stderr, "Error: Unable to open /dev/null for writing: %s\n",
+                       strerror(errno));
+
+               exit(1);
+       }
+
+       status = uc_vm_execute(vm, handler, &v);
+       exitcode = (int)ucv_int64_get(v);
+
+       ucv_put(v);
+
+       switch (status) {
+       case STATUS_OK:
+               break;
+
+       case STATUS_EXIT:
+               fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode);
+               exit(exitcode ? exitcode : 1);
+
+       case ERROR_COMPILE:
+               fprintf(stderr, "Error: Compilation error while executing ucode handler\n");
+               exit(1);
+
+       case ERROR_RUNTIME:
+               fprintf(stderr, "Error: Runtime error while executing ucode handler\n");
+               exit(2);
+       }
+
+       v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL);
+
+       if (!ucv_is_callable(v)) {
+               fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n");
+               exit(1);
+       }
+
+       uc_vm_exception_handler_set(vm, uh_ucode_exception);
+
+       ucv_gc(vm);
+
+       fclose(vm->output);
+
+       vm->output = stdout;
+}
+
+static void
+ucode_main(struct client *cl, struct path_info *pi, char *url)
+{
+       uc_vm_t *vm = &current_prefix->ctx;
+       uc_value_t *req, *hdr, *res;
+       int path_len, prefix_len;
+       struct blob_attr *cur;
+       struct env_var *var;
+       char *str;
+       int rem;
+
+       /* new env table for this request */
+       req = ucv_object_new(vm);
+
+       prefix_len = strlen(pi->name);
+       path_len = strlen(url);
+       str = strchr(url, '?');
+
+       if (str) {
+               if (*(str + 1))
+                       pi->query = str + 1;
+
+               path_len = str - url;
+       }
+
+       if (prefix_len > 0 && pi->name[prefix_len - 1] == '/')
+               prefix_len--;
+
+       if (path_len > prefix_len) {
+               ucv_object_add(req, "PATH_INFO",
+                       ucv_string_new_length(url + prefix_len, path_len - prefix_len));
+       }
+
+       for (var = ops->get_process_vars(cl, pi); var->name; var++) {
+               if (!var->value)
+                       continue;
+
+               ucv_object_add(req, var->name, ucv_string_new(var->value));
+       }
+
+       ucv_object_add(req, "HTTP_VERSION",
+               ucv_double_new(0.9 + (cl->request.version / 10.0)));
+
+       hdr = ucv_object_new(vm);
+
+       blob_for_each_attr(cur, cl->hdr.head, rem)
+               ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur)));
+
+       ucv_object_add(req, "headers", hdr);
+
+       res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req);
+
+       ucv_put(req);
+       ucv_put(res);
+
+       exit(0);
+}
+
+static void
+ucode_handle_request(struct client *cl, char *url, struct path_info *pi)
+{
+       struct ucode_prefix *p;
+       static struct path_info _pi;
+
+       list_for_each_entry(p, &conf.ucode_prefix, list) {
+               if (!ops->path_match(p->prefix, url))
+                       continue;
+
+               pi = &_pi;
+               pi->name = p->prefix;
+               pi->phys = p->handler;
+
+               current_prefix = p;
+
+               if (!ops->create_process(cl, pi, url, ucode_main)) {
+                       ops->client_error(cl, 500, "Internal Server Error",
+                                         "Failed to create CGI process: %s",
+                                         strerror(errno));
+               }
+
+               return;
+       }
+
+       ops->client_error(cl, 500, "Internal Server Error",
+                         "Failed to lookup matching handler");
+}
+
+static bool
+check_ucode_url(const char *url)
+{
+       struct ucode_prefix *p;
+
+       list_for_each_entry(p, &conf.ucode_prefix, list)
+               if (ops->path_match(p->prefix, url))
+                       return true;
+
+       return false;
+}
+
+static struct dispatch_handler ucode_dispatch = {
+       .script = true,
+       .check_url = check_ucode_url,
+       .handle_request = ucode_handle_request,
+};
+
+static int
+ucode_plugin_init(const struct uhttpd_ops *o, struct config *c)
+{
+       struct ucode_prefix *p;
+
+       ops = o;
+       _conf = c;
+
+       list_for_each_entry(p, &conf.ucode_prefix, list)
+               uh_ucode_state_init(p);
+
+       ops->dispatch_add(&ucode_dispatch);
+       return 0;
+}
+
+struct uhttpd_plugin uhttpd_plugin = {
+       .init = ucode_plugin_init,
+};
index e61e176d39e38880f341bb9412902d89fbd98bd8..d230b31343cfbcaa15dc8e60803c275078988283 100644 (file)
--- a/uhttpd.h
+++ b/uhttpd.h
@@ -33,6 +33,9 @@
 #include <libubus.h>
 #include <json-c/json.h>
 #endif
+#ifdef HAVE_UCODE
+#include <ucode/vm.h>
+#endif
 #ifdef HAVE_TLS
 #include <libubox/ustream-ssl.h>
 #endif
@@ -59,6 +62,15 @@ struct lua_prefix {
        void *ctx;
 };
 
+#ifdef HAVE_UCODE
+struct ucode_prefix {
+       struct list_head list;
+       const char *handler;
+       const char *prefix;
+       uc_vm_t ctx;
+};
+#endif
+
 struct config {
        const char *docroot;
        const char *realm;
@@ -85,6 +97,9 @@ struct config {
        int events_retry;
        struct list_head cgi_alias;
        struct list_head lua_prefix;
+#ifdef HAVE_UCODE
+       struct list_head ucode_prefix;
+#endif
 };
 
 struct auth_realm {