Fix #9953 handled HEAD (#9957)

* Fix #9953 handled HEAD

Fix #9953 so that if a Handler self handles HEAD by not writing content, then the length checks do not fire if 0 bytes have been written.

* updates from review
This commit is contained in:
Greg Wilkins 2023-06-23 17:21:27 +02:00 committed by GitHub
parent f6e963c841
commit 62e6cf2b76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 79 additions and 7 deletions

View File

@ -1176,16 +1176,15 @@ public class HttpChannelState implements HttpChannel, Components
{ {
long length = BufferUtil.length(content); long length = BufferUtil.length(content);
long totalWritten;
HttpChannelState httpChannelState; HttpChannelState httpChannelState;
HttpStream stream = null; HttpStream stream = null;
Throwable failure = null; Throwable failure;
MetaData.Response responseMetaData = null; MetaData.Response responseMetaData = null;
try (AutoLock ignored = _request._lock.lock()) try (AutoLock ignored = _request._lock.lock())
{ {
httpChannelState = _request.lockedGetHttpChannelState(); httpChannelState = _request.lockedGetHttpChannelState();
long committedContentLength = httpChannelState._committedContentLength; long committedContentLength = httpChannelState._committedContentLength;
totalWritten = _contentBytesWritten + length; long totalWritten = _contentBytesWritten + length;
long contentLength = committedContentLength >= 0 ? committedContentLength : getHeaders().getLongField(HttpHeader.CONTENT_LENGTH); long contentLength = committedContentLength >= 0 ? committedContentLength : getHeaders().getLongField(HttpHeader.CONTENT_LENGTH);
if (_writeCallback != null) if (_writeCallback != null)
@ -1193,11 +1192,14 @@ public class HttpChannelState implements HttpChannel, Components
else else
{ {
failure = getFailure(httpChannelState); failure = getFailure(httpChannelState);
if (failure == null && contentLength >= 0) if (failure == null && contentLength >= 0 && totalWritten != contentLength)
{ {
// If the content length were not compatible with what was written, then we need to abort. // If the content length were not compatible with what was written, then we need to abort.
String lengthError = (totalWritten > contentLength) ? "written %d > %d content-length" String lengthError = null;
: (last && totalWritten < contentLength) ? "written %d < %d content-length" : null; if (totalWritten > contentLength)
lengthError = "written %d > %d content-length";
else if (last && !(totalWritten == 0 && HttpMethod.HEAD.is(_request.getMethod())))
lengthError = "written %d < %d content-length";
if (lengthError != null) if (lengthError != null)
{ {
String message = lengthError.formatted(totalWritten, contentLength); String message = lengthError.formatted(totalWritten, contentLength);
@ -1439,7 +1441,7 @@ public class HttpChannelState implements HttpChannel, Components
long totalWritten = response._contentBytesWritten; long totalWritten = response._contentBytesWritten;
long committedContentLength = httpChannelState._committedContentLength; long committedContentLength = httpChannelState._committedContentLength;
if (committedContentLength >= 0 && committedContentLength != totalWritten) if (committedContentLength >= 0 && committedContentLength != totalWritten && !(totalWritten == 0 && HttpMethod.HEAD.is(_request.getMethod())))
failure = new IOException("content-length %d != %d written".formatted(committedContentLength, totalWritten)); failure = new IOException("content-length %d != %d written".formatted(committedContentLength, totalWritten));
// is the request fully consumed? // is the request fully consumed?

View File

@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractConnection;
@ -1334,6 +1335,75 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
} }
} }
@Test
public void testHeadHandled() throws Exception
{
startServer(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, 10);
if (HttpMethod.HEAD.is(request.getMethod()))
{
if (request.getHttpURI().getCanonicalPath().equals("/writeNull"))
response.write(true, null, callback);
else
callback.succeeded();
}
else
{
Content.Sink.write(response, true, "123456789\n", callback);
}
return true;
}
});
_httpConfiguration.setSendDateHeader(false);
_httpConfiguration.setSendServerVersion(false);
_httpConfiguration.setSendXPoweredBy(false);
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream();
os.write("""
GET / HTTP/1.1
Host: localhost
HEAD / HTTP/1.1
Host: localhost
HEAD /writeNull HTTP/1.1
Host: localhost
GET / HTTP/1.1
Host: localhost
Connection: close
""".getBytes(StandardCharsets.ISO_8859_1));
String in = IO.toString(is);
assertThat(in.replace("\r", ""), is("""
HTTP/1.1 200 OK
Content-Length: 10
123456789
HTTP/1.1 200 OK
Content-Length: 10
HTTP/1.1 200 OK
Content-Length: 10
HTTP/1.1 200 OK
Content-Length: 10
Connection: close
123456789
"""));
}
}
@Test @Test
public void testBlockedClient() throws Exception public void testBlockedClient() throws Exception
{ {