uhttpd/file: fix string out of buffer range on uh_defer_script
[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;
119 size_t arridx;
120 ssize_t len = 0;
121 char *p;
122
123 for (arridx = 0; arridx < nargs; arridx++) {
124 val = uc_fn_arg(arridx);
125
126 if (ucv_type(val) == UC_STRING) {
127 len += write(STDOUT_FILENO, ucv_string_get(val), ucv_string_length(val));
128 }
129 else if (val != NULL) {
130 p = ucv_to_string(vm, val);
131 len += p ? write(STDOUT_FILENO, p, strlen(p)) : 0;
132 free(p);
133 }
134 }
135
136 return ucv_int64_new(len);
137 }
138
139 static uc_value_t *
140 uh_ucode_strconvert(uc_vm_t *vm, size_t nargs, int (*convert)(char *, int, const char *, int))
141 {
142 uc_value_t *val = uc_fn_arg(0);
143 static char out_buf[4096];
144 int out_len;
145 char *p;
146
147 if (ucv_type(val) == UC_STRING) {
148 out_len = convert(out_buf, sizeof(out_buf),
149 ucv_string_get(val), ucv_string_length(val));
150 }
151 else if (val != NULL) {
152 p = ucv_to_string(vm, val);
153 out_len = p ? convert(out_buf, sizeof(out_buf), p, strlen(p)) : 0;
154 free(p);
155 }
156 else {
157 out_len = 0;
158 }
159
160 if (out_len < 0) {
161 const char *error;
162
163 if (out_len == -1)
164 error = "buffer overflow";
165 else
166 error = "malformed string";
167
168 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
169 "%s on URL conversion\n", error);
170
171 return NULL;
172 }
173
174 return ucv_string_new_length(out_buf, out_len);
175 }
176
177 static uc_value_t *
178 uh_ucode_urldecode(uc_vm_t *vm, size_t nargs)
179 {
180 return uh_ucode_strconvert(vm, nargs, ops->urldecode);
181 }
182
183 static uc_value_t *
184 uh_ucode_urlencode(uc_vm_t *vm, size_t nargs)
185 {
186 return uh_ucode_strconvert(vm, nargs, ops->urlencode);
187 }
188
189 static uc_parse_config_t config = {
190 .strict_declarations = false,
191 .lstrip_blocks = true,
192 .trim_blocks = true
193 };
194
195 static void
196 uh_ucode_exception(uc_vm_t *vm, uc_exception_t *ex)
197 {
198 uc_value_t *ctx;
199
200 if (ex->type == EXCEPTION_EXIT)
201 return;
202
203 printf("Status: 500 Internal Server Error\r\n\r\n"
204 "Exception while executing ucode program %s:\n",
205 current_prefix->handler);
206
207 switch (ex->type) {
208 case EXCEPTION_SYNTAX: printf("Syntax error"); break;
209 case EXCEPTION_RUNTIME: printf("Runtime error"); break;
210 case EXCEPTION_TYPE: printf("Type error"); break;
211 case EXCEPTION_REFERENCE: printf("Reference error"); break;
212 default: printf("Error");
213 }
214
215 printf(": %s\n", ex->message);
216
217 ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL);
218
219 if (ctx)
220 printf("%s\n", ucv_string_get(ctx));
221 }
222
223 static void
224 uh_ucode_state_init(struct ucode_prefix *ucode)
225 {
226 char *syntax_error = NULL;
227 uc_vm_t *vm = &ucode->ctx;
228 uc_program_t *handler;
229 uc_vm_status_t status;
230 uc_source_t *src;
231 uc_value_t *v;
232 int exitcode;
233
234 uc_search_path_init(&config.module_search_path);
235 uc_vm_init(vm, &config);
236 uc_stdlib_load(uc_vm_scope_get(vm));
237
238 /* build uhttpd api table */
239 v = ucv_object_new(vm);
240
241 ucv_object_add(v, "send", ucv_cfunction_new("send", uh_ucode_send));
242 ucv_object_add(v, "sendc", ucv_get(ucv_object_get(v, "send", NULL)));
243 ucv_object_add(v, "recv", ucv_cfunction_new("recv", uh_ucode_recv));
244 ucv_object_add(v, "urldecode", ucv_cfunction_new("urldecode", uh_ucode_urldecode));
245 ucv_object_add(v, "urlencode", ucv_cfunction_new("urlencode", uh_ucode_urlencode));
246 ucv_object_add(v, "docroot", ucv_string_new(conf.docroot));
247
248 ucv_object_add(uc_vm_scope_get(vm), "uhttpd", v);
249
250 src = uc_source_new_file(ucode->handler);
251
252 if (!src) {
253 fprintf(stderr, "Error: Unable to open ucode handler: %s\n",
254 strerror(errno));
255
256 exit(1);
257 }
258
259 handler = uc_compile(&config, src, &syntax_error);
260
261 uc_source_put(src);
262
263 if (!handler) {
264 fprintf(stderr, "Error: Unable to compile ucode handler: %s\n",
265 syntax_error);
266
267 exit(1);
268 }
269
270 free(syntax_error);
271
272 vm->output = fopen("/dev/null", "w");
273
274 if (!vm->output) {
275 fprintf(stderr, "Error: Unable to open /dev/null for writing: %s\n",
276 strerror(errno));
277
278 exit(1);
279 }
280
281 status = uc_vm_execute(vm, handler, &v);
282 exitcode = (int)ucv_int64_get(v);
283
284 uc_program_put(handler);
285 ucv_put(v);
286
287 switch (status) {
288 case STATUS_OK:
289 break;
290
291 case STATUS_EXIT:
292 fprintf(stderr, "Error: The ucode handler invoked exit(%d)\n", exitcode);
293 exit(exitcode ? exitcode : 1);
294
295 case ERROR_COMPILE:
296 fprintf(stderr, "Error: Compilation error while executing ucode handler\n");
297 exit(1);
298
299 case ERROR_RUNTIME:
300 fprintf(stderr, "Error: Runtime error while executing ucode handler\n");
301 exit(2);
302 }
303
304 v = ucv_object_get(uc_vm_scope_get(vm), UH_UCODE_CB, NULL);
305
306 if (!ucv_is_callable(v)) {
307 fprintf(stderr, "Error: The ucode handler declares no " UH_UCODE_CB "() callback.\n");
308 exit(1);
309 }
310
311 uc_vm_exception_handler_set(vm, uh_ucode_exception);
312
313 ucv_gc(vm);
314
315 fclose(vm->output);
316
317 vm->output = stdout;
318 }
319
320 static void
321 ucode_main(struct client *cl, struct path_info *pi, char *url)
322 {
323 uc_vm_t *vm = &current_prefix->ctx;
324 uc_value_t *req, *hdr, *res;
325 int path_len, prefix_len;
326 struct blob_attr *cur;
327 struct env_var *var;
328 char *str;
329 int rem;
330
331 /* new env table for this request */
332 req = ucv_object_new(vm);
333
334 prefix_len = strlen(pi->name);
335 path_len = strlen(url);
336 str = strchr(url, '?');
337
338 if (str) {
339 if (*(str + 1))
340 pi->query = str + 1;
341
342 path_len = str - url;
343 }
344
345 if (prefix_len > 0 && pi->name[prefix_len - 1] == '/')
346 prefix_len--;
347
348 if (path_len > prefix_len) {
349 ucv_object_add(req, "PATH_INFO",
350 ucv_string_new_length(url + prefix_len, path_len - prefix_len));
351 }
352
353 for (var = ops->get_process_vars(cl, pi); var->name; var++) {
354 if (!var->value)
355 continue;
356
357 ucv_object_add(req, var->name, ucv_string_new(var->value));
358 }
359
360 ucv_object_add(req, "HTTP_VERSION",
361 ucv_double_new(0.9 + (cl->request.version / 10.0)));
362
363 hdr = ucv_object_new(vm);
364
365 blob_for_each_attr(cur, cl->hdr.head, rem)
366 ucv_object_add(hdr, blobmsg_name(cur), ucv_string_new(blobmsg_data(cur)));
367
368 ucv_object_add(req, "headers", hdr);
369
370 res = uc_vm_invoke(vm, UH_UCODE_CB, 1, req);
371
372 ucv_put(req);
373 ucv_put(res);
374
375 exit(0);
376 }
377
378 static void
379 ucode_handle_request(struct client *cl, char *url, struct path_info *pi)
380 {
381 struct ucode_prefix *p;
382 static struct path_info _pi;
383
384 list_for_each_entry(p, &conf.ucode_prefix, list) {
385 if (!ops->path_match(p->prefix, url))
386 continue;
387
388 pi = &_pi;
389 pi->name = p->prefix;
390 pi->phys = p->handler;
391
392 current_prefix = p;
393
394 if (!ops->create_process(cl, pi, url, ucode_main)) {
395 ops->client_error(cl, 500, "Internal Server Error",
396 "Failed to create CGI process: %s",
397 strerror(errno));
398 }
399
400 return;
401 }
402
403 ops->client_error(cl, 500, "Internal Server Error",
404 "Failed to lookup matching handler");
405 }
406
407 static bool
408 check_ucode_url(const char *url)
409 {
410 struct ucode_prefix *p;
411
412 list_for_each_entry(p, &conf.ucode_prefix, list)
413 if (ops->path_match(p->prefix, url))
414 return true;
415
416 return false;
417 }
418
419 static struct dispatch_handler ucode_dispatch = {
420 .script = true,
421 .check_url = check_ucode_url,
422 .handle_request = ucode_handle_request,
423 };
424
425 static int
426 ucode_plugin_init(const struct uhttpd_ops *o, struct config *c)
427 {
428 struct ucode_prefix *p;
429
430 ops = o;
431 _conf = c;
432
433 list_for_each_entry(p, &conf.ucode_prefix, list)
434 uh_ucode_state_init(p);
435
436 ops->dispatch_add(&ucode_dispatch);
437 return 0;
438 }
439
440 struct uhttpd_plugin uhttpd_plugin = {
441 .init = ucode_plugin_init,
442 };