catalinux / Conn (public) (License: LGPLv2) (since 2016-03-01) (hash sha1)
Net library for easy building ipv4/ipv6 network daemons/clients

/Conn_web.c (95be3dead1c6fee0917051220e74aef99f49daed) (10966 bytes) (mode 100644) (type blob)

/* ############ Web server functions ########### */

#include "Conn_config.h"

#include <openssl/sha.h>
#include <openssl/evp.h>

#include "Conn_intern.h"
#include "Conn_web.h"


/*
 * Allocates a web server structure (Conn_web)
 */
static struct Conn_web *Conn_web_alloc(void)
{
	struct Conn_web *ret;

	ret = (struct Conn_web *) malloc(sizeof(struct Conn_web));
	if (ret == NULL)
		return NULL;

	ret->urls = NULL;

	return ret;
}

/*
 * Frees a Conn_web structure
 */
static void Conn_web_free(struct Conn_web *web)
{
	struct Conn_web_url *u, *next;

	u = web->urls;
	while (u) {
		next = u->next;
		free(u->url);
		if (u->type == CONN_WEB_TYPE_PATH)
			free(u->path);
		free(u);
		u = next;
	}

	free(web);
}

/*
 * Creates a web server and attach it to a C
 * Returns 0 if OK, -1 on error.
 */
int Conn_web_create(struct Conn *C)
{
	struct Conn_web *web;

	Log(10, "%s\n", __func__);

	web = Conn_web_alloc();
	if (!web)
		return -1;

	if (C->web)
		Conn_web_free(C->web);

	C->web = web;
	Log(10, "\tattached!\n");

	return 0;
}

/*
 * Returns the string request type
 */
static char *Conn_web_req_type(const unsigned char req_type)
{
	switch (req_type) {
	case CONN_WEB_REQ_GET:	return "GET";
	case CONN_WEB_REQ_POST:	return "POST";
	}

	return "UNK";
}

/*
 * Attach a function to an URL
 * Example: Conn_web_script(C, "/cgi-bin/script1", function_script1)
 */
int Conn_web_script(struct Conn *C, const char *url,
	void(*cb)(struct Conn *C))
{
	struct Conn_web_url *u, *p;

	u = (struct Conn_web_url *) malloc(sizeof(struct Conn_web_url));
	if (!u)
		return -1;

	u->type = CONN_WEB_TYPE_SCRIPT;
	u->next = NULL;

	u->url = strdup(url);
	if (!u->url) {
		free(u);
		return -1;
	}
	u->cb = cb;

	if (C->web->urls == NULL) {
		C->web->urls = u;
	} else {
		p = C->web->urls;
		while (p->next)
			p = p->next;
		p->next = u;
	}

	return 0;
}

/*
 * Attach a dir/file to an URL
 * Example: Conn_web_path(C, "/img", "/home/images/v1")
 */
int Conn_web_path(struct Conn *C, const char *url, const char *path)
{
	struct Conn_web_url *u, *p;

	u = (struct Conn_web_url *) malloc(sizeof(struct Conn_web_url));
	if (!u)
		return -1;

	u->type = CONN_WEB_TYPE_PATH;
	u->next = NULL;

	u->url = strdup(url);
	if (!u->url) {
		free(u);
		return -1;
	}

	u->path = strdup(path);
	if (!u->path) {
		free(u->url);
		free(u);
		return -1;
	}

	if (C->web->urls == NULL) {
		C->web->urls = u;
	} else {
		p = C->web->urls;
		while (p->next)
			p = p->next;
		p->next = u;
	}

	return 0;
}

/*
 * Loads a file and outputs it to a connection
 */
static int Conn_web_read_file(struct Conn *C, const char *path)
{
	/*
	We have a problem here. If file is big, we need to fill a buffer
	and return. when buffer is getting low, refill.
	*/

	return 0;
}

static void Conn_web_dispatch_path(struct Conn *C, struct Conn_web_url *u)
{
	char tmp[128], body[128];

	Log(2, "%llu %s req_type=[%s] url=[%s] http_protcol=%hhu\n",
		C->id, __func__, Conn_web_req_type(C->web_req->req_type),
		C->web_req->url, C->web_req->http_protocol);

	if (C->web_req->req_type != CONN_WEB_REQ_GET) {
		/* TODO: send some error */
		return;
	}

	snprintf(body, sizeof(body), "path");
	snprintf(tmp, sizeof(tmp),
		"HTTP/1.1 200 OK\r\n"
		"Content-Length: %zu\r\n"
		"\r\n"
		"%s",
		strlen(body), body);
	Conn_enqueue(C, tmp, (unsigned int) strlen(tmp));
}

/*
 * Dispatch a web server request
 */
void Conn_web_dispatch(struct Conn *C)
{
	struct Conn_web_url *u = NULL;
	char *end_header, *start_header, *sep, *url, *paras = "";
	char tmp[128];
	unsigned char http_protocol = 11;
	unsigned char req_type = 0;

	Log(10, "%llu %s\n", C->id, __func__);

	if (C->web_req->u) { // This is for websocket
		Log(10, "\tWe have C->web_req->u; just call the callback\n");
		C->web_req->u->cb(C);
		return;
	}

	url = Conn_ibuf(C);

	end_header = Conn_strstr(C, "\r\n\r\n");
	if (end_header == NULL)
		return;

	/* parsing first line */
	sep = Conn_strstr(C, "\r\n");
	*sep = '\0';
        start_header = sep + 2;

	Log(20, "\turl=%s\n", url);
	Log(20, "\theaders:\n%s\n", start_header);

	if (strncmp(url, "GET ", 4) == 0) {
		req_type = CONN_WEB_REQ_GET;
		url += 4;
	} else if (strncmp(url, "POST ", 5) == 0) {
		req_type = CONN_WEB_REQ_POST;
		url += 5;
	}

	if (unlikely(req_type == 0)) {
		/* Invalid request */
		snprintf(tmp, sizeof(tmp), "HTTP/1.1 500 Invalid request\r\n"
			"\r\n"
			"Invalid request");
		Conn_enqueue(C, tmp, (unsigned int) strlen(tmp));
		return;
	}

	sep = strchr(url, ' ');
	if (sep)
		*sep = '\0';
	Log(10, "\tRequest req_type=%s [%s]!\n",
		Conn_web_req_type(req_type), url);

	/* TODO: parse HTTP type */
	if (sep) {
		sep = strstr(sep + 1, "HTTP/");
		if (sep) {
			unsigned char tmp8;

			sep += 5;
			tmp8 = (unsigned char)(sep[0] - '0');
			if ((sep[1] == '.') && (sep[2] != '\0'))
				http_protocol = (unsigned char)(tmp8 * 10 + sep[2] - '0');
		}
	}

	sep = strchr(url, '?');
	if (sep) {
		*sep = '\0';
		paras = sep + 1;
	}

	u = C->web->urls;
	while (u) {
		char *dump;
		int match;

		Log(10, "\tComparing [%s] with [%s]...\n",
			url, u->url);

		if (u->type == CONN_WEB_TYPE_PATH)
			match = strncmp(url, u->url, strlen(u->url));
		else
			match = strcmp(url, u->url);

		if (match != 0) {
			u = u->next;
			continue;
		}

		C->web_req->req_type = req_type;
		C->web_req->url = strdup(url);
		C->web_req->paras = strdup(paras);
		C->web_req->http_protocol = http_protocol;
		C->web_req->header = start_header;
		Conn_eat(C, end_header + 4 - Conn_ibuf(C));
		dump = Conn_dump(Conn_ibuf(C), Conn_iqlen(C));
		Log(20, "\tNOW ibuf (after eating header): [%s]\n", dump);
		free(dump);

		if (u->type == CONN_WEB_TYPE_PATH) {
			Conn_web_dispatch_path(C, u);
		} else {
			Log(20, "\tDEBUG: Set C->web->u!\n");
			C->web_req->u = u;
			u->cb(C);
		}

		free(C->web_req->paras); C->web_req->paras = NULL;
		free(C->web_req->url); C->web_req->url = NULL;
		C->web_req->header = NULL;

		/* TODO: also, if not keepalive is present */
		if (C->web_req->http_protocol == 10)
			Conn_close(C);

		/* TODO: start timer to close connection */

		break;
	}

	if (unlikely(!u)) {
		/* not found */
		Log(10, "\tNo match, sending 404\n");
		snprintf(tmp, sizeof(tmp), "HTTP/1.1 404 Not found\r\n"
			"\r\n"
			"Path not found");
		Conn_enqueue(C, tmp, strlen(tmp));
	}
}

/*
 * Returns a pointer to the HTTP headers
 */
char *Conn_web_header(const struct Conn *C)
{
	return C->web_req->header;
}

/*
 * Lookups a header (example Content-Type) and fills @out with the result.
 * Returns -1 on error, 0 if not found, 1 if found.
 */
int Conn_web_header_lookup(char *out, const size_t out_size,
	const struct Conn *C, const char *h)
{
	unsigned short int hlen, i;
	const char *pos;

	Log(10, "%llu %s h=%s\n", C->id, __func__, h);

	*out = '\0';

	pos = C->web_req->header;
	if (!pos) {
		Log(0, "\tHeader is NULL!\n");
		return -1;
	}

	hlen = strlen(h);
	while (1) {
		Log(20, "\tpos=%p [%c]\n", pos, pos[0]);
		if (strncmp(pos, "\r\n", 2) == 0)
			return -1;

		if (strncasecmp(pos, h, hlen) != 0) {
			pos = strstr(pos, "\r\n");
			if (pos == NULL)
				return 0;
			pos += 2;
			continue;
		}

		/* Found it! */
		pos += hlen;
		while (*pos == ' ')
			pos++;
		if (*pos != ':')
			return -1;
		pos++;
		while (*pos == ' ')
			pos++;

		i = 0;
		while (1) {
			if ((i == out_size - 1) || (*pos == '\r')
				|| (*pos == '\n')) {
				out[i] = '\0';
				return 1;
			}

			out[i++] = *pos;
			pos++;
		}
	}
}


/* websocket */
char Conn_web_is_ws(const struct Conn *C)
{
	if (!C->web_req)
		return 0;

	return C->web_req->req_type == CONN_WEB_REQ_WS;
}

void Conn_web_ws_negociate(struct Conn *C)
{
	int r;
	char key[128], accept[64];
	unsigned char sha1[20];

	Log(20, "%llu %s\n", C->id, __func__);

	r = Conn_web_header_lookup(key, sizeof(key) - 40 - 1, C,
		"Sec-WebSocket-Key");
	if (r != 1) {
		Log(20, "\tCannot find Sec-WebSocket-Key header!\n");
		Conn_close(C);
		return;
	}

	Log(21, "\treceived key=[%s]\n", key);
	strcat(key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
	Log(21, "\tfinal key=%s\n", key);
	/* Compute base64(sha1(key)) */
	SHA1((unsigned char *) key, strlen(key), sha1);
	EVP_EncodeBlock((unsigned char *) accept, sha1, sizeof(sha1));

	Conn_eatall(C);

	Conn_printf(C,
		"HTTP/1.1 101 Switching Protocols\r\n"
		"Server: websocket1\r\n"
		"Upgrade: websocket\r\n"
		"Connection: Upgrade\r\n"
		"Sec-WebSocket-Accept: %s\r\n"
		"\r\n",
		accept);

	Conn_kick(C);
	C->web_req->req_type = CONN_WEB_REQ_WS;
}

void Conn_web_ws_log(const struct Conn_web_ws *w)
{
	Log(0, "fin=%hhu opcode=%hhu mask=%hhu"
		" len=%llu maskkey=0x%hhx%hhx%hhx%hhx\n",
		w->fin, w->opcode, w->mask, w->len,
		w->maskkey[0], w->maskkey[1], w->maskkey[2], w->maskkey[3]);
}

/*
 * Parses a buffer for a websocket packet
 * Example: 88 82 5c c7 68 b9 5f 2e
 * Returns -1 on error, 0 if we do not have enough input, else the len
 */
int Conn_web_ws_parse(struct Conn_web_ws *w, struct Conn *C)
{
	unsigned char len_bytes = 0;
	char *buf;
	unsigned int i, len;

	Log(20, "%llu %s\n", C->id, __func__);

	if (Conn_iqlen(C) < 2)
		return 0;

	buf = Conn_ibuf(C);
	w->fin = buf[0] >> 7;
	w->opcode = buf[0];
	w->mask = buf[1] >> 7;
	w->len = buf[1] & 0x7F;
	Conn_eat(C, 2);

	if (w->opcode == 0x8) {
		Conn_set_error("remote sent close opcode");
		return -1;
	}

	if (unlikely(w->opcode != 0x1)) {
		Conn_set_error("opcode is not 'text' (%hhu)", w->opcode);
		return -1;
	}

	if (w->mask != 1) {
		Conn_set_error("input is not masked");
		return -1;
	}

	if (w->len <= 125) {
		len_bytes = 0;
	} else if (w->len == 126) {
		len_bytes = 2;
	} else {
		len_bytes = 8;
	}

	if (Conn_iqlen(C) < (unsigned int)(len_bytes + 4))
		return 0;

	buf = Conn_ibuf(C);
	if (w->len == 126) {
		w->len = (buf[0] << 8) | buf[1];
	} else if (w->len == 127) {
		w->len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
		buf += 4;
		w->len <<= 32;
		w->len |= (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
	}
	Conn_eat(C, len_bytes);
	Log(20, "\tw->len=%llu\n", w->len);

	buf = Conn_ibuf(C);
	w->maskkey[0] = buf[0];
	w->maskkey[1] = buf[1];
	w->maskkey[2] = buf[2];
	w->maskkey[3] = buf[3];
	Conn_eat(C, 4);

	if (Conn_iqlen(C) < w->len)
		return 0;

	buf = Conn_ibuf(C);
	len = Conn_iqlen(C);
	for (i = 0; i < len; i++)
		buf[i] ^= w->maskkey[i % 4];

	return len;
}

/*
 * Send bytes respecting the websocket protocol
 * @opcode (1 = text)
 * @final: 0 (more data will follow) or 1 (final data)
 */
void Conn_web_ws_enqueue(struct Conn *C, const unsigned char opcode,
	const unsigned char final, const char *s, const unsigned int len)
{
	unsigned char buf[10], i;

	i = 0;
	buf[i++] = (!!final << 7) | opcode; // FIN | opcode (1 = text)
	if (len <= 125) {
		buf[i++] = len;
	} else if (len <= 65535) {
		buf[i++] = 126;
		buf[i++] = len >> 8;
		buf[i++] = len;
	} else {
		buf[i++] = 0;
		buf[i++] = 0;
		buf[i++] = 0;
		buf[i++] = 0;
		buf[i++] = len >> 24;
		buf[i++] = len >> 16;
		buf[i++] = len >> 8;
		buf[i++] = len;
	}
	Conn_enqueue_wait(C, buf, i);
	Conn_enqueue(C, s, len);
}



Mode Type Size Ref File
100644 blob 129 38f2534580e0aace0e6a5b49d79ada2c2ca162be .exclude
100644 blob 155 f46bab40e9d32df6e6ef7ab643931d4e1019d9bf .gitignore
100644 blob 169 c003c095218f64ad33aeb89987f61eb575557d96 .mailmap
100644 blob 1945 fecf0e7a7e8580485101a179685aedc7e00affbb Changelog.pre109
100644 blob 79498 7ad881b5be242d5fdac3ceb550ab4d140344db49 Conn.c
100644 blob 5849 c20cfbd2cfe0b094ed52e61b428b92da19b5daae Conn.h
100644 blob 917 5423bbceb9236d56d8ee827877d8b6d65986c490 Conn.spec.in
100644 blob 747 662c3f3fe8d0a3d23770631d7a0a260719d81e62 Conn_config.h.in
100644 blob 5507 95294798236381d591db36ef84ab53040183fccf Conn_intern.h
100644 blob 10966 95be3dead1c6fee0917051220e74aef99f49daed Conn_web.c
100644 blob 93 4754320eef2b558b97b9c75bd01e545f102670b7 Conn_web.h
100644 blob 30 d987fa5df957830331139935d517009e2911b0cf INSTALL
100644 blob 25275 92b8903ff3fea7f49ef5c041b67a087bca21c5ec LICENSE
100644 blob 1215 0518f7d179939cbc33d13f6626fc45f94db1bf68 Makefile.in
100644 blob 29 e214257f87a28e8fb0413b627cf7ee76ade2e94c Makefile.include.in
100644 blob 192 5b11bdfb23857d8588845465aef993b320596b44 README
100644 blob 19376 5fbc10d9b77d79cd5f62c5f972cc4f7171a4c4f5 TODO
100755 blob 30 92c4bc48245c00408cd7e1fd89bc1a03058f4ce4 configure
040000 tree - d4c9c4a69c5cfa2a84316967185f1661b6817779 docs
100755 blob 16779 274273c95ecfd0d46b63e1e3e8fbd24c204586c9 duilder
100644 blob 1274 25314f6d244d5e701d7c2a22826f00f2cf242651 duilder.conf
040000 tree - 8373602f32aa7000178a7d17fac694f480c246cd examples
040000 tree - 5643f06c34660e576e6c5d0dee5ac74a2bf34f51 tests
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/catalinux/Conn

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/catalinux/Conn

Clone this repository using git:
git clone git://git.rocketgit.com/user/catalinux/Conn

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main