serve

serve.git
git clone git://git.lenczewski.org/serve.git
Log | Files | Refs

connection.c (12294B)


      1 #include "website.h"
      2 
      3 extern inline enum http_method
      4 method_from_str(char *method);
      5 
      6 extern inline char const *
      7 method_str(enum http_method method);
      8 
      9 extern inline enum http_version
     10 version_from_str(char *veresion);
     11 
     12 extern inline char const *
     13 version_str(enum http_version version);
     14 
     15 extern inline char const *
     16 status_str(enum http_status status);
     17 
     18 extern inline char const *
     19 content_encoding_str(enum content_encoding encoding);
     20 
     21 extern inline enum content_encoding
     22 content_encoding_from_list(char *list);
     23 
     24 extern inline char const *
     25 content_type_str(enum content_type type);
     26 
     27 extern inline bool
     28 content_type_is_text(enum content_type type);
     29 
     30 extern inline enum content_type
     31 content_type_from_mime(char *mime);
     32 
     33 extern inline enum content_type
     34 content_type_from_ext(char *ext, size_t len);
     35 
     36 void
     37 parse_http_request(char *src, struct http_request *request, struct http_headers *headers)
     38 {
     39 	char *saveptr;
     40 	request->method = method_from_str(strtok_r(src, " ", &saveptr));
     41 	char *rawuri = strtok_r(NULL, " ", &saveptr);
     42 	request->uri = uri_parse(rawuri, strlen(rawuri));
     43 	request->version = version_from_str(strtok_r(NULL, "\r\n", &saveptr));
     44 
     45 	memset(headers, 0, sizeof *headers);
     46 
     47 	headers->keep_alive = request->version > HTTP_09;
     48 
     49 	char *header;
     50 	while ((header = strtok_r(NULL, "\r\n", &saveptr))) {
     51 		char *val;
     52 		char *key = strtok_r(header, ": ", &val);
     53 
     54 		while (isspace(*val)) /* strip leading whitespace */
     55 			val++;
     56 
     57 		for (char *ptr = key; *ptr; ptr++) /* normalise as lowercase */
     58 			*ptr = isalpha(*ptr) ? tolower(*ptr) : *ptr;
     59 
     60 		if (strcmp(key, "connection") == 0) {
     61 			for (char *ptr = val; *ptr; ptr++)
     62 				*ptr = isalpha(*ptr) ? tolower(*ptr) : *ptr;
     63 			headers->keep_alive = !(strcmp(val, "close") == 0);
     64 		} else if (strcmp(key, "accept") == 0) {
     65 			headers->accept = val;
     66 		} else if (strcmp(key, "accept-encoding") == 0) {
     67 			headers->encoding = content_encoding_from_list(val);
     68 		} else if (strcmp(key, "range") == 0) {
     69 			// TODO: implement me!
     70 		} else if (strcmp(key, "content-type") == 0) {
     71 			headers->content_type = content_type_from_mime(val);
     72 		} else if (strcmp(key, "content-length") == 0) {
     73 			headers->content_length = strtoull(val, NULL, 0);
     74 		}
     75 	}
     76 }
     77 
     78 void
     79 handle_accept(struct io_uring *ioring, struct connection *conn, int sock)
     80 {
     81 	conn->socket = sock;
     82 	conn->cap = sizeof conn->buf;
     83 	conn->len = conn->cur = 0;
     84 
     85 	struct timespec now;
     86 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
     87 	conn->timeout = get_timeout(&now, CONNECTION_TIMEOUT_MS);
     88 
     89 	enqueue_recv(ioring, &conn->ioreq, conn->socket, conn->buf, conn->cap, 0);
     90 }
     91 
     92 static bool
     93 have_request_headers(struct connection *conn)
     94 {
     95 	char *last_parse_end = conn->buf + conn->cur;
     96 	char *hdrs = MAX(last_parse_end - (strlen(HTTP_TERM) - 1), conn->buf);
     97 	char *hdrs_end = strnstr(hdrs, conn->len - conn->cur, HTTP_TERM);
     98 
     99 	if (hdrs_end)
    100 		*hdrs_end = '\0';
    101 
    102 	return hdrs_end != NULL;
    103 }
    104 
    105 void
    106 handle_request_chunk(struct io_uring *ioring, struct connection *conn, int webroot, int res)
    107 {
    108 	struct timespec now;
    109 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
    110 //	if (get_timestamp(&conn->timeout) < get_timestamp(&now))
    111 //		goto error_timeout;
    112 
    113 	conn->len += res;
    114 
    115 	int have_headers = have_request_headers(conn);
    116 	conn->cur += res;
    117 
    118 	if (!have_headers && conn->len == conn->cap) /* cannot read more data */
    119 		goto error_payload_too_large;
    120 
    121 	if (!have_headers) { /* can read more data */
    122 		enqueue_recv(ioring, &conn->ioreq, conn->socket,
    123 			     conn->buf + conn->len, conn->cap - conn->len, 0);
    124 		return;
    125 	}
    126 
    127 	/* have request header, parse it */
    128 	handle_request(ioring, conn, webroot);
    129 	return;
    130 
    131 error_timeout:
    132 	error_connection(ioring, conn, HTTP_408, false);
    133 	return;
    134 
    135 error_payload_too_large:
    136 	error_connection(ioring, conn, HTTP_413, true);
    137 	return;
    138 }
    139 
    140 static bool
    141 sanitise_uri_to_path(struct uri *uri, char buf[static PATH_MAX])
    142 {
    143 	if (uri->path.len == 0)
    144 		return false;
    145 
    146 	if (!urifrag_try_normalise(&uri->path))
    147 		return false;
    148 
    149 	urifrag_remove_dot_segments(&uri->path);
    150 
    151 	if (PATH_MAX <= uri->path.len)
    152 		return false;
    153 
    154 	if (*(uri->path.ptr) == '/') {
    155 		uri->path.ptr++;
    156 		uri->path.len--;
    157 	}
    158 
    159 	char *end = stpncpy(buf, uri->path.ptr, uri->path.len);
    160 	*end = '\0';
    161 
    162 	size_t written = end - buf;
    163 	if (!written || end[-1] == '/') {
    164 		if (PATH_MAX < written + strlen("index.html"))
    165 			return false;
    166 
    167 		*stpncpy(end, "index.html", PATH_MAX - written - strlen("index.html")) = '\0';
    168 	}
    169 
    170 	return true;
    171 }
    172 
    173 static void
    174 prepare_response_headers(struct connection *conn, enum http_status status,
    175 			 struct http_headers *headers)
    176 {
    177 	conn->len = conn->cur = 0;
    178 
    179 	conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    180 			      "HTTP/1.1 %d %s" HTTP_SEP, status, status_str(status));
    181 
    182 	conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    183 			      "Connection: %s" HTTP_SEP, headers->keep_alive ? "keep-alive" : "close");
    184 
    185 	conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    186 			      "Content-Length: %" PRIu64 HTTP_SEP, headers->content_length);
    187 
    188 	if (headers->have_range) {
    189 		conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    190 				      "Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 HTTP_SEP,
    191 				      headers->range.begin, headers->range.end, headers->content_length);
    192 	}
    193 
    194 	if (headers->content_length) {
    195 		conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    196 				      "Content-Type: %s", content_type_str(headers->content_type));
    197 
    198 		if (content_type_is_text(headers->content_type)) {
    199 			conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    200 					      "; charset=utf-8");
    201 		}
    202 
    203 		conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len, HTTP_SEP);
    204 
    205 		if (headers->encoding) {
    206 			conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len,
    207 					      "Content-Encoding: %s" HTTP_SEP,
    208 					      content_encoding_str(headers->encoding));
    209 		}
    210 	}
    211 
    212 	conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len, HTTP_SEP);
    213 
    214 	assert(conn->len < conn->cap);
    215 
    216 	if (opts.verbose)
    217 		printf("response headers: %zu bytes\n---\n%.*s",
    218 				conn->len, (int) conn->len, conn->buf);
    219 }
    220 
    221 void
    222 handle_request(struct io_uring *ioring, struct connection *conn, int webroot)
    223 {
    224 	parse_http_request(conn->buf, &conn->request, &conn->request_headers);
    225 
    226 	if (opts.verbose) {
    227 	printf("\trequest: %s, %s\n", method_str(conn->request.method), version_str(conn->request.version));
    228 	printf("\turi: path: '%.*s'\n", conn->request.uri.path.len, conn->request.uri.path.ptr);
    229 	printf("\tkeep-alive: %d\n", conn->request_headers.keep_alive);
    230 	printf("\taccept: %s\n", conn->request_headers.accept);
    231 	printf("\taccept-encoding: %d\n", conn->request_headers.encoding);
    232 	printf("\trange: %" PRIu64 "-%" PRIu64 "\n", conn->request_headers.range.begin, conn->request_headers.range.end);
    233 	printf("\tcontent-type: %d\n", conn->request_headers.content_type);
    234 	printf("\tcontent-length: %" PRIu64 "\n", conn->request_headers.content_length);
    235 	}
    236 
    237 	if (conn->request.method != HTTP_HEAD && conn->request.method != HTTP_GET)
    238 		goto error_not_implemented;
    239 
    240 	if (conn->request.version != HTTP_11)
    241 		goto error_http_version_not_supported;
    242 
    243 	if (conn->request_headers.content_length) /* we do not support request bodies */
    244 		goto error_unprocessable_request;
    245 
    246 	char path[PATH_MAX];
    247 	if (!sanitise_uri_to_path(&conn->request.uri, path))
    248 		goto error_bad_request;
    249 
    250 	if (opts.verbose) {
    251 		printf("\tsanitised uri: '%s'\n", path);
    252 	}
    253 
    254 	// TODO: read from file cache instead of reading directly?
    255 	conn->response.fd = openat(webroot, path, O_RDONLY | O_CLOEXEC);
    256 	if (conn->response.fd < 0) {
    257 		if (errno == EACCES)
    258 			goto error_forbidden;
    259 
    260 		if (errno == ENOENT)
    261 			goto error_not_found;
    262 
    263 		goto error_internal_server_error;
    264 	}
    265 
    266 	struct stat statbuf;
    267 	if (fstat(conn->response.fd, &statbuf) < 0) {
    268 		close(conn->response.fd);
    269 		goto error_internal_server_error;
    270 	}
    271 
    272 	if (!S_ISREG(statbuf.st_mode)) {
    273 		close(conn->response.fd);
    274 		goto error_not_found;
    275 	}
    276 
    277 	conn->response.len = statbuf.st_size;
    278 	conn->response.off = 0;
    279 
    280 	if (conn->request_headers.have_range) {
    281 		if (conn->request_headers.range.begin < conn->request_headers.range.end)
    282 			goto error_bad_request;
    283 
    284 		if (conn->response.len < conn->request_headers.range.begin ||
    285 		    conn->response.len < conn->request_headers.range.end)
    286 			goto error_range_not_satisfiable;
    287 
    288 		conn->response.len = conn->request_headers.range.end
    289 				   - conn->request_headers.range.begin;
    290 		conn->response.off = conn->request_headers.range.begin;
    291 	}
    292 
    293 	char *ext = strrchr(path, '.');
    294 	conn->response_headers = (struct http_headers) {
    295 		.keep_alive = conn->request_headers.keep_alive,
    296 		.encoding = ENCODING_NONE,
    297 		.have_range = conn->request_headers.have_range,
    298 		.range = conn->request_headers.range,
    299 		.content_type = content_type_from_ext(ext, strlen(ext)),
    300 		.content_length = conn->response.len,
    301 	};
    302 
    303 	struct timespec now;
    304 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
    305 	conn->timeout = get_timeout(&now, CONNECTION_TIMEOUT_MS);
    306 
    307 	enum http_status status = conn->response_headers.have_range ? HTTP_206 : HTTP_200;
    308 	prepare_response_headers(conn, status, &conn->response_headers);
    309 
    310 	if (conn->request.method == HTTP_HEAD) {
    311 		close(conn->response.fd); /* no body to send, dont delay close */
    312 		conn->response.fd = -1;
    313 
    314 		enqueue_send(ioring, &conn->ioreq, conn->socket, conn->buf, conn->len, 0);
    315 	} else if (conn->request.method == HTTP_GET) { /* prepare body */
    316 		size_t len = MIN(conn->response.len, conn->cap - conn->len);
    317 		enqueue_read(ioring, &conn->ioreq, conn->response.fd,
    318 			     conn->buf + conn->len, len, conn->response.off);
    319 	}
    320 
    321 	return;
    322 
    323 error_bad_request:
    324 	error_connection(ioring, conn, HTTP_400, true);
    325 	return;
    326 
    327 error_forbidden:
    328 	error_connection(ioring, conn, HTTP_403, true);
    329 	return;
    330 
    331 error_not_found:
    332 	error_connection(ioring, conn, HTTP_404, true);
    333 	return;
    334 
    335 error_range_not_satisfiable:
    336 	error_connection(ioring, conn, HTTP_416, true);
    337 	return;
    338 
    339 error_unprocessable_request:
    340 	error_connection(ioring, conn, HTTP_422, true);
    341 	return;
    342 
    343 error_internal_server_error:
    344 	error_connection(ioring, conn, HTTP_500, true);
    345 	return;
    346 
    347 error_not_implemented:
    348 	error_connection(ioring, conn, HTTP_501, true);
    349 	return;
    350 
    351 error_http_version_not_supported:
    352 	error_connection(ioring, conn, HTTP_505, true);
    353 	return;
    354 }
    355 
    356 void
    357 handle_response_chunk(struct io_uring *ioring, struct connection *conn, int res)
    358 {
    359 	struct timespec now;
    360 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
    361 //	if (get_timestamp(&conn->timeout) < get_timestamp(&now))
    362 //		goto error_timeout;
    363 
    364 	conn->cur += res;
    365 
    366 	if (conn->cur < conn->len) { /* have remaining buffered data to send */
    367 		enqueue_send(ioring, &conn->ioreq, conn->socket,
    368 			     conn->buf + conn->cur, conn->len - conn->cur, 0);
    369 		return;
    370 	}
    371 
    372 	if (conn->response.len) { /* have remaining data to read into buffer */
    373 		conn->len = conn->cur = 0;
    374 
    375 		size_t len = MIN(conn->response.len, conn->cap);
    376 		enqueue_read(ioring, &conn->ioreq, conn->response.fd,
    377 			     conn->buf, len, conn->response.off);
    378 		return;
    379 	} else if (conn->response.fd > 0) { /* no more data to read, close file */
    380 		close(conn->response.fd);
    381 		conn->response.fd = -1;
    382 	}
    383 
    384 	if (conn->response_headers.keep_alive) {
    385 		conn->len = conn->cur = 0;
    386 		enqueue_recv(ioring, &conn->ioreq, conn->socket,
    387 			     conn->buf, conn->cap, 0);
    388 		return;
    389 	}
    390 
    391 error_timeout:
    392 	close_connection(ioring, conn);
    393 	return;
    394 }
    395 
    396 void
    397 handle_fileread_chunk(struct io_uring *ioring, struct connection *conn, int res)
    398 {
    399 	conn->response.off += res;
    400 	conn->response.len -= res;
    401 	conn->len += res;
    402 
    403 	enqueue_send(ioring, &conn->ioreq, conn->socket, conn->buf, conn->len, 0);
    404 }
    405 
    406 void
    407 error_connection(struct io_uring *ioring, struct connection *conn,
    408 		 enum http_status err, bool linger)
    409 {
    410 	struct http_headers error_headers = {
    411 		.keep_alive = linger,
    412 		.content_length = 0,
    413 	};
    414 
    415 	prepare_response_headers(conn, err, &error_headers);
    416 	enqueue_send(ioring, &conn->ioreq, conn->socket, conn->buf, conn->len, 0);
    417 }
    418 
    419 void
    420 close_connection(struct io_uring *ioring, struct connection *conn)
    421 {
    422 	enqueue_close(ioring, &conn->ioreq, conn->socket);
    423 }
    424 
    425 extern inline int
    426 connection_pool_init(struct connection_pool *pool, size_t capacity);
    427 
    428 extern inline struct connection *
    429 connection_pool_alloc(struct connection_pool *pool);
    430 
    431 extern inline void
    432 connection_pool_free(struct connection_pool *restrict pool,
    433 		     struct connection *restrict conn);