From c280d54e1bc79de4424fabc3fad011cc15587b81 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 20 Mar 2014 20:39:47 +0100 Subject: [PATCH 1/1] Initial import Signed-off-by: Felix Fietkau --- .gitignore | 9 + CMakeLists.txt | 26 +++ uclient-backend.h | 35 ++++ uclient-example.c | 85 ++++++++ uclient-http.c | 481 ++++++++++++++++++++++++++++++++++++++++++++++ uclient-utils.c | 77 ++++++++ uclient-utils.h | 16 ++ uclient.c | 200 +++++++++++++++++++ uclient.h | 54 ++++++ 9 files changed, 983 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 uclient-backend.h create mode 100644 uclient-example.c create mode 100644 uclient-http.c create mode 100644 uclient-utils.c create mode 100644 uclient-utils.h create mode 100644 uclient.c create mode 100644 uclient.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9d74f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Makefile +CMakeCache.txt +CMakeFiles +*.cmake +*.a +*.so +*.dylib +install_manifest.txt +uclient-example diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0d48175 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 2.6) + +INCLUDE(CheckIncludeFiles) + +PROJECT(uclient C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +ADD_LIBRARY(uclient SHARED uclient.c uclient-http.c uclient-utils.c) +TARGET_LINK_LIBRARIES(uclient ubox ustream-ssl) + +ADD_EXECUTABLE(uclient-example uclient-example.c) +TARGET_LINK_LIBRARIES(uclient-example uclient) + +INSTALL(FILES uclient.h uclient-utils.h + DESTINATION include/libubox +) +INSTALL(TARGETS uclient + LIBRARY DESTINATION lib +) diff --git a/uclient-backend.h b/uclient-backend.h new file mode 100644 index 0000000..f72c11f --- /dev/null +++ b/uclient-backend.h @@ -0,0 +1,35 @@ +#ifndef __UCLIENT_INTERNAL_H +#define __UCLIENT_INTERNAL_H + +struct uclient_url; + +struct uclient_backend { + const char * const * prefix; + + struct uclient *(*alloc)(void); + void (*free)(struct uclient *cl); + + int (*connect)(struct uclient *cl); + int (*request)(struct uclient *cl); + + int (*read)(struct uclient *cl, char *buf, unsigned int len); + int (*write)(struct uclient *cl, char *buf, unsigned int len); + int (*set_write_len)(struct uclient *cl, unsigned int len); +}; + +struct uclient_url { + const struct uclient_backend *backend; + int prefix; + + const char *host; + const char *port; + const char *location; + + const char *auth; +}; + +extern const struct uclient_backend uclient_backend_http; +void uclient_backend_set_eof(struct uclient *cl); +void uclient_backend_reset_state(struct uclient *cl); + +#endif diff --git a/uclient-example.c b/uclient-example.c new file mode 100644 index 0000000..b66468a --- /dev/null +++ b/uclient-example.c @@ -0,0 +1,85 @@ +#include + +#include "uclient.h" + +static void example_header_done(struct uclient *cl) +{ + struct blob_attr *cur; + int rem; + + fprintf(stderr, "Headers: \n"); + blobmsg_for_each_attr(cur, cl->meta, rem) { + fprintf(stderr, "%s=%s\n", blobmsg_name(cur), (char *) blobmsg_data(cur)); + } + + fprintf(stderr, "Contents:\n"); +} + +static void example_read_data(struct uclient *cl) +{ + char buf[256]; + int len; + + while (1) { + len = uclient_read(cl, buf, sizeof(buf)); + if (!len) + return; + + fwrite(buf, len, 1, stderr); + } +} + +static void example_request_sm(struct uclient *cl) +{ + static int i = 0; + + switch (i++) { + case 0: + uclient_connect(cl); + uclient_http_set_request_type(cl, "HEAD"); + uclient_request(cl); + break; + case 1: + uclient_connect(cl); + uclient_http_set_request_type(cl, "GET"); + uclient_request(cl); + break; + default: + uclient_free(cl); + uloop_end(); + break; + }; +} + +static void example_eof(struct uclient *cl) +{ + example_request_sm(cl); +} + +static const struct uclient_cb cb = { + .header_done = example_header_done, + .data_read = example_read_data, + .data_eof = example_eof, +}; + +int main(int argc, char **argv) +{ + struct uclient *cl; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + uloop_init(); + cl = uclient_new(argv[1], &cb); + if (!cl) { + fprintf(stderr, "Failed to allocate uclient context\n"); + return 1; + } + example_request_sm(cl); + uloop_run(); + uloop_done(); + + return 0; +} diff --git a/uclient-http.c b/uclient-http.c new file mode 100644 index 0000000..bf0b051 --- /dev/null +++ b/uclient-http.c @@ -0,0 +1,481 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "uclient.h" +#include "uclient-utils.h" +#include "uclient-backend.h" + +static struct ustream_ssl_ctx *ssl_ctx; + +enum request_type { + REQ_GET, + REQ_HEAD, + REQ_POST, + __REQ_MAX +}; + +enum http_state { + HTTP_STATE_INIT, + HTTP_STATE_HEADERS_SENT, + HTTP_STATE_REQUEST_DONE, + HTTP_STATE_RECV_HEADERS, + HTTP_STATE_RECV_DATA, + HTTP_STATE_ERROR, +}; + +static const char * const request_types[__REQ_MAX] = { + [REQ_GET] = "GET", + [REQ_HEAD] = "HEAD", + [REQ_POST] = "POST", +}; + +struct uclient_http { + struct uclient uc; + + struct ustream *us; + + struct ustream_fd ufd; + struct ustream_ssl ussl; + + bool ssl; + enum request_type req_type; + enum http_state state; + + unsigned int send_len; + + struct blob_buf headers; + struct blob_buf meta; +}; + +enum { + PREFIX_HTTP, + PREFIX_HTTPS, + __PREFIX_MAX, +}; + +static const char * const uclient_http_prefix[] = { + [PREFIX_HTTP] = "http://", + [PREFIX_HTTPS] = "https://", + [__PREFIX_MAX] = NULL +}; + +static int uclient_do_connect(struct uclient_http *uh, const char *port) +{ + int fd; + + if (uh->uc.url->port) + port = uh->uc.url->port; + + fd = usock(USOCK_TCP | USOCK_NONBLOCK, uh->uc.url->host, port); + if (fd < 0) + return -1; + + ustream_fd_init(&uh->ufd, fd); + return 0; +} + +static void uclient_notify_eof(struct uclient_http *uh) +{ + struct ustream *us = uh->us; + + if (!us->eof && !us->write_error) + return; + + if (ustream_pending_data(us, false)) + return; + + uclient_backend_set_eof(&uh->uc); +} + +static void uclient_parse_http_line(struct uclient_http *uh, char *data) +{ + char *name; + char *sep; + + if (uh->state == HTTP_STATE_REQUEST_DONE) { + uh->state = HTTP_STATE_RECV_HEADERS; + return; + } + + if (!*data) { + uh->state = HTTP_STATE_RECV_DATA; + uh->uc.meta = uh->meta.head; + if (uh->uc.cb->header_done) + uh->uc.cb->header_done(&uh->uc); + return; + } + + sep = strchr(data, ':'); + if (!sep) + return; + + *(sep++) = 0; + + for (name = data; *name; name++) + *name = tolower(*name); + + name = data; + while (isspace(*sep)) + sep++; + + blobmsg_add_string(&uh->meta, name, sep); +} + +static void __uclient_notify_read(struct uclient_http *uh) +{ + struct uclient *uc = &uh->uc; + char *data; + int len; + + if (uh->state < HTTP_STATE_REQUEST_DONE) + return; + + data = ustream_get_read_buf(uh->us, &len); + if (!data || !len) + return; + + if (uh->state < HTTP_STATE_RECV_DATA) { + char *sep; + int cur_len; + + do { + sep = strstr(data, "\r\n"); + if (!sep) + break; + + /* Check for multi-line HTTP headers */ + if (sep > data) { + if (!sep[2]) + return; + + if (isspace(sep[2]) && sep[2] != '\r') { + sep[0] = ' '; + sep[1] = ' '; + continue; + } + } + + *sep = 0; + cur_len = sep + 2 - data; + uclient_parse_http_line(uh, data); + ustream_consume(uh->us, cur_len); + len -= cur_len; + + data = ustream_get_read_buf(uh->us, &len); + } while (uh->state < HTTP_STATE_RECV_DATA); + + if (!len) + return; + } + + if (uh->state == HTTP_STATE_RECV_DATA && uc->cb->data_read) + uc->cb->data_read(uc); +} + +static void uclient_notify_read(struct ustream *us, int bytes) +{ + struct uclient_http *uh = container_of(us, struct uclient_http, ufd.stream); + + __uclient_notify_read(uh); +} + +static void uclient_notify_state(struct ustream *us) +{ + struct uclient_http *uh = container_of(us, struct uclient_http, ufd.stream); + + uclient_notify_eof(uh); +} + +static int uclient_setup_http(struct uclient_http *uh) +{ + struct ustream *us = &uh->ufd.stream; + int ret; + + uh->us = us; + us->string_data = true; + us->notify_state = uclient_notify_state; + us->notify_read = uclient_notify_read; + + ret = uclient_do_connect(uh, "80"); + if (ret) + return ret; + + return 0; +} + +static void uclient_ssl_notify_read(struct ustream *us, int bytes) +{ + struct uclient_http *uh = container_of(us, struct uclient_http, ussl.stream); + + __uclient_notify_read(uh); +} + +static void uclient_ssl_notify_state(struct ustream *us) +{ + struct uclient_http *uh = container_of(us, struct uclient_http, ussl.stream); + + uclient_notify_eof(uh); +} + +static int uclient_setup_https(struct uclient_http *uh) +{ + struct ustream *us = &uh->ussl.stream; + int ret; + + uh->ssl = true; + uh->us = us; + + ret = uclient_do_connect(uh, "443"); + if (ret) + return ret; + + if (!ssl_ctx) + ssl_ctx = ustream_ssl_context_new(false); + + us->string_data = true; + us->notify_state = uclient_ssl_notify_state; + us->notify_read = uclient_ssl_notify_read; + ustream_ssl_init(&uh->ussl, &uh->ufd.stream, ssl_ctx, false); + + return 0; +} + +static void uclient_http_disconnect(struct uclient_http *uh) +{ + uclient_backend_reset_state(&uh->uc); + + if (!uh->us) + return; + + if (uh->ssl) + ustream_free(&uh->ussl.stream); + ustream_free(&uh->ufd.stream); + close(uh->ufd.fd.fd); + uh->us = NULL; +} + +static int uclient_http_connect(struct uclient *cl) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + uclient_http_disconnect(uh); + blob_buf_init(&uh->meta, 0); + + uh->ssl = cl->url->prefix == PREFIX_HTTPS; + uh->state = HTTP_STATE_INIT; + + if (uh->ssl) + return uclient_setup_https(uh); + else + return uclient_setup_http(uh); +} + +static struct uclient *uclient_http_alloc(void) +{ + struct uclient_http *uh; + + uh = calloc_a(sizeof(*uh)); + blob_buf_init(&uh->headers, 0); + + return &uh->uc; +} + +static void uclient_http_free(struct uclient *cl) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + uclient_http_disconnect(uh); + blob_buf_free(&uh->headers); + blob_buf_free(&uh->meta); + free(uh); +} + +int +uclient_http_set_request_type(struct uclient *cl, const char *type) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + int i; + + if (cl->backend != &uclient_backend_http) + return -1; + + if (uh->state > HTTP_STATE_INIT) + return -1; + + for (i = 0; i < ARRAY_SIZE(request_types); i++) { + if (strcmp(request_types[i], type) != 0) + continue; + + uh->req_type = i; + return 0; + } + + return -1; +} + +int +uclient_http_reset_headers(struct uclient *cl, const char *name, const char *value) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + blob_buf_init(&uh->headers, 0); + + return 0; +} + +int +uclient_http_set_header(struct uclient *cl, const char *name, const char *value) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + if (cl->backend != &uclient_backend_http) + return -1; + + if (uh->state > HTTP_STATE_INIT) + return -1; + + blobmsg_add_string(&uh->headers, name, value); + return 0; +} + +#define ustream_printf(us, ...) do { \ + fprintf(stderr, "send: " __VA_ARGS__); \ + ustream_printf(us, __VA_ARGS__); \ +} while (0) + + +static void +uclient_http_send_headers(struct uclient_http *uh) +{ + struct uclient_url *url = uh->uc.url; + struct blob_attr *cur; + int rem; + + if (uh->state >= HTTP_STATE_HEADERS_SENT) + return; + + ustream_printf(uh->us, + "%s /%s HTTP/1.0\r\n" + "Host: %s\r\n", + request_types[uh->req_type], + url->location, url->host); + + blobmsg_for_each_attr(cur, uh->headers.head, rem) + ustream_printf(uh->us, "%s: %s\n", blobmsg_name(cur), (char *) blobmsg_data(cur)); + + if (url->auth) { + int auth_len = strlen(url->auth); + char *auth_buf; + + if (auth_len > 512) + return; + + auth_buf = alloca(base64_len(auth_len) + 1); + base64_encode(url->auth, auth_len, auth_buf); + ustream_printf(uh->us, "Authorization: Basic %s\r\n", auth_buf); + } + + if (uh->send_len > 0) + ustream_printf(uh->us, "Content-Length: %d", uh->send_len); + + ustream_printf(uh->us, "\r\n"); +} + +static int +uclient_http_set_write_len(struct uclient *cl, unsigned int len) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + if (uh->state >= HTTP_STATE_HEADERS_SENT) + return -1; + + if (uh->req_type == REQ_GET || uh->req_type == REQ_HEAD) { + fprintf(stderr, "Sending data is not supported for %s requests\n", + request_types[uh->req_type]); + return -1; + } + + uh->send_len = len; + return 0; +} + +static int +uclient_http_send_data(struct uclient *cl, char *buf, unsigned int len) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + if (uh->state >= HTTP_STATE_REQUEST_DONE) + return -1; + + uclient_http_send_headers(uh); + + if (len > uh->send_len) { + fprintf(stderr, "%s: ignoring %d extra data bytes\n", __func__, uh->send_len - len); + len = uh->send_len; + } + + ustream_write(uh->us, buf, len, false); + + return len; +} + +static int +uclient_http_request_done(struct uclient *cl) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + + if (uh->state >= HTTP_STATE_REQUEST_DONE) + return -1; + + if (uh->send_len > 0) + fprintf(stderr, "%s: missing %d POST data bytes\n", __func__, uh->send_len); + + uclient_http_send_headers(uh); + uh->state = HTTP_STATE_REQUEST_DONE; + + return 0; +} + +static int +uclient_http_read(struct uclient *cl, char *buf, unsigned int len) +{ + struct uclient_http *uh = container_of(cl, struct uclient_http, uc); + int data_len; + char *data; + + if (uh->state < HTTP_STATE_RECV_DATA) + return 0; + + data = ustream_get_read_buf(uh->us, &data_len); + if (!data || !data_len) + return 0; + + if (len > data_len) + len = data_len; + + memcpy(buf, data, len); + ustream_consume(uh->us, len); + uclient_notify_eof(uh); + + return len; +} + +const struct uclient_backend uclient_backend_http __hidden = { + .prefix = uclient_http_prefix, + + .alloc = uclient_http_alloc, + .free = uclient_http_free, + .connect = uclient_http_connect, + + .read = uclient_http_read, + .write = uclient_http_send_data, + .request = uclient_http_request_done, + + .set_write_len = uclient_http_set_write_len, +}; diff --git a/uclient-utils.c b/uclient-utils.c new file mode 100644 index 0000000..0d09974 --- /dev/null +++ b/uclient-utils.c @@ -0,0 +1,77 @@ +#include +#include +#include + +#include "uclient-utils.h" + +static const char *b64 = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void base64_encode(const void *inbuf, unsigned int len, void *outbuf) +{ + unsigned char *out = outbuf; + const uint8_t *in = inbuf; + unsigned int i; + int pad = len % 3; + + for (i = 0; i < len - pad; i += 3) { + uint32_t in3 = (in[0] << 16) | (in[1] << 8) | in[2]; + int k; + + for (k = 3; k >= 0; k--) { + out[k] = b64[in3 & 0x3f]; + in3 >>= 6; + } + in += 3; + out += 4; + } + + if (pad) { + uint32_t in2 = in[0] << (16 - 6); + + out[3] = '='; + + if (pad > 1) { + in2 |= in[1] << (8 - 6); + out[2] = b64[in2 & 0x3f]; + } else { + out[2] = '='; + } + + in2 >>= 6; + out[1] = b64[in2 & 0x3f]; + in2 >>= 6; + out[0] = b64[in2 & 0x3f]; + + out += 4; + } + + *out = '\0'; +} + +int uclient_urldecode(const char *in, char *out, bool decode_plus) +{ + static char dec[3]; + int ret = 0; + char c; + + while ((c = *(in++))) { + if (c == '%') { + if (!isxdigit(in[0]) || !isxdigit(in[1])) + return -1; + + dec[0] = in[0]; + dec[1] = in[1]; + c = strtol(dec, NULL, 16); + in += 2; + } else if (decode_plus && c == '+') { + c = ' '; + } + + *(out++) = c; + ret++; + } + + *out = 0; + return ret; +} diff --git a/uclient-utils.h b/uclient-utils.h new file mode 100644 index 0000000..a7eaf1c --- /dev/null +++ b/uclient-utils.h @@ -0,0 +1,16 @@ +#ifndef __UCLIENT_UTILS_H +#define __UCLIENT_UTILS_H + +#include + +static inline int base64_len(int len) +{ + return ((len + 2) / 3) * 4; +} + +void base64_encode(const void *inbuf, unsigned int len, void *out); + +int uclient_urldecode(const char *in, char *out, bool decode_plus); + + +#endif diff --git a/uclient.c b/uclient.c new file mode 100644 index 0000000..a5c416e --- /dev/null +++ b/uclient.c @@ -0,0 +1,200 @@ +#include +#include "uclient.h" +#include "uclient-utils.h" +#include "uclient-backend.h" + +static struct uclient_url *uclient_get_url(const char *url_str) +{ + static const struct uclient_backend *backends[] = { + &uclient_backend_http, + }; + + const struct uclient_backend *backend; + const char * const *prefix = NULL; + struct uclient_url *url; + char *url_buf, *next; + int i; + + for (i = 0; i < ARRAY_SIZE(backends); i++) { + int prefix_len = 0; + + for (prefix = backends[i]->prefix; *prefix; prefix++) { + prefix_len = strlen(*prefix); + + if (!strncmp(url_str, *prefix, prefix_len)) + break; + } + + if (!*prefix) + continue; + + url_str += prefix_len; + backend = backends[i]; + break; + } + + if (!*prefix) + return NULL; + + url = calloc_a(sizeof(*url), &url_buf, strlen(url_str) + 1); + url->backend = backend; + strcpy(url_buf, url_str); + + next = strchr(url_buf, '/'); + if (next) { + *next = 0; + url->location = next + 1; + } else { + url->location = "/"; + } + + url->host = url_buf; + next = strchr(url_buf, '@'); + if (next) { + *next = 0; + url->host = next + 1; + + if (uclient_urldecode(url_buf, url_buf, false) < 0) + goto free; + + url->auth = url_buf; + } + + /* Literal IPv6 address */ + if (*url->host == '[') { + url->host++; + next = strrchr(url->host, ']'); + if (!next) + goto free; + + *(next++) = 0; + if (*next == ':') + url->port = next + 1; + } else { + next = strrchr(url->host, ':'); + if (next) + url->port = next + 1; + } + + return url; + +free: + free(url); + return NULL; +} + +struct uclient *uclient_new(const char *url_str, const struct uclient_cb *cb) +{ + struct uclient *cl; + struct uclient_url *url; + + url = uclient_get_url(url_str); + if (!url) + return NULL; + + cl = url->backend->alloc(); + if (!cl) + return NULL; + + cl->backend = url->backend; + cl->cb = cb; + cl->url = url; + + return cl; +} + +int uclient_connect_url(struct uclient *cl, const char *url_str) +{ + struct uclient_url *url = cl->url; + + if (url_str) { + url = uclient_get_url(url_str); + if (!url) + return -1; + + if (url->backend != cl->backend) + return -1; + + free(cl->url); + cl->url = url; + } + + return cl->backend->connect(cl); +} + +void uclient_free(struct uclient *cl) +{ + struct uclient_url *url = cl->url; + + if (cl->backend->free) + cl->backend->free(cl); + else + free(cl); + + free(url); +} + +int uclient_write(struct uclient *cl, char *buf, int len) +{ + if (!cl->backend->write) + return -1; + + return cl->backend->write(cl, buf, len); +} + +int uclient_request(struct uclient *cl) +{ + if (!cl->backend->request) + return -1; + + return cl->backend->request(cl); +} + +int uclient_read(struct uclient *cl, char *buf, int len) +{ + if (!cl->backend->read) + return -1; + + return cl->backend->read(cl, buf, len); +} + +static void __uclient_backend_change_state(struct uloop_timeout *timeout) +{ + struct uclient *cl = container_of(timeout, struct uclient, timeout); + + if (cl->error && cl->cb->error) + cl->cb->error(cl); + else if (cl->eof && cl->cb->data_eof) + cl->cb->data_eof(cl); +} + +static void uclient_backend_change_state(struct uclient *cl) +{ + cl->timeout.cb = __uclient_backend_change_state; + uloop_timeout_set(&cl->timeout, 1); +} + +void uclient_backend_set_error(struct uclient *cl) +{ + if (cl->error) + return; + + cl->error = true; + uclient_backend_change_state(cl); +} + +void __hidden uclient_backend_set_eof(struct uclient *cl) +{ + if (cl->eof || cl->error) + return; + + cl->eof = true; + uclient_backend_change_state(cl); +} + +void __hidden uclient_backend_reset_state(struct uclient *cl) +{ + cl->error = false; + cl->eof = false; + uloop_timeout_cancel(&cl->timeout); +} diff --git a/uclient.h b/uclient.h new file mode 100644 index 0000000..d328b14 --- /dev/null +++ b/uclient.h @@ -0,0 +1,54 @@ +#ifndef __LIBUBOX_UCLIENT_H +#define __LIBUBOX_UCLIENT_H + +#include +#include +#include + +struct uclient_cb; +struct uclient_backend; + +struct uclient { + const struct uclient_backend *backend; + const struct uclient_cb *cb; + + struct uclient_url *url; + void *priv; + + bool eof; + bool error; + int status_code; + struct blob_attr *meta; + + struct uloop_timeout timeout; +}; + +struct uclient_cb { + void (*data_read)(struct uclient *cl); + void (*data_sent)(struct uclient *cl); + void (*data_eof)(struct uclient *cl); + void (*header_done)(struct uclient *cl); + void (*error)(struct uclient *cl); +}; + +struct uclient *uclient_new(const char *url, const struct uclient_cb *cb); +void uclient_free(struct uclient *cl); + +int uclient_connect_url(struct uclient *cl, const char *url_str); + +static inline int uclient_connect(struct uclient *cl) +{ + return uclient_connect_url(cl, NULL); +} + + +int uclient_read(struct uclient *cl, char *buf, int len); +int uclient_write(struct uclient *cl, char *buf, int len); +int uclient_request(struct uclient *cl); + +/* HTTP */ +int uclient_http_set_header(struct uclient *cl, const char *name, const char *value); +int uclient_http_reset_headers(struct uclient *cl, const char *name, const char *value); +int uclient_http_set_request_type(struct uclient *cl, const char *type); + +#endif -- 2.30.2