| 
									
										
										
										
											2023-12-13 23:59:11 +00:00
										 |  |  | #include "http.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "log.h"
 | 
					
						
							|  |  |  | #include "mem.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "picohttpparser.h"
 | 
					
						
							|  |  |  | #include "uv.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-17 17:44:54 +00:00
										 |  |  | #include <assert.h>
 | 
					
						
							| 
									
										
										
										
											2023-12-13 23:59:11 +00:00
										 |  |  | #include <string.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
 | 
					
						
							|  |  |  | #include <alloca.h>
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct _tf_http_connection_t | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_http_t* http; | 
					
						
							|  |  |  | 	uv_tcp_t tcp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	char buffer[8192]; | 
					
						
							|  |  |  | 	size_t buffer_length; | 
					
						
							|  |  |  | 	int parsed_length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tf_http_callback_t* callback; | 
					
						
							|  |  |  | 	void* user_data; | 
					
						
							|  |  |  | } tf_http_connection_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct _tf_http_handler_t | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const char* pattern; | 
					
						
							|  |  |  | 	tf_http_callback_t* callback; | 
					
						
							|  |  |  | 	void* user_data; | 
					
						
							|  |  |  | } tf_http_handler_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct _tf_http_t | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	uv_tcp_t** listeners; | 
					
						
							|  |  |  | 	int listeners_count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tf_http_connection_t** connections; | 
					
						
							|  |  |  | 	int connections_count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tf_http_handler_t* handlers; | 
					
						
							|  |  |  | 	int handlers_count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	uv_loop_t* loop; | 
					
						
							|  |  |  | } tf_http_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | tf_http_t* tf_http_create(uv_loop_t* loop) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_http_t* http = tf_malloc(sizeof(tf_http_t)); | 
					
						
							|  |  |  | 	*http = (tf_http_t) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		.loop = loop, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	return http; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_http_connection_t* connection = handle->data; | 
					
						
							|  |  |  | 	*buf = uv_buf_init(connection->buffer + connection->buffer_length, sizeof(connection->buffer) - connection->buffer_length); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, void** out_user_data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	for (int i = 0; i < http->handlers_count; i++) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		if (!http->handlers[i].pattern) | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			*out_callback = http->handlers[i].callback; | 
					
						
							|  |  |  | 			*out_user_data = http->handlers[i].user_data; | 
					
						
							|  |  |  | 			return true; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 01:59:23 +00:00
										 |  |  | static void _http_on_write(uv_write_t* write, int status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_free(write); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 23:59:11 +00:00
										 |  |  | void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_http_connection_t* connection = stream->data; | 
					
						
							|  |  |  | 	if (read_size > 0) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		connection->buffer_length += read_size; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const char* method = NULL; | 
					
						
							|  |  |  | 		size_t method_length = 0; | 
					
						
							|  |  |  | 		const char* path = NULL; | 
					
						
							|  |  |  | 		size_t path_length = 0; | 
					
						
							|  |  |  | 		int minor_version = 0; | 
					
						
							|  |  |  | 		struct phr_header headers[100]; | 
					
						
							|  |  |  | 		size_t header_count = sizeof(headers) / sizeof(*headers); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		int parse_result = phr_parse_request(connection->buffer, connection->buffer_length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, connection->parsed_length); | 
					
						
							|  |  |  | 		connection->parsed_length = connection->buffer_length; | 
					
						
							|  |  |  | 		if (parse_result > 0) | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			if (_http_find_handler(connection->http, path, &connection->callback, &connection->user_data) && connection->callback) | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				char* method_copy = alloca(method_length + 1); | 
					
						
							|  |  |  | 				memcpy(method_copy, method, method_length); | 
					
						
							|  |  |  | 				method_copy[method_length] = '\0'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				char* path_copy = alloca(path_length + 1); | 
					
						
							|  |  |  | 				memcpy(path_copy, path, path_length); | 
					
						
							|  |  |  | 				path_copy[path_length] = '\0'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				tf_http_request_t request = | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					.connection = connection, | 
					
						
							|  |  |  | 					.phase = k_http_callback_phase_headers_received, | 
					
						
							|  |  |  | 					.method = method_copy, | 
					
						
							|  |  |  | 					.path = path_copy, | 
					
						
							|  |  |  | 					.headers = headers, | 
					
						
							|  |  |  | 					.headers_count = (int)header_count, | 
					
						
							|  |  |  | 					.user_data = connection->user_data, | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 				connection->callback(&request); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			tf_printf("phr_parse_request: %d\n", parse_result); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void _http_on_connection(uv_stream_t* stream, int status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_http_t* http = stream->data; | 
					
						
							|  |  |  | 	tf_http_connection_t* connection = tf_malloc(sizeof(tf_http_connection_t)); | 
					
						
							|  |  |  | 	*connection = (tf_http_connection_t) { .http = http, .tcp = { .data = connection } }; | 
					
						
							|  |  |  | 	int r = uv_tcp_init(connection->http->loop, &connection->tcp); | 
					
						
							|  |  |  | 	if (r) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		tf_printf("uv_tcp_init: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 		tf_free(connection); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r = uv_accept(stream, (uv_stream_t*)&connection->tcp); | 
					
						
							|  |  |  | 	if (r) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		tf_printf("uv_accept: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 		uv_close((uv_handle_t*)&connection->tcp, NULL); | 
					
						
							|  |  |  | 		tf_free(connection); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r = uv_read_start((uv_stream_t*)&connection->tcp, _http_allocate_buffer, _http_on_read); | 
					
						
							|  |  |  | 	if (r) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		tf_printf("uv_read-start: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 		uv_close((uv_handle_t*)&connection->tcp, NULL); | 
					
						
							|  |  |  | 		tf_free(connection); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	http->connections = tf_realloc(http->connections, http->connections_count + 1); | 
					
						
							|  |  |  | 	http->connections[http->connections_count++] = connection; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void tf_http_listen(tf_http_t* http, int port) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	uv_tcp_t* tcp = tf_malloc(sizeof(uv_tcp_t)); | 
					
						
							|  |  |  | 	*tcp = (uv_tcp_t) { .data = http }; | 
					
						
							|  |  |  | 	int r = uv_tcp_init(http->loop, tcp); | 
					
						
							|  |  |  | 	if (r) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		tf_printf("uv_tcp_init: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (r == 0) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct sockaddr_in addr = | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			.sin_family = AF_INET, | 
					
						
							|  |  |  | 			.sin_addr = { .s_addr = INADDR_ANY }, | 
					
						
							|  |  |  | 			.sin_port = ntohs(port), | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		r = uv_tcp_bind(tcp, (struct sockaddr*)&addr, 0); | 
					
						
							|  |  |  | 		if (r) | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			tf_printf("uv_tcp_bind: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (r == 0) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		r = uv_listen((uv_stream_t*)tcp, 16, _http_on_connection); | 
					
						
							|  |  |  | 		if (r) | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			tf_printf("uv_listen: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (r == 0) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		http->listeners = tf_realloc(http->listeners, sizeof(uv_tcp_t*) * (http->listeners_count + 1)); | 
					
						
							|  |  |  | 		http->listeners[http->listeners_count++] = tcp; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); | 
					
						
							|  |  |  | 	http->handlers[http->handlers_count++] = (tf_http_handler_t) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		.pattern = tf_strdup(pattern), | 
					
						
							|  |  |  | 		.callback = callback, | 
					
						
							|  |  |  | 		.user_data = user_data, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void tf_http_destroy(tf_http_t* http) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_free(http); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-12-17 17:44:54 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | static const char* _http_status_text(int status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (status) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 	case 200: return "OK"; | 
					
						
							|  |  |  | 	case 404: return "File not found"; | 
					
						
							|  |  |  | 	default: return "Unknown"; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void _http_on_shutdown(uv_shutdown_t* request, int status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	tf_free(request); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, void* body, size_t content_length) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const char* status_text = _http_status_text(status); | 
					
						
							|  |  |  | 	/* HTTP/1.x 200 OK\r\n */ | 
					
						
							|  |  |  | 	int headers_length = 8 + 1 + 3 + 1 + strlen(status_text) + 2; | 
					
						
							|  |  |  | 	for (int i = 0; i < headers_count; i += 2) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		/* Key: Value\r\n */ | 
					
						
							|  |  |  | 		headers_length += strlen(headers[i]) + 2 + strlen(headers[i + 1]) + 2; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	/* \r\n */ | 
					
						
							|  |  |  | 	headers_length += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	uv_write_t* write = tf_malloc(sizeof(uv_write_t) + headers_length + content_length + 1); | 
					
						
							|  |  |  | 	*write = (uv_write_t) { .data = request->connection }; | 
					
						
							|  |  |  | 	char* buffer = (char*)(write + 1); | 
					
						
							|  |  |  | 	int offset = snprintf(buffer, headers_length + 1, "HTTP/1.0 %03d %s\r\n", status, status_text); | 
					
						
							|  |  |  | 	for (int i = 0; i < headers_count; i += 2) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		offset += snprintf(buffer + offset, headers_length + 1 - offset, "%s: %s\r\n", headers[i], headers[i + 1]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	offset += snprintf(buffer + offset, headers_length + 1 - offset, "\r\n"); | 
					
						
							|  |  |  | 	assert(offset == headers_length); | 
					
						
							|  |  |  | 	if (content_length) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		memcpy(buffer + offset, body, content_length); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	int r = uv_write(write, (uv_stream_t*)&request->connection->tcp, &(uv_buf_t) { .base = buffer, .len = headers_length + content_length }, 1, _http_on_write); | 
					
						
							|  |  |  | 	if (r) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		tf_printf("uv_write: %s\n", uv_strerror(r)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	uv_shutdown_t* shutdown_request = tf_malloc(sizeof(uv_shutdown_t)); | 
					
						
							|  |  |  | 	*shutdown_request = (uv_shutdown_t) { .data = request }; | 
					
						
							|  |  |  | 	uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown); | 
					
						
							|  |  |  | } |