Initial import
authorFelix Fietkau <nbd@openwrt.org>
Thu, 20 Mar 2014 19:39:47 +0000 (20:39 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Thu, 20 Mar 2014 19:39:47 +0000 (20:39 +0100)
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
uclient-backend.h [new file with mode: 0644]
uclient-example.c [new file with mode: 0644]
uclient-http.c [new file with mode: 0644]
uclient-utils.c [new file with mode: 0644]
uclient-utils.h [new file with mode: 0644]
uclient.c [new file with mode: 0644]
uclient.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f9d74f7
--- /dev/null
@@ -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 (file)
index 0000000..0d48175
--- /dev/null
@@ -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 (file)
index 0000000..f72c11f
--- /dev/null
@@ -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 (file)
index 0000000..b66468a
--- /dev/null
@@ -0,0 +1,85 @@
+#include <libubox/blobmsg.h>
+
+#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 <URL>\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 (file)
index 0000000..bf0b051
--- /dev/null
@@ -0,0 +1,481 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include <libubox/ustream.h>
+#include <libubox/ustream-ssl.h>
+#include <libubox/usock.h>
+#include <libubox/blobmsg.h>
+
+#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 (file)
index 0000000..0d09974
--- /dev/null
@@ -0,0 +1,77 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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 (file)
index 0000000..a7eaf1c
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __UCLIENT_UTILS_H
+#define __UCLIENT_UTILS_H
+
+#include <stdbool.h>
+
+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 (file)
index 0000000..a5c416e
--- /dev/null
+++ b/uclient.c
@@ -0,0 +1,200 @@
+#include <libubox/ustream-ssl.h>
+#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 (file)
index 0000000..d328b14
--- /dev/null
+++ b/uclient.h
@@ -0,0 +1,54 @@
+#ifndef __LIBUBOX_UCLIENT_H
+#define __LIBUBOX_UCLIENT_H
+
+#include <libubox/blob.h>
+#include <libubox/ustream.h>
+#include <libubox/ustream-ssl.h>
+
+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