2 * uclient - ustream based protocol client library - ucode binding
4 * Copyright (C) 2024 Felix Fietkau <nbd@openwrt.org>
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.
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.
18 #include <libubox/uloop.h>
19 #include <libubox/blobmsg.h>
20 #include <ucode/module.h>
23 static uc_resource_type_t
*uc_uclient_type
;
24 static uc_value_t
*registry
;
25 static uc_vm_t
*uc_vm
;
27 struct uc_uclient_priv
{
29 const struct ustream_ssl_ops
*ssl_ops
;
30 struct ustream_ssl_ctx
*ssl_ctx
;
36 static void uc_uclient_register(struct uc_uclient_priv
*ucl
, uc_value_t
*cb
)
40 len
= ucv_array_length(registry
);
41 for (i
= 0; i
< len
; i
++)
42 if (!ucv_array_get(registry
, i
))
45 ucv_array_set(registry
, i
, ucv_get(cb
));
49 static void free_uclient(void *ptr
)
51 struct uclient
*cl
= ptr
;
52 struct uc_uclient_priv
*ucl
;
58 ucv_array_set(registry
, ucl
->idx
, NULL
);
59 ucv_array_set(registry
, ucl
->idx
+ 1, NULL
);
65 uc_uclient_free(uc_vm_t
*vm
, size_t nargs
)
67 struct uclient
**cl
= uc_fn_this("uclient");
76 uc_uclient_ssl_init(uc_vm_t
*vm
, size_t nargs
)
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);
91 uclient_http_set_ssl_ctx(cl
, NULL
, NULL
, false);
96 ctx
= uclient_new_ssl_context(&ops
);
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
))
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
))
115 if ((cur
= ucv_object_get(args
, "ca_files", NULL
)) != NULL
) {
118 if (ucv_type(cur
) != UC_ARRAY
)
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
);
129 str
= ucv_string_get(c
);
133 ops
->context_add_ca_crt_file(ctx
, str
);
139 if ((cur
= ucv_object_get(args
, "verify", NULL
)) != NULL
)
140 verify
= ucv_is_truish(cur
);
142 ops
->context_set_require_validation(ctx
, verify
);
143 uclient_http_set_ssl_ctx(cl
, ops
, ctx
, verify
);
145 return ucv_boolean_new(true);
148 ops
->context_free(ctx
);
153 uc_uclient_set_timeout(uc_vm_t
*vm
, size_t nargs
)
155 struct uclient
*cl
= uc_fn_thisval("uclient");
156 uc_value_t
*val
= uc_fn_arg(0);
158 if (!cl
|| ucv_type(val
) != UC_INTEGER
)
161 if (uclient_set_timeout(cl
, ucv_int64_get(val
)))
164 return ucv_boolean_new(true);
168 uc_uclient_set_url(uc_vm_t
*vm
, size_t nargs
)
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);
174 if (!cl
|| ucv_type(url
) != UC_STRING
||
175 (auth_str
&& ucv_type(auth_str
) != UC_STRING
))
178 if (uclient_set_url(cl
, ucv_string_get(url
), ucv_string_get(auth_str
)))
181 return ucv_boolean_new(true);
185 uc_uclient_set_proxy_url(uc_vm_t
*vm
, size_t nargs
)
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);
191 if (!cl
|| ucv_type(url
) != UC_STRING
||
192 (auth_str
&& ucv_type(auth_str
) != UC_STRING
))
195 if (uclient_set_proxy_url(cl
, ucv_string_get(url
), ucv_string_get(auth_str
)))
198 return ucv_boolean_new(true);
202 uc_uclient_get_headers(uc_vm_t
*vm
, size_t nargs
)
204 struct uclient
*cl
= uc_fn_thisval("uclient");
205 struct blob_attr
*cur
;
212 ret
= ucv_object_new(uc_vm
);
213 blobmsg_for_each_attr(cur
, cl
->meta
, rem
) {
216 if (blobmsg_type(cur
) != BLOBMSG_TYPE_STRING
)
219 str
= ucv_string_new(blobmsg_get_string(cur
));
220 ucv_object_add(ret
, blobmsg_name(cur
), ucv_get(str
));
227 uc_uclient_connect(uc_vm_t
*vm
, size_t nargs
)
229 struct uclient
*cl
= uc_fn_thisval("uclient");
231 if (!cl
|| uclient_connect(cl
))
234 return ucv_boolean_new(true);
238 uc_uclient_disconnect(uc_vm_t
*vm
, size_t nargs
)
240 struct uclient
*cl
= uc_fn_thisval("uclient");
245 uclient_disconnect(cl
);
247 return ucv_boolean_new(true);
251 __uc_uclient_cb(struct uclient
*cl
, const char *name
, uc_value_t
*arg
)
253 struct uc_uclient_priv
*ucl
= cl
->priv
;
255 uc_value_t
*cb
, *cb_obj
;
257 cb_obj
= ucv_array_get(registry
, ucl
->idx
);
261 cb
= ucv_property_get(cb_obj
, name
);
265 if (!ucv_is_callable(cb
))
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
));
272 uc_vm_stack_push(vm
, ucv_get(arg
));
274 if (uc_vm_call(vm
, true, !!arg
+ 1) != EXCEPTION_NONE
) {
276 vm
->exhandler(vm
, &vm
->exception
);
280 return uc_vm_stack_pop(vm
);
284 uc_write_str(struct uclient
*cl
, uc_value_t
*val
)
286 uclient_write(cl
, ucv_string_get(val
), ucv_string_length(val
));
289 static bool uc_cb_data_write(struct uclient
*cl
)
291 struct uc_uclient_priv
*ucl
= cl
->priv
;
296 val
= __uc_uclient_cb(cl
, "get_post_data", ucv_int64_new(ucl
->offset
));
297 if (ucv_type(val
) != UC_STRING
)
300 len
= ucv_string_length(val
);
305 uc_write_str(cl
, val
);
314 uc_uclient_request(uc_vm_t
*vm
, size_t nargs
)
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);
321 const char *type_str
= ucv_string_get(type
);
323 if (!cl
|| !type_str
)
329 if (uclient_http_set_request_type(cl
, type_str
))
332 uclient_http_reset_headers(cl
);
334 if ((cur
= ucv_property_get(arg
, "headers")) != NULL
) {
335 if (ucv_type(cur
) != UC_OBJECT
)
338 ucv_object_foreach(cur
, key
, val
) {
344 if (ucv_type(val
) == UC_STRING
) {
345 uclient_http_set_header(cl
, key
, ucv_string_get(val
));
349 str
= ucv_to_string(uc_vm
, val
);
350 uclient_http_set_header(cl
, key
, str
);
355 if ((cur
= ucv_property_get(arg
, "post_data")) != NULL
) {
356 if (ucv_type(cur
) != UC_STRING
)
359 uc_write_str(cl
, cur
);
362 while (uc_cb_data_write(cl
))
363 if (uclient_pending_bytes(cl
, true))
364 return ucv_boolean_new(true);
367 if (uclient_request(cl
))
370 return ucv_boolean_new(true);
374 uc_uclient_redirect(uc_vm_t
*vm
, size_t nargs
)
376 struct uclient
*cl
= uc_fn_thisval("uclient");
378 if (!cl
|| uclient_http_redirect(cl
))
381 return ucv_boolean_new(true);
385 uc_uclient_status(uc_vm_t
*vm
, size_t nargs
)
387 struct uclient
*cl
= uc_fn_thisval("uclient");
388 char addr
[INET6_ADDRSTRLEN
];
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
)));
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
)));
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
)));
413 uc_uclient_read(uc_vm_t
*vm
, size_t nargs
)
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];
428 cur
= uclient_read(cl
, buf
, len
);
433 strbuf
= ucv_stringbuf_new();
435 ucv_stringbuf_addstr(strbuf
, buf
, cur
);
442 return ucv_stringbuf_finish(strbuf
);
446 uc_uclient_cb(struct uclient
*cl
, const char *name
, uc_value_t
*arg
)
448 ucv_put(__uc_uclient_cb(cl
, name
, arg
));
451 static void uc_cb_data_read(struct uclient
*cl
)
453 uc_uclient_cb(cl
, "data_read", NULL
);
456 static void uc_cb_data_sent(struct uclient
*cl
)
458 struct uc_uclient_priv
*ucl
= cl
->priv
;
460 if (ucl
->offset
< 0 || uclient_pending_bytes(cl
, true))
463 while (uc_cb_data_write(cl
))
464 if (uclient_pending_bytes(cl
, true))
471 static void uc_cb_data_eof(struct uclient
*cl
)
473 uc_uclient_cb(cl
, "data_eof", NULL
);
476 static void uc_cb_header_done(struct uclient
*cl
)
478 uc_uclient_cb(cl
, "header_done", NULL
);
481 static void uc_cb_error(struct uclient
*cl
, int code
)
483 uc_uclient_cb(cl
, "error", ucv_int64_new(code
));
487 uc_uclient_new(uc_vm_t
*vm
, size_t nargs
)
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
;
503 if (ucv_type(url
) != UC_STRING
||
504 (auth_str
&& ucv_type(auth_str
) != UC_STRING
) ||
505 ucv_type(cb
) != UC_OBJECT
)
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
;
520 cl
= uclient_new(ucv_string_get(url
), ucv_string_get(auth_str
), &ucl
->cb
);
527 uc_uclient_register(ucl
, cb
);
528 ucl
->resource
= ucv_resource_new(uc_uclient_type
, cl
);
530 return ucl
->resource
;
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
},
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
},
546 { "read", uc_uclient_read
},
549 static const uc_function_list_t global_fns
[] = {
550 { "new", uc_uclient_new
},
553 void uc_module_init(uc_vm_t
*vm
, uc_value_t
*scope
)
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
);