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:
+57
-27
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user