diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-04-28 00:35:32 +0200 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-04-28 00:35:32 +0200 |
commit | 46464c93e3d34d20901e07ff4601fb9d8462981f (patch) | |
tree | 52cfa9708d86e5cf3518d12023fb76fdb7f91331 | |
parent | 1cf694b165dd18f2bd4e3bd33ba203b45a20c316 (diff) | |
parent | 51283f9f1df5dedcba35f40367ef5d4ab1a55e0b (diff) |
Merge branch 'master' into bind-to-device-master
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | client.c | 24 | ||||
-rw-r--r-- | examples/lua/handler.lua | 16 | ||||
-rw-r--r-- | examples/ucode/dump-env.uc | 22 | ||||
-rw-r--r-- | examples/ucode/handler.uc | 7 | ||||
-rw-r--r-- | file.c | 4 | ||||
-rw-r--r-- | listen.c | 2 | ||||
-rw-r--r-- | main.c | 98 | ||||
-rw-r--r-- | proc.c | 2 | ||||
-rw-r--r-- | tls.c | 7 | ||||
-rw-r--r-- | tls.h | 4 | ||||
-rw-r--r-- | ubus.c | 538 | ||||
-rw-r--r-- | ucode.c | 436 | ||||
-rw-r--r-- | uhttpd.h | 21 |
14 files changed, 1073 insertions, 116 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ae8ba4..564842d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -95,7 +95,8 @@ 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); + cl->request.connection_close = true; + uh_request_done(cl); } static void uh_set_client_timeout(struct client *cl, int timeout) @@ -137,6 +138,7 @@ void uh_request_done(struct client *cl) void __printf(4, 5) uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...) { + struct http_request *r = &cl->request; va_list arg; uh_http_header(cl, code, summary); @@ -150,6 +152,17 @@ uh_client_error(struct client *cl, int code, const char *summary, const char *fm va_end(arg); } + /* Close the connection even when keep alive is set, when it + * contains a request body, as it was not read and we are + * currently out of sync. Without handling this the body will be + * interpreted as part of the next request. The alternative + * would be to read and discard the request body here. + */ + if (r->transfer_chunked || r->content_length > 0) { + cl->state = CLIENT_STATE_CLOSE; + cl->request.connection_close = true; + } + uh_request_done(cl); } @@ -194,8 +207,7 @@ static int client_parse_request(struct client *cl, char *data) 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) + if (req->version < UH_HTTP_VER_1_1 || !conf.http_keepalive) req->connection_close = true; return CLIENT_STATE_HEADER; @@ -396,6 +408,7 @@ void client_poll_post_data(struct client *cl) { struct dispatch *d = &cl->dispatch; struct http_request *r = &cl->request; + enum client_state st; char *buf; int len; @@ -460,10 +473,13 @@ void client_poll_post_data(struct client *cl) buf = ustream_get_read_buf(cl->us, &len); if (!r->content_length && !r->transfer_chunked && cl->state != CLIENT_STATE_DONE) { + st = cl->state; + if (cl->dispatch.data_done) cl->dispatch.data_done(cl); - cl->state = CLIENT_STATE_DONE; + if (cl->state == st) + cl->state = CLIENT_STATE_DONE; } } diff --git a/examples/lua/handler.lua b/examples/lua/handler.lua new file mode 100644 index 0000000..dbcea4a --- /dev/null +++ b/examples/lua/handler.lua @@ -0,0 +1,16 @@ +function handle_request(env) + uhttpd.send("Status: 200 OK\r\n") + uhttpd.send("Content-Type: text/html\r\n\r\n") + + uhttpd.send("<h1>Headers</h1>\n") + for k, v in pairs(env.headers) do + uhttpd.send(string.format("<strong>%s</strong>: %s<br>\n", k, v)) + end + + uhttpd.send("<h1>Environment</h1>\n") + for k, v in pairs(env) do + if type(v) == "string" then + uhttpd.send(string.format("<code>%s=%s</code><br>\n", k, v)) + end + end +end diff --git a/examples/ucode/dump-env.uc b/examples/ucode/dump-env.uc new file mode 100644 index 0000000..6bd7317 --- /dev/null +++ b/examples/ucode/dump-env.uc @@ -0,0 +1,22 @@ +Status: 200 OK +Content-Type: text/html + +<h1>Headers</h1> + +{% for (let k, v in env.headers): %} +<strong>{{ replace(k, /(^|-)(.)/g, (m0, d, c) => d + uc(c)) }}</strong>: {{ v }}<br> +{% endfor %} + +<h1>Environment</h1> + +{% for (let k, v in env): if (type(v) == 'string'): %} +<code>{{ k }}={{ v }}</code><br> +{% endif; endfor %} + +{% if (env.CONTENT_LENGTH > 0): %} +<h1>Body Contents</h1> + +{% for (let chunk = uhttpd.recv(64); chunk != null; chunk = uhttpd.recv(64)): %} +<code>{{ replace(chunk, /[^[:graph:]]/g, '.') }}</code><br> +{% endfor %} +{% endif %} diff --git a/examples/ucode/handler.uc b/examples/ucode/handler.uc new file mode 100644 index 0000000..e71ac26 --- /dev/null +++ b/examples/ucode/handler.uc @@ -0,0 +1,7 @@ +{% + +'use strict'; + +global.handle_request = function(env) { + include("dump-env.uc", { env }); +}; @@ -546,7 +546,8 @@ static void uh_file_dirlist(struct client *cl, struct path_info *pi) } uh_file_response_200(cl, NULL); - ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n"); + ustream_printf(cl->us, "Content-Type: text/html; charset=%s\r\n\r\n", + conf.dirlist_charset ? conf.dirlist_charset : "UTF-8"); uh_chunk_printf(cl, "<html><head><title>Index of %s</title></head>" @@ -753,6 +754,7 @@ static void uh_complete_request(struct client *cl) cl->dispatch.data_blocked = false; uh_invoke_script(cl, dr->d, dr->path ? &dr->pi : NULL); client_poll_post_data(cl); + ustream_poll(cl->us); } } @@ -194,7 +194,7 @@ int uh_socket_bind(const char *host, const char *port, const char *device, bool l->fd.fd = sock; l->tls = tls; - l->addr = *(struct sockaddr_in6 *)p->ai_addr; + memcpy(&l->addr, p->ai_addr, p->ai_addrlen); list_add_tail(&l->list, &listeners); bound++; @@ -141,10 +141,12 @@ static int usage(const char *name) " -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" + " -P ciphers Colon separated list of allowed TLS ciphers\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" + " -b string Use given charset for directory listings, default to UTF-8\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" @@ -152,14 +154,19 @@ static int usage(const char *name) " -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" + " -l string URL prefix for Lua handler\n" + " -L file Path to Lua handler script, -l and -L may be repeated in pairs\n" +#endif +#ifdef HAVE_UCODE + " -o string URL prefix for ucode handler\n" + " -O file Path to ucode handler script, -o and -O may be repeated in pairs\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" + " -e Events subscription reconnection time (retry value)\n" #endif " -x string URL prefix for CGI handler, default is '/cgi-bin'\n" " -y alias[=path] URL alias handle\n" @@ -188,6 +195,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 +251,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; @@ -251,11 +280,14 @@ int main(int argc, char **argv) int bound = 0; #ifdef HAVE_TLS int n_tls = 0; - const char *tls_key = NULL, *tls_crt = NULL; + const char *tls_key = NULL, *tls_crt = NULL, *tls_ciphers = NULL; #endif #ifdef HAVE_LUA const char *lua_prefix = NULL, *lua_handler = NULL; #endif +#ifdef HAVE_UCODE + const char *ucode_prefix = NULL, *ucode_handler = NULL; +#endif char *device = NULL; BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX); @@ -264,7 +296,7 @@ int main(int argc, char **argv) init_defaults_pre(); signal(SIGPIPE, SIG_IGN); - while ((ch = getopt(argc, argv, "A:aB:C:c:Dd:E:fh:H: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:aB:b:C: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': @@ -275,6 +307,10 @@ int main(int argc, char **argv) tls_key = optarg; break; + case 'P': + tls_ciphers = optarg; + break; + case 'q': conf.tls_redirect = 1; break; @@ -285,6 +321,7 @@ int main(int argc, char **argv) #else case 'C': case 'K': + case 'P': case 'q': case 's': fprintf(stderr, "uhttpd: TLS support not compiled, " @@ -302,6 +339,7 @@ int main(int argc, char **argv) case 'p': optarg = strdup(optarg); bound += add_listener_arg(optarg, device, (ch == 's')); + free(optarg); break; case 'h': @@ -339,6 +377,10 @@ int main(int argc, char **argv) uh_index_add(optarg); break; + case 'b': + conf.dirlist_charset = optarg; + break; + case 'S': conf.no_symlinks = 1; break; @@ -479,6 +521,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; @@ -495,11 +569,16 @@ int main(int argc, char **argv) case 'X': conf.ubus_cors = 1; break; + + case 'e': + conf.events_retry = atoi(optarg); + break; #else case 'a': case 'u': case 'U': case 'X': + case 'e': fprintf(stderr, "uhttpd: UBUS support not compiled, " "ignoring -%c\n", ch); break; @@ -534,7 +613,7 @@ int main(int argc, char **argv) return 1; } - if (uh_tls_init(tls_key, tls_crt)) + if (uh_tls_init(tls_key, tls_crt, tls_ciphers)) return 1; } #endif @@ -548,6 +627,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; @@ -225,11 +225,9 @@ static void proc_handle_header(struct relay *r, const char *name, const char *va 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), @@ -31,7 +31,7 @@ static struct ustream_ssl_ops *ops; static void *dlh; static void *ctx; -int uh_tls_init(const char *key, const char *crt) +int uh_tls_init(const char *key, const char *crt, const char *ciphers) { static bool _init = false; @@ -63,6 +63,11 @@ int uh_tls_init(const char *key, const char *crt) return -EINVAL; } + if (ciphers && ops->context_set_ciphers(ctx, ciphers)) { + fprintf(stderr, "No recognized ciphers in cipher list\n"); + return -EINVAL; + } + return 0; } @@ -22,13 +22,13 @@ #ifdef HAVE_TLS -int uh_tls_init(const char *key, const char *crt); +int uh_tls_init(const char *key, const char *crt, const char *ciphers); 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) +static inline int uh_tls_init(const char *key, const char *crt, const char *ciphers) { return -1; } @@ -48,7 +48,7 @@ enum { 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_PARAMS] = { .name = "params", .type = BLOBMSG_TYPE_UNSPEC }, [RPC_ID] = { .name = "id", .type = BLOBMSG_TYPE_UNSPEC }, }; @@ -73,6 +73,7 @@ struct rpc_data { struct list_data { bool verbose; + bool add_object; struct blob_buf *buf; }; @@ -111,6 +112,30 @@ enum cors_hdr { __HDR_MAX }; +enum ubus_hdr { + HDR_AUTHORIZATION, + __HDR_UBUS_MAX +}; + +static const char *uh_ubus_get_auth(const struct blob_attr *attr) +{ + static const struct blobmsg_policy hdr_policy[__HDR_UBUS_MAX] = { + [HDR_AUTHORIZATION] = { "authorization", BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[__HDR_UBUS_MAX]; + + blobmsg_parse(hdr_policy, __HDR_UBUS_MAX, tb, blob_data(attr), blob_len(attr)); + + if (tb[HDR_AUTHORIZATION]) { + const char *tmp = blobmsg_get_string(tb[HDR_AUTHORIZATION]); + + if (!strncasecmp(tmp, "Bearer ", 7)) + return tmp + 7; + } + + return UH_UBUS_DEFAULT_SID; +} + static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout); static void uh_ubus_next_batched_request(struct client *cl) @@ -139,7 +164,7 @@ static void uh_ubus_add_cors_headers(struct client *cl) { char *hdr = (char *) blobmsg_data(tb[HDR_ACCESS_CONTROL_REQUEST_METHOD]); - if (strcmp(hdr, "POST") && strcmp(hdr, "OPTIONS")) + if (strcmp(hdr, "GET") && strcmp(hdr, "POST") && strcmp(hdr, "OPTIONS")) return; } @@ -150,18 +175,18 @@ static void uh_ubus_add_cors_headers(struct client *cl) ustream_printf(cl->us, "Access-Control-Allow-Headers: %s\r\n", blobmsg_get_string(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-Methods: GET, POST, OPTIONS\r\n"); ustream_printf(cl->us, "Access-Control-Allow-Credentials: true\r\n"); } -static void uh_ubus_send_header(struct client *cl) +static void uh_ubus_send_header(struct client *cl, int code, const char *summary, const char *content_type) { - ops->http_header(cl, 200, "OK"); + ops->http_header(cl, code, summary); if (conf.ubus_cors) uh_ubus_add_cors_headers(cl); - ustream_printf(cl->us, "Content-Type: application/json\r\n"); + ustream_printf(cl->us, "Content-Type: %s\r\n", content_type); if (cl->request.method == UH_HTTP_MSG_OPTIONS) ustream_printf(cl->us, "Content-Length: 0\r\n"); @@ -169,7 +194,7 @@ static void uh_ubus_send_header(struct client *cl) ustream_printf(cl->us, "\r\n"); } -static void uh_ubus_send_response(struct client *cl) +static void uh_ubus_send_response(struct client *cl, struct blob_buf *buf) { struct dispatch_ubus *du = &cl->dispatch.ubus; const char *sep = ""; @@ -178,7 +203,7 @@ static void uh_ubus_send_response(struct client *cl) if (du->array && du->array_idx > 1) sep = ","; - str = blobmsg_format_json(buf.head, true); + str = blobmsg_format_json(buf->head, true); ops->chunk_printf(cl, "%s%s", sep, str); free(str); @@ -189,41 +214,236 @@ static void uh_ubus_send_response(struct client *cl) return ops->request_done(cl); } -static void uh_ubus_init_response(struct client *cl) +static void uh_ubus_init_json_rpc_response(struct client *cl, struct blob_buf *buf) { 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"); + 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); + blobmsg_add_json_element(buf, "id", obj2); else - blobmsg_add_field(&buf, BLOBMSG_TYPE_UNSPEC, "id", NULL, 0); + blobmsg_add_field(buf, BLOBMSG_TYPE_UNSPEC, "id", NULL, 0); } -static void uh_ubus_json_error(struct client *cl, enum rpc_error type) +static void uh_ubus_json_rpc_error(struct client *cl, enum rpc_error type) { void *c; - uh_ubus_init_response(cl); + blob_buf_init(&buf, 0); + + uh_ubus_init_json_rpc_response(cl, &buf); 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); + uh_ubus_send_response(cl, &buf); +} + +static void uh_ubus_error(struct client *cl, int code, const char *message) +{ + blob_buf_init(&buf, 0); + + blobmsg_add_u32(&buf, "code", code); + blobmsg_add_string(&buf, "message", message); + uh_ubus_send_response(cl, &buf); +} + +static void uh_ubus_posix_error(struct client *cl, int err) +{ + uh_ubus_error(cl, -err, strerror(err)); +} + +static void uh_ubus_ubus_error(struct client *cl, int err) +{ + uh_ubus_error(cl, err, ubus_strerror(err)); +} + +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; } +/* GET requests handling */ + +static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv); + +static void uh_ubus_handle_get_list(struct client *cl, const char *path) +{ + static struct blob_buf tmp; + struct list_data data = { .verbose = true, .add_object = !path, .buf = &tmp}; + struct blob_attr *cur; + int rem; + int err; + + blob_buf_init(&tmp, 0); + + err = ubus_lookup(ctx, path, uh_ubus_list_cb, &data); + if (err) { + uh_ubus_send_header(cl, 500, "Ubus Protocol Error", "application/json"); + uh_ubus_ubus_error(cl, err); + return; + } + + blob_buf_init(&buf, 0); + blob_for_each_attr(cur, tmp.head, rem) + blobmsg_add_blob(&buf, cur); + + uh_ubus_send_header(cl, 200, "OK", "application/json"); + uh_ubus_send_response(cl, &buf); +} + +static int uh_ubus_subscription_notification_cb(struct ubus_context *ctx, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, + struct blob_attr *msg) +{ + struct ubus_subscriber *s; + struct dispatch_ubus *du; + struct client *cl; + char *json; + + s = container_of(obj, struct ubus_subscriber, obj); + du = container_of(s, struct dispatch_ubus, sub); + cl = container_of(du, struct client, dispatch.ubus); + + json = blobmsg_format_json(msg, true); + if (json) { + ops->chunk_printf(cl, "event: %s\ndata: %s\n\n", method, json); + free(json); + } + + return 0; +} + +static void uh_ubus_subscription_notification_remove_cb(struct ubus_context *ctx, struct ubus_subscriber *s, uint32_t id) +{ + struct dispatch_ubus *du; + struct client *cl; + + du = container_of(s, struct dispatch_ubus, sub); + cl = container_of(du, struct client, dispatch.ubus); + + ubus_unregister_subscriber(ctx, &du->sub); + + ops->request_done(cl); +} + +static void uh_ubus_handle_get_subscribe(struct client *cl, const char *path) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + const char *sid; + uint32_t id; + int err; + + sid = uh_ubus_get_auth(cl->hdr.head); + + if (!conf.ubus_noauth && !uh_ubus_allowed(sid, path, ":subscribe")) { + uh_ubus_send_header(cl, 200, "OK", "application/json"); + uh_ubus_posix_error(cl, EACCES); + return; + } + + du->sub.cb = uh_ubus_subscription_notification_cb; + du->sub.remove_cb = uh_ubus_subscription_notification_remove_cb; + + uh_client_ref(cl); + + err = ubus_register_subscriber(ctx, &du->sub); + if (err) + goto err_unref; + + err = ubus_lookup_id(ctx, path, &id); + if (err) + goto err_unregister; + + err = ubus_subscribe(ctx, &du->sub, id); + if (err) + goto err_unregister; + + uh_ubus_send_header(cl, 200, "OK", "text/event-stream"); + + if (conf.events_retry) + ops->chunk_printf(cl, "retry: %d\n", conf.events_retry); + + return; + +err_unregister: + ubus_unregister_subscriber(ctx, &du->sub); +err_unref: + uh_client_unref(cl); + if (err) { + uh_ubus_send_header(cl, 200, "OK", "application/json"); + uh_ubus_ubus_error(cl, err); + } +} + +static void uh_ubus_handle_get(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + const char *url = du->url_path; + + url += strlen(conf.ubus_prefix); + + if (!strcmp(url, "/list") || !strncmp(url, "/list/", strlen("/list/"))) { + url += strlen("/list"); + + uh_ubus_handle_get_list(cl, *url ? url + 1 : NULL); + } else if (!strncmp(url, "/subscribe/", strlen("/subscribe/"))) { + url += strlen("/subscribe"); + + uh_ubus_handle_get_subscribe(cl, url + 1); + } else { + ops->http_header(cl, 404, "Not Found"); + ustream_printf(cl->us, "\r\n"); + ops->request_done(cl); + } +} + +/* POST requests handling */ + 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); + struct blob_attr *cur; + int len; - blobmsg_add_field(&du->buf, BLOBMSG_TYPE_TABLE, "", blob_data(msg), blob_len(msg)); + blob_for_each_attr(cur, msg, len) + blobmsg_add_blob(&du->buf, cur); } static void @@ -235,14 +455,53 @@ uh_ubus_request_cb(struct ubus_request *req, int ret) void *r; int rem; + blob_buf_init(&buf, 0); + 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); + + /* Legacy format always uses "result" array - even for errors and empty + * results. */ + if (du->legacy) { + void *c; + + uh_ubus_init_json_rpc_response(cl, &buf); + r = blobmsg_open_array(&buf, "result"); + blobmsg_add_u32(&buf, "", ret); + + if (blob_len(du->buf.head)) { + c = blobmsg_open_table(&buf, NULL); + blob_for_each_attr(cur, du->buf.head, rem) + blobmsg_add_blob(&buf, cur); + blobmsg_close_table(&buf, c); + } + + blobmsg_close_array(&buf, r); + uh_ubus_send_response(cl, &buf); + return; + } + + if (ret) { + void *c; + + uh_ubus_init_json_rpc_response(cl, &buf); + c = blobmsg_open_table(&buf, "error"); + blobmsg_add_u32(&buf, "code", ret); + blobmsg_add_string(&buf, "message", ubus_strerror(ret)); + blobmsg_close_table(&buf, c); + uh_ubus_send_response(cl, &buf); + } else { + uh_ubus_init_json_rpc_response(cl, &buf); + if (blob_len(du->buf.head)) { + r = blobmsg_open_table(&buf, "result"); + blob_for_each_attr(cur, du->buf.head, rem) + blobmsg_add_blob(&buf, cur); + blobmsg_close_table(&buf, r); + } else { + blobmsg_add_field(&buf, BLOBMSG_TYPE_UNSPEC, "result", NULL, 0); + } + uh_ubus_send_response(cl, &buf); + } + } static void @@ -252,7 +511,7 @@ uh_ubus_timeout_cb(struct uloop_timeout *timeout) struct client *cl = container_of(du, struct client, dispatch.ubus); ubus_abort_request(ctx, &du->req); - uh_ubus_json_error(cl, ERROR_TIMEOUT); + uh_ubus_json_rpc_error(cl, ERROR_TIMEOUT); } static void uh_ubus_close_fds(struct client *cl) @@ -279,16 +538,19 @@ static void uh_ubus_request_free(struct client *cl) if (du->req_pending) ubus_abort_request(ctx, &du->req); + + free(du->url_path); + du->url_path = NULL; } static void uh_ubus_single_error(struct client *cl, enum rpc_error type) { - uh_ubus_send_header(cl); - uh_ubus_json_error(cl, type); + uh_ubus_send_header(cl, 200, "OK", "application/json"); + uh_ubus_json_rpc_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) +static void uh_ubus_send_request(struct client *cl, const char *sid, struct blob_attr *args) { struct dispatch *d = &cl->dispatch; struct dispatch_ubus *du = &d->ubus; @@ -299,7 +561,7 @@ static void uh_ubus_send_request(struct client *cl, json_object *obj, const char 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); + return uh_ubus_json_rpc_error(cl, ERROR_PARAMS); blobmsg_add_blob(&req, cur); } @@ -309,7 +571,7 @@ static void uh_ubus_send_request(struct client *cl, json_object *obj, const char 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); + return uh_ubus_json_rpc_error(cl, ERROR_INTERNAL); du->req.data_cb = uh_ubus_request_data_cb; du->req.complete_cb = uh_ubus_request_cb; @@ -326,7 +588,7 @@ static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *o struct blob_attr *sig, *attr; struct list_data *data = priv; int rem, rem2; - void *t, *o; + void *t, *o=NULL; if (!data->verbose) { blobmsg_add_string(data->buf, NULL, obj->path); @@ -336,7 +598,12 @@ static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *o if (!obj->signature) return; - o = blobmsg_open_table(data->buf, obj->path); + if (data->add_object) { + o = blobmsg_open_table(data->buf, obj->path); + if (!o) + return; + } + blob_for_each_attr(sig, obj->signature, rem) { t = blobmsg_open_table(data->buf, blobmsg_name(sig)); rem2 = blobmsg_data_len(sig); @@ -367,13 +634,15 @@ static void uh_ubus_list_cb(struct ubus_context *ctx, struct ubus_object_data *o } blobmsg_close_table(data->buf, t); } - blobmsg_close_table(data->buf, o); + + if (data->add_object) + blobmsg_close_table(data->buf, o); } -static void uh_ubus_send_list(struct client *cl, json_object *obj, struct blob_attr *params) +static void uh_ubus_send_list(struct client *cl, struct blob_attr *params) { struct blob_attr *cur, *dup; - struct list_data data = { .buf = &cl->dispatch.ubus.buf, .verbose = false }; + struct list_data data = { .buf = &cl->dispatch.ubus.buf, .verbose = false, .add_object = true }; void *r; int rem; @@ -402,21 +671,15 @@ static void uh_ubus_send_list(struct client *cl, json_object *obj, struct blob_a uh_client_unref(cl); - uh_ubus_init_response(cl); + blob_buf_init(&buf, 0); + uh_ubus_init_json_rpc_response(cl, &buf); blobmsg_add_blob(&buf, blob_data(data.buf->head)); - uh_ubus_send_response(cl); + uh_ubus_send_response(cl, &buf); } 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)); @@ -440,24 +703,38 @@ static bool parse_json_rpc(struct rpc_data *d, struct blob_attr *data) if (!d->params) return false; - blobmsg_parse_array(data_policy, ARRAY_SIZE(data_policy), tb2, + return true; +} + +static void parse_call_params(struct rpc_data *d) +{ + 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[4]; + + if (!d->params || blobmsg_type(d->params) != BLOBMSG_TYPE_ARRAY) + return; + + blobmsg_parse_array(data_policy, ARRAY_SIZE(data_policy), tb, blobmsg_data(d->params), blobmsg_data_len(d->params)); - if (tb2[0]) - d->sid = blobmsg_data(tb2[0]); + if (tb[0]) + d->sid = blobmsg_data(tb[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]); + if (tb[1]) + d->object = blobmsg_data(tb[1]); - d->data = tb2[3]; + if (tb[2]) + d->function = blobmsg_data(tb[2]); - return true; + d->data = tb[3]; } static void uh_ubus_init_batch(struct client *cl) @@ -465,7 +742,7 @@ static void uh_ubus_init_batch(struct client *cl) struct dispatch_ubus *du = &cl->dispatch.ubus; du->array = true; - uh_ubus_send_header(cl); + uh_ubus_send_header(cl, 200, "OK", "application/json"); ops->chunk_printf(cl, "["); } @@ -475,44 +752,12 @@ static void uh_ubus_complete_batch(struct client *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; + static struct blob_buf req; uh_client_ref(cl); @@ -520,14 +765,16 @@ static void uh_ubus_handle_request_object(struct client *cl, struct json_object goto error; du->jsobj_cur = obj; - blob_buf_init(&buf, 0); - if (!blobmsg_add_object(&buf, obj)) + blob_buf_init(&req, 0); + if (!blobmsg_add_object(&req, obj)) goto error; - if (!parse_json_rpc(&data, buf.head)) + if (!parse_json_rpc(&data, req.head)) goto error; if (!strcmp(data.method, "call")) { + parse_call_params(&data); + if (!data.sid || !data.object || !data.function || !data.data) goto error; @@ -542,11 +789,11 @@ static void uh_ubus_handle_request_object(struct client *cl, struct json_object goto error; } - uh_ubus_send_request(cl, obj, data.sid, data.data); + uh_ubus_send_request(cl, data.sid, data.data); goto out; } else if (!strcmp(data.method, "list")) { - uh_ubus_send_list(cl, obj, data.params); + uh_ubus_send_list(cl, data.params); goto out; } else { @@ -555,7 +802,7 @@ static void uh_ubus_handle_request_object(struct client *cl, struct json_object } error: - uh_ubus_json_error(cl, err); + uh_ubus_json_rpc_error(cl, err); out: if (data.params) free(data.params); @@ -585,7 +832,7 @@ static void uh_ubus_data_done(struct client *cl) switch (obj ? json_object_get_type(obj) : json_type_null) { case json_type_object: - uh_ubus_send_header(cl); + uh_ubus_send_header(cl, 200, "OK", "application/json"); return uh_ubus_handle_request_object(cl, obj); case json_type_array: uh_ubus_init_batch(cl); @@ -595,6 +842,80 @@ static void uh_ubus_data_done(struct client *cl) } } +static void uh_ubus_call(struct client *cl, const char *path, const char *sid) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + struct json_object *obj = du->jsobj; + struct rpc_data data = {}; + enum rpc_error err = ERROR_PARSE; + static struct blob_buf req; + + uh_client_ref(cl); + + if (!obj || json_object_get_type(obj) != json_type_object) + goto error; + + uh_ubus_send_header(cl, 200, "OK", "application/json"); + + du->jsobj_cur = obj; + blob_buf_init(&req, 0); + if (!blobmsg_add_object(&req, obj)) + goto error; + + if (!parse_json_rpc(&data, req.head)) + goto error; + + du->func = data.method; + if (ubus_lookup_id(ctx, path, &du->obj)) { + err = ERROR_OBJECT; + goto error; + } + + if (!conf.ubus_noauth && !uh_ubus_allowed(sid, path, data.method)) { + err = ERROR_ACCESS; + goto error; + } + + uh_ubus_send_request(cl, sid, data.params); + goto out; + +error: + uh_ubus_json_rpc_error(cl, err); +out: + if (data.params) + free(data.params); + + uh_client_unref(cl); +} + +static void uh_ubus_handle_post(struct client *cl) +{ + struct dispatch_ubus *du = &cl->dispatch.ubus; + const char *url = du->url_path; + const char *auth; + + /* Treat both: /foo AND /foo/ as legacy requests. */ + if (ops->path_match(conf.ubus_prefix, url) && strlen(url) - strlen(conf.ubus_prefix) <= 1) { + du->legacy = true; + uh_ubus_data_done(cl); + return; + } + + auth = uh_ubus_get_auth(cl->hdr.head); + + url += strlen(conf.ubus_prefix); + + if (!strncmp(url, "/call/", strlen("/call/"))) { + url += strlen("/call/"); + + uh_ubus_call(cl, url, auth); + } else { + ops->http_header(cl, 404, "Not Found"); + ustream_printf(cl->us, "\r\n"); + ops->request_done(cl); + } +} + static int uh_ubus_data_send(struct client *cl, const char *data, int len) { struct dispatch_ubus *du = &cl->dispatch.ubus; @@ -617,27 +938,44 @@ error: static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi) { struct dispatch *d = &cl->dispatch; + struct dispatch_ubus *du = &d->ubus; + char *chr; - blob_buf_init(&buf, 0); + du->url_path = strdup(url); + if (!du->url_path) { + ops->client_error(cl, 500, "Internal Server Error", "Failed to allocate resources"); + return; + } + chr = strchr(du->url_path, '?'); + if (chr) + chr[0] = '\0'; + + du->legacy = false; switch (cl->request.method) { + case UH_HTTP_MSG_GET: + uh_ubus_handle_get(cl); + break; case UH_HTTP_MSG_POST: d->data_send = uh_ubus_data_send; - d->data_done = uh_ubus_data_done; + d->data_done = uh_ubus_handle_post; d->close_fds = uh_ubus_close_fds; d->free = uh_ubus_request_free; - d->ubus.jstok = json_tokener_new(); - break; + du->jstok = json_tokener_new(); + return; case UH_HTTP_MSG_OPTIONS: - uh_ubus_send_header(cl); + uh_ubus_send_header(cl, 200, "OK", "application/json"); ops->request_done(cl); break; default: ops->client_error(cl, 400, "Bad Request", "Invalid Request"); } + + free(du->url_path); + du->url_path = NULL; } static bool @@ -0,0 +1,436 @@ +/* + * 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_program_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); + + uc_program_put(handler); + 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 = ¤t_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, +}; @@ -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; @@ -69,6 +81,7 @@ struct config { const char *cgi_path; const char *ubus_prefix; const char *ubus_socket; + const char *dirlist_charset; int no_symlinks; int no_dirlists; int network_timeout; @@ -82,8 +95,12 @@ struct config { int ubus_noauth; int ubus_cors; int cgi_prefix_len; + 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 { @@ -209,6 +226,7 @@ struct dispatch_ubus { struct json_tokener *jstok; struct json_object *jsobj; struct json_object *jsobj_cur; + char *url_path; int post_len; uint32_t obj; @@ -218,6 +236,9 @@ struct dispatch_ubus { bool req_pending; bool array; int array_idx; + bool legacy; /* Got legacy request => use legacy reply */ + + struct ubus_subscriber sub; }; #endif |