uclient: fix http regression
[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, *cb_obj;
256
257 cb_obj = ucv_array_get(registry, ucl->idx);
258 if (!cb_obj)
259 return NULL;
260
261 cb = ucv_property_get(cb_obj, name);
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 uc_vm_stack_push(vm, ucv_get(cb_obj));
271 if (arg)
272 uc_vm_stack_push(vm, ucv_get(arg));
273
274 if (uc_vm_call(vm, true, !!arg + 1) != EXCEPTION_NONE) {
275 if (vm->exhandler)
276 vm->exhandler(vm, &vm->exception);
277 return NULL;
278 }
279
280 return uc_vm_stack_pop(vm);
281 }
282
283 static void
284 uc_write_str(struct uclient *cl, uc_value_t *val)
285 {
286 uclient_write(cl, ucv_string_get(val), ucv_string_length(val));
287 }
288
289 static bool uc_cb_data_write(struct uclient *cl)
290 {
291 struct uc_uclient_priv *ucl = cl->priv;
292 bool ret = false;
293 uc_value_t *val;
294 size_t len;
295
296 val = __uc_uclient_cb(cl, "get_post_data", ucv_int64_new(ucl->offset));
297 if (ucv_type(val) != UC_STRING)
298 goto out;
299
300 len = ucv_string_length(val);
301 if (!len)
302 goto out;
303
304 ucl->offset += len;
305 uc_write_str(cl, val);
306 ret = true;
307
308 out:
309 ucv_put(val);
310 return ret;
311 }
312
313 static uc_value_t *
314 uc_uclient_request(uc_vm_t *vm, size_t nargs)
315 {
316 struct uclient *cl = uc_fn_thisval("uclient");
317 struct uc_uclient_priv *ucl;
318 uc_value_t *type = uc_fn_arg(0);
319 uc_value_t *arg = uc_fn_arg(1);
320 uc_value_t *cur;
321 const char *type_str = ucv_string_get(type);
322
323 if (!cl || !type_str)
324 return NULL;
325
326 ucl = cl->priv;
327 ucl->offset = 0;
328
329 if (uclient_http_set_request_type(cl, type_str))
330 return NULL;
331
332 uclient_http_reset_headers(cl);
333
334 if ((cur = ucv_property_get(arg, "headers")) != NULL) {
335 if (ucv_type(cur) != UC_OBJECT)
336 return NULL;
337
338 ucv_object_foreach(cur, key, val) {
339 char *str;
340
341 if (!val)
342 continue;
343
344 if (ucv_type(val) == UC_STRING) {
345 uclient_http_set_header(cl, key, ucv_string_get(val));
346 continue;
347 }
348
349 str = ucv_to_string(uc_vm, val);
350 uclient_http_set_header(cl, key, str);
351 free(str);
352 }
353 }
354
355 if ((cur = ucv_property_get(arg, "post_data")) != NULL) {
356 if (ucv_type(cur) != UC_STRING)
357 return NULL;
358
359 uc_write_str(cl, cur);
360 }
361
362 while (uc_cb_data_write(cl))
363 if (uclient_pending_bytes(cl, true))
364 return ucv_boolean_new(true);
365
366 ucl->offset = -1;
367 if (uclient_request(cl))
368 return NULL;
369
370 return ucv_boolean_new(true);
371 }
372
373 static uc_value_t *
374 uc_uclient_redirect(uc_vm_t *vm, size_t nargs)
375 {
376 struct uclient *cl = uc_fn_thisval("uclient");
377
378 if (!cl || uclient_http_redirect(cl))
379 return NULL;
380
381 return ucv_boolean_new(true);
382 }
383
384 static uc_value_t *
385 uc_uclient_status(uc_vm_t *vm, size_t nargs)
386 {
387 struct uclient *cl = uc_fn_thisval("uclient");
388 char addr[INET6_ADDRSTRLEN];
389 uc_value_t *ret;
390 int port;
391
392 if (!cl)
393 return NULL;
394
395 ret = ucv_object_new(vm);
396 ucv_object_add(ret, "eof", ucv_boolean_new(cl->eof));
397 ucv_object_add(ret, "data_eof", ucv_boolean_new(cl->data_eof));
398 ucv_object_add(ret, "status", ucv_int64_new(cl->status_code));
399 ucv_object_add(ret, "redirect", ucv_boolean_new(uclient_http_status_redirect(cl)));
400
401 uclient_get_addr(addr, &port, &cl->local_addr);
402 ucv_object_add(ret, "local_addr", ucv_get(ucv_string_new(addr)));
403 ucv_object_add(ret, "local_port", ucv_get(ucv_int64_new(port)));
404
405 uclient_get_addr(addr, &port, &cl->remote_addr);
406 ucv_object_add(ret, "remote_addr", ucv_get(ucv_string_new(addr)));
407 ucv_object_add(ret, "remote_port", ucv_get(ucv_int64_new(port)));
408
409 return ret;
410 }
411
412 static uc_value_t *
413 uc_uclient_read(uc_vm_t *vm, size_t nargs)
414 {
415 struct uclient *cl = uc_fn_thisval("uclient");
416 size_t len = ucv_int64_get(uc_fn_arg(0));
417 uc_stringbuf_t *strbuf = NULL;
418 static char buf[4096];
419 int cur;
420
421 if (!cl)
422 return NULL;
423
424 if (!len)
425 len = sizeof(buf);
426
427 while (len > 0) {
428 cur = uclient_read(cl, buf, len);
429 if (cur <= 0)
430 break;
431
432 if (!strbuf)
433 strbuf = ucv_stringbuf_new();
434
435 ucv_stringbuf_addstr(strbuf, buf, cur);
436 len -= cur;
437 }
438
439 if (!strbuf)
440 return NULL;
441
442 return ucv_stringbuf_finish(strbuf);
443 }
444
445 static void
446 uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg)
447 {
448 ucv_put(__uc_uclient_cb(cl, name, arg));
449 }
450
451 static void uc_cb_data_read(struct uclient *cl)
452 {
453 uc_uclient_cb(cl, "data_read", NULL);
454 }
455
456 static void uc_cb_data_sent(struct uclient *cl)
457 {
458 struct uc_uclient_priv *ucl = cl->priv;
459
460 if (ucl->offset < 0 || uclient_pending_bytes(cl, true))
461 return;
462
463 while (uc_cb_data_write(cl))
464 if (uclient_pending_bytes(cl, true))
465 return;
466
467 ucl->offset = -1;
468 uclient_request(cl);
469 }
470
471 static void uc_cb_data_eof(struct uclient *cl)
472 {
473 uc_uclient_cb(cl, "data_eof", NULL);
474 }
475
476 static void uc_cb_header_done(struct uclient *cl)
477 {
478 uc_uclient_cb(cl, "header_done", NULL);
479 }
480
481 static void uc_cb_error(struct uclient *cl, int code)
482 {
483 uc_uclient_cb(cl, "error", ucv_int64_new(code));
484 }
485
486 static uc_value_t *
487 uc_uclient_new(uc_vm_t *vm, size_t nargs)
488 {
489 struct uc_uclient_priv *ucl;
490 uc_value_t *url = uc_fn_arg(0);
491 uc_value_t *auth_str = uc_fn_arg(1);
492 uc_value_t *cb = uc_fn_arg(2);
493 static bool _init_done;
494 struct uclient *cl;
495
496 if (!_init_done) {
497 uloop_init();
498 _init_done = true;
499 }
500
501 uc_vm = vm;
502
503 if (ucv_type(url) != UC_STRING ||
504 (auth_str && ucv_type(auth_str) != UC_STRING) ||
505 ucv_type(cb) != UC_OBJECT)
506 return NULL;
507
508 ucl = calloc(1, sizeof(*ucl));
509 if (ucv_property_get(cb, "data_read"))
510 ucl->cb.data_read = uc_cb_data_read;
511 if (ucv_property_get(cb, "get_post_data"))
512 ucl->cb.data_sent = uc_cb_data_sent;
513 if (ucv_property_get(cb, "data_eof"))
514 ucl->cb.data_eof = uc_cb_data_eof;
515 if (ucv_property_get(cb, "header_done"))
516 ucl->cb.header_done = uc_cb_header_done;
517 if (ucv_property_get(cb, "error"))
518 ucl->cb.error = uc_cb_error;
519
520 cl = uclient_new(ucv_string_get(url), ucv_string_get(auth_str), &ucl->cb);
521 if (!cl) {
522 free(ucl);
523 return NULL;
524 }
525
526 cl->priv = ucl;
527 uc_uclient_register(ucl, cb);
528 ucl->resource = ucv_resource_new(uc_uclient_type, cl);
529
530 return ucl->resource;
531 }
532 static const uc_function_list_t uclient_fns[] = {
533 { "free", uc_uclient_free },
534 { "ssl_init", uc_uclient_ssl_init },
535 { "set_url", uc_uclient_set_url },
536 { "set_proxy_url", uc_uclient_set_proxy_url },
537 { "set_timeout", uc_uclient_set_timeout },
538 { "get_headers", uc_uclient_get_headers },
539
540 { "connect", uc_uclient_connect },
541 { "disconnect", uc_uclient_disconnect },
542 { "request", uc_uclient_request },
543 { "redirect", uc_uclient_redirect },
544 { "status", uc_uclient_status },
545
546 { "read", uc_uclient_read },
547 };
548
549 static const uc_function_list_t global_fns[] = {
550 { "new", uc_uclient_new },
551 };
552
553 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
554 {
555 uc_uclient_type = uc_type_declare(vm, "uclient", uclient_fns, free_uclient);
556 registry = ucv_array_new(vm);
557 uc_vm_registry_set(vm, "uclient.registry", registry);
558 uc_function_list_register(scope, global_fns);
559 }