f408576ef66822d848c3e701e42410feb3e1df05
[project/uhttpd.git] / ucode.c
1 /*
2 * uhttpd - Tiny single-threaded httpd
3 *
4 * Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
5 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <libubox/blobmsg.h>
21 #include <ucode/compiler.h>
22 #include <ucode/lib.h>
23 #include <ucode/vm.h>
24 #include <stdio.h>
25 #include <poll.h>
26
27 #include "uhttpd.h"
28 #include "plugin.h"
29
30 #define UH_UCODE_CB "handle_request"
31
32 static const struct uhttpd_ops *ops;
33 static struct config *_conf;
34 #define conf (*_conf)
35
36 static struct ucode_prefix *current_prefix;
37
38 static uc_value_t *
39 uh_ucode_recv(uc_vm_t *vm, size_t nargs)
40 {
41 static struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN };
42 int data_len = 0, len = BUFSIZ, rlen, r;
43 uc_value_t *v = uc_fn_arg(0);
44 uc_stringbuf_t *buf;
45
46 if (ucv_type(v) == UC_INTEGER) {
47 len = ucv_int64_get(v);
48 }
49 else if (v != NULL) {
50 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument not an integer");
51
52 return NULL;
53 }
54
55 buf = ucv_stringbuf_new();
56
57 while (len > 0) {
58 rlen = (len < BUFSIZ) ? len : BUFSIZ;
59
60 if (printbuf_memset(buf, -1, 0, rlen)) {
61 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory");
62 printbuf_free(buf);
63
64 return NULL;
65 }
66
67 buf->bpos -= rlen;
68 r = read(STDIN_FILENO, buf->buf + buf->bpos, rlen);
69
70 if (r < 0) {
71 if (errno == EWOULDBLOCK || errno == EAGAIN) {
72 pfd.revents = 0;
73 poll(&pfd, 1, 1000);
74
75 if (pfd.revents & POLLIN)
76 continue;
77 }
78
79 if (errno == EINTR)
80 continue;
81
82 if (!data_len)
83 data_len = -1;
84
85 break;
86 }
87
88 buf->bpos += r;
89 data_len += r;
90 len -= r;
91
92 if (r != rlen)
93 break;
94 }
95
96 if (data_len > 0) {
97 /* add final guard \0 but do not count it */
98 if (printbuf_memset(buf, -1, 0, 1)) {
99 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory");
100 printbuf_free(buf);
101
102 return NULL;
103 }
104
105 buf->bpos--;
106
107 return ucv_stringbuf_finish(buf);
108 }
109
110 printbuf_free(buf);
111
112 return NULL;
113 }
114
115 static uc_value_t *
116 uh_ucode_send(uc_vm_t *vm, size_t nargs)
117 {
118 uc_value_t *val = uc_fn_arg(0);
119 ssize_t len;
120 char *p;
121
122 if (ucv_type(val) == UC_STRING) {
123 len = write(STDOUT_FILENO, ucv_string_get(val), ucv_string_length(val));
124 }
125 else if (val != NULL) {
126 p = ucv_to_string(vm, val);
127 len = p ? write(STDOUT_FILENO, p, strlen(p)) : 0;
128 free(p);
129 }
130 else {
131 len = 0;
132 }
133
134 return ucv_int64_new(len);
135 }
136
137 static uc_value_t *
138 uh_ucode_strconvert(uc_vm_t *vm, size_t nargs, int (*convert)(char *, int, const char *, int))
139 {
140 uc_value_t *val = uc_fn_arg(0);
141 static char out_buf[4096];
142 int out_len;
143 char *p;
144
145 if (ucv_type(val) == UC_STRING) {
146 out_len = convert(out_buf, sizeof(out_buf),
147 ucv_string_get(val), ucv_string_length(val));
148 }
149 else if (val != NULL) {
150 p = ucv_to_string(vm, val);
151 out_len = p ? convert(out_buf, sizeof(out_buf), p, strlen(p)) : 0;
152 free(p);
153 }
154 else {
155 out_len = 0;
156 }
157
158 if (out_len < 0) {
159 const char *error;
160
161 if (out_len == -1)
162 error = "buffer overflow";
163 else
164 error = "malformed string";
165
166 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
167 "%s on URL conversion\n", error);
168
169 return NULL;
170 }
171
172 return ucv_string_new_length(out_buf, out_len);
173 }
174
175 static uc_value_t *
176 uh_ucode_urldecode(uc_vm_t *vm, size_t nargs)
177 {
178 return uh_ucode_strconvert(vm, nargs, ops->urldecode);
179 }
180
181 static uc_value_t *
182 uh_ucode_urlencode(uc_vm_t *vm, size_t nargs)
183 {
184 return uh_ucode_strconvert(vm, nargs, ops->urlencode);
185 }
186
187 static uc_parse_config_t config = {
188 .strict_declarations = false,
189 .lstrip_blocks = true,
190 .trim_blocks = true
191 };
192
193 static void
194 uh_ucode_exception(uc_vm_t *vm, uc_exception_t *ex)
195 {
196 uc_value_t *ctx;
197
198 printf("Status: 500 Internal Server Error\r\n\r\n"
199 "Exception while executing ucode program %s:\n",
200 current_prefix->handler);
201
202 switch (ex->type) {
203 case EXCEPTION_SYNTAX: printf("Syntax error"); break;
204 case EXCEPTION_RUNTIME: printf("Runtime error"); break;
205 case EXCEPTION_TYPE: printf("Type error"); break;
206 case EXCEPTION_REFERENCE: printf("Reference error"); break;
207 default: printf("Error");
208 }
209
210 printf(": %s\n", ex->message);
211
212 ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL);
213
214 if (ctx)
215 printf("%s\n", ucv_string_get(ctx));
216 }
217
218 static void
219 uh_ucode_state_init(struct ucode_prefix *ucode)
220 {
221 char *syntax_error = NULL;
222 uc_vm_t *vm = &ucode->ctx;
223 uc_function_t *handler;
224 uc_vm_status_t status;
225 uc_source_t *src;
226 uc_value_t *v;
227 int exitcode;
228
229 uc_vm_init(vm, &config);
230 uc_stdlib_load(uc_vm_scope_get(vm));
231
232 /* build uhttpd api table */
233 v = ucv_object_new(vm);
234
235 ucv_object_add(v, "send", ucv_cfunction_new("send", uh_ucode_send));
236 ucv_object_add(v, "sendc", ucv_get(ucv_object_get(v, "send", NULL)));
237 ucv_object_add(v, "recv", ucv_cfunction_new("recv", uh_ucode_recv));
238 ucv_object_add(v, "urldecode", ucv_cfunction_new("urldecode", uh_ucode_urldecode));
239 ucv_object_add(v, "urlencode", ucv_cfunction_new("urlencode", uh_ucode_urlencode));
240 ucv_object_add(v, "docroot", ucv_string_new(conf.docroot));
241
242 ucv_object_add(uc_vm_scope_get(vm), "uhttpd", v);
243
244 src = uc_source_new_file(ucode->handler);
245
246 if (!src) {
247 fprintf(stderr, "Error: Unable to open ucode handler: %s\n",
248 strerror(errno));
249
250 exit(1);
251 }
252
253 handler = uc_compile(&config, src, &syntax_error);
254
255 uc_source_put(src);
256
257 if (!handler) {
258 fprintf(stderr, "Error: Unable to compile ucode handler: %s\n",
259 syntax_error);
260
261 exit(1);
262 }
263
264 free(syntax_error);
265
266 vm->output = fopen("/dev/null", "w");
267
268 if (!vm->output) {
269 fprintf(stderr, "Error: Unable to open /dev/null for writing: %s\n",
270 strerror(errno));
271
272 exit(1);
273 }
274
275 status = uc_vm_execute(vm, handler, &v);
276 exitcode = (int)ucv_int64_get(v);
277
278 ucv_put(v);
279
280 switch (status) {
281 case STATUS_OK:
282 break;
283
284 case STATUS_EXIT:
285 fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode);
286 exit(exitcode ? exitcode : 1);
287
288 case ERROR_COMPILE:
289 fprintf(stderr, "Error: Compilation error while executing ucode handler\n");
290 exit(1);
291
292 case ERROR_RUNTIME:
293 fprintf(stderr, "Error: Runtime error while executing ucode handler\n");
294 exit(2);
295 }
296
297 v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL);
298
299 if (!ucv_is_callable(v)) {
300 fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n");
301 exit(1);
302 }
303
304 uc_vm_exception_handler_set(vm, uh_ucode_exception);
305
306 ucv_gc(vm);
307
308 fclose(vm->output);
309
310 vm->output = stdout;
311 }
312
313 static void
314 ucode_main(struct client *cl, struct path_info *pi, char *url)
315 {
316 uc_vm_t *vm = &current_prefix->ctx;
317 uc_value_t *req, *hdr, *res;
318 int path_len, prefix_len;
319 struct blob_attr *cur;
320 struct env_var *var;
321 char *str;
322 int rem;
323
324 /* new env table for this request */
325 req = ucv_object_new(vm);
326
327 prefix_len = strlen(pi->name);
328 path_len = strlen(url);
329 str = strchr(url, '?');
330
331 if (str) {
332 if (*(str + 1))
333 pi->query = str + 1;
334
335 path_len = str - url;
336 }
337
338 if (prefix_len > 0 && pi->name[prefix_len - 1] == '/')
339 prefix_len--;
340
341 if (path_len > prefix_len) {
342 ucv_object_add(req, "PATH_INFO",
343 ucv_string_new_length(url + prefix_len, path_len - prefix_len));
344 }
345
346 for (var = ops->get_process_vars(cl, pi); var->name; var++) {
347 if (!var->value)
348 continue;
349
350 ucv_object_add(req, var->name, ucv_string_new(var->value));
351 }
352
353 ucv_object_add(req, "HTTP_VERSION",
354 ucv_double_new(0.9 + (cl->request.version / 10.0)));
355
356 hdr = ucv_object_new(vm);
357
358 blob_for_each_attr(cur, cl->hdr.head, rem)
359 ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur)));
360
361 ucv_object_add(req, "headers", hdr);
362
363 res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req);
364
365 ucv_put(req);
366 ucv_put(res);
367
368 exit(0);
369 }
370
371 static void
372 ucode_handle_request(struct client *cl, char *url, struct path_info *pi)
373 {
374 struct ucode_prefix *p;
375 static struct path_info _pi;
376
377 list_for_each_entry(p, &conf.ucode_prefix, list) {
378 if (!ops->path_match(p->prefix, url))
379 continue;
380
381 pi = &_pi;
382 pi->name = p->prefix;
383 pi->phys = p->handler;
384
385 current_prefix = p;
386
387 if (!ops->create_process(cl, pi, url, ucode_main)) {
388 ops->client_error(cl, 500, "Internal Server Error",
389 "Failed to create CGI process: %s",
390 strerror(errno));
391 }
392
393 return;
394 }
395
396 ops->client_error(cl, 500, "Internal Server Error",
397 "Failed to lookup matching handler");
398 }
399
400 static bool
401 check_ucode_url(const char *url)
402 {
403 struct ucode_prefix *p;
404
405 list_for_each_entry(p, &conf.ucode_prefix, list)
406 if (ops->path_match(p->prefix, url))
407 return true;
408
409 return false;
410 }
411
412 static struct dispatch_handler ucode_dispatch = {
413 .script = true,
414 .check_url = check_ucode_url,
415 .handle_request = ucode_handle_request,
416 };
417
418 static int
419 ucode_plugin_init(const struct uhttpd_ops *o, struct config *c)
420 {
421 struct ucode_prefix *p;
422
423 ops = o;
424 _conf = c;
425
426 list_for_each_entry(p, &conf.ucode_prefix, list)
427 uh_ucode_state_init(p);
428
429 ops->dispatch_add(&ucode_dispatch);
430 return 0;
431 }
432
433 struct uhttpd_plugin uhttpd_plugin = {
434 .init = ucode_plugin_init,
435 };