/* Copyright: Boaz Segev, 2017-2019 License: MIT */ #include #include #include #include #include #include #include #include /* ***************************************************************************** The HTTP/1.1 Protocol Object ***************************************************************************** */ typedef struct http1pr_s { http_fio_protocol_s p; http1_parser_s parser; http_s request; uintptr_t buf_len; uintptr_t max_header_size; uintptr_t header_size; uint8_t close; uint8_t is_client; uint8_t stop; uint8_t buf[]; } http1pr_s; struct http_vtable_s HTTP1_VTABLE; /* initialized later on */ /* ***************************************************************************** Internal Helpers ***************************************************************************** */ #define parser2http(x) \ ((http1pr_s *)((uintptr_t)(x) - (uintptr_t)(&((http1pr_s *)0)->parser))) inline static void h1_reset(http1pr_s *p) { p->header_size = 0; } #define http1_pr2handle(pr) (((http1pr_s *)(pr))->request) #define handle2pr(h) ((http1pr_s *)h->private_data.flag) static fio_str_info_s http1pr_status2str(uintptr_t status); /* cleanup an HTTP/1.1 handler object */ static inline void http1_after_finish(http_s *h) { http1pr_s *p = handle2pr(h); p->stop = p->stop & (~1UL); if (h != &p->request) { http_s_destroy(h, 0); fio_free(h); } else { http_s_clear(h, p->p.settings->log); } if (p->close) fio_close(p->p.uuid); } /* ***************************************************************************** HTTP Request / Response (Virtual) Functions ***************************************************************************** */ struct header_writer_s { FIOBJ dest; FIOBJ name; FIOBJ value; }; static int write_header(FIOBJ o, void *w_) { struct header_writer_s *w = w_; if (!o) return 0; if (fiobj_hash_key_in_loop()) { w->name = fiobj_hash_key_in_loop(); } if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) { fiobj_each1(o, 0, write_header, w); return 0; } fio_str_info_s name = fiobj_obj2cstr(w->name); fio_str_info_s str = fiobj_obj2cstr(o); if (!str.data) return 0; // fiobj_str_capa_assert(w->dest, // fiobj_obj2cstr(w->dest).len + name.len + str.len + // 5); fiobj_str_write(w->dest, name.data, name.len); fiobj_str_write(w->dest, ":", 1); fiobj_str_write(w->dest, str.data, str.len); fiobj_str_write(w->dest, "\r\n", 2); return 0; } static FIOBJ headers2str(http_s *h, uintptr_t padding) { if (!h->method && !!h->status_str) return FIOBJ_INVALID; static uintptr_t connection_hash; if (!connection_hash) connection_hash = fiobj_hash_string("connection", 10); struct header_writer_s w; { const uintptr_t header_length_guess = fiobj_hash_count(h->private_data.out_headers) * 64; w.dest = fiobj_str_buf(header_length_guess + padding); } http1pr_s *p = handle2pr(h); if (p->is_client == 0) { fio_str_info_s t = http1pr_status2str(h->status); fiobj_str_write(w.dest, t.data, t.len); FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, connection_hash); if (tmp) { t = fiobj_obj2cstr(tmp); if (t.data[0] == 'c' || t.data[0] == 'C') p->close = 1; } else { tmp = fiobj_hash_get2(h->headers, connection_hash); if (tmp) { t = fiobj_obj2cstr(tmp); if (!t.data || !t.len || t.data[0] == 'k' || t.data[0] == 'K') fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); else { fiobj_str_write(w.dest, "connection:close\r\n", 18); p->close = 1; } } else { t = fiobj_obj2cstr(h->version); if (!p->close && t.len > 7 && t.data && t.data[5] == '1' && t.data[6] == '.' && t.data[7] == '1') fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); else { fiobj_str_write(w.dest, "connection:close\r\n", 18); p->close = 1; } } } } else { if (h->method) { fiobj_str_join(w.dest, h->method); fiobj_str_write(w.dest, " ", 1); } else { fiobj_str_write(w.dest, "GET ", 4); } fiobj_str_join(w.dest, h->path); if (h->query) { fiobj_str_write(w.dest, "?", 1); fiobj_str_join(w.dest, h->query); } fiobj_str_write(w.dest, " HTTP/1.1\r\n", 11); /* make sure we have a host header? */ static uint64_t host_hash; if (!host_hash) host_hash = fiobj_hash_string("host", 4); FIOBJ tmp; if (!fiobj_hash_get2(h->private_data.out_headers, host_hash) && (tmp = fiobj_hash_get2(h->headers, host_hash))) { fiobj_str_write(w.dest, "host:", 5); fiobj_str_join(w.dest, tmp); fiobj_str_write(w.dest, "\r\n", 2); } if (!fiobj_hash_get2(h->private_data.out_headers, connection_hash)) fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23); } fiobj_each1(h->private_data.out_headers, 0, write_header, &w); fiobj_str_write(w.dest, "\r\n", 2); return w.dest; } /** Should send existing headers and data */ static int http1_send_body(http_s *h, void *data, uintptr_t length) { FIOBJ packet = headers2str(h, length); if (!packet) { http1_after_finish(h); return -1; } fiobj_str_write(packet, data, length); fiobj_send_free((handle2pr(h)->p.uuid), packet); http1_after_finish(h); return 0; } /** Should send existing headers and file */ static int http1_sendfile(http_s *h, int fd, uintptr_t length, uintptr_t offset) { FIOBJ packet = headers2str(h, 0); if (!packet) { close(fd); http1_after_finish(h); return -1; } if (length < HTTP_MAX_HEADER_LENGTH) { /* optimize away small files */ fio_str_info_s s = fiobj_obj2cstr(packet); fiobj_str_capa_assert(packet, s.len + length); s = fiobj_obj2cstr(packet); intptr_t i = pread(fd, s.data + s.len, length, offset); if (i < 0) { close(fd); fiobj_send_free((handle2pr(h)->p.uuid), packet); fio_close((handle2pr(h)->p.uuid)); return -1; } close(fd); fiobj_str_resize(packet, s.len + i); fiobj_send_free((handle2pr(h)->p.uuid), packet); http1_after_finish(h); return 0; } fiobj_send_free((handle2pr(h)->p.uuid), packet); fio_sendfile((handle2pr(h)->p.uuid), fd, offset, length); http1_after_finish(h); return 0; } /** Should send existing headers or complete streaming */ static void htt1p_finish(http_s *h) { FIOBJ packet = headers2str(h, 0); if (packet) fiobj_send_free((handle2pr(h)->p.uuid), packet); else { // fprintf(stderr, "WARNING: invalid call to `htt1p_finish`\n"); } http1_after_finish(h); } /** Push for data - unsupported. */ static int http1_push_data(http_s *h, void *data, uintptr_t length, FIOBJ mime_type) { return -1; (void)h; (void)data; (void)length; (void)mime_type; } /** Push for files - unsupported. */ static int http1_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) { return -1; (void)h; (void)filename; (void)mime_type; } /** * Called befor a pause task, */ static void http1_on_pause(http_s *h, http_fio_protocol_s *pr) { ((http1pr_s *)pr)->stop = 1; fio_suspend(pr->uuid); (void)h; } /** * called after the resume task had completed. */ static void http1_on_resume(http_s *h, http_fio_protocol_s *pr) { if (!((http1pr_s *)pr)->stop) { fio_force_event(pr->uuid, FIO_EVENT_ON_DATA); } (void)h; } static intptr_t http1_hijack(http_s *h, fio_str_info_s *leftover) { if (leftover) { intptr_t len = handle2pr(h)->buf_len - (intptr_t)(handle2pr(h)->parser.state.next - handle2pr(h)->buf); if (len) { *leftover = (fio_str_info_s){ .len = len, .data = (char *)handle2pr(h)->parser.state.next}; } else { *leftover = (fio_str_info_s){.len = 0, .data = NULL}; } } handle2pr(h)->stop = 3; intptr_t uuid = handle2pr(h)->p.uuid; fio_attach(uuid, NULL); return uuid; } /* ***************************************************************************** Websockets Upgrading ***************************************************************************** */ static void http1_websocket_client_on_upgrade(http_s *h, char *proto, size_t len) { http1pr_s *p = handle2pr(h); websocket_settings_s *args = h->udata; const intptr_t uuid = handle2pr(h)->p.uuid; http_settings_s *set = handle2pr(h)->p.settings; set->udata = NULL; http_finish(h); p->stop = 1; websocket_attach(uuid, set, args, p->parser.state.next, p->buf_len - (intptr_t)(p->parser.state.next - p->buf)); fio_free(args); (void)proto; (void)len; } static void http1_websocket_client_on_failed(http_s *h) { websocket_settings_s *s = h->udata; if (s->on_close) s->on_close(0, s->udata); fio_free(h->udata); h->udata = http_settings(h)->udata = NULL; } static void http1_websocket_client_on_hangup(http_settings_s *settings) { websocket_settings_s *s = settings->udata; if (s) { if (s->on_close) s->on_close(0, s->udata); fio_free(settings->udata); settings->udata = NULL; } } static int http1_http2websocket_server(http_s *h, websocket_settings_s *args) { // A static data used for all websocket connections. static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; static uintptr_t sec_version = 0; static uintptr_t sec_key = 0; if (!sec_version) sec_version = fiobj_hash_string("sec-websocket-version", 21); if (!sec_key) sec_key = fiobj_hash_string("sec-websocket-key", 17); FIOBJ tmp = fiobj_hash_get2(h->headers, sec_version); if (!tmp) goto bad_request; fio_str_info_s stmp = fiobj_obj2cstr(tmp); if (stmp.len != 2 || stmp.data[0] != '1' || stmp.data[1] != '3') goto bad_request; tmp = fiobj_hash_get2(h->headers, sec_key); if (!tmp) goto bad_request; stmp = fiobj_obj2cstr(tmp); fio_sha1_s sha1 = fio_sha1_init(); fio_sha1_write(&sha1, stmp.data, stmp.len); fio_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1); tmp = fiobj_str_buf(32); stmp = fiobj_obj2cstr(tmp); fiobj_str_resize(tmp, fio_base64_encode(stmp.data, fio_sha1_result(&sha1), 20)); http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE)); http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET)); http_set_header(h, HTTP_HEADER_WS_SEC_KEY, tmp); h->status = 101; http1pr_s *pr = handle2pr(h); const intptr_t uuid = handle2pr(h)->p.uuid; http_settings_s *set = handle2pr(h)->p.settings; http_finish(h); pr->stop = 1; websocket_attach(uuid, set, args, pr->parser.state.next, pr->buf_len - (intptr_t)(pr->parser.state.next - pr->buf)); return 0; bad_request: http_send_error(h, 400); if (args->on_close) args->on_close(0, args->udata); return -1; } static int http1_http2websocket_client(http_s *h, websocket_settings_s *args) { http1pr_s *p = handle2pr(h); /* We're done with the HTTP stage, so we call the `on_finish` */ if (p->p.settings->on_finish) p->p.settings->on_finish(p->p.settings); /* Copy the Websocket setting arguments to the HTTP settings `udata` */ p->p.settings->udata = fio_malloc(sizeof(*args)); ((websocket_settings_s *)(p->p.settings->udata))[0] = *args; /* Set callbacks */ p->p.settings->on_finish = http1_websocket_client_on_hangup; /* unknown */ p->p.settings->on_upgrade = http1_websocket_client_on_upgrade; /* sucess */ p->p.settings->on_response = http1_websocket_client_on_failed; /* failed */ p->p.settings->on_request = http1_websocket_client_on_failed; /* failed */ /* Set headers */ http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE)); http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET)); http_set_header(h, HTTP_HVALUE_WS_SEC_VERSION, fiobj_dup(HTTP_HVALUE_WS_VERSION)); /* we don't set the Origin header since we're not a browser... should we? */ // http_set_header( // h, HTTP_HEADER_ORIGIN, // fiobj_dup(fiobj_hash_get2(h->private_data.out_headers, // fiobj_obj2hash(HTTP_HEADER_HOST)))); /* create nonce */ uint64_t key[2]; /* 16 bytes */ key[0] = (uintptr_t)h ^ (uint64_t)fio_last_tick().tv_sec; key[1] = (uintptr_t)args->udata ^ (uint64_t)fio_last_tick().tv_nsec; FIOBJ encoded = fiobj_str_buf(26); /* we need 24 really. */ fio_str_info_s tmp = fiobj_obj2cstr(encoded); tmp.len = fio_base64_encode(tmp.data, (char *)key, 16); fiobj_str_resize(encoded, tmp.len); http_set_header(h, HTTP_HEADER_WS_SEC_CLIENT_KEY, encoded); http_finish(h); return 0; } static int http1_http2websocket(http_s *h, websocket_settings_s *args) { assert(h); http1pr_s *p = handle2pr(h); if (p->is_client == 0) { return http1_http2websocket_server(h, args); } return http1_http2websocket_client(h, args); } /* ***************************************************************************** EventSource Support (SSE) ***************************************************************************** */ #undef http_upgrade2sse typedef struct { fio_protocol_s p; http_sse_internal_s *sse; } http1_sse_fio_protocol_s; static void http1_sse_on_ready(intptr_t uuid, fio_protocol_s *p_) { http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; if (p->sse->sse.on_ready) p->sse->sse.on_ready(&p->sse->sse); (void)uuid; } static uint8_t http1_sse_on_shutdown(intptr_t uuid, fio_protocol_s *p_) { http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; if (p->sse->sse.on_shutdown) p->sse->sse.on_shutdown(&p->sse->sse); return 0; (void)uuid; } static void http1_sse_on_close(intptr_t uuid, fio_protocol_s *p_) { http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_; http_sse_destroy(p->sse); fio_free(p); (void)uuid; } static void http1_sse_ping(intptr_t uuid, fio_protocol_s *p_) { fio_write2(uuid, .data.buffer = ": ping\n\n", .length = 8, .after.dealloc = FIO_DEALLOC_NOOP); (void)p_; } /** * Upgrades an HTTP connection to an EventSource (SSE) connection. * * Thie `http_s` handle will be invalid after this call. * * On HTTP/1.1 connections, this will preclude future requests using the same * connection. */ static int http1_upgrade2sse(http_s *h, http_sse_s *sse) { const intptr_t uuid = handle2pr(h)->p.uuid; /* send response */ h->status = 200; http_set_header(h, HTTP_HEADER_CONTENT_TYPE, fiobj_dup(HTTP_HVALUE_SSE_MIME)); http_set_header(h, HTTP_HEADER_CACHE_CONTROL, fiobj_dup(HTTP_HVALUE_NO_CACHE)); http_set_header(h, HTTP_HEADER_CONTENT_ENCODING, fiobj_str_new("identity", 8)); handle2pr(h)->stop = 1; htt1p_finish(h); /* avoid the enforced content length in http_finish */ /* switch protocol to SSE */ http1_sse_fio_protocol_s *sse_pr = fio_malloc(sizeof(*sse_pr)); if (!sse_pr) goto failed; *sse_pr = (http1_sse_fio_protocol_s){ .p = { .on_ready = http1_sse_on_ready, .on_shutdown = http1_sse_on_shutdown, .on_close = http1_sse_on_close, .ping = http1_sse_ping, }, .sse = fio_malloc(sizeof(*(sse_pr->sse))), }; if (!sse_pr->sse) goto failed; http_sse_init(sse_pr->sse, uuid, &HTTP1_VTABLE, sse); fio_timeout_set(uuid, handle2pr(h)->p.settings->ws_timeout); if (sse->on_open) sse->on_open(&sse_pr->sse->sse); fio_attach(uuid, &sse_pr->p); return 0; failed: fio_close(handle2pr(h)->p.uuid); if (sse->on_close) sse->on_close(sse); return -1; (void)sse; } #undef http_sse_write /** * Writes data to an EventSource (SSE) connection. * * See the {struct http_sse_write_args} for possible named arguments. */ static int http1_sse_write(http_sse_s *sse, FIOBJ str) { return fiobj_send_free(((http_sse_internal_s *)sse)->uuid, str); } /** * Closes an EventSource (SSE) connection. */ static int http1_sse_close(http_sse_s *sse) { fio_close(((http_sse_internal_s *)sse)->uuid); return 0; } /* ***************************************************************************** Virtual Table Decleration ***************************************************************************** */ struct http_vtable_s HTTP1_VTABLE = { .http_send_body = http1_send_body, .http_sendfile = http1_sendfile, .http_finish = htt1p_finish, .http_push_data = http1_push_data, .http_push_file = http1_push_file, .http_on_pause = http1_on_pause, .http_on_resume = http1_on_resume, .http_hijack = http1_hijack, .http2websocket = http1_http2websocket, .http_upgrade2sse = http1_upgrade2sse, .http_sse_write = http1_sse_write, .http_sse_close = http1_sse_close, }; void *http1_vtable(void) { return (void *)&HTTP1_VTABLE; } /* ***************************************************************************** Parser Callbacks ***************************************************************************** */ /** called when a request was received. */ static int http1_on_request(http1_parser_s *parser) { http1pr_s *p = parser2http(parser); http_on_request_handler______internal(&http1_pr2handle(p), p->p.settings); if (p->request.method && !p->stop) http_finish(&p->request); h1_reset(p); return fio_is_closed(p->p.uuid); } /** called when a response was received. */ static int http1_on_response(http1_parser_s *parser) { http1pr_s *p = parser2http(parser); http_on_response_handler______internal(&http1_pr2handle(p), p->p.settings); if (p->request.status_str && !p->stop) http_finish(&p->request); h1_reset(p); return fio_is_closed(p->p.uuid); } /** called when a request method is parsed. */ static int http1_on_method(http1_parser_s *parser, char *method, size_t method_len) { http1_pr2handle(parser2http(parser)).method = fiobj_str_new(method, method_len); parser2http(parser)->header_size += method_len; return 0; } /** called when a response status is parsed. the status_str is the string * without the prefixed numerical status indicator.*/ static int http1_on_status(http1_parser_s *parser, size_t status, char *status_str, size_t len) { http1_pr2handle(parser2http(parser)).status_str = fiobj_str_new(status_str, len); http1_pr2handle(parser2http(parser)).status = status; parser2http(parser)->header_size += len; return 0; } /** called when a request path (excluding query) is parsed. */ static int http1_on_path(http1_parser_s *parser, char *path, size_t len) { http1_pr2handle(parser2http(parser)).path = fiobj_str_new(path, len); parser2http(parser)->header_size += len; return 0; } /** called when a request path (excluding query) is parsed. */ static int http1_on_query(http1_parser_s *parser, char *query, size_t len) { http1_pr2handle(parser2http(parser)).query = fiobj_str_new(query, len); parser2http(parser)->header_size += len; return 0; } /** called when a the HTTP/1.x version is parsed. */ static int http1_on_version(http1_parser_s *parser, char *version, size_t len) { http1_pr2handle(parser2http(parser)).version = fiobj_str_new(version, len); parser2http(parser)->header_size += len; /* start counting - occurs on the first line of both requests and responses */ #if FIO_HTTP_EXACT_LOGGING clock_gettime(CLOCK_REALTIME, &http1_pr2handle(parser2http(parser)).received_at); #else http1_pr2handle(parser2http(parser)).received_at = fio_last_tick(); #endif return 0; } /** called when a header is parsed. */ static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len, char *data, size_t data_len) { FIOBJ sym; FIOBJ obj; if (!http1_pr2handle(parser2http(parser)).headers) { FIO_LOG_ERROR("(http1 parse ordering error) missing HashMap for header " "%s: %s", name, data); http_send_error2(500, parser2http(parser)->p.uuid, parser2http(parser)->p.settings); return -1; } parser2http(parser)->header_size += name_len + data_len; if (parser2http(parser)->header_size >= parser2http(parser)->max_header_size || fiobj_hash_count(http1_pr2handle(parser2http(parser)).headers) > HTTP_MAX_HEADER_COUNT) { if (parser2http(parser)->p.settings->log) { FIO_LOG_WARNING("(HTTP) security alert - header flood detected."); } http_send_error(&http1_pr2handle(parser2http(parser)), 413); return -1; } sym = fiobj_str_new(name, name_len); obj = fiobj_str_new(data, data_len); set_header_add(http1_pr2handle(parser2http(parser)).headers, sym, obj); fiobj_free(sym); return 0; } /** called when a body chunk is parsed. */ static int http1_on_body_chunk(http1_parser_s *parser, char *data, size_t data_len) { if (parser->state.content_length > (ssize_t)parser2http(parser)->p.settings->max_body_size || parser->state.read > (ssize_t)parser2http(parser)->p.settings->max_body_size) { http_send_error(&http1_pr2handle(parser2http(parser)), 413); return -1; /* test every time, in case of chunked data */ } if (!parser->state.read) { if (parser->state.content_length > 0 && parser->state.content_length <= HTTP_MAX_HEADER_LENGTH) { http1_pr2handle(parser2http(parser)).body = fiobj_data_newstr(); } else { http1_pr2handle(parser2http(parser)).body = fiobj_data_newtmpfile(); } } fiobj_data_write(http1_pr2handle(parser2http(parser)).body, data, data_len); return 0; } /** called when a protocol error occurred. */ static int http1_on_error(http1_parser_s *parser) { if (parser2http(parser)->close) return -1; FIO_LOG_DEBUG("HTTP parser error."); fio_close(parser2http(parser)->p.uuid); return -1; } /* ***************************************************************************** Connection Callbacks ***************************************************************************** */ static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) { if (fio_pending(uuid) > 4) { goto throttle; } ssize_t i = 0; size_t org_len = p->buf_len; int pipeline_limit = 8; if (!p->buf_len) return; do { i = http1_parse(&p->parser, p->buf + (org_len - p->buf_len), p->buf_len); p->buf_len -= i; --pipeline_limit; } while (i && p->buf_len && pipeline_limit && !p->stop); if (p->buf_len && org_len != p->buf_len) { memmove(p->buf, p->buf + (org_len - p->buf_len), p->buf_len); } if (p->buf_len == HTTP_MAX_HEADER_LENGTH) { /* no room to read... parser not consuming data */ if (p->request.method) http_send_error(&p->request, 413); else { p->request.method = fiobj_str_tmp(); http_send_error(&p->request, 413); } } if (!pipeline_limit) { fio_force_event(uuid, FIO_EVENT_ON_DATA); } return; throttle: /* throttle busy clients (slowloris) */ p->stop |= 4; fio_suspend(uuid); FIO_LOG_DEBUG("(HTTP/1,1) throttling client at %.*s", (int)fio_peer_addr(uuid).len, fio_peer_addr(uuid).data); } /** called when a data is available, but will not run concurrently */ static void http1_on_data(intptr_t uuid, fio_protocol_s *protocol) { http1pr_s *p = (http1pr_s *)protocol; if (p->stop) { fio_suspend(uuid); return; } ssize_t i = 0; if (HTTP_MAX_HEADER_LENGTH - p->buf_len) i = fio_read(uuid, p->buf + p->buf_len, HTTP_MAX_HEADER_LENGTH - p->buf_len); if (i > 0) { p->buf_len += i; } http1_consume_data(uuid, p); } /** called when the connection was closed, but will not run concurrently */ static void http1_on_close(intptr_t uuid, fio_protocol_s *protocol) { http1_destroy(protocol); (void)uuid; } /** called when the connection was closed, but will not run concurrently */ static void http1_on_ready(intptr_t uuid, fio_protocol_s *protocol) { /* resume slow clients from suspension */ http1pr_s *p = (http1pr_s *)protocol; if (p->stop & 4) { p->stop ^= 4; /* flip back the bit, so it's zero */ fio_force_event(uuid, FIO_EVENT_ON_DATA); } (void)protocol; } /** called when a data is available for the first time */ static void http1_on_data_first_time(intptr_t uuid, fio_protocol_s *protocol) { http1pr_s *p = (http1pr_s *)protocol; ssize_t i; i = fio_read(uuid, p->buf + p->buf_len, HTTP_MAX_HEADER_LENGTH - p->buf_len); if (i <= 0) return; p->buf_len += i; /* ensure future reads skip this first time HTTP/2.0 test */ p->p.protocol.on_data = http1_on_data; if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) { FIO_LOG_WARNING("client claimed unsupported HTTP/2 prior knowledge."); fio_close(uuid); return; } /* Finish handling the same way as the normal `on_data` */ http1_consume_data(uuid, p); } /* ***************************************************************************** Public API ***************************************************************************** */ /** Creates an HTTP1 protocol object and handles any unread data in the buffer * (if any). */ fio_protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings, void *unread_data, size_t unread_length) { if (unread_data && unread_length > HTTP_MAX_HEADER_LENGTH) return NULL; http1pr_s *p = fio_malloc(sizeof(*p) + HTTP_MAX_HEADER_LENGTH); // FIO_LOG_DEBUG("Allocated HTTP/1.1 protocol at. %p", (void *)p); FIO_ASSERT_ALLOC(p); *p = (http1pr_s){ .p.protocol = { .on_data = http1_on_data_first_time, .on_close = http1_on_close, .on_ready = http1_on_ready, }, .p.uuid = uuid, .p.settings = settings, .max_header_size = settings->max_header_size, .is_client = settings->is_client, }; http_s_new(&p->request, &p->p, &HTTP1_VTABLE); if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) { memcpy(p->buf, unread_data, unread_length); p->buf_len = unread_length; } fio_attach(uuid, &p->p.protocol); if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) { fio_force_event(uuid, FIO_EVENT_ON_DATA); } return &p->p.protocol; } /** Manually destroys the HTTP1 protocol object. */ void http1_destroy(fio_protocol_s *pr) { http1pr_s *p = (http1pr_s *)pr; http1_pr2handle(p).status = 0; http_s_destroy(&http1_pr2handle(p), 0); fio_free(p); // FIO_LOG_DEBUG("Deallocated HTTP/1.1 protocol at. %p", (void *)p); } /* ***************************************************************************** Protocol Data ***************************************************************************** */ // clang-format off #define HTTP_SET_STATUS_STR(status, str) [((status)-100)] = { .data = (char*)("HTTP/1.1 " #status " " str "\r\n"), .len = (sizeof("HTTP/1.1 " #status " " str "\r\n") - 1) } // #undef HTTP_SET_STATUS_STR // clang-format on static fio_str_info_s http1pr_status2str(uintptr_t status) { static fio_str_info_s status2str[] = { HTTP_SET_STATUS_STR(100, "Continue"), HTTP_SET_STATUS_STR(101, "Switching Protocols"), HTTP_SET_STATUS_STR(102, "Processing"), HTTP_SET_STATUS_STR(103, "Early Hints"), HTTP_SET_STATUS_STR(200, "OK"), HTTP_SET_STATUS_STR(201, "Created"), HTTP_SET_STATUS_STR(202, "Accepted"), HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"), HTTP_SET_STATUS_STR(204, "No Content"), HTTP_SET_STATUS_STR(205, "Reset Content"), HTTP_SET_STATUS_STR(206, "Partial Content"), HTTP_SET_STATUS_STR(207, "Multi-Status"), HTTP_SET_STATUS_STR(208, "Already Reported"), HTTP_SET_STATUS_STR(226, "IM Used"), HTTP_SET_STATUS_STR(300, "Multiple Choices"), HTTP_SET_STATUS_STR(301, "Moved Permanently"), HTTP_SET_STATUS_STR(302, "Found"), HTTP_SET_STATUS_STR(303, "See Other"), HTTP_SET_STATUS_STR(304, "Not Modified"), HTTP_SET_STATUS_STR(305, "Use Proxy"), HTTP_SET_STATUS_STR(306, "(Unused), "), HTTP_SET_STATUS_STR(307, "Temporary Redirect"), HTTP_SET_STATUS_STR(308, "Permanent Redirect"), HTTP_SET_STATUS_STR(400, "Bad Request"), HTTP_SET_STATUS_STR(403, "Forbidden"), HTTP_SET_STATUS_STR(404, "Not Found"), HTTP_SET_STATUS_STR(401, "Unauthorized"), HTTP_SET_STATUS_STR(402, "Payment Required"), HTTP_SET_STATUS_STR(405, "Method Not Allowed"), HTTP_SET_STATUS_STR(406, "Not Acceptable"), HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"), HTTP_SET_STATUS_STR(408, "Request Timeout"), HTTP_SET_STATUS_STR(409, "Conflict"), HTTP_SET_STATUS_STR(410, "Gone"), HTTP_SET_STATUS_STR(411, "Length Required"), HTTP_SET_STATUS_STR(412, "Precondition Failed"), HTTP_SET_STATUS_STR(413, "Payload Too Large"), HTTP_SET_STATUS_STR(414, "URI Too Long"), HTTP_SET_STATUS_STR(415, "Unsupported Media Type"), HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"), HTTP_SET_STATUS_STR(417, "Expectation Failed"), HTTP_SET_STATUS_STR(421, "Misdirected Request"), HTTP_SET_STATUS_STR(422, "Unprocessable Entity"), HTTP_SET_STATUS_STR(423, "Locked"), HTTP_SET_STATUS_STR(424, "Failed Dependency"), HTTP_SET_STATUS_STR(425, "Unassigned"), HTTP_SET_STATUS_STR(426, "Upgrade Required"), HTTP_SET_STATUS_STR(427, "Unassigned"), HTTP_SET_STATUS_STR(428, "Precondition Required"), HTTP_SET_STATUS_STR(429, "Too Many Requests"), HTTP_SET_STATUS_STR(430, "Unassigned"), HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"), HTTP_SET_STATUS_STR(500, "Internal Server Error"), HTTP_SET_STATUS_STR(501, "Not Implemented"), HTTP_SET_STATUS_STR(502, "Bad Gateway"), HTTP_SET_STATUS_STR(503, "Service Unavailable"), HTTP_SET_STATUS_STR(504, "Gateway Timeout"), HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"), HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"), HTTP_SET_STATUS_STR(507, "Insufficient Storage"), HTTP_SET_STATUS_STR(508, "Loop Detected"), HTTP_SET_STATUS_STR(509, "Unassigned"), HTTP_SET_STATUS_STR(510, "Not Extended"), HTTP_SET_STATUS_STR(511, "Network Authentication Required"), }; fio_str_info_s ret = (fio_str_info_s){.len = 0, .data = NULL}; if (status >= 100 && (status - 100) < sizeof(status2str) / sizeof(status2str[0])) ret = status2str[status - 100]; if (!ret.data) { ret = status2str[400]; } return ret; } #undef HTTP_SET_STATUS_STR