add ucode binding
[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 unsigned int idx;
32 };
33
34 static void uc_uclient_register(struct uc_uclient_priv *ucl, uc_value_t *res, uc_value_t *cb)
35 {
36 size_t i, len;
37
38 len = ucv_array_length(registry);
39 for (i = 0; i < len; i += 2)
40 if (!ucv_array_get(registry, i))
41 break;
42
43 ucv_array_set(registry, i, ucv_get(res));
44 ucv_array_set(registry, i + 1, ucv_get(cb));
45 ucl->idx = i;
46 }
47
48 static void free_uclient(void *ptr)
49 {
50 struct uclient *cl = ptr;
51 struct uc_uclient_priv *ucl;
52
53 if (!cl)
54 return;
55
56 ucl = cl->priv;
57 ucv_array_set(registry, ucl->idx, NULL);
58 ucv_array_set(registry, ucl->idx + 1, NULL);
59 uclient_free(cl);
60 free(ucl);
61 }
62
63 static uc_value_t *
64 uc_uclient_free(uc_vm_t *vm, size_t nargs)
65 {
66 struct uclient **cl = uc_fn_this("uclient");
67
68 free_uclient(*cl);
69 *cl = NULL;
70
71 return NULL;
72 }
73
74 static uc_value_t *
75 uc_uclient_ssl_init(uc_vm_t *vm, size_t nargs)
76 {
77 struct uclient *cl = uc_fn_thisval("uclient");
78 const struct ustream_ssl_ops *ops;
79 struct ustream_ssl_ctx *ctx;
80 struct uc_uclient_priv *ucl;
81 uc_value_t *args = uc_fn_arg(0);
82 bool verify = false;
83 uc_value_t *cur;
84
85 if (!cl)
86 return NULL;
87
88 ucl = cl->priv;
89 if (ucl->ssl_ctx) {
90 uclient_http_set_ssl_ctx(cl, NULL, NULL, false);
91 ucl->ssl_ctx = NULL;
92 ucl->ssl_ops = NULL;
93 }
94
95 ctx = uclient_new_ssl_context(&ops);
96 if (!ctx)
97 return NULL;
98
99 ucl->ssl_ops = ops;
100 ucl->ssl_ctx = ctx;
101
102 if ((cur = ucv_object_get(args, "cert_file", NULL)) != NULL) {
103 const char *str = ucv_string_get(cur);
104 if (!str || ops->context_set_crt_file(ctx, str))
105 goto err;
106 }
107
108 if ((cur = ucv_object_get(args, "key_file", NULL)) != NULL) {
109 const char *str = ucv_string_get(cur);
110 if (!str || ops->context_set_key_file(ctx, str))
111 goto err;
112 }
113
114 if ((cur = ucv_object_get(args, "ca_files", NULL)) != NULL) {
115 size_t len;
116
117 if (ucv_type(cur) != UC_ARRAY)
118 goto err;
119
120 len = ucv_array_length(cur);
121 for (size_t i = 0; i < len; i++) {
122 uc_value_t *c = ucv_array_get(cur, i);
123 const char *str;
124
125 if (!c)
126 continue;
127
128 str = ucv_string_get(c);
129 if (!str)
130 goto err;
131
132 ops->context_add_ca_crt_file(ctx, str);
133 }
134
135 verify = true;
136 }
137
138 if ((cur = ucv_object_get(args, "verify", NULL)) != NULL)
139 verify = ucv_is_truish(cur);
140
141 ops->context_set_require_validation(ctx, verify);
142 uclient_http_set_ssl_ctx(cl, ops, ctx, verify);
143
144 return ucv_boolean_new(true);
145
146 err:
147 ops->context_free(ctx);
148 return NULL;
149 }
150
151 static uc_value_t *
152 uc_uclient_set_timeout(uc_vm_t *vm, size_t nargs)
153 {
154 struct uclient *cl = uc_fn_thisval("uclient");
155 uc_value_t *val = uc_fn_arg(0);
156
157 if (!cl || ucv_type(val) != UC_INTEGER)
158 return NULL;
159
160 if (uclient_set_timeout(cl, ucv_int64_get(val)))
161 return NULL;
162
163 return ucv_boolean_new(true);
164 }
165
166 static uc_value_t *
167 uc_uclient_set_url(uc_vm_t *vm, size_t nargs)
168 {
169 struct uclient *cl = uc_fn_thisval("uclient");
170 uc_value_t *url = uc_fn_arg(0);
171 uc_value_t *auth_str = uc_fn_arg(1);
172
173 if (!cl || ucv_type(url) != UC_STRING ||
174 (auth_str && ucv_type(auth_str) != UC_STRING))
175 return NULL;
176
177 if (uclient_set_url(cl, ucv_string_get(url), ucv_string_get(auth_str)))
178 return NULL;
179
180 return ucv_boolean_new(true);
181 }
182
183 static uc_value_t *
184 uc_uclient_set_proxy_url(uc_vm_t *vm, size_t nargs)
185 {
186 struct uclient *cl = uc_fn_thisval("uclient");
187 uc_value_t *url = uc_fn_arg(0);
188 uc_value_t *auth_str = uc_fn_arg(1);
189
190 if (!cl || ucv_type(url) != UC_STRING ||
191 (auth_str && ucv_type(auth_str) != UC_STRING))
192 return NULL;
193
194 if (uclient_set_proxy_url(cl, ucv_string_get(url), ucv_string_get(auth_str)))
195 return NULL;
196
197 return ucv_boolean_new(true);
198 }
199
200 static uc_value_t *
201 uc_uclient_get_headers(uc_vm_t *vm, size_t nargs)
202 {
203 struct uclient *cl = uc_fn_thisval("uclient");
204 struct blob_attr *cur;
205 uc_value_t *ret;
206 size_t rem;
207
208 if (!cl)
209 return NULL;
210
211 ret = ucv_object_new(uc_vm);
212 blobmsg_for_each_attr(cur, cl->meta, rem) {
213 uc_value_t *str;
214
215 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
216 continue;
217
218 str = ucv_string_new(blobmsg_get_string(cur));
219 ucv_object_add(ret, blobmsg_name(cur), ucv_get(str));
220 }
221
222 return ret;
223 }
224
225 static uc_value_t *
226 uc_uclient_connect(uc_vm_t *vm, size_t nargs)
227 {
228 struct uclient *cl = uc_fn_thisval("uclient");
229
230 if (!cl || uclient_connect(cl))
231 return NULL;
232
233 return ucv_boolean_new(true);
234 }
235
236 static uc_value_t *
237 uc_uclient_disconnect(uc_vm_t *vm, size_t nargs)
238 {
239 struct uclient *cl = uc_fn_thisval("uclient");
240
241 if (!cl)
242 return NULL;
243
244 uclient_disconnect(cl);
245
246 return ucv_boolean_new(true);
247 }
248
249 static uc_value_t *
250 uc_uclient_request(uc_vm_t *vm, size_t nargs)
251 {
252 struct uclient *cl = uc_fn_thisval("uclient");
253 uc_value_t *type = uc_fn_arg(0);
254 uc_value_t *arg = uc_fn_arg(1);
255 uc_value_t *cur;
256 const char *type_str = ucv_string_get(type);
257
258 if (!cl || !type_str)
259 return NULL;
260
261 if (uclient_http_set_request_type(cl, type_str))
262 return NULL;
263
264 uclient_http_reset_headers(cl);
265
266 if ((cur = ucv_object_get(arg, "headers", NULL)) != NULL) {
267 if (ucv_type(cur) != UC_OBJECT)
268 return NULL;
269
270 ucv_object_foreach(cur, key, val) {
271 char *str;
272
273 if (!val)
274 continue;
275
276 if (ucv_type(val) == UC_STRING) {
277 uclient_http_set_header(cl, key, ucv_string_get(val));
278 continue;
279 }
280
281 str = ucv_to_string(uc_vm, val);
282 uclient_http_set_header(cl, key, str);
283 free(str);
284 }
285 }
286
287 if (uclient_request(cl))
288 return NULL;
289
290 return ucv_boolean_new(true);
291 }
292
293 static uc_value_t *
294 uc_uclient_redirect(uc_vm_t *vm, size_t nargs)
295 {
296 struct uclient *cl = uc_fn_thisval("uclient");
297
298 if (!cl || uclient_http_redirect(cl))
299 return NULL;
300
301 return ucv_boolean_new(true);
302 }
303
304 static uc_value_t *
305 uc_uclient_status(uc_vm_t *vm, size_t nargs)
306 {
307 struct uclient *cl = uc_fn_thisval("uclient");
308 char addr[INET6_ADDRSTRLEN];
309 uc_value_t *ret;
310 int port;
311
312 if (!cl)
313 return NULL;
314
315 ret = ucv_object_new(vm);
316 ucv_object_add(ret, "eof", ucv_boolean_new(cl->eof));
317 ucv_object_add(ret, "data_eof", ucv_boolean_new(cl->data_eof));
318 ucv_object_add(ret, "status", ucv_int64_new(cl->status_code));
319 ucv_object_add(ret, "redirect", ucv_boolean_new(uclient_http_status_redirect(cl)));
320
321 uclient_get_addr(addr, &port, &cl->local_addr);
322 ucv_object_add(ret, "local_addr", ucv_get(ucv_string_new(addr)));
323 ucv_object_add(ret, "local_port", ucv_get(ucv_int64_new(port)));
324
325 uclient_get_addr(addr, &port, &cl->remote_addr);
326 ucv_object_add(ret, "remote_addr", ucv_get(ucv_string_new(addr)));
327 ucv_object_add(ret, "remote_port", ucv_get(ucv_int64_new(port)));
328
329 return ret;
330 }
331
332 static uc_value_t *
333 uc_uclient_read(uc_vm_t *vm, size_t nargs)
334 {
335 struct uclient *cl = uc_fn_thisval("uclient");
336 size_t len = ucv_int64_get(uc_fn_arg(0));
337 uc_stringbuf_t *strbuf = NULL;
338 static char buf[4096];
339 int cur;
340
341 if (!cl)
342 return NULL;
343
344 if (!len)
345 len = sizeof(buf);
346
347 while (len > 0) {
348 cur = uclient_read(cl, buf, len);
349 if (cur <= 0)
350 break;
351
352 if (!strbuf)
353 strbuf = ucv_stringbuf_new();
354
355 ucv_stringbuf_addstr(strbuf, buf, cur);
356 len -= cur;
357 }
358
359 if (!strbuf)
360 return NULL;
361
362 return ucv_stringbuf_finish(strbuf);
363 }
364
365 static uc_value_t *
366 uc_uclient_write(uc_vm_t *vm, size_t nargs)
367 {
368 struct uclient *cl = uc_fn_thisval("uclient");
369
370 if (!cl)
371 return NULL;
372
373 for (size_t i = 0; i < nargs; i++)
374 if (ucv_type(uc_fn_arg(i)) != UC_STRING)
375 return NULL;
376
377 for (size_t i = 0; i < nargs; i++) {
378 uc_value_t *cur = uc_fn_arg(i);
379
380 uclient_write(cl, ucv_string_get(cur), ucv_string_length(cur));
381 }
382
383 return ucv_boolean_new(true);
384 }
385
386 static void uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg)
387 {
388 struct uc_uclient_priv *ucl = cl->priv;
389 uc_value_t *cl_res, *cb;
390 uc_vm_t *vm = uc_vm;
391
392 cb = ucv_array_get(registry, ucl->idx + 1);
393 if (!cb)
394 return;
395
396 cb = ucv_object_get(cb, name, NULL);
397 if (!cb)
398 return;
399
400 if (!ucv_is_callable(cb))
401 return;
402
403 cl_res = ucv_array_get(registry, ucl->idx);
404 uc_vm_stack_push(vm, ucv_get(cl_res));
405 uc_vm_stack_push(vm, ucv_get(cb));
406 if (arg)
407 uc_vm_stack_push(vm, ucv_get(arg));
408
409 if (uc_vm_call(vm, true, !!arg) != EXCEPTION_NONE) {
410 if (vm->exhandler)
411 vm->exhandler(vm, &vm->exception);
412 return;
413 }
414
415 ucv_put(uc_vm_stack_pop(vm));
416 }
417
418 static void uc_cb_data_read(struct uclient *cl)
419 {
420 uc_uclient_cb(cl, "data_read", NULL);
421 }
422
423 static void uc_cb_data_sent(struct uclient *cl)
424 {
425 uc_uclient_cb(cl, "data_sent", NULL);
426 }
427
428 static void uc_cb_data_eof(struct uclient *cl)
429 {
430 uc_uclient_cb(cl, "data_eof", NULL);
431 }
432
433 static void uc_cb_header_done(struct uclient *cl)
434 {
435 uc_uclient_cb(cl, "header_done", NULL);
436 }
437
438 static void uc_cb_error(struct uclient *cl, int code)
439 {
440 uc_uclient_cb(cl, "error", ucv_int64_new(code));
441 }
442
443 static uc_value_t *
444 uc_uclient_new(uc_vm_t *vm, size_t nargs)
445 {
446 struct uc_uclient_priv *ucl;
447 uc_value_t *url = uc_fn_arg(0);
448 uc_value_t *auth_str = uc_fn_arg(1);
449 uc_value_t *cb = uc_fn_arg(2);
450 static bool _init_done;
451 struct uclient *cl;
452 uc_value_t *ret;
453
454 if (!_init_done) {
455 uloop_init();
456 _init_done = true;
457 }
458
459 uc_vm = vm;
460
461 if (ucv_type(url) != UC_STRING ||
462 (auth_str && ucv_type(auth_str) != UC_STRING) ||
463 ucv_type(cb) != UC_OBJECT)
464 return NULL;
465
466 ucl = calloc(1, sizeof(*ucl));
467 if (ucv_object_get(cb, "data_read", NULL))
468 ucl->cb.data_read = uc_cb_data_read;
469 if (ucv_object_get(cb, "data_sent", NULL))
470 ucl->cb.data_sent = uc_cb_data_sent;
471 if (ucv_object_get(cb, "data_eof", NULL))
472 ucl->cb.data_eof = uc_cb_data_eof;
473 if (ucv_object_get(cb, "header_done", NULL))
474 ucl->cb.header_done = uc_cb_header_done;
475 if (ucv_object_get(cb, "error", NULL))
476 ucl->cb.error = uc_cb_error;
477
478 cl = uclient_new(ucv_string_get(url), ucv_string_get(auth_str), &ucl->cb);
479 if (!cl) {
480 free(ucl);
481 return NULL;
482 }
483
484 cl->priv = ucl;
485 ret = ucv_resource_new(uc_uclient_type, cl);
486 uc_uclient_register(ucl, ret, cb);
487
488 return ret;
489 }
490 static const uc_function_list_t uclient_fns[] = {
491 { "free", uc_uclient_free },
492 { "ssl_init", uc_uclient_ssl_init },
493 { "set_url", uc_uclient_set_url },
494 { "set_proxy_url", uc_uclient_set_proxy_url },
495 { "set_timeout", uc_uclient_set_timeout },
496 { "get_headers", uc_uclient_get_headers },
497
498 { "connect", uc_uclient_connect },
499 { "disconnect", uc_uclient_disconnect },
500 { "request", uc_uclient_request },
501 { "redirect", uc_uclient_redirect },
502 { "status", uc_uclient_status },
503
504 { "read", uc_uclient_read },
505 { "write", uc_uclient_write },
506 };
507
508 static const uc_function_list_t global_fns[] = {
509 { "new", uc_uclient_new },
510 };
511
512 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
513 {
514 uc_uclient_type = uc_type_declare(vm, "uclient", uclient_fns, free_uclient);
515 registry = ucv_array_new(vm);
516 uc_vm_registry_set(vm, "uclient.registry", registry);
517 uc_function_list_register(scope, global_fns);
518 }