ucode: adjust to latest ucode api
[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_program_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 uc_program_put(handler);
279 ucv_put(v);
280
281 switch (status) {
282 case STATUS_OK:
283 break;
284
285 case STATUS_EXIT:
286 fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode);
287 exit(exitcode ? exitcode : 1);
288
289 case ERROR_COMPILE:
290 fprintf(stderr, "Error: Compilation error while executing ucode handler\n");
291 exit(1);
292
293 case ERROR_RUNTIME:
294 fprintf(stderr, "Error: Runtime error while executing ucode handler\n");
295 exit(2);
296 }
297
298 v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL);
299
300 if (!ucv_is_callable(v)) {
301 fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n");
302 exit(1);
303 }
304
305 uc_vm_exception_handler_set(vm, uh_ucode_exception);
306
307 ucv_gc(vm);
308
309 fclose(vm->output);
310
311 vm->output = stdout;
312 }
313
314 static void
315 ucode_main(struct client *cl, struct path_info *pi, char *url)
316 {
317 uc_vm_t *vm = &current_prefix->ctx;
318 uc_value_t *req, *hdr, *res;
319 int path_len, prefix_len;
320 struct blob_attr *cur;
321 struct env_var *var;
322 char *str;
323 int rem;
324
325 /* new env table for this request */
326 req = ucv_object_new(vm);
327
328 prefix_len = strlen(pi->name);
329 path_len = strlen(url);
330 str = strchr(url, '?');
331
332 if (str) {
333 if (*(str + 1))
334 pi->query = str + 1;
335
336 path_len = str - url;
337 }
338
339 if (prefix_len > 0 && pi->name[prefix_len - 1] == '/')
340 prefix_len--;
341
342 if (path_len > prefix_len) {
343 ucv_object_add(req, "PATH_INFO",
344 ucv_string_new_length(url + prefix_len, path_len - prefix_len));
345 }
346
347 for (var = ops->get_process_vars(cl, pi); var->name; var++) {
348 if (!var->value)
349 continue;
350
351 ucv_object_add(req, var->name, ucv_string_new(var->value));
352 }
353
354 ucv_object_add(req, "HTTP_VERSION",
355 ucv_double_new(0.9 + (cl->request.version / 10.0)));
356
357 hdr = ucv_object_new(vm);
358
359 blob_for_each_attr(cur, cl->hdr.head, rem)
360 ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur)));
361
362 ucv_object_add(req, "headers", hdr);
363
364 res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req);
365
366 ucv_put(req);
367 ucv_put(res);
368
369 exit(0);
370 }
371
372 static void
373 ucode_handle_request(struct client *cl, char *url, struct path_info *pi)
374 {
375 struct ucode_prefix *p;
376 static struct path_info _pi;
377
378 list_for_each_entry(p, &conf.ucode_prefix, list) {
379 if (!ops->path_match(p->prefix, url))
380 continue;
381
382 pi = &_pi;
383 pi->name = p->prefix;
384 pi->phys = p->handler;
385
386 current_prefix = p;
387
388 if (!ops->create_process(cl, pi, url, ucode_main)) {
389 ops->client_error(cl, 500, "Internal Server Error",
390 "Failed to create CGI process: %s",
391 strerror(errno));
392 }
393
394 return;
395 }
396
397 ops->client_error(cl, 500, "Internal Server Error",
398 "Failed to lookup matching handler");
399 }
400
401 static bool
402 check_ucode_url(const char *url)
403 {
404 struct ucode_prefix *p;
405
406 list_for_each_entry(p, &conf.ucode_prefix, list)
407 if (ops->path_match(p->prefix, url))
408 return true;
409
410 return false;
411 }
412
413 static struct dispatch_handler ucode_dispatch = {
414 .script = true,
415 .check_url = check_ucode_url,
416 .handle_request = ucode_handle_request,
417 };
418
419 static int
420 ucode_plugin_init(const struct uhttpd_ops *o, struct config *c)
421 {
422 struct ucode_prefix *p;
423
424 ops = o;
425 _conf = c;
426
427 list_for_each_entry(p, &conf.ucode_prefix, list)
428 uh_ucode_state_init(p);
429
430 ops->dispatch_add(&ucode_dispatch);
431 return 0;
432 }
433
434 struct uhttpd_plugin uhttpd_plugin = {
435 .init = ucode_plugin_init,
436 };