ucode: fix a few ucode binding issues
[project/uclient.git] / ucode.c
1 /*
2 * uclient - ustream based protocol client library - ucode binding
3 *
4 * Copyright (C) 2024 Felix Fietkau <nbd@openwrt.org>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include <libubox/uloop.h>
19 #include <libubox/blobmsg.h>
20 #include <ucode/module.h>
21 #include "uclient.h"
22
23 static uc_resource_type_t *uc_uclient_type;
24 static uc_value_t *registry;
25 static uc_vm_t *uc_vm;
26
27 struct uc_uclient_priv {
28 struct uclient_cb cb;
29 const struct ustream_ssl_ops *ssl_ops;
30 struct ustream_ssl_ctx *ssl_ctx;
31 uc_value_t *resource;
32 unsigned int idx;
33 int offset;
34 };
35
36 static void uc_uclient_register(struct uc_uclient_priv *ucl, uc_value_t *cb)
37 {
38 size_t i, len;
39
40 len = ucv_array_length(registry);
41 for (i = 0; i < len; i++)
42 if (!ucv_array_get(registry, i))
43 break;
44
45 ucv_array_set(registry, i, ucv_get(cb));
46 ucl->idx = i;
47 }
48
49 static void free_uclient(void *ptr)
50 {
51 struct uclient *cl = ptr;
52 struct uc_uclient_priv *ucl;
53
54 if (!cl)
55 return;
56
57 ucl = cl->priv;
58 ucv_array_set(registry, ucl->idx, NULL);
59 ucv_array_set(registry, ucl->idx + 1, NULL);
60 uclient_free(cl);
61 free(ucl);
62 }
63
64 static uc_value_t *
65 uc_uclient_free(uc_vm_t *vm, size_t nargs)
66 {
67 struct uclient **cl = uc_fn_this("uclient");
68
69 free_uclient(*cl);
70 *cl = NULL;
71
72 return NULL;
73 }
74
75 static uc_value_t *
76 uc_uclient_ssl_init(uc_vm_t *vm, size_t nargs)
77 {
78 struct uclient *cl = uc_fn_thisval("uclient");
79 const struct ustream_ssl_ops *ops;
80 struct ustream_ssl_ctx *ctx;
81 struct uc_uclient_priv *ucl;
82 uc_value_t *args = uc_fn_arg(0);
83 bool verify = false;
84 uc_value_t *cur;
85
86 if (!cl)
87 return NULL;
88
89 ucl = cl->priv;
90 if (ucl->ssl_ctx) {
91 uclient_http_set_ssl_ctx(cl, NULL, NULL, false);
92 ucl->ssl_ctx = NULL;
93 ucl->ssl_ops = NULL;
94 }
95
96 ctx = uclient_new_ssl_context(&ops);
97 if (!ctx)
98 return NULL;
99
100 ucl->ssl_ops = ops;
101 ucl->ssl_ctx = ctx;
102
103 if ((cur = ucv_object_get(args, "cert_file", NULL)) != NULL) {
104 const char *str = ucv_string_get(cur);
105 if (!str || ops->context_set_crt_file(ctx, str))
106 goto err;
107 }
108
109 if ((cur = ucv_object_get(args, "key_file", NULL)) != NULL) {
110 const char *str = ucv_string_get(cur);
111 if (!str || ops->context_set_key_file(ctx, str))
112 goto err;
113 }
114
115 if ((cur = ucv_object_get(args, "ca_files", NULL)) != NULL) {
116 size_t len;
117
118 if (ucv_type(cur) != UC_ARRAY)
119 goto err;
120
121 len = ucv_array_length(cur);
122 for (size_t i = 0; i < len; i++) {
123 uc_value_t *c = ucv_array_get(cur, i);
124 const char *str;
125
126 if (!c)
127 continue;
128
129 str = ucv_string_get(c);
130 if (!str)
131 goto err;
132
133 ops->context_add_ca_crt_file(ctx, str);
134 }
135
136 verify = true;
137 }
138
139 if ((cur = ucv_object_get(args, "verify", NULL)) != NULL)
140 verify = ucv_is_truish(cur);
141
142 ops->context_set_require_validation(ctx, verify);
143 uclient_http_set_ssl_ctx(cl, ops, ctx, verify);
144
145 return ucv_boolean_new(true);
146
147 err:
148 ops->context_free(ctx);
149 return NULL;
150 }
151
152 static uc_value_t *
153 uc_uclient_set_timeout(uc_vm_t *vm, size_t nargs)
154 {
155 struct uclient *cl = uc_fn_thisval("uclient");
156 uc_value_t *val = uc_fn_arg(0);
157
158 if (!cl || ucv_type(val) != UC_INTEGER)
159 return NULL;
160
161 if (uclient_set_timeout(cl, ucv_int64_get(val)))
162 return NULL;
163
164 return ucv_boolean_new(true);
165 }
166
167 static uc_value_t *
168 uc_uclient_set_url(uc_vm_t *vm, size_t nargs)
169 {
170 struct uclient *cl = uc_fn_thisval("uclient");
171 uc_value_t *url = uc_fn_arg(0);
172 uc_value_t *auth_str = uc_fn_arg(1);
173
174 if (!cl || ucv_type(url) != UC_STRING ||
175 (auth_str && ucv_type(auth_str) != UC_STRING))
176 return NULL;
177
178 if (uclient_set_url(cl, ucv_string_get(url), ucv_string_get(auth_str)))
179 return NULL;
180
181 return ucv_boolean_new(true);
182 }
183
184 static uc_value_t *
185 uc_uclient_set_proxy_url(uc_vm_t *vm, size_t nargs)
186 {
187 struct uclient *cl = uc_fn_thisval("uclient");
188 uc_value_t *url = uc_fn_arg(0);
189 uc_value_t *auth_str = uc_fn_arg(1);
190
191 if (!cl || ucv_type(url) != UC_STRING ||
192 (auth_str && ucv_type(auth_str) != UC_STRING))
193 return NULL;
194
195 if (uclient_set_proxy_url(cl, ucv_string_get(url), ucv_string_get(auth_str)))
196 return NULL;
197
198 return ucv_boolean_new(true);
199 }
200
201 static uc_value_t *
202 uc_uclient_get_headers(uc_vm_t *vm, size_t nargs)
203 {
204 struct uclient *cl = uc_fn_thisval("uclient");
205 struct blob_attr *cur;
206 uc_value_t *ret;
207 size_t rem;
208
209 if (!cl)
210 return NULL;
211
212 ret = ucv_object_new(uc_vm);
213 blobmsg_for_each_attr(cur, cl->meta, rem) {
214 uc_value_t *str;
215
216 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
217 continue;
218
219 str = ucv_string_new(blobmsg_get_string(cur));
220 ucv_object_add(ret, blobmsg_name(cur), ucv_get(str));
221 }
222
223 return ret;
224 }
225
226 static uc_value_t *
227 uc_uclient_connect(uc_vm_t *vm, size_t nargs)
228 {
229 struct uclient *cl = uc_fn_thisval("uclient");
230
231 if (!cl || uclient_connect(cl))
232 return NULL;
233
234 return ucv_boolean_new(true);
235 }
236
237 static uc_value_t *
238 uc_uclient_disconnect(uc_vm_t *vm, size_t nargs)
239 {
240 struct uclient *cl = uc_fn_thisval("uclient");
241
242 if (!cl)
243 return NULL;
244
245 uclient_disconnect(cl);
246
247 return ucv_boolean_new(true);
248 }
249
250 static uc_value_t *
251 __uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg)
252 {
253 struct uc_uclient_priv *ucl = cl->priv;
254 uc_vm_t *vm = uc_vm;
255 uc_value_t *cb;
256
257 cb = ucv_array_get(registry, ucl->idx);
258 if (!cb)
259 return NULL;
260
261 cb = ucv_object_get(cb, name, NULL);
262 if (!cb)
263 return NULL;
264
265 if (!ucv_is_callable(cb))
266 return NULL;
267
268 uc_vm_stack_push(vm, ucv_get(ucl->resource));
269 uc_vm_stack_push(vm, ucv_get(cb));
270 if (arg)
271 uc_vm_stack_push(vm, ucv_get(arg));
272
273 if (uc_vm_call(vm, true, !!arg) != EXCEPTION_NONE) {
274 if (vm->exhandler)
275 vm->exhandler(vm, &vm->exception);
276 return NULL;
277 }
278
279 return uc_vm_stack_pop(vm);
280 }
281
282 static void
283 uc_write_str(struct uclient *cl, uc_value_t *val)
284 {
285 uclient_write(cl, ucv_string_get(val), ucv_string_length(val));
286 }
287
288 static bool uc_cb_data_write(struct uclient *cl)
289 {
290 struct uc_uclient_priv *ucl = cl->priv;
291 bool ret = false;
292 uc_value_t *val;
293 size_t len;
294
295 val = __uc_uclient_cb(cl, "get_post_data", ucv_int64_new(ucl->offset));
296 if (ucv_type(val) != UC_STRING)
297 goto out;
298
299 len = ucv_string_length(val);
300 if (!len)
301 goto out;
302
303 ucl->offset += len;
304 uc_write_str(cl, val);
305 ret = true;
306
307 out:
308 ucv_put(val);
309 return ret;
310 }
311
312 static uc_value_t *
313 uc_uclient_request(uc_vm_t *vm, size_t nargs)
314 {
315 struct uclient *cl = uc_fn_thisval("uclient");
316 struct uc_uclient_priv *ucl;
317 uc_value_t *type = uc_fn_arg(0);
318 uc_value_t *arg = uc_fn_arg(1);
319 uc_value_t *cur;
320 const char *type_str = ucv_string_get(type);
321
322 if (!cl || !type_str)
323 return NULL;
324
325 ucl = cl->priv;
326 ucl->offset = 0;
327
328 if (uclient_http_set_request_type(cl, type_str))
329 return NULL;
330
331 uclient_http_reset_headers(cl);
332
333 if ((cur = ucv_object_get(arg, "headers", NULL)) != NULL) {
334 if (ucv_type(cur) != UC_OBJECT)
335 return NULL;
336
337 ucv_object_foreach(cur, key, val) {
338 char *str;
339
340 if (!val)
341 continue;
342
343 if (ucv_type(val) == UC_STRING) {
344 uclient_http_set_header(cl, key, ucv_string_get(val));
345 continue;
346 }
347
348 str = ucv_to_string(uc_vm, val);
349 uclient_http_set_header(cl, key, str);
350 free(str);
351 }
352 }
353
354 if ((cur = ucv_object_get(arg, "post_data", NULL)) != NULL) {
355 if (ucv_type(cur) != UC_STRING)
356 return NULL;
357
358 uc_write_str(cl, cur);
359 }
360
361 while (uc_cb_data_write(cl))
362 if (uclient_pending_bytes(cl, true))
363 return ucv_boolean_new(true);
364
365 ucl->offset = -1;
366 if (uclient_request(cl))
367 return NULL;
368
369 return ucv_boolean_new(true);
370 }
371
372 static uc_value_t *
373 uc_uclient_redirect(uc_vm_t *vm, size_t nargs)
374 {
375 struct uclient *cl = uc_fn_thisval("uclient");
376
377 if (!cl || uclient_http_redirect(cl))
378 return NULL;
379
380 return ucv_boolean_new(true);
381 }
382
383 static uc_value_t *
384 uc_uclient_status(uc_vm_t *vm, size_t nargs)
385 {
386 struct uclient *cl = uc_fn_thisval("uclient");
387 char addr[INET6_ADDRSTRLEN];
388 uc_value_t *ret;
389 int port;
390
391 if (!cl)
392 return NULL;
393
394 ret = ucv_object_new(vm);
395 ucv_object_add(ret, "eof", ucv_boolean_new(cl->eof));
396 ucv_object_add(ret, "data_eof", ucv_boolean_new(cl->data_eof));
397 ucv_object_add(ret, "status", ucv_int64_new(cl->status_code));
398 ucv_object_add(ret, "redirect", ucv_boolean_new(uclient_http_status_redirect(cl)));
399
400 uclient_get_addr(addr, &port, &cl->local_addr);
401 ucv_object_add(ret, "local_addr", ucv_get(ucv_string_new(addr)));
402 ucv_object_add(ret, "local_port", ucv_get(ucv_int64_new(port)));
403
404 uclient_get_addr(addr, &port, &cl->remote_addr);
405 ucv_object_add(ret, "remote_addr", ucv_get(ucv_string_new(addr)));
406 ucv_object_add(ret, "remote_port", ucv_get(ucv_int64_new(port)));
407
408 return ret;
409 }
410
411 static uc_value_t *
412 uc_uclient_read(uc_vm_t *vm, size_t nargs)
413 {
414 struct uclient *cl = uc_fn_thisval("uclient");
415 size_t len = ucv_int64_get(uc_fn_arg(0));
416 uc_stringbuf_t *strbuf = NULL;
417 static char buf[4096];
418 int cur;
419
420 if (!cl)
421 return NULL;
422
423 if (!len)
424 len = sizeof(buf);
425
426 while (len > 0) {
427 cur = uclient_read(cl, buf, len);
428 if (cur <= 0)
429 break;
430
431 if (!strbuf)
432 strbuf = ucv_stringbuf_new();
433
434 ucv_stringbuf_addstr(strbuf, buf, cur);
435 len -= cur;
436 }
437
438 if (!strbuf)
439 return NULL;
440
441 return ucv_stringbuf_finish(strbuf);
442 }
443
444 static void
445 uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg)
446 {
447 ucv_put(__uc_uclient_cb(cl, name, arg));
448 }
449
450 static void uc_cb_data_read(struct uclient *cl)
451 {
452 uc_uclient_cb(cl, "data_read", NULL);
453 }
454
455 static void uc_cb_data_sent(struct uclient *cl)
456 {
457 struct uc_uclient_priv *ucl = cl->priv;
458
459 if (ucl->offset < 0 || uclient_pending_bytes(cl, true))
460 return;
461
462 while (uc_cb_data_write(cl))
463 if (uclient_pending_bytes(cl, true))
464 return;
465
466 ucl->offset = -1;
467 uclient_request(cl);
468 }
469
470 static void uc_cb_data_eof(struct uclient *cl)
471 {
472 uc_uclient_cb(cl, "data_eof", NULL);
473 }
474
475 static void uc_cb_header_done(struct uclient *cl)
476 {
477 uc_uclient_cb(cl, "header_done", NULL);
478 }
479
480 static void uc_cb_error(struct uclient *cl, int code)
481 {
482 uc_uclient_cb(cl, "error", ucv_int64_new(code));
483 }
484
485 static uc_value_t *
486 uc_uclient_new(uc_vm_t *vm, size_t nargs)
487 {
488 struct uc_uclient_priv *ucl;
489 uc_value_t *url = uc_fn_arg(0);
490 uc_value_t *auth_str = uc_fn_arg(1);
491 uc_value_t *cb = uc_fn_arg(2);
492 static bool _init_done;
493 struct uclient *cl;
494
495 if (!_init_done) {
496 uloop_init();
497 _init_done = true;
498 }
499
500 uc_vm = vm;
501
502 if (ucv_type(url) != UC_STRING ||
503 (auth_str && ucv_type(auth_str) != UC_STRING) ||
504 ucv_type(cb) != UC_OBJECT)
505 return NULL;
506
507 ucl = calloc(1, sizeof(*ucl));
508 if (ucv_object_get(cb, "data_read", NULL))
509 ucl->cb.data_read = uc_cb_data_read;
510 if (ucv_object_get(cb, "get_post_data", NULL))
511 ucl->cb.data_sent = uc_cb_data_sent;
512 if (ucv_object_get(cb, "data_eof", NULL))
513 ucl->cb.data_eof = uc_cb_data_eof;
514 if (ucv_object_get(cb, "header_done", NULL))
515 ucl->cb.header_done = uc_cb_header_done;
516 if (ucv_object_get(cb, "error", NULL))
517 ucl->cb.error = uc_cb_error;
518
519 cl = uclient_new(ucv_string_get(url), ucv_string_get(auth_str), &ucl->cb);
520 if (!cl) {
521 free(ucl);
522 return NULL;
523 }
524
525 cl->priv = ucl;
526 uc_uclient_register(ucl, cb);
527 ucl->resource = ucv_resource_new(uc_uclient_type, cl);
528
529 return ucl->resource;
530 }
531 static const uc_function_list_t uclient_fns[] = {
532 { "free", uc_uclient_free },
533 { "ssl_init", uc_uclient_ssl_init },
534 { "set_url", uc_uclient_set_url },
535 { "set_proxy_url", uc_uclient_set_proxy_url },
536 { "set_timeout", uc_uclient_set_timeout },
537 { "get_headers", uc_uclient_get_headers },
538
539 { "connect", uc_uclient_connect },
540 { "disconnect", uc_uclient_disconnect },
541 { "request", uc_uclient_request },
542 { "redirect", uc_uclient_redirect },
543 { "status", uc_uclient_status },
544
545 { "read", uc_uclient_read },
546 };
547
548 static const uc_function_list_t global_fns[] = {
549 { "new", uc_uclient_new },
550 };
551
552 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
553 {
554 uc_uclient_type = uc_type_declare(vm, "uclient", uclient_fns, free_uclient);
555 registry = ucv_array_new(vm);
556 uc_vm_registry_set(vm, "uclient.registry", registry);
557 uc_function_list_register(scope, global_fns);
558 }