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);