ソースを参照

Initial import

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Felix Fietkau 10 年 前
コミット
c280d54e1b
9 ファイル変更983 行追加0 行削除
  1. 9 0
      .gitignore
  2. 26 0
      CMakeLists.txt
  3. 35 0
      uclient-backend.h
  4. 85 0
      uclient-example.c
  5. 481 0
      uclient-http.c
  6. 77 0
      uclient-utils.c
  7. 16 0
      uclient-utils.h
  8. 200 0
      uclient.c
  9. 54 0
      uclient.h

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+Makefile
+CMakeCache.txt
+CMakeFiles
+*.cmake
+*.a
+*.so
+*.dylib
+install_manifest.txt
+uclient-example

+ 26 - 0
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
+)

+ 35 - 0
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

+ 85 - 0
uclient-example.c

@@ -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;
+}

+ 481 - 0
uclient-http.c

@@ -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,
+};

+ 77 - 0
uclient-utils.c

@@ -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;
+}

+ 16 - 0
uclient-utils.h

@@ -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

+ 200 - 0
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);
+}

+ 54 - 0
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