diff --git a/lib/monkey/mk_server/mk_http_parser.c b/lib/monkey/mk_server/mk_http_parser.c index 9413528ab6e..3c6da08e3a7 100644 --- a/lib/monkey/mk_server/mk_http_parser.c +++ b/lib/monkey/mk_server/mk_http_parser.c @@ -233,6 +233,10 @@ static inline int header_lookup(struct mk_http_parser *p, char *buffer) mk_list_add(&header->_head, &p->header_list); if (i == MK_HEADER_HOST) { + if (header->val.len == 0) { + return -MK_CLIENT_BAD_REQUEST; + } + /* Handle a possible port number in the Host header */ int sep = str_searchr(header->val.data, ':', header->val.len); if (sep > 0) { @@ -281,6 +285,11 @@ static inline int header_lookup(struct mk_http_parser *p, char *buffer) p->header_content_length = val; } else if (i == MK_HEADER_CONNECTION) { + if (header->val.len == 0) { + p->header_connection = MK_HTTP_PARSER_CONN_UNKNOWN; + return 0; + } + /* Check Connection: Keep-Alive */ if (header->val.len == sizeof(MK_CONN_KEEP_ALIVE) - 1) { if (header_cmp(MK_CONN_KEEP_ALIVE, @@ -321,6 +330,10 @@ static inline int header_lookup(struct mk_http_parser *p, char *buffer) } } else if (i == MK_HEADER_TRANSFER_ENCODING) { + if (header->val.len == 0) { + return 0; + } + /* Check Transfer-Encoding: chunked */ pos = mk_string_search_n(header->val.data, "chunked", @@ -367,6 +380,10 @@ static inline int header_lookup(struct mk_http_parser *p, char *buffer) } } else if (i == MK_HEADER_UPGRADE) { + if (header->val.len == 0) { + return -MK_CLIENT_BAD_REQUEST; + } + if (header_cmp(MK_UPGRADE_H2C, header->val.data, header->val.len) == 0) { p->header_upgrade = MK_HTTP_PARSER_UPGRADE_H2C; @@ -995,11 +1012,36 @@ int mk_http_parser(struct mk_http_request *req, struct mk_http_parser *p, } /* Parsing the header value */ else if (p->status == MK_ST_HEADER_VALUE) { - /* Trim left, set starts only when found something != ' ' */ - if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { + /* Empty field values are valid; trim optional whitespace. */ + if (buffer[p->i] == '\r') { + p->header_val = p->i; + mark_end(); + + ret = header_lookup(p, buffer); + if (ret != 0) { + if (ret < -1) { + mk_http_error(-ret, req->session, req, server); + } + return MK_HTTP_PARSER_ERROR; + } + + /* Try to catch next LF */ + if (p->i + 1 < len) { + if (buffer[p->i + 1] == '\n') { + p->i++; + p->status = MK_ST_HEADER_KEY; + p->chars = -1; + start_next(); + } + } + + p->status = MK_ST_HEADER_END; + start_next(); + } + else if (buffer[p->i] == '\n') { return MK_HTTP_PARSER_ERROR; } - else if (buffer[p->i] != ' ') { + else if (buffer[p->i] != ' ' && buffer[p->i] != '\t') { p->status = MK_ST_HEADER_VAL_STARTS; p->start = p->header_val = p->i; } diff --git a/src/http_server/flb_http_server_http1.c b/src/http_server/flb_http_server_http1.c index e9d930b7706..5c247c299b9 100644 --- a/src/http_server/flb_http_server_http1.c +++ b/src/http_server/flb_http_server_http1.c @@ -566,6 +566,12 @@ int flb_http1_server_session_ingest(struct flb_http1_server_session *session, * performance overhead in exchange for ensuring safety. */ } + else if (result == MK_HTTP_PARSER_ERROR) { + /* The caller closes the session when a parser error is returned. */ + session->stream.status = HTTP_STREAM_STATUS_ERROR; + + return HTTP_SERVER_PROVIDER_ERROR; + } dummy_mk_http_request_init(&session->inner_session, &session->inner_request); mk_http_parser_init(&session->inner_parser); diff --git a/tests/runtime/in_http.c b/tests/runtime/in_http.c index ba745e24ed2..71d0b96c750 100644 --- a/tests/runtime/in_http.c +++ b/tests/runtime/in_http.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #ifndef _WIN32 @@ -413,6 +414,169 @@ static void test_ctx_destroy(struct test_ctx *ctx) flb_free(ctx); } +static int send_empty_header_request(void) +{ + const char *body = "{\"test\":\"msg\"}"; + const char *request_template = + "POST / HTTP/1.1\r\n" + "Host: 127.0.0.1\r\n" + "Content-Length: %zu\r\n" + "Content-Type: application/json\r\n" + "X-Empty:\r\n" + "X-Empty-Whitespace: \r\n" + "\r\n" + "%s"; + struct sockaddr_in address; + struct timeval timeout; + char request[512]; + char response[256]; + int fd; + int ret; + int response_length; + size_t request_length; + size_t request_offset; + ssize_t written; + ssize_t received; + + ret = snprintf(request, sizeof(request), request_template, + strlen(body), body); + if (ret <= 0 || (size_t) ret >= sizeof(request)) { + return -1; + } + request_length = (size_t) ret; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons(9880); + + if (connect(fd, (struct sockaddr *) &address, sizeof(address)) < 0) { + close(fd); + return -1; + } + + request_offset = 0; + while (request_offset < request_length) { + written = send(fd, request + request_offset, + request_length - request_offset, 0); + if (written <= 0) { + close(fd); + return -1; + } + request_offset += (size_t) written; + } + + timeout.tv_sec = 2; + timeout.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, + &timeout, sizeof(timeout)) != 0) { + close(fd); + return -1; + } + + response_length = 0; + while (response_length < (int) sizeof(response) - 1) { + received = recv(fd, response + response_length, + sizeof(response) - 1 - (size_t) response_length, 0); + if (received <= 0) { + close(fd); + return -1; + } + response_length += (int) received; + response[response_length] = '\0'; + if (strstr(response, "\r\n") != NULL) { + break; + } + } + close(fd); + if (response_length == 0 || strstr(response, "\r\n") == NULL) { + return -1; + } + + return strstr(response, "HTTP/1.1 201") != NULL ? 0 : -1; +} + +static int send_invalid_header_request(void) +{ + const char *body = "{\"test\":\"msg\"}"; + const char *request_template = + "POST / HTTP/1.1\r\n" + "Host: 127.0.0.1\r\n" + "Content-Length: %zu\r\n" + "Content-Type: application/json\r\n" + "Upgrade:\r\n" + "\r\n" + "%s"; + struct sockaddr_in address; + struct timeval timeout; + char request[512]; + char response; + int fd; + int ret; + size_t request_length; + size_t request_offset; + ssize_t written; + ssize_t received; + + ret = snprintf(request, sizeof(request), request_template, + strlen(body), body); + if (ret <= 0 || (size_t) ret >= sizeof(request)) { + return -1; + } + request_length = (size_t) ret; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons(9880); + + if (connect(fd, (struct sockaddr *) &address, sizeof(address)) < 0) { + close(fd); + return -1; + } + + request_offset = 0; + while (request_offset < request_length) { + written = send(fd, request + request_offset, + request_length - request_offset, 0); + if (written <= 0) { + close(fd); + return -1; + } + request_offset += (size_t) written; + } + + timeout.tv_sec = 2; + timeout.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, + &timeout, sizeof(timeout)) != 0) { + close(fd); + return -1; + } + + received = recv(fd, &response, sizeof(response), 0); + close(fd); + + if (received == 0) { + return 0; + } + if (received < 0 && (errno == ECONNRESET || errno == EPIPE)) { + return 0; + } + + return -1; +} + void flb_test_http(void) { struct flb_lib_out_cb cb_data; @@ -469,13 +633,22 @@ void flb_test_http(void) TEST_MSG("http response code error. expect: 201, got: %d\n", c->resp.status); } + /* Ensure the first request has reached the callback before the regression. */ + flb_time_msleep(1500); + num = get_output_num(); + TEST_CHECK(num > 0); + + TEST_CHECK(send_empty_header_request() == 0); + /* waiting to flush */ flb_time_msleep(1500); - num = get_output_num(); - if (!TEST_CHECK(num > 0)) { - TEST_MSG("no outputs"); + if (!TEST_CHECK(get_output_num() > num)) { + TEST_MSG("empty-header request did not reach the callback"); } + + TEST_CHECK(send_invalid_header_request() == 0); + flb_http_client_destroy(c); flb_upstream_conn_release(ctx->httpc->u_conn); test_ctx_destroy(ctx);