Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2222 "100-Continue is not correctly responded for chunked requ… #2224

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -16,6 +16,8 @@

package org.glassfish.grizzly.http.server;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
Expand All @@ -42,14 +44,16 @@ public class HttpContinueTest {
private static final int PORT = 9495;

private final int numberOfExtraHttpHandlers;
private final boolean chunkedTransferEncoding;

public HttpContinueTest(final int numberOfExtraHttpHandlers) {
public HttpContinueTest(final int numberOfExtraHttpHandlers, final boolean chunkedTransferEncoding) {
this.numberOfExtraHttpHandlers = numberOfExtraHttpHandlers;
this.chunkedTransferEncoding = chunkedTransferEncoding;
}

@Parameters
public static Collection<Object[]> getNumberOfExtraHttpHandlers() {
return Arrays.asList(new Object[][] { { 0 }, { 5 } });
return Arrays.asList(new Object[][] { { 0, FALSE }, { 0, TRUE }, { 5, FALSE }, { 5, TRUE } });
}

// ------------------------------------------------------------ Test Methods
Expand Down Expand Up @@ -79,7 +83,11 @@ public void service(Request request, Response response) throws Exception {
out.write("POST /path HTTP/1.1\r\n".getBytes());
out.write(("Host: localhost:" + PORT + "\r\n").getBytes());
out.write("Content-Type: application/x-www-form-urlencoded\r\n".getBytes());
out.write("Content-Length: 7\r\n".getBytes());
if (chunkedTransferEncoding) {
out.write("Transfer-Encoding: chunked\r\n".getBytes());
} else {
out.write("Content-Length: 7\r\n".getBytes());
}
out.write("Expect: 100-continue\r\n".getBytes());
out.write("\r\n".getBytes());

Expand All @@ -101,7 +109,11 @@ public void service(Request request, Response response) throws Exception {
assertEquals("HTTP/1.1 100 Continue", sb.toString().trim());

// send post data now that we have clearance
out.write("a=hello\r\n\r\n".getBytes());
if (chunkedTransferEncoding) {
out.write("7\r\na=hello\r\n0\r\n\r\n".getBytes());
} else {
out.write("a=hello\r\n\r\n".getBytes());
}
assertEquals("hello", future.get(10, TimeUnit.SECONDS));
sb.setLength(0);
for (;;) {
Expand Down Expand Up @@ -132,16 +144,23 @@ public void testExpectationIgnored() throws Exception {
try {
server.start();
s = SocketFactory.getDefault().createSocket("localhost", PORT);
s.setSoTimeout(10 * 1000);
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
StringBuilder post = new StringBuilder();
post.append("POST /path HTTP/1.1\r\n");
post.append("Host: localhost:").append(PORT).append("\r\n");
post.append("Expect: 100-continue\r\n");
post.append("Content-Type: application/x-www-form-urlencoded\r\n");
post.append("Content-Length: 7\r\n");
post.append("\r\n");
post.append("a=hello\r\n\r\n");
if (chunkedTransferEncoding) {
post.append("Transfer-Encoding: chunked\r\n");
post.append("\r\n");
post.append("7\r\na=hello\r\n0\r\n\r\n");
} else {
post.append("Content-Length: 7\r\n");
post.append("\r\n");
post.append("a=hello\r\n\r\n");
}

out.write(post.toString().getBytes());

Expand Down Expand Up @@ -175,13 +194,18 @@ public void testFailedExpectation() throws Exception {
try {
server.start();
s = SocketFactory.getDefault().createSocket("localhost", PORT);
s.setSoTimeout(10 * 1000);
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();

out.write("POST /path HTTP/1.1\r\n".getBytes());
out.write(("Host: localhost:" + PORT + "\r\n").getBytes());
out.write("Content-Type: application/x-www-form-urlencoded\r\n".getBytes());
out.write("Content-Length: 7\r\n".getBytes());
if (chunkedTransferEncoding) {
out.write("Transfer-Encoding: chunked\r\n".getBytes());
} else {
out.write("Content-Length: 7\r\n".getBytes());
}
out.write("Expect: 100-Continue-Extension\r\n".getBytes());
out.write("\r\n".getBytes());

Expand Down Expand Up @@ -223,13 +247,18 @@ protected boolean sendAcknowledgment(Request request, Response response) throws
try {
server.start();
s = SocketFactory.getDefault().createSocket("localhost", PORT);
s.setSoTimeout(10 * 1000);
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();

out.write("POST /path HTTP/1.1\r\n".getBytes());
out.write(("Host: localhost:" + PORT + "\r\n").getBytes());
out.write("Content-Type: application/x-www-form-urlencoded\r\n".getBytes());
out.write("Content-Length: 7\r\n".getBytes());
if (chunkedTransferEncoding) {
out.write("Transfer-Encoding: chunked\r\n".getBytes());
} else {
out.write("Content-Length: 7\r\n".getBytes());
}
out.write("Expect: 100-Continue\r\n".getBytes());
out.write("\r\n".getBytes());

Expand All @@ -243,7 +272,12 @@ protected boolean sendAcknowledgment(Request request, Response response) throws
}
}

assertEquals("HTTP/1.1 417 Expectation Failed", sb.toString().trim());
if (chunkedTransferEncoding) {
// chunked transfer encoding doesn't support custom HttpHandler#sendAcknowledgment()
assertEquals("HTTP/1.1 100 Continue", sb.toString().trim());
} else {
assertEquals("HTTP/1.1 417 Expectation Failed", sb.toString().trim());
}

} finally {
server.shutdownNow();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -530,11 +530,11 @@ protected boolean onHttpHeaderParsed(final HttpHeader httpHeader, final Buffer b

final ServerHttpRequestImpl request = (ServerHttpRequestImpl) httpHeader;

prepareRequest(request, buffer.hasRemaining());
prepareRequest(request, buffer.hasRemaining(), ctx);
return request.getProcessingState().error;
}

private void prepareRequest(final ServerHttpRequestImpl request, final boolean hasReadyContent) {
private void prepareRequest(final ServerHttpRequestImpl request, final boolean hasReadyContent, final FilterChainContext ctx) {

final ProcessingState state = request.getProcessingState();
final HttpResponsePacket response = request.getResponse();
Expand Down Expand Up @@ -644,9 +644,12 @@ private void prepareRequest(final ServerHttpRequestImpl request, final boolean h
}

if (request.requiresAcknowledgement()) {
// if we have any request content, we can ignore the Expect
// request
request.requiresAcknowledgement(isHttp11 && !hasReadyContent);
if (!isHttp11 || hasReadyContent) {
// if we have any request content, we can ignore the Expect request
request.requiresAcknowledgement(false);
} else if (request.isChunked()) {
sendAcknowledgment(request, response, ctx);
}
}
}

Expand Down Expand Up @@ -1066,6 +1069,25 @@ private boolean checkContentLengthRemainder(final HttpRequestPacket httpRequest)
|| ((HttpPacketParsing) httpRequest).getContentParsingState().chunkRemainder <= maxPayloadRemainderToSkip;
}

// similar to HttpHandler#sendAcknowledgment()
private void sendAcknowledgment(final HttpRequestPacket request, final HttpResponsePacket response,
final FilterChainContext ctx) {
if ("100-continue".equalsIgnoreCase(request.getHeader(Header.Expect))) {
// 100-continue is intercepted and acknowledged with a response line with the status 100 in Chunked Transfer Coding.
// The request processing will continue after acknowledgment of the expectation.
response.setStatus(HttpStatus.CONINTUE_100);
response.setAcknowledgement(true);
final Buffer resBuf = encodeHttpPacket(ctx, response);
if (resBuf != null) {
HttpProbeNotifier.notifyDataSent(this, ctx.getConnection(), resBuf);
ctx.write(resBuf);
}
} else {
response.setStatus(HttpStatus.EXPECTATION_FAILED_417);
sendBadRequestResponse(ctx, response);
}
}

// ---------------------------------------------------------- Nested Classes

private static class KeepAliveContext {
Expand Down