Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions lib/monkey/mk_server/mk_http_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Comment on lines +1016 to +1020

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid semantic scans from empty header values

When an empty known header reaches header_lookup, header->val.len becomes 0; for Transfer-Encoding: that length is passed to mk_string_search_n, whose len <= 0 path falls back to scanning the NUL-terminated remainder of the request. In plugins/in_http/http_conn.c the buffer is NUL-terminated before parsing, so a request like Transfer-Encoding:\r\nContent-Length: ...\r\n\r\n{"msg":"chunked"} is misclassified as chunked and mk_http_parser_ok returns pending instead of processing the Content-Length body. Please either reject empty semantic headers such as Transfer-Encoding or bypass their value parsing when the value length is zero.

Useful? React with 👍 / 👎.

@kimonus kimonus Jul 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction: commit 2d920dd now bypasses zero-length Transfer-Encoding semantic scanning, preventing mk_string_search_n from reading beyond the field. Empty Host and Content-Length remain rejected.

if (ret != 0) {
if (ret < -1) {
mk_http_error(-ret, req->session, req, server);
}
return MK_HTTP_PARSER_ERROR;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/* 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;
}
Expand Down
6 changes: 6 additions & 0 deletions src/http_server/flb_http_server_http1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
179 changes: 176 additions & 3 deletions tests/runtime/in_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#ifndef _WIN32
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/* 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);
Expand Down