Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;

import org.elasticsearch.core.SuppressForbidden;

Expand Down Expand Up @@ -92,7 +91,7 @@ public class Netty4HttpContentSizeHandler extends ChannelInboundHandlerAdapter {
private final int maxContentLength;
private final HttpRequestDecoder decoder; // need to reset decoder after sending 413
private int currentContentLength; // chunked encoding does not provide content length, need to track actual length
private boolean contentExpected;
private boolean ignoreContent;

public Netty4HttpContentSizeHandler(HttpRequestDecoder decoder, int maxContentLength) {
this.maxContentLength = maxContentLength;
Expand All @@ -110,7 +109,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
}

private void handleRequest(ChannelHandlerContext ctx, HttpRequest request) {
contentExpected = false;
ignoreContent = true;
if (request.decoderResult().isFailure()) {
ctx.fireChannelRead(request);
return;
Expand All @@ -135,13 +134,10 @@ private void handleRequest(ChannelHandlerContext ctx, HttpRequest request) {
// See https://www.rfc-editor.org/rfc/rfc9110.html#section-10.1.1-11.3
// this content will result in HttpRequestDecoder failure and send downstream
decoder.reset();
ctx.writeAndFlush(TOO_LARGE.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
// Client is sending oversized content, we cannot safely take it. Closing channel.
ctx.writeAndFlush(TOO_LARGE_CLOSE.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE);
}
ctx.writeAndFlush(TOO_LARGE.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
contentExpected = true;
ignoreContent = false;
currentContentLength = 0;
if (isContinueExpected) {
ctx.writeAndFlush(CONTINUE.retainedDuplicate());
Expand All @@ -152,19 +148,16 @@ private void handleRequest(ChannelHandlerContext ctx, HttpRequest request) {
}

private void handleContent(ChannelHandlerContext ctx, HttpContent msg) {
if (contentExpected) {
if (ignoreContent) {
msg.release();
} else {
currentContentLength += msg.content().readableBytes();
if (currentContentLength > maxContentLength) {
msg.release();
ctx.writeAndFlush(TOO_LARGE_CLOSE.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE);
} else {
ctx.fireChannelRead(msg);
}
} else {
msg.release();
if (msg != LastHttpContent.EMPTY_LAST_CONTENT) {
ctx.close(); // there is no reliable recovery from unexpected content, closing channel
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,20 @@ public void testMixedContent() {
}

/**
* Assert that handler returns 413 Request Entity Too Large and close channel for
* oversized request with content.
* Assert that handler returns 413 Request Entity Too Large and skip following content.
*/
public void testEntityTooLargeWithContentWithoutExpect() {
var sendRequest = httpRequest();
HttpUtil.setContentLength(sendRequest, OVERSIZED_LENGTH);
var unexpectedContent = lastHttpContent(OVERSIZED_LENGTH);
channel.writeInbound(encode(sendRequest, unexpectedContent));
var resp = (FullHttpResponse) channel.readOutbound();
assertEquals(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, resp.status());
assertFalse(channel.isOpen());
resp.release();
for (int i = 0; i < REPS; i++) {
var sendRequest = httpRequest();
HttpUtil.setContentLength(sendRequest, OVERSIZED_LENGTH);
var unexpectedContent = lastHttpContent(OVERSIZED_LENGTH);
channel.writeInbound(encode(sendRequest, unexpectedContent));
var resp = (FullHttpResponse) channel.readOutbound();
assertEquals(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, resp.status());
resp.release();
assertNull("request and content should not pass", channel.readInbound());
assertTrue("should not close channel", channel.isOpen());
}
}

/**
Expand Down