Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/http/client.c b/src/http/client.c
new file mode 100644
index 0000000..39afa39
--- /dev/null
+++ b/src/http/client.c
@@ -0,0 +1,701 @@
+/**
+ * @file http/client.c HTTP Client
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_tmr.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_dns.h>
+#include <re_msg.h>
+#include <re_http.h>
+#include "http.h"
+
+
+enum {
+	CONN_TIMEOUT = 30000,
+	RECV_TIMEOUT = 60000,
+	IDLE_TIMEOUT = 900000,
+	BUFSIZE_MAX  = 524288,
+	CONN_BSIZE   = 256,
+};
+
+struct http_cli {
+	struct list reql;
+	struct hash *ht_conn;
+	struct dnsc *dnsc;
+	struct tls *tls;
+};
+
+struct conn;
+
+struct http_req {
+	struct http_chunk chunk;
+	struct sa srvv[16];
+	struct le le;
+	struct http_req **reqp;
+	struct http_cli *cli;
+	struct http_msg *msg;
+	struct dns_query *dq;
+	struct conn *conn;
+	struct mbuf *mbreq;
+	struct mbuf *mb;
+	char *host;
+	http_resp_h *resph;
+	http_data_h *datah;
+	http_conn_h *connh;
+	void *arg;
+	size_t rx_len;
+	unsigned srvc;
+	uint16_t port;
+	bool chunked;
+	bool secure;
+	bool close;
+};
+
+
+struct conn {
+	struct tmr tmr;
+	struct sa addr;
+	struct le he;
+	struct http_req *req;
+	struct tls_conn *sc;
+	struct tcp_conn *tc;
+	uint64_t usec;
+};
+
+
+static void req_close(struct http_req *req, int err,
+		      const struct http_msg *msg);
+static int req_connect(struct http_req *req);
+static void timeout_handler(void *arg);
+
+
+static void cli_destructor(void *arg)
+{
+	struct http_cli *cli = arg;
+	struct le *le = cli->reql.head;
+
+	while (le) {
+		struct http_req *req = le->data;
+
+		le = le->next;
+		req_close(req, ECONNABORTED, NULL);
+	}
+
+	hash_flush(cli->ht_conn);
+	mem_deref(cli->ht_conn);
+	mem_deref(cli->dnsc);
+	mem_deref(cli->tls);
+}
+
+
+static void req_destructor(void *arg)
+{
+	struct http_req *req = arg;
+
+	list_unlink(&req->le);
+	mem_deref(req->msg);
+	mem_deref(req->dq);
+	mem_deref(req->conn);
+	mem_deref(req->mbreq);
+	mem_deref(req->mb);
+	mem_deref(req->host);
+}
+
+
+static void conn_destructor(void *arg)
+{
+	struct conn *conn = arg;
+
+	tmr_cancel(&conn->tmr);
+	hash_unlink(&conn->he);
+	mem_deref(conn->sc);
+	mem_deref(conn->tc);
+}
+
+
+static void conn_idle(struct conn *conn)
+{
+	tmr_start(&conn->tmr, IDLE_TIMEOUT, timeout_handler, conn);
+	conn->req = NULL;
+}
+
+
+static void req_close(struct http_req *req, int err,
+		      const struct http_msg *msg)
+{
+	list_unlink(&req->le);
+	req->dq = mem_deref(req->dq);
+	req->datah = NULL;
+
+	if (req->conn) {
+		if (req->connh)
+			req->connh(req->conn->tc, req->conn->sc, req->arg);
+
+		if (err || req->close || req->connh)
+			mem_deref(req->conn);
+		else
+			conn_idle(req->conn);
+
+		req->conn = NULL;
+	}
+
+	req->connh = NULL;
+
+	if (req->reqp) {
+		*req->reqp = NULL;
+		req->reqp = NULL;
+	}
+
+	if (req->resph) {
+		if (msg)
+			msg->mb->pos = 0;
+
+		req->resph(err, msg, req->arg);
+		req->resph = NULL;
+	}
+
+	mem_deref(req);
+}
+
+
+static void try_next(struct conn *conn, int err)
+{
+	struct http_req *req = conn->req;
+	bool retry = conn->usec > 1;
+
+	mem_deref(conn);
+
+	if (!req)
+		return;
+
+	req->conn = NULL;
+
+	if (retry)
+		++req->srvc;
+
+	if (req->srvc > 0 && !req->msg) {
+
+		err = req_connect(req);
+		if (!err)
+			return;
+	}
+
+	req_close(req, err, NULL);
+}
+
+
+static int write_body_buf(struct http_msg *msg, const uint8_t *buf, size_t sz)
+{
+	if ((msg->mb->pos + sz) > BUFSIZE_MAX)
+		return EOVERFLOW;
+
+	return mbuf_write_mem(msg->mb, buf, sz);
+}
+
+
+static int write_body(struct http_req *req, struct mbuf *mb)
+{
+	const size_t size = min(mbuf_get_left(mb), req->rx_len);
+	int err;
+
+	if (size == 0)
+		return 0;
+
+	if (req->datah)
+		err = req->datah(mbuf_buf(mb), size, req->msg, req->arg);
+	else
+		err = write_body_buf(req->msg, mbuf_buf(mb), size);
+
+	if (err)
+		return err;
+
+	req->rx_len -= size;
+	mb->pos     += size;
+
+	return 0;
+}
+
+
+static int req_recv(struct http_req *req, struct mbuf *mb, bool *last)
+{
+	int err;
+
+	*last = false;
+
+	if (!req->chunked) {
+
+		err = write_body(req, mb);
+		if (err)
+			return err;
+
+		if (req->rx_len == 0)
+			*last = true;
+
+		return 0;
+	}
+
+	while (mbuf_get_left(mb)) {
+
+		if (req->rx_len == 0) {
+
+			err = http_chunk_decode(&req->chunk, mb, &req->rx_len);
+			if (err == ENODATA)
+				return 0;
+			else if (err)
+				return err;
+			else if (req->rx_len == 0) {
+				*last = true;
+				return 0;
+			}
+		}
+
+		err = write_body(req, mb);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+
+static void timeout_handler(void *arg)
+{
+	struct conn *conn = arg;
+
+	try_next(conn, ETIMEDOUT);
+}
+
+
+static void estab_handler(void *arg)
+{
+	struct conn *conn = arg;
+	struct http_req *req = conn->req;
+	int err;
+
+	if (!req)
+		return;
+
+	err = tcp_send(conn->tc, req->mbreq);
+	if (err) {
+		try_next(conn, err);
+		return;
+	}
+
+	tmr_start(&conn->tmr, RECV_TIMEOUT, timeout_handler, conn);
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+	const struct http_hdr *hdr;
+	struct conn *conn = arg;
+	struct http_req *req = conn->req;
+	size_t pos;
+	bool last;
+	int err;
+
+	if (!req)
+		return;
+
+	if (req->msg) {
+		err = req_recv(req, mb, &last);
+		if (err || last)
+			goto out;
+
+		return;
+	}
+
+	if (req->mb) {
+
+		const size_t len = mbuf_get_left(mb);
+
+		if ((mbuf_get_left(req->mb) + len) > BUFSIZE_MAX) {
+			err = EOVERFLOW;
+			goto out;
+		}
+
+		pos = req->mb->pos;
+		req->mb->pos = req->mb->end;
+
+		err = mbuf_write_mem(req->mb, mbuf_buf(mb), len);
+		if (err)
+			goto out;
+
+		req->mb->pos = pos;
+	}
+	else {
+		req->mb = mem_ref(mb);
+	}
+
+	pos = req->mb->pos;
+
+	err = http_msg_decode(&req->msg, req->mb, false);
+	if (err) {
+		if (err == ENODATA) {
+			req->mb->pos = pos;
+			return;
+		}
+		goto out;
+	}
+
+	if (req->datah)
+		tmr_cancel(&conn->tmr);
+
+	hdr = http_msg_hdr(req->msg, HTTP_HDR_CONNECTION);
+	if (hdr && !pl_strcasecmp(&hdr->val, "close"))
+		req->close = true;
+
+	if (http_msg_hdr_has_value(req->msg, HTTP_HDR_TRANSFER_ENCODING,
+				   "chunked"))
+		req->chunked = true;
+	else
+		req->rx_len = req->msg->clen;
+
+	err = req_recv(req, req->mb, &last);
+	if (err || last)
+		goto out;
+
+	return;
+
+ out:
+	req_close(req, err, req->msg);
+}
+
+
+static void close_handler(int err, void *arg)
+{
+	struct conn *conn = arg;
+
+	try_next(conn, err ? err : ECONNRESET);
+}
+
+
+static bool conn_cmp(struct le *le, void *arg)
+{
+	const struct conn *conn = le->data;
+	const struct http_req *req = arg;
+
+	if (!sa_cmp(&req->srvv[req->srvc], &conn->addr, SA_ALL))
+		return false;
+
+	if (req->secure != !!conn->sc)
+		return false;
+
+	return conn->req == NULL;
+}
+
+
+static int conn_connect(struct http_req *req)
+{
+	const struct sa *addr = &req->srvv[req->srvc];
+	struct conn *conn;
+	int err;
+
+	conn = list_ledata(hash_lookup(req->cli->ht_conn,
+				       sa_hash(addr, SA_ALL), conn_cmp, req));
+	if (conn) {
+		err = tcp_send(conn->tc, req->mbreq);
+		if (!err) {
+			tmr_start(&conn->tmr, RECV_TIMEOUT,
+				  timeout_handler, conn);
+
+			req->conn = conn;
+			conn->req = req;
+
+			++conn->usec;
+
+			return 0;
+		}
+
+		mem_deref(conn);
+	}
+
+	conn = mem_zalloc(sizeof(*conn), conn_destructor);
+	if (!conn)
+		return ENOMEM;
+
+	hash_append(req->cli->ht_conn, sa_hash(addr, SA_ALL), &conn->he, conn);
+
+	conn->addr = *addr;
+	conn->usec = 1;
+
+	err = tcp_connect(&conn->tc, addr, estab_handler, recv_handler,
+			  close_handler, conn);
+	if (err)
+		goto out;
+
+#ifdef USE_TLS
+	if (req->secure) {
+
+		err = tls_start_tcp(&conn->sc, req->cli->tls, conn->tc, 0);
+		if (err)
+			goto out;
+	}
+#endif
+
+	tmr_start(&conn->tmr, CONN_TIMEOUT, timeout_handler, conn);
+
+	req->conn = conn;
+	conn->req = req;
+
+ out:
+	if (err)
+		mem_deref(conn);
+
+	return err;
+}
+
+
+static int req_connect(struct http_req *req)
+{
+	int err = EINVAL;
+
+	while (req->srvc > 0) {
+
+		--req->srvc;
+
+		req->mb = mem_deref(req->mb);
+
+		err = conn_connect(req);
+		if (!err)
+			break;
+	}
+
+	return err;
+}
+
+
+static bool rr_handler(struct dnsrr *rr, void *arg)
+{
+	struct http_req *req = arg;
+
+	if (req->srvc >= ARRAY_SIZE(req->srvv))
+		return true;
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		sa_set_in(&req->srvv[req->srvc++], rr->rdata.a.addr,
+			  req->port);
+		break;
+
+	case DNS_TYPE_AAAA:
+		sa_set_in6(&req->srvv[req->srvc++], rr->rdata.aaaa.addr,
+			   req->port);
+		break;
+	}
+
+	return false;
+}
+
+
+static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			  struct list *authl, struct list *addl, void *arg)
+{
+	struct http_req *req = arg;
+	(void)hdr;
+	(void)authl;
+	(void)addl;
+
+	dns_rrlist_apply2(ansl, req->host, DNS_TYPE_A, DNS_TYPE_AAAA,
+			  DNS_CLASS_IN, true, rr_handler, req);
+	if (req->srvc == 0) {
+		err = err ? err : EDESTADDRREQ;
+		goto fail;
+	}
+
+	err = req_connect(req);
+	if (err)
+		goto fail;
+
+	return;
+
+ fail:
+	req_close(req, err, NULL);
+}
+
+
+/**
+ * Send an HTTP request
+ *
+ * @param reqp      Pointer to allocated HTTP request object
+ * @param cli       HTTP Client
+ * @param met       Request method
+ * @param uri       Request URI
+ * @param resph     Response handler
+ * @param datah     Content handler (optional)
+ * @param arg       Handler argument
+ * @param fmt       Formatted HTTP headers and body (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
+		 const char *uri, http_resp_h *resph, http_data_h *datah,
+		 void *arg, const char *fmt, ...)
+{
+	struct pl scheme, host, port, path;
+	struct http_req *req;
+	uint16_t defport;
+	bool secure;
+	va_list ap;
+	int err;
+
+	if (!cli || !met || !uri)
+		return EINVAL;
+
+	if (re_regex(uri, strlen(uri), "[a-z]+://[^:/]+[:]*[0-9]*[^]+",
+		     &scheme, &host, NULL, &port, &path) || scheme.p != uri)
+		return EINVAL;
+
+	if (!pl_strcasecmp(&scheme, "http") ||
+	    !pl_strcasecmp(&scheme, "ws")) {
+		secure  = false;
+		defport = 80;
+	}
+#ifdef USE_TLS
+	else if (!pl_strcasecmp(&scheme, "https") ||
+		 !pl_strcasecmp(&scheme, "wss")) {
+		secure  = true;
+		defport = 443;
+	}
+#endif
+	else
+		return ENOTSUP;
+
+	req = mem_zalloc(sizeof(*req), req_destructor);
+	if (!req)
+		return ENOMEM;
+
+	list_append(&cli->reql, &req->le, req);
+
+	req->cli    = cli;
+	req->secure = secure;
+	req->port   = pl_isset(&port) ? pl_u32(&port) : defport;
+	req->resph  = resph;
+	req->datah  = datah;
+	req->arg    = arg;
+
+	err = pl_strdup(&req->host, &host);
+	if (err)
+		goto out;
+
+	req->mbreq = mbuf_alloc(1024);
+	if (!req->mbreq) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = mbuf_printf(req->mbreq,
+			  "%s %r HTTP/1.1\r\n"
+			  "Host: %r\r\n",
+			  met, &path, &host);
+	if (fmt) {
+		va_start(ap, fmt);
+		err |= mbuf_vprintf(req->mbreq, fmt, ap);
+		va_end(ap);
+	}
+	else {
+		err |= mbuf_write_str(req->mbreq, "\r\n");
+	}
+	if (err)
+		goto out;
+
+	req->mbreq->pos = 0;
+
+	if (!sa_set_str(&req->srvv[0], req->host, req->port)) {
+
+		req->srvc = 1;
+
+		err = req_connect(req);
+		if (err)
+			goto out;
+	}
+	else {
+		err = dnsc_query(&req->dq, cli->dnsc, req->host,
+				 DNS_TYPE_A, DNS_CLASS_IN, true,
+				 query_handler, req);
+		if (err)
+			goto out;
+	}
+
+ out:
+	if (err)
+		mem_deref(req);
+	else if (reqp) {
+		req->reqp = reqp;
+		*reqp = req;
+	}
+
+	return err;
+}
+
+
+/**
+ * Set HTTP request connection handler
+ *
+ * @param req   HTTP request object
+ * @param connh Connection handler
+ */
+void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh)
+{
+	if (!req)
+		return;
+
+	req->connh = connh;
+}
+
+
+/**
+ * Allocate an HTTP client instance
+ *
+ * @param clip      Pointer to allocated HTTP client
+ * @param dnsc      DNS Client
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc)
+{
+	struct http_cli *cli;
+	int err;
+
+	if (!clip || !dnsc)
+		return EINVAL;
+
+	cli = mem_zalloc(sizeof(*cli), cli_destructor);
+	if (!cli)
+		return ENOMEM;
+
+	err = hash_alloc(&cli->ht_conn, CONN_BSIZE);
+	if (err)
+		goto out;
+
+#ifdef USE_TLS
+	err = tls_alloc(&cli->tls, TLS_METHOD_SSLV23, NULL, NULL);
+#else
+	err = 0;
+#endif
+	if (err)
+		goto out;
+
+	cli->dnsc = mem_ref(dnsc);
+
+ out:
+	if (err)
+		mem_deref(cli);
+	else
+		*clip = cli;
+
+	return err;
+}