diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/buffer.c | 81 | ||||
| -rw-r--r-- | src/http.c | 203 | ||||
| -rw-r--r-- | src/main.c | 116 | ||||
| -rw-r--r-- | src/meson.build | 3 | ||||
| -rw-r--r-- | src/util.c | 57 | 
5 files changed, 460 insertions, 0 deletions
| diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..0759f5d --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,81 @@ +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> + +#include "buffer.h" +#include "util.h" + +static void buf_realloc(struct buffer *buf, size_t new_size) { +	if (new_size > buf->size) { +		buf->size = new_size + 1; +		buf->data = realloc(buf->data, buf->size); +	} +} + +struct buffer *buf_new(const char *init) { +	struct buffer *buf = malloc(sizeof(struct buffer)); +	if (init) { +		buf->data = strdup(init); +		buf->len = strlen(init); +		buf->size = buf->len + 1; +	} else { +		buf->size = 1024; +		buf->data = malloc(buf->size); +		*buf->data = '\0'; +		buf->len = 0; +	} + +	return buf; +} + +void buf_del(struct buffer **buf) { +	assert(*buf); +	struct buffer *tmp = *buf; +	free(tmp->data); +	free(tmp); +	*buf = NULL; +} + +struct buffer *buf_dup(struct buffer *src) { +	struct buffer *dest = malloc(sizeof(struct buffer)); +	dest->len = src->len; +	dest->size = src->size; +	dest->data = malloc(dest->size); +	memcpy(dest->data, src->data, dest->size); +	return dest; +} + +char *buf_strdup(struct buffer *buf) { +	return strdup(buf->data); +} + +void buf_append(struct buffer *buf, const char *str) { +	size_t len = strlen(str); +	buf_realloc(buf, buf->len + len + 1); +	strncat(buf->data, str, buf->size); +} + +void buf_printf(struct buffer *buf, char *fmt, ...) { +	va_list ap; +	size_t len; + +	va_start(ap, fmt); +	len = vsnprintf(buf->data, buf->size, fmt, ap); +	va_end(ap); + +	if (len >= buf->size) { +		buf_realloc(buf, len + 1); +		va_start(ap, fmt); +		len = vsnprintf(buf->data, buf->size, fmt, ap); +		va_end(ap); +	} + +	if (len < 0 || len >= buf->size) { +		fprintf(stderr, "buf_printf: failed to format str"); +		exit(EXIT_FAILURE); +	} + +	buf->len = len; +} diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..793f257 --- /dev/null +++ b/src/http.c @@ -0,0 +1,203 @@ +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <cjson/cJSON.h> +#include <stdio.h> + +#include "http.h" +#include "util.h" +#include "buffer.h" + +struct http { +	struct request_handler *requests; +	size_t req_siz, n_requests; +}; + +struct request_handler { +	char *method; +	char *path; +	void *data; +	request_callback *callback; +}; + +struct header_item { +	char *key; +	char *value; +	struct header_item *next; +}; + +bool parse_query(char *path, char **query) { +	char *sep = strchr(path, '?'); +	if (!sep) { +		*query = NULL; +		return false; +	} +	 +	*sep = '\0'; +	*query = strdup(sep + 1); + +	return true; +} + +void add_header(struct header_list *list, char *header, char *value) { +	size_t index = hash(header, MAX_HEADERS); +	struct header_item to_insert = {0}; +	struct header_item *item = NULL; +	to_insert.key = strdup(header); +	to_insert.value = strdup(value); + +	if (!list->items[index]) { +		list->items[index] = malloc(sizeof(struct header_item)); +		*(list->items[index]) = to_insert; +	} else { +		item = list->items[index]; +		while (item->next != NULL) { +			item = item->next; +		} +		item->next = malloc(sizeof(struct header_item)); +		*(item->next) = to_insert; +	} +} + +const char *http_get_header(struct request *req, const char *key) { +	size_t index = hash(key, MAX_HEADERS); + +	struct header_item *item = req->header.items[index]; + +	while (item != NULL) { +		if (strcmp(item->key, key) == 0) { +			return item->value; +		} +		item = item->next; +	} + +	return NULL; +} + +enum parse_result { +	PARSE_OK, +	PARSE_ERR_UNSUPPORTED_HTTP_VERSION, +	PARSE_MALFORMED_INPUT +}; + +static enum parse_result parse_request(struct request *req, const char *request) { +	assert(req); +	assert(request); + +	char *copy = strdup(request); +	char *saveptr, *line; +	char *path, *query; +	char *header; + +	char *data = strstr(copy, "\r\n\r\n"); +	if (data) { +		*data = '\0'; +		data += 4; +	} + +	line = strtok_r(copy, "\r\n", &saveptr); + +	path = malloc(strlen(line) + 1); +	req->method = malloc(strlen(line) + 1); + +	if (sscanf(line, "%s %s HTTP/%s", req->method, path, req->http_version) != 3) { +		free(copy); +		free(path); +		free(req->method); +		return PARSE_MALFORMED_INPUT; +	} + +	if (parse_query(path, &query)) { +		req->path = path; +		req->query = query; +	} else { +		req->path = path; +		req->query = strdup(""); +	} + +	for (line = strtok_r(NULL, "\r\n", &saveptr); +			line != NULL && strlen(line) > 0; +			line = strtok_r(NULL, "\r\n", &saveptr)) { + +		header = strchr(line, ':'); +		if (!header) +			continue; +		*(header++) = '\0'; +		while (*header == ' ') +			header++; +		add_header(&req->header, line, header); +	} + +	if (data) +		req->data = strdup(data); + +	return PARSE_OK; +} + +struct buffer *http_build_reply(struct reply *reply) { +	assert(reply); + +	struct buffer *buf = buf_new(NULL); + +	buf_printf(buf, +			"HTTP/1.1 %d %s\r\n" +			"Content-Length: %ld\r\n" +			"Content-Type: %s\r\n" +			"\r\n" +			"%s", +			reply->status, "OK", strlen(reply->body), "application/json", reply->body); + +	return buf; +} + +struct reply *http_handle_request(struct http *http, struct buffer *req) { +	struct request data = {0}; +	struct reply *ret = NULL; + +	if (parse_request(&data, req->data) != PARSE_OK) { +		// todo: log +		return NULL; +	} + +	for (size_t i = 0; i < http->n_requests; i++) { +		if (strcmp(data.method, http->requests[i].method) == 0 && +			strcmp(data.path, http->requests[i].path) == 0) { +			ret = http->requests[i].callback(&data, http->requests[i].data); +		} +	} + +	if (!ret) { +		ret = malloc(sizeof(struct reply)); +		ret->status = 404; +		ret->body = strdup(""); +	} + +	return ret; +} + +struct http *http_init(void) { +	struct http *http = malloc(sizeof(struct http)); +	http->requests = malloc(sizeof(struct request_handler) * 5); +	http->n_requests = 0; +	http->req_siz = 5; +	return http; +} + +bool http_register_handler(struct http *http, char *method, char *path, +		void *data, request_callback *callback) { +	if (http->n_requests + 1 >= http->req_siz) { +		http->req_siz += 10; +		http->requests = realloc(http->requests, +				sizeof(struct request_handler) * http->req_siz); +		memset(http->requests, 0, +				sizeof(struct request_handler) * (http->req_siz - http->n_requests)); +	} +	struct request_handler *handle = &http->requests[http->n_requests++]; + +	handle->method = strdup(method); +	handle->path = strdup(path); +	handle->data = data; +	handle->callback = callback; + +	return true; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..09cf045 --- /dev/null +++ b/src/main.c @@ -0,0 +1,116 @@ +#include <stdio.h> +#include <cjson/cJSON.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> + +#include "buffer.h" +#include "http.h" + +static struct reply *handle(struct request *req, void *data) { +	(void)data; +	struct reply *ret = malloc(sizeof(struct reply)); +	cJSON *obj = cJSON_CreateObject(); +	cJSON_AddStringToObject(obj, "path", req->path); +	cJSON_AddStringToObject(obj, "query", req->query); +	cJSON_AddStringToObject(obj, "method", req->method); +	cJSON_AddStringToObject(obj, "version", req->http_version); +	cJSON_AddStringToObject(obj, "hostname", http_get_header(req, "Host")); +	if (req->data) { +		cJSON_AddItemReferenceToObject(obj, "data", cJSON_Parse(req->data)); +	} + +	ret->status = 200; +	ret->body = cJSON_Print(obj); +	cJSON_Delete(obj); + +	return ret; +} + +int main(int argc, char **argv) { +	(void)argc; +	(void)argv; +	int server_fd = 0; +	int client_fd = 0; +	struct sockaddr_in addr = { +		.sin_family = AF_INET, +		.sin_addr.s_addr = INADDR_ANY, +		.sin_port = htons(45748), +	}; + +	int addrlen = sizeof(addr); + +	if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { +		perror("socket failed"); +		return EXIT_FAILURE; +	} + +	int opt = 1; + +	if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { +		perror("setsockopt failed"); +		return EXIT_FAILURE; +	} + +	if (bind(server_fd, (struct sockaddr*)&addr, addrlen) < 0) { +		perror("bind failed"); +		return EXIT_FAILURE; +	} + +	if (listen(server_fd, 16) < 0) { +		perror("listen failed"); +		return EXIT_FAILURE; +	} + +	bool running = true; +	ssize_t rlen; +	char buf[1025]; +	struct buffer *request, *reply; +	struct reply *rpy; + +	struct http *http = http_init(); + +	http_register_handler(http, "GET", "/", NULL, &handle); +	http_register_handler(http, "GET", "/nyaa", NULL, &handle); +	http_register_handler(http, "POST", "/nyaa", NULL, &handle); + +	while (running) { +		if ((client_fd = accept(server_fd, +						(struct sockaddr*)&addr, (socklen_t*)&addrlen)) < 0) { +			perror("accept failed"); +			return EXIT_FAILURE; +		} +		 +		if ((rlen = read(client_fd, buf, 1024)) < 0) { +			perror("read failed"); +			return EXIT_FAILURE; +		} + +		buf[rlen] = '\0'; +		request = buf_new(buf); + +		while (rlen >= 1024) { +			if ((rlen = read(client_fd, buf, 1024)) < 0) { +				perror("read failed"); +				return EXIT_FAILURE; +			} +			buf[rlen] = '\0'; +			buf_append(request, buf); +		} + +		printf("%s\n", request->data); + +		rpy = http_handle_request(http, request); +		reply = http_build_reply(rpy); +		free(rpy); + +		write(client_fd, reply->data, reply->size); + +		buf_del(&request); + +		close(client_fd); +	} +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..1590aef --- /dev/null +++ b/src/meson.build @@ -0,0 +1,3 @@ +executable('main', ['main.c', 'http.c', 'util.c', 'buffer.c'], +  dependencies : jsondep, +  include_directories: incdir) diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..e6b7977 --- /dev/null +++ b/src/util.c @@ -0,0 +1,57 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "util.h" + +int vasprintf(char **dest, const char *fmt, va_list ap) { +	assert(*dest == NULL); +	char *buf = NULL; size_t len, size; +	va_list ap2; + +	size = 4096; +	buf = malloc(size); + +	va_copy(ap2, ap); +	len = vsnprintf(buf, size, fmt, ap); +	va_end(ap); + +	if (len >= size) { +		size = len + 1; +		buf = realloc(buf, size); +		va_copy(ap2, ap); +		len = vsnprintf(buf, size, fmt, ap); +		va_end(ap2); +	} +	if (len < 0 || len >= size) { +		fprintf(stderr, "asprintf: failed to format buffer"); +		free(buf); +		exit(EXIT_FAILURE); +	} + +	*dest = buf; +	return len; +} + +int asprintf(char **dest, const char *fmt, ...) { +	va_list ap; +	int ret; +	va_start(ap, fmt); +	ret = vasprintf(dest, fmt, ap); +	va_end(ap); +	return ret; +} + +size_t hash(const char *str, int max_val) { +	size_t ret = 0; + +	for (const char *p = str; *p != '\0'; p++) { +		ret += (size_t)*p + ((size_t)*p << 6) + ((size_t)*p << 16); +	} + +	ret = ret % max_val; + +	return ret; +} | 
