/* * uclient - ustream based protocol client library - ucode binding * * Copyright (C) 2024 Felix Fietkau * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "uclient.h" static uc_resource_type_t *uc_uclient_type; static uc_value_t *registry; static uc_vm_t *uc_vm; struct uc_uclient_priv { struct uclient_cb cb; const struct ustream_ssl_ops *ssl_ops; struct ustream_ssl_ctx *ssl_ctx; uc_value_t *resource; unsigned int idx; int offset; }; static void uc_uclient_register(struct uc_uclient_priv *ucl, uc_value_t *cb) { size_t i, len; len = ucv_array_length(registry); for (i = 0; i < len; i++) if (!ucv_array_get(registry, i)) break; ucv_array_set(registry, i, ucv_get(cb)); ucl->idx = i; } static void free_uclient(void *ptr) { struct uclient *cl = ptr; struct uc_uclient_priv *ucl; if (!cl) return; ucl = cl->priv; ucv_array_set(registry, ucl->idx, NULL); ucv_array_set(registry, ucl->idx + 1, NULL); uclient_free(cl); free(ucl); } static uc_value_t * uc_uclient_free(uc_vm_t *vm, size_t nargs) { struct uclient **cl = uc_fn_this("uclient"); free_uclient(*cl); *cl = NULL; return NULL; } static uc_value_t * uc_uclient_ssl_init(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); const struct ustream_ssl_ops *ops; struct ustream_ssl_ctx *ctx; struct uc_uclient_priv *ucl; uc_value_t *args = uc_fn_arg(0); bool verify = false; uc_value_t *cur; if (!cl) return NULL; ucl = cl->priv; if (ucl->ssl_ctx) { uclient_http_set_ssl_ctx(cl, NULL, NULL, false); ucl->ssl_ctx = NULL; ucl->ssl_ops = NULL; } ctx = uclient_new_ssl_context(&ops); if (!ctx) return NULL; ucl->ssl_ops = ops; ucl->ssl_ctx = ctx; if ((cur = ucv_object_get(args, "cert_file", NULL)) != NULL) { const char *str = ucv_string_get(cur); if (!str || ops->context_set_crt_file(ctx, str)) goto err; } if ((cur = ucv_object_get(args, "key_file", NULL)) != NULL) { const char *str = ucv_string_get(cur); if (!str || ops->context_set_key_file(ctx, str)) goto err; } if ((cur = ucv_object_get(args, "ca_files", NULL)) != NULL) { size_t len; if (ucv_type(cur) != UC_ARRAY) goto err; len = ucv_array_length(cur); for (size_t i = 0; i < len; i++) { uc_value_t *c = ucv_array_get(cur, i); const char *str; if (!c) continue; str = ucv_string_get(c); if (!str) goto err; ops->context_add_ca_crt_file(ctx, str); } verify = true; } if ((cur = ucv_object_get(args, "verify", NULL)) != NULL) verify = ucv_is_truish(cur); ops->context_set_require_validation(ctx, verify); uclient_http_set_ssl_ctx(cl, ops, ctx, verify); return ucv_boolean_new(true); err: ops->context_free(ctx); return NULL; } static uc_value_t * uc_uclient_set_timeout(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); uc_value_t *val = uc_fn_arg(0); if (!cl || ucv_type(val) != UC_INTEGER) return NULL; if (uclient_set_timeout(cl, ucv_int64_get(val))) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_set_url(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); uc_value_t *url = uc_fn_arg(0); uc_value_t *auth_str = uc_fn_arg(1); if (!cl || ucv_type(url) != UC_STRING || (auth_str && ucv_type(auth_str) != UC_STRING)) return NULL; if (uclient_set_url(cl, ucv_string_get(url), ucv_string_get(auth_str))) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_set_proxy_url(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); uc_value_t *url = uc_fn_arg(0); uc_value_t *auth_str = uc_fn_arg(1); if (!cl || ucv_type(url) != UC_STRING || (auth_str && ucv_type(auth_str) != UC_STRING)) return NULL; if (uclient_set_proxy_url(cl, ucv_string_get(url), ucv_string_get(auth_str))) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_get_headers(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); struct blob_attr *cur; uc_value_t *ret; size_t rem; if (!cl) return NULL; ret = ucv_object_new(uc_vm); blobmsg_for_each_attr(cur, cl->meta, rem) { uc_value_t *str; if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) continue; str = ucv_string_new(blobmsg_get_string(cur)); ucv_object_add(ret, blobmsg_name(cur), ucv_get(str)); } return ret; } static uc_value_t * uc_uclient_connect(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); if (!cl || uclient_connect(cl)) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_disconnect(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); if (!cl) return NULL; uclient_disconnect(cl); return ucv_boolean_new(true); } static uc_value_t * __uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg) { struct uc_uclient_priv *ucl = cl->priv; uc_vm_t *vm = uc_vm; uc_value_t *cb, *cb_obj; cb_obj = ucv_array_get(registry, ucl->idx); if (!cb_obj) return NULL; cb = ucv_property_get(cb_obj, name); if (!cb) return NULL; if (!ucv_is_callable(cb)) return NULL; uc_vm_stack_push(vm, ucv_get(ucl->resource)); uc_vm_stack_push(vm, ucv_get(cb)); uc_vm_stack_push(vm, ucv_get(cb_obj)); if (arg) uc_vm_stack_push(vm, ucv_get(arg)); if (uc_vm_call(vm, true, !!arg + 1) != EXCEPTION_NONE) { if (vm->exhandler) vm->exhandler(vm, &vm->exception); return NULL; } return uc_vm_stack_pop(vm); } static void uc_write_str(struct uclient *cl, uc_value_t *val) { uclient_write(cl, ucv_string_get(val), ucv_string_length(val)); } static bool uc_cb_data_write(struct uclient *cl) { struct uc_uclient_priv *ucl = cl->priv; bool ret = false; uc_value_t *val; size_t len; val = __uc_uclient_cb(cl, "get_post_data", ucv_int64_new(ucl->offset)); if (ucv_type(val) != UC_STRING) goto out; len = ucv_string_length(val); if (!len) goto out; ucl->offset += len; uc_write_str(cl, val); ret = true; out: ucv_put(val); return ret; } static uc_value_t * uc_uclient_request(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); struct uc_uclient_priv *ucl; uc_value_t *type = uc_fn_arg(0); uc_value_t *arg = uc_fn_arg(1); uc_value_t *cur; const char *type_str = ucv_string_get(type); if (!cl || !type_str) return NULL; ucl = cl->priv; ucl->offset = 0; if (uclient_http_set_request_type(cl, type_str)) return NULL; uclient_http_reset_headers(cl); if ((cur = ucv_property_get(arg, "headers")) != NULL) { if (ucv_type(cur) != UC_OBJECT) return NULL; ucv_object_foreach(cur, key, val) { char *str; if (!val) continue; if (ucv_type(val) == UC_STRING) { uclient_http_set_header(cl, key, ucv_string_get(val)); continue; } str = ucv_to_string(uc_vm, val); uclient_http_set_header(cl, key, str); free(str); } } if ((cur = ucv_property_get(arg, "post_data")) != NULL) { if (ucv_type(cur) != UC_STRING) return NULL; uc_write_str(cl, cur); } while (uc_cb_data_write(cl)) if (uclient_pending_bytes(cl, true)) return ucv_boolean_new(true); ucl->offset = -1; if (uclient_request(cl)) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_redirect(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); if (!cl || uclient_http_redirect(cl)) return NULL; return ucv_boolean_new(true); } static uc_value_t * uc_uclient_status(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); char addr[INET6_ADDRSTRLEN]; uc_value_t *ret; int port; if (!cl) return NULL; ret = ucv_object_new(vm); ucv_object_add(ret, "eof", ucv_boolean_new(cl->eof)); ucv_object_add(ret, "data_eof", ucv_boolean_new(cl->data_eof)); ucv_object_add(ret, "status", ucv_int64_new(cl->status_code)); ucv_object_add(ret, "redirect", ucv_boolean_new(uclient_http_status_redirect(cl))); uclient_get_addr(addr, &port, &cl->local_addr); ucv_object_add(ret, "local_addr", ucv_get(ucv_string_new(addr))); ucv_object_add(ret, "local_port", ucv_get(ucv_int64_new(port))); uclient_get_addr(addr, &port, &cl->remote_addr); ucv_object_add(ret, "remote_addr", ucv_get(ucv_string_new(addr))); ucv_object_add(ret, "remote_port", ucv_get(ucv_int64_new(port))); return ret; } static uc_value_t * uc_uclient_read(uc_vm_t *vm, size_t nargs) { struct uclient *cl = uc_fn_thisval("uclient"); size_t len = ucv_int64_get(uc_fn_arg(0)); uc_stringbuf_t *strbuf = NULL; static char buf[4096]; int cur; if (!cl) return NULL; if (!len) len = sizeof(buf); while (len > 0) { cur = uclient_read(cl, buf, len); if (cur <= 0) break; if (!strbuf) strbuf = ucv_stringbuf_new(); ucv_stringbuf_addstr(strbuf, buf, cur); len -= cur; } if (!strbuf) return NULL; return ucv_stringbuf_finish(strbuf); } static void uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg) { ucv_put(__uc_uclient_cb(cl, name, arg)); } static void uc_cb_data_read(struct uclient *cl) { uc_uclient_cb(cl, "data_read", NULL); } static void uc_cb_data_sent(struct uclient *cl) { struct uc_uclient_priv *ucl = cl->priv; if (ucl->offset < 0 || uclient_pending_bytes(cl, true)) return; while (uc_cb_data_write(cl)) if (uclient_pending_bytes(cl, true)) return; ucl->offset = -1; uclient_request(cl); } static void uc_cb_data_eof(struct uclient *cl) { uc_uclient_cb(cl, "data_eof", NULL); } static void uc_cb_header_done(struct uclient *cl) { uc_uclient_cb(cl, "header_done", NULL); } static void uc_cb_error(struct uclient *cl, int code) { uc_uclient_cb(cl, "error", ucv_int64_new(code)); } static uc_value_t * uc_uclient_new(uc_vm_t *vm, size_t nargs) { struct uc_uclient_priv *ucl; uc_value_t *url = uc_fn_arg(0); uc_value_t *auth_str = uc_fn_arg(1); uc_value_t *cb = uc_fn_arg(2); static bool _init_done; struct uclient *cl; if (!_init_done) { uloop_init(); _init_done = true; } uc_vm = vm; if (ucv_type(url) != UC_STRING || (auth_str && ucv_type(auth_str) != UC_STRING) || ucv_type(cb) != UC_OBJECT) return NULL; ucl = calloc(1, sizeof(*ucl)); if (ucv_property_get(cb, "data_read")) ucl->cb.data_read = uc_cb_data_read; if (ucv_property_get(cb, "get_post_data")) ucl->cb.data_sent = uc_cb_data_sent; if (ucv_property_get(cb, "data_eof")) ucl->cb.data_eof = uc_cb_data_eof; if (ucv_property_get(cb, "header_done")) ucl->cb.header_done = uc_cb_header_done; if (ucv_property_get(cb, "error")) ucl->cb.error = uc_cb_error; cl = uclient_new(ucv_string_get(url), ucv_string_get(auth_str), &ucl->cb); if (!cl) { free(ucl); return NULL; } cl->priv = ucl; uc_uclient_register(ucl, cb); ucl->resource = ucv_resource_new(uc_uclient_type, cl); return ucl->resource; } static const uc_function_list_t uclient_fns[] = { { "free", uc_uclient_free }, { "ssl_init", uc_uclient_ssl_init }, { "set_url", uc_uclient_set_url }, { "set_proxy_url", uc_uclient_set_proxy_url }, { "set_timeout", uc_uclient_set_timeout }, { "get_headers", uc_uclient_get_headers }, { "connect", uc_uclient_connect }, { "disconnect", uc_uclient_disconnect }, { "request", uc_uclient_request }, { "redirect", uc_uclient_redirect }, { "status", uc_uclient_status }, { "read", uc_uclient_read }, }; static const uc_function_list_t global_fns[] = { { "new", uc_uclient_new }, }; void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_uclient_type = uc_type_declare(vm, "uclient", uclient_fns, free_uclient); registry = ucv_array_new(vm); uc_vm_registry_set(vm, "uclient.registry", registry); uc_function_list_register(scope, global_fns); }