feat: reject HTTP pipelined requests with 400 error and close upstream connection

Add detection of pipelined requests in handle_client_read by checking if buffered data starts with a new HTTP request. When a pipelined request is detected, the server now sends a 400 Bad Request response with a descriptive body, closes the upstream connection, and transitions the client state to CLIENT_STATE_CLOSING. Previously, the server would silently close the upstream connection and continue reading headers. The change also includes a comprehensive test that validates the rejection behavior, response content, and connection state transitions for pipelined requests.
This commit is contained in:
2026-01-27 16:15:20 +00:00
parent e914b08c29
commit ab70643e1f
3 changed files with 249 additions and 27 deletions
+57 -27
View File
@@ -717,20 +717,36 @@ static void handle_client_read(connection_t *conn) {
char *data_start = buf->data + buf->head;
size_t data_len = buffer_available_read(buf);
if (data_len >= 1) {
if (data_len >= 4) {
int looks_like_new_request = http_is_request_start(data_start, data_len);
if (!looks_like_new_request) {
if (looks_like_new_request) {
log_info("[ROUTING-PIPELINE] Pipelined request detected on fd=%d, rejecting with 400", conn->fd);
if (conn->pair) {
connection_close(conn->pair->fd);
conn->pair = NULL;
}
char *body = "400 Bad Request - Request pipelining is not supported";
char header[512];
int len = snprintf(header, sizeof(header),
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
strlen(body), body);
if (buffer_ensure_capacity(&conn->write_buf, len) == 0) {
memcpy(conn->write_buf.data, header, len);
conn->write_buf.tail = len;
}
conn->state = CLIENT_STATE_CLOSING;
conn->request.keep_alive = 0;
return;
}
log_debug("Pipelined request detected on fd %d, closing upstream fd %d",
conn->fd, conn->pair->fd);
connection_close(conn->pair->fd);
conn->pair = NULL;
conn->state = CLIENT_STATE_READING_HEADERS;
} else {
return;
}
}
@@ -845,10 +861,16 @@ static void handle_client_read(connection_t *conn) {
}
}
char routing_tag[16];
snprintf(routing_tag, sizeof(routing_tag), "%-15.15s", conn->request.host);
log_info("[%s] Forwarding request for fd=%d: %s %s",
routing_tag, conn->fd, conn->request.method, conn->request.uri);
if (route) {
log_info("[ROUTING] fd=%d method=%s uri=%s host=%s -> FORWARDING to %s:%d",
conn->fd, conn->request.method, conn->request.uri, conn->request.host,
route->upstream_host, route->upstream_port);
} else {
char routing_tag[16];
snprintf(routing_tag, sizeof(routing_tag), "%-15.15s", conn->request.host);
log_info("[%s] Forwarding request for fd=%d: %s %s",
routing_tag, conn->fd, conn->request.method, conn->request.uri);
}
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
@@ -968,22 +990,28 @@ static void handle_forwarding(connection_t *conn) {
size_t data_len = buffer_available_read(&conn->read_buf);
if (data_len >= 4 && http_is_request_start(data_start, data_len)) {
log_debug("Pipelined request detected in handle_forwarding on fd %d, closing upstream fd %d",
conn->fd, pair->fd);
log_info("[ROUTING-PIPELINE] Pipelined request detected on fd=%d, rejecting with 400", conn->fd);
connection_close(pair->fd);
conn->pair = NULL;
conn->state = CLIENT_STATE_READING_HEADERS;
conn->content_type_checked = 0;
conn->is_textual_content = 0;
conn->response_headers_parsed = 0;
conn->original_content_length = 0;
conn->content_length_delta = 0;
conn->patch_blocked = 0;
conn->half_closed = 0;
conn->write_shutdown = 0;
char *body = "400 Bad Request - Request pipelining is not supported";
char header[512];
int len = snprintf(header, sizeof(header),
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
strlen(body), body);
handle_client_read(conn);
if (buffer_ensure_capacity(&conn->write_buf, len) == 0) {
memcpy(conn->write_buf.data, header, len);
conn->write_buf.tail = len;
}
conn->state = CLIENT_STATE_CLOSING;
conn->request.keep_alive = 0;
return;
}
}
@@ -1267,7 +1295,9 @@ static void handle_write_event(connection_t *conn) {
connection_modify_epoll(conn->fd, EPOLLIN);
if (buffer_available_read(&conn->read_buf) > 0) {
if (conn->state == CLIENT_STATE_CLOSING) {
connection_close(conn->fd);
} else if (buffer_available_read(&conn->read_buf) > 0) {
handle_client_read(conn);
}
} else {