c03b6f0eb842e711e09f3b7f5557f64c6f3fb811
[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 if (ex->type == EXCEPTION_EXIT)
199 return;
200
201 printf("Status: 500 Internal Server Error\r\n\r\n"
202 "Exception while executing ucode program %s:\n",
203 current_prefix->handler);
204
205 switch (ex->type) {
206 case EXCEPTION_SYNTAX: printf("Syntax error"); break;
207 case EXCEPTION_RUNTIME: printf("Runtime error"); break;
208 case EXCEPTION_TYPE: printf("Type error"); break;
209 case EXCEPTION_REFERENCE: printf("Reference error"); break;
210 default: printf("Error");
211 }
212
213 printf(": %s\n", ex->message);
214
215 ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL);
216
217 if (ctx)
218 printf("%s\n", ucv_string_get(ctx));
219 }
220
221 static void
222 uh_ucode_state_init(struct ucode_prefix *ucode)
223 {
224 char *syntax_error = NULL;
225 uc_vm_t *vm = &ucode->ctx;
226 uc_program_t *handler;
227 uc_vm_status_t status;
228 uc_source_t *src;
229 uc_value_t *v;
230 int exitcode;
231
232 uc_vm_init(vm, &config);
233 uc_stdlib_load(uc_vm_scope_get(vm));
234
235 /* build uhttpd api table */
236 v = ucv_object_new(vm);
237
238 ucv_object_add(v, "send", ucv_cfunction_new("send", uh_ucode_send));
239 ucv_object_add(v, "sendc", ucv_get(ucv_object_get(v, "send", NULL)));
240 ucv_object_add(v, "recv", ucv_cfunction_new("recv", uh_ucode_recv));
241 ucv_object_add(v, "urldecode", ucv_cfunction_new("urldecode", uh_ucode_urldecode));
242 ucv_object_add(v, "urlencode", ucv_cfunction_new("urlencode", uh_ucode_urlencode));
243 ucv_object_add(v, "docroot", ucv_string_new(conf.docroot));
244
245 ucv_object_add(uc_vm_scope_get(vm), "uhttpd", v);
246
247 src = uc_source_new_file(ucode->handler);
248
249 if (!src) {
250 fprintf(stderr, "Error: Unable to open ucode handler: %s\n",
251 strerror(errno));
252
253 exit(1);
254 }
255
256 handler = uc_compile(&config, src, &syntax_error);
257
258 uc_source_put(src);
259
260 if (!handler) {
261 fprintf(stderr, "Error: Unable to compile ucode handler: %s\n",
262 syntax_error);
263
264 exit(1);
265 }
266
267 free(syntax_error);
268
269 vm->output = fopen("/dev/null", "w");
270
271 if (!vm->output) {
272 fprintf(stderr, "Error: Unable to open /dev/null for writing: %s\n",
273 strerror(errno));
274
275 exit(1);
276 }
277
278 status = uc_vm_execute(vm, handler, &v);
279 exitcode = (int)ucv_int64_get(v);
280
281 uc_program_put(handler);
282 ucv_put(v);
283
284 switch (status) {
285 case STATUS_OK:
286 break;
287
288 case STATUS_EXIT:
289 fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode);
290 exit(exitcode ? exitcode : 1);
291
292 case ERROR_COMPILE:
293 fprintf(stderr, "Error: Compilation error while executing ucode handler\n");
294 exit(1);
295
296 case ERROR_RUNTIME:
297 fprintf(stderr, "Error: Runtime error while executing ucode handler\n");
298 exit(2);
299 }
300
301 v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL);
302
303 if (!ucv_is_callable(v)) {
304 fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n");
305 exit(1);
306 }
307
308 uc_vm_exception_handler_set(vm, uh_ucode_exception);
309
310 ucv_gc(vm);
311
312 fclose(vm->output);
313
314 vm->output = stdout;
315 }
316
317 static void
318 ucode_main(struct client *cl, struct path_info *pi, char *url)
319 {
320 uc_vm_t *vm = &current_prefix->ctx;
321 uc_value_t *req, *hdr, *res;
322 int path_len, prefix_len;
323 struct blob_attr *cur;
324 struct env_var *var;
325 char *str;
326 int rem;
327
328 /* new env table for this request */
329 req = ucv_object_new(vm);
330
331 prefix_len = strlen(pi->name);
332 path_len = strlen(url);
333 str = strchr(url, '?');
334
335 if (str) {
336 if (*(str + 1))
337 pi->query = str + 1;
338
339 path_len = str - url;
340 }
341
342 if (prefix_len > 0 && pi->name[prefix_len - 1] == '/')
343 prefix_len--;
344
345 if (path_len > prefix_len) {
346 ucv_object_add(req, "PATH_INFO",
347 ucv_string_new_length(url + prefix_len, path_len - prefix_len));
348 }
349
350 for (var = ops->get_process_vars(cl, pi); var->name; var++) {
351 if (!var->value)
352 continue;
353
354 ucv_object_add(req, var->name, ucv_string_new(var->value));
355 }
356
357 ucv_object_add(req, "HTTP_VERSION",
358 ucv_double_new(0.9 + (cl->request.version / 10.0)));
359
360 hdr = ucv_object_new(vm);
361
362 blob_for_each_attr(cur, cl->hdr.head, rem)
363 ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur)));
364
365 ucv_object_add(req, "headers", hdr);
366
367 res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req);
368
369 ucv_put(req);
370 ucv_put(res);
371
372 exit(0);
373 }
374
375 static void
376 ucode_handle_request(struct client *cl, char *url, struct path_info *pi)
377 {
378 struct ucode_prefix *p;
379 static struct path_info _pi;
380
381 list_for_each_entry(p, &conf.ucode_prefix, list) {
382 if (!ops->path_match(p->prefix, url))
383 continue;
384
385 pi = &_pi;
386 pi->name = p->prefix;
387 pi->phys = p->handler;
388
389 current_prefix = p;
390
391 if (!ops->create_process(cl, pi, url, ucode_main)) {
392 ops->client_error(cl, 500, "Internal Server Error",
393 "Failed to create CGI process: %s",
394 strerror(errno));
395 }
396
397 return;
398 }
399
400 ops->client_error(cl, 500, "Internal Server Error",
401 "Failed to lookup matching handler");
402 }
403
404 static bool
405 check_ucode_url(const char *url)
406 {
407 struct ucode_prefix *p;
408
409 list_for_each_entry(p, &conf.ucode_prefix, list)
410 if (ops->path_match(p->prefix, url))
411 return true;
412
413 return false;
414 }
415
416 static struct dispatch_handler ucode_dispatch = {
417 .script = true,
418 .check_url = check_ucode_url,
419 .handle_request = ucode_handle_request,
420 };
421
422 static int
423 ucode_plugin_init(const struct uhttpd_ops *o, struct config *c)
424 {
425 struct ucode_prefix *p;
426
427 ops = o;
428 _conf = c;
429
430 list_for_each_entry(p, &conf.ucode_prefix, list)
431 uh_ucode_state_init(p);
432
433 ops->dispatch_add(&ucode_dispatch);
434 return 0;
435 }
436
437 struct uhttpd_plugin uhttpd_plugin = {
438 .init = ucode_plugin_init,
439 };