From e611e6d0ff0ba31070abacdb53037d3e9a4115a6 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 29 Mar 2024 14:09:52 +0100 Subject: [PATCH] add ucode binding Supports the full API of the C library and includes a simple example script Signed-off-by: Felix Fietkau --- CMakeLists.txt | 16 ++ uclient-test.uc | 50 +++++ ucode.c | 518 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+) create mode 100755 uclient-test.uc create mode 100644 ucode.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 07796a6..c4aced7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") FIND_PATH(ubox_include_dir libubox/ustream-ssl.h) INCLUDE_DIRECTORIES(${ubox_include_dir}) +OPTION(BUILD_UCODE "build ucode plugin" ON) IF(BUILD_STATIC) FIND_LIBRARY(ubox_library NAMES ubox.a) @@ -21,6 +22,10 @@ ELSE(BUILD_STATIC) FIND_LIBRARY(ubox_library NAMES ubox) ENDIF(BUILD_STATIC) +IF(APPLE) + SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") +ENDIF() + SET(LIB_SOURCES uclient.c uclient-http.c uclient-utils.c) ADD_LIBRARY(uclient SHARED ${LIB_SOURCES}) TARGET_LINK_LIBRARIES(uclient ${ubox_library} dl) @@ -47,6 +52,16 @@ IF(UNIT_TESTING) ENDIF() ENDIF() +IF(BUILD_UCODE) + ADD_LIBRARY(uclient_lib MODULE ucode.c) + SET_TARGET_PROPERTIES(uclient_lib PROPERTIES OUTPUT_NAME uclient PREFIX "") + TARGET_LINK_OPTIONS(uclient_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) + TARGET_LINK_LIBRARIES(uclient_lib uclient) + INSTALL(TARGETS uclient_lib + LIBRARY DESTINATION lib/ucode + ) +ENDIF() + INSTALL(FILES uclient.h uclient-utils.h DESTINATION include/libubox ) @@ -54,3 +69,4 @@ INSTALL(TARGETS uclient uclient-fetch LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) + diff --git a/uclient-test.uc b/uclient-test.uc new file mode 100755 index 0000000..b8df00e --- /dev/null +++ b/uclient-test.uc @@ -0,0 +1,50 @@ +#!/usr/bin/env ucode +'use strict'; +import { basename, stdout } from "fs"; +let uloop = require("uloop"); +let uclient = require("uclient"); + +function fetch_data() { + let data; + while (length(data = uc.read()) > 0) + print(data); +} + +let url = shift(ARGV); +if (!url) { + warn(`Usage: ${basename(sourcepath())} \n`); + exit(1); +} + +uloop.init(); +uc = uclient.new(url, null, { + header_done: () => { + warn(sprintf("Headers: %.J\nStatus: %.J\n", uc.get_headers(), uc.status())); + }, + data_read: fetch_data, + data_eof: () => { + stdout.flush(); + uloop.end(); + }, + error: (code) => { + warn(`Error: ${code}\n`); + uloop.end(); + } +}); + +if (!uc.ssl_init({ verify: false })) { + warn(`Failed to initialize SSL\n`); + exit(1); +} + +if (!uc.connect()) { + warn(`Failed to connect\n`); + exit(1); +} + +if (!uc.request("GET")) { + warn(`Failed to send request\n`); + exit(1); +} + +uloop.run(); diff --git a/ucode.c b/ucode.c new file mode 100644 index 0000000..f1cf5dd --- /dev/null +++ b/ucode.c @@ -0,0 +1,518 @@ +/* + * 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; + unsigned int idx; +}; + +static void uc_uclient_register(struct uc_uclient_priv *ucl, uc_value_t *res, uc_value_t *cb) +{ + size_t i, len; + + len = ucv_array_length(registry); + for (i = 0; i < len; i += 2) + if (!ucv_array_get(registry, i)) + break; + + ucv_array_set(registry, i, ucv_get(res)); + ucv_array_set(registry, i + 1, 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_request(uc_vm_t *vm, size_t nargs) +{ + struct uclient *cl = uc_fn_thisval("uclient"); + 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; + + if (uclient_http_set_request_type(cl, type_str)) + return NULL; + + uclient_http_reset_headers(cl); + + if ((cur = ucv_object_get(arg, "headers", NULL)) != 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 (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 uc_value_t * +uc_uclient_write(uc_vm_t *vm, size_t nargs) +{ + struct uclient *cl = uc_fn_thisval("uclient"); + + if (!cl) + return NULL; + + for (size_t i = 0; i < nargs; i++) + if (ucv_type(uc_fn_arg(i)) != UC_STRING) + return NULL; + + for (size_t i = 0; i < nargs; i++) { + uc_value_t *cur = uc_fn_arg(i); + + uclient_write(cl, ucv_string_get(cur), ucv_string_length(cur)); + } + + return ucv_boolean_new(true); +} + +static void uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg) +{ + struct uc_uclient_priv *ucl = cl->priv; + uc_value_t *cl_res, *cb; + uc_vm_t *vm = uc_vm; + + cb = ucv_array_get(registry, ucl->idx + 1); + if (!cb) + return; + + cb = ucv_object_get(cb, name, NULL); + if (!cb) + return; + + if (!ucv_is_callable(cb)) + return; + + cl_res = ucv_array_get(registry, ucl->idx); + uc_vm_stack_push(vm, ucv_get(cl_res)); + uc_vm_stack_push(vm, ucv_get(cb)); + if (arg) + uc_vm_stack_push(vm, ucv_get(arg)); + + if (uc_vm_call(vm, true, !!arg) != EXCEPTION_NONE) { + if (vm->exhandler) + vm->exhandler(vm, &vm->exception); + return; + } + + ucv_put(uc_vm_stack_pop(vm)); +} + +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) +{ + uc_uclient_cb(cl, "data_sent", NULL); +} + +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; + uc_value_t *ret; + + 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_object_get(cb, "data_read", NULL)) + ucl->cb.data_read = uc_cb_data_read; + if (ucv_object_get(cb, "data_sent", NULL)) + ucl->cb.data_sent = uc_cb_data_sent; + if (ucv_object_get(cb, "data_eof", NULL)) + ucl->cb.data_eof = uc_cb_data_eof; + if (ucv_object_get(cb, "header_done", NULL)) + ucl->cb.header_done = uc_cb_header_done; + if (ucv_object_get(cb, "error", NULL)) + 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; + ret = ucv_resource_new(uc_uclient_type, cl); + uc_uclient_register(ucl, ret, cb); + + return ret; +} +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 }, + { "write", uc_uclient_write }, +}; + +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); +} -- 2.30.2