Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x
Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
commit
203eef4029
|
@ -686,17 +686,24 @@ public class HttpGenerator
|
|||
_endOfContent = EndOfContent.NO_CONTENT;
|
||||
|
||||
// But it is an error if there actually is content
|
||||
if (_contentPrepared > 0 || contentLength > 0)
|
||||
if (_contentPrepared > 0)
|
||||
throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Content for no content response");
|
||||
|
||||
if (contentLengthField)
|
||||
{
|
||||
if (_contentPrepared == 0 && last)
|
||||
if (response != null && response.getStatus() == HttpStatus.NOT_MODIFIED_304)
|
||||
putContentLength(header, contentLength);
|
||||
else if (contentLength > 0)
|
||||
{
|
||||
// TODO discard content for backward compatibility with 9.3 releases
|
||||
// TODO review if it is still needed in 9.4 or can we just throw.
|
||||
content.clear();
|
||||
contentLength = 0;
|
||||
if (_contentPrepared == 0 && last)
|
||||
{
|
||||
// TODO discard content for backward compatibility with 9.3 releases
|
||||
// TODO review if it is still needed in 9.4 or can we just throw.
|
||||
content.clear();
|
||||
}
|
||||
else
|
||||
throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Content for no content response");
|
||||
}
|
||||
else
|
||||
throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Content for no content response");
|
||||
}
|
||||
}
|
||||
// Else if we are HTTP/1.1 and the content length is unknown and we are either persistent
|
||||
|
|
|
@ -170,6 +170,7 @@ public class HttpParser
|
|||
private Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune?
|
||||
private EndOfContent _endOfContent;
|
||||
private boolean _hasContentLength;
|
||||
private boolean _hasTransferEncoding;
|
||||
private long _contentLength = -1;
|
||||
private long _contentPosition;
|
||||
private int _chunkLength;
|
||||
|
@ -916,6 +917,9 @@ public class HttpParser
|
|||
switch (_header)
|
||||
{
|
||||
case CONTENT_LENGTH:
|
||||
if (_hasTransferEncoding)
|
||||
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
|
||||
|
||||
if (_hasContentLength)
|
||||
{
|
||||
checkViolation(MULTIPLE_CONTENT_LENGTHS);
|
||||
|
@ -924,9 +928,6 @@ public class HttpParser
|
|||
}
|
||||
_hasContentLength = true;
|
||||
|
||||
if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
|
||||
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
|
||||
|
||||
if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
|
||||
{
|
||||
_contentLength = convertContentLength(_valueString);
|
||||
|
@ -938,9 +939,15 @@ public class HttpParser
|
|||
break;
|
||||
|
||||
case TRANSFER_ENCODING:
|
||||
_hasTransferEncoding = true;
|
||||
|
||||
if (_hasContentLength)
|
||||
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
|
||||
|
||||
// we encountered another Transfer-Encoding header, but chunked was already set
|
||||
if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
|
||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Transfer-Encoding, chunked not last");
|
||||
|
||||
if (HttpHeaderValue.CHUNKED.is(_valueString))
|
||||
{
|
||||
_endOfContent = EndOfContent.CHUNKED_CONTENT;
|
||||
|
@ -949,15 +956,26 @@ public class HttpParser
|
|||
else
|
||||
{
|
||||
List<String> values = new QuotedCSV(_valueString).getValues();
|
||||
if (!values.isEmpty() && HttpHeaderValue.CHUNKED.is(values.get(values.size() - 1)))
|
||||
int chunked = -1;
|
||||
int len = values.size();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
_endOfContent = EndOfContent.CHUNKED_CONTENT;
|
||||
_contentLength = -1;
|
||||
if (HttpHeaderValue.CHUNKED.is(values.get(i)))
|
||||
{
|
||||
if (chunked != -1)
|
||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Transfer-Encoding, multiple chunked tokens");
|
||||
chunked = i;
|
||||
// declared chunked
|
||||
_endOfContent = EndOfContent.CHUNKED_CONTENT;
|
||||
_contentLength = -1;
|
||||
}
|
||||
// we have a non-chunked token after a declared chunked token
|
||||
else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
|
||||
{
|
||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Transfer-Encoding, chunked not last");
|
||||
}
|
||||
}
|
||||
else if (values.stream().anyMatch(HttpHeaderValue.CHUNKED::is))
|
||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad chunking");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HOST:
|
||||
|
@ -1098,6 +1116,17 @@ public class HttpParser
|
|||
return _handler.messageComplete();
|
||||
}
|
||||
|
||||
// We found Transfer-Encoding headers, but none declared the 'chunked' token
|
||||
if (_hasTransferEncoding && _endOfContent != EndOfContent.CHUNKED_CONTENT)
|
||||
{
|
||||
if (_responseHandler == null || _endOfContent != EndOfContent.EOF_CONTENT)
|
||||
{
|
||||
// Transfer-Encoding chunked not specified
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Transfer-Encoding, chunked not last");
|
||||
}
|
||||
}
|
||||
|
||||
// Was there a required host header?
|
||||
if (!_host && _version == HttpVersion.HTTP_1_1 && _requestHandler != null)
|
||||
{
|
||||
|
@ -1779,6 +1808,7 @@ public class HttpParser
|
|||
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
|
||||
_contentLength = -1;
|
||||
_hasContentLength = false;
|
||||
_hasTransferEncoding = false;
|
||||
_contentPosition = 0;
|
||||
_responseStatus = 0;
|
||||
_contentChunk = null;
|
||||
|
|
|
@ -979,7 +979,7 @@ public class HttpParserTest
|
|||
assertEquals("GET", _methodOrVersion);
|
||||
assertEquals("/chunk", _uriOrStatus);
|
||||
assertEquals("HTTP/1.0", _versionOrReason);
|
||||
assertThat(_bad, containsString("Bad chunking"));
|
||||
assertThat(_bad, containsString("Bad Transfer-Encoding"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -500,7 +500,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
|
||||
// RFC 7230, section 3.3.
|
||||
if (!_request.isHead() && !_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
if (!_request.isHead() &&
|
||||
_response.getStatus() != HttpStatus.NOT_MODIFIED_304 &&
|
||||
!_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
{
|
||||
if (sendErrorOrAbort("Insufficient content written"))
|
||||
break;
|
||||
|
|
|
@ -188,13 +188,16 @@ public class InetAccessHandler extends HandlerWrapper
|
|||
protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request)
|
||||
{
|
||||
String name = baseRequest.getHttpChannel().getConnector().getName();
|
||||
boolean filterAppliesToConnector = _names.test(name);
|
||||
boolean allowedByAddr = _addrs.test(addr);
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
Boolean allowedByName = _names.isIncludedAndNotExcluded(name);
|
||||
Boolean allowedByAddr = _addrs.isIncludedAndNotExcluded(addr);
|
||||
LOG.debug("{} allowedByName={} allowedByAddr={} for {}/{}", this, allowedByName, allowedByAddr, addr, request);
|
||||
LOG.debug("name = {}/{} addr={}/{} appliesToConnector={} allowedByAddr={}",
|
||||
name, _names, addr, _addrs, filterAppliesToConnector, allowedByAddr);
|
||||
}
|
||||
return _names.test(name) && _addrs.test(addr);
|
||||
if (!filterAppliesToConnector)
|
||||
return true;
|
||||
return allowedByAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -113,7 +113,7 @@ public class DumpHandler extends AbstractHandler.ErrorDispatchHandler
|
|||
writer.write("<pre>\nlocal=" + request.getLocalAddr() + ":" + request.getLocalPort() + "\n</pre>\n");
|
||||
writer.write("<pre>\nremote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\n</pre>\n");
|
||||
writer.write("<h3>Header:</h3><pre>");
|
||||
writer.write(request.getMethod() + " " + request.getRequestURI() + " " + request.getProtocol() + "\n");
|
||||
writer.write(String.format("%4s %s %s\n", request.getMethod(), request.getRequestURI(), request.getProtocol()));
|
||||
Enumeration<String> headers = request.getHeaderNames();
|
||||
while (headers.hasMoreElements())
|
||||
{
|
||||
|
|
|
@ -30,10 +30,13 @@ import java.io.IOException;
|
|||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -54,6 +57,9 @@ import org.hamcrest.Matchers;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
@ -265,43 +271,172 @@ public class HttpConnectionTest
|
|||
}
|
||||
}
|
||||
|
||||
static final int CHUNKED = -1;
|
||||
static final int DQUOTED_CHUNKED = -2;
|
||||
static final int BAD_CHUNKED = -3;
|
||||
static final int UNKNOWN_TE = -4;
|
||||
|
||||
public static Stream<Arguments> http11ContentLengthAndChunkedData()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of(new int[]{CHUNKED, 8}),
|
||||
Arguments.of(new int[]{8, CHUNKED}),
|
||||
Arguments.of(new int[]{8, CHUNKED, 8}),
|
||||
Arguments.of(new int[]{DQUOTED_CHUNKED, 8}),
|
||||
Arguments.of(new int[]{8, DQUOTED_CHUNKED}),
|
||||
Arguments.of(new int[]{8, DQUOTED_CHUNKED, 8}),
|
||||
Arguments.of(new int[]{BAD_CHUNKED, 8}),
|
||||
Arguments.of(new int[]{8, BAD_CHUNKED}),
|
||||
Arguments.of(new int[]{8, BAD_CHUNKED, 8}),
|
||||
Arguments.of(new int[]{UNKNOWN_TE, 8}),
|
||||
Arguments.of(new int[]{8, UNKNOWN_TE}),
|
||||
Arguments.of(new int[]{8, UNKNOWN_TE, 8}),
|
||||
Arguments.of(new int[]{8, UNKNOWN_TE, CHUNKED, DQUOTED_CHUNKED, BAD_CHUNKED, 8})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* More then 1 Content-Length is a bad requests per HTTP rfcs.
|
||||
*/
|
||||
@Test
|
||||
public void testHttp11ContentLengthAndChunk() throws Exception
|
||||
@ParameterizedTest
|
||||
@MethodSource("http11ContentLengthAndChunkedData")
|
||||
public void testHttp11ContentLengthAndChunk(int[] contentLengths) throws Exception
|
||||
{
|
||||
HttpParser.LOG.info("badMessage: 400 Bad messages EXPECTED...");
|
||||
int[][] contentLengths = {
|
||||
{-1, 8},
|
||||
{8, -1},
|
||||
{8, -1, 8},
|
||||
};
|
||||
|
||||
for (int x = 0; x < contentLengths.length; x++)
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.append("POST / HTTP/1.1\r\n");
|
||||
request.append("Host: local\r\n");
|
||||
for (int n = 0; n < contentLengths.length; n++)
|
||||
{
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.append("POST /?id=").append(Integer.toString(x)).append(" HTTP/1.1\r\n");
|
||||
request.append("Host: local\r\n");
|
||||
int[] clen = contentLengths[x];
|
||||
for (int n = 0; n < clen.length; n++)
|
||||
switch (contentLengths[n])
|
||||
{
|
||||
if (clen[n] == -1)
|
||||
case CHUNKED:
|
||||
request.append("Transfer-Encoding: chunked\r\n");
|
||||
else
|
||||
request.append("Content-Length: ").append(Integer.toString(clen[n])).append("\r\n");
|
||||
break;
|
||||
case DQUOTED_CHUNKED:
|
||||
request.append("Transfer-Encoding: \"chunked\"\r\n");
|
||||
break;
|
||||
case BAD_CHUNKED:
|
||||
request.append("Transfer-Encoding: 'chunked'\r\n");
|
||||
break;
|
||||
case UNKNOWN_TE:
|
||||
request.append("Transfer-Encoding: bogus\r\n");
|
||||
break;
|
||||
default:
|
||||
request.append("Content-Length: ").append(contentLengths[n]).append("\r\n");
|
||||
break;
|
||||
}
|
||||
request.append("Content-Type: text/plain\r\n");
|
||||
request.append("Connection: close\r\n");
|
||||
request.append("\r\n");
|
||||
request.append("8;\r\n"); // chunk header
|
||||
request.append("abcdefgh"); // actual content of 8 bytes
|
||||
request.append("\r\n0;\r\n"); // last chunk
|
||||
|
||||
String rawResponse = connector.getResponse(request.toString());
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat("Response.status", response.getStatus(), is(HttpServletResponse.SC_BAD_REQUEST));
|
||||
}
|
||||
request.append("Content-Type: text/plain\r\n");
|
||||
request.append("\r\n");
|
||||
request.append("8;\r\n"); // chunk header
|
||||
request.append("abcdefgh"); // actual content of 8 bytes
|
||||
request.append("\r\n0;\r\n\r\n"); // last chunk
|
||||
|
||||
String rawResponse = connector.getResponse(request.toString());
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat("Response.status", response.getStatus(), is(HttpServletResponse.SC_BAD_REQUEST));
|
||||
}
|
||||
|
||||
/**
|
||||
* Examples of valid Chunked behaviors.
|
||||
*/
|
||||
public static Stream<Arguments> http11TransferEncodingChunked()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of(Arrays.asList("chunked, ")), // results in 1 entry
|
||||
Arguments.of(Arrays.asList(", chunked")),
|
||||
|
||||
// invalid tokens with chunked as last
|
||||
// no conflicts, chunked token is specified and is last, will result in chunked
|
||||
Arguments.of(Arrays.asList("bogus, chunked")),
|
||||
Arguments.of(Arrays.asList("'chunked', chunked")), // apostrophe characters with and without
|
||||
Arguments.of(Arrays.asList("identity, chunked")), // identity was removed in RFC2616 errata and has been dropped in RFC7230
|
||||
|
||||
// multiple headers
|
||||
Arguments.of(Arrays.asList("identity", "chunked")), // 2 separate headers
|
||||
Arguments.of(Arrays.asList("", "chunked")) // 2 separate headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Chunked Transfer-Encoding behavior indicated by
|
||||
* https://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("http11TransferEncodingChunked")
|
||||
public void testHttp11TransferEncodingChunked(List<String> tokens) throws Exception
|
||||
{
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.append("POST / HTTP/1.1\r\n");
|
||||
request.append("Host: local\r\n");
|
||||
tokens.forEach((token) -> request.append("Transfer-Encoding: ").append(token).append("\r\n"));
|
||||
request.append("Content-Type: text/plain\r\n");
|
||||
request.append("\r\n");
|
||||
request.append("8;\r\n"); // chunk header
|
||||
request.append("abcdefgh"); // actual content of 8 bytes
|
||||
request.append("\r\n0;\r\n\r\n"); // last chunk
|
||||
|
||||
System.out.println(request.toString());
|
||||
|
||||
String rawResponse = connector.getResponse(request.toString());
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat("Response.status (" + response.getReason() + ")", response.getStatus(), is(HttpServletResponse.SC_OK));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> http11TransferEncodingInvalidChunked()
|
||||
{
|
||||
return Stream.of(
|
||||
// == Results in 400 Bad Request
|
||||
Arguments.of(Arrays.asList("bogus", "identity")), // 2 separate headers
|
||||
|
||||
Arguments.of(Arrays.asList("bad")),
|
||||
Arguments.of(Arrays.asList("identity")), // identity was removed in RFC2616 errata and has been dropped in RFC7230
|
||||
Arguments.of(Arrays.asList("'chunked'")), // apostrophe characters
|
||||
Arguments.of(Arrays.asList("`chunked`")), // backtick "quote" characters
|
||||
Arguments.of(Arrays.asList("[chunked]")), // bracketed (seen as mistake in several REST libraries)
|
||||
Arguments.of(Arrays.asList("{chunked}")), // json'd (seen as mistake in several REST libraries)
|
||||
Arguments.of(Arrays.asList("\u201Cchunked\u201D")), // opening and closing (fancy) double quotes characters
|
||||
|
||||
// invalid tokens with chunked not as last
|
||||
Arguments.of(Arrays.asList("chunked, bogus")),
|
||||
Arguments.of(Arrays.asList("chunked, 'chunked'")),
|
||||
Arguments.of(Arrays.asList("chunked, identity")),
|
||||
Arguments.of(Arrays.asList("chunked, identity, chunked")), // duplicate chunked
|
||||
Arguments.of(Arrays.asList("chunked", "identity")), // 2 separate header lines
|
||||
|
||||
// multiple chunked tokens present
|
||||
Arguments.of(Arrays.asList("chunked", "identity", "chunked")), // 3 separate header lines
|
||||
Arguments.of(Arrays.asList("chunked", "chunked")), // 2 separate header lines
|
||||
Arguments.of(Arrays.asList("chunked, chunked")) // on same line
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bad Transfer-Encoding behavior as indicated by
|
||||
* https://tools.ietf.org/html/rfc7230#section-3.3.1
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("http11TransferEncodingInvalidChunked")
|
||||
public void testHttp11TransferEncodingInvalidChunked(List<String> tokens) throws Exception
|
||||
{
|
||||
HttpParser.LOG.info("badMessage: 400 Bad messages EXPECTED...");
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.append("POST / HTTP/1.1\r\n");
|
||||
request.append("Host: local\r\n");
|
||||
tokens.forEach((token) -> request.append("Transfer-Encoding: ").append(token).append("\r\n"));
|
||||
request.append("Content-Type: text/plain\r\n");
|
||||
request.append("\r\n");
|
||||
request.append("8;\r\n"); // chunk header
|
||||
request.append("abcdefgh"); // actual content of 8 bytes
|
||||
request.append("\r\n0;\r\n\r\n"); // last chunk
|
||||
|
||||
System.out.println(request.toString());
|
||||
|
||||
String rawResponse = connector.getResponse(request.toString());
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
assertThat("Response.status", response.getStatus(), is(HttpServletResponse.SC_BAD_REQUEST));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -549,11 +684,10 @@ public class HttpConnectionTest
|
|||
"Host: localhost\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n" +
|
||||
"A\r\n" +
|
||||
"0123456789\r\n" +
|
||||
"0\r\n");
|
||||
"0\r\n\r\n");
|
||||
|
||||
int offset = 0;
|
||||
offset = checkContains(response, offset, "HTTP/1.1 200");
|
||||
|
|
|
@ -24,6 +24,7 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.tools.HttpTester;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
|
@ -432,6 +433,21 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("httpVersions")
|
||||
public void testSetContentLengthAnd304Status(HttpVersion httpVersion) throws Exception
|
||||
{
|
||||
server.setHandler(new SetContentLength304Handler());
|
||||
server.start();
|
||||
|
||||
HttpTester.Response response = executeRequest(httpVersion);
|
||||
assertThat("response code", response.getStatus(), is(304));
|
||||
assertThat(response, containsHeaderValue("content-length", "32768"));
|
||||
byte[] content = response.getContentBytes();
|
||||
assertThat(content.length, is(0));
|
||||
assertFalse(response.isEarlyEOF());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("httpVersions")
|
||||
public void testSetContentLengthFlushAndWriteInsufficientBytes(HttpVersion httpVersion) throws Exception
|
||||
|
@ -519,6 +535,21 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
|
|||
}
|
||||
}
|
||||
|
||||
private class SetContentLength304Handler extends AbstractHandler
|
||||
{
|
||||
private SetContentLength304Handler()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentLength(32768);
|
||||
response.setStatus(HttpStatus.NOT_MODIFIED_304);
|
||||
}
|
||||
}
|
||||
|
||||
private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler
|
||||
{
|
||||
private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException)
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -106,6 +107,33 @@ public class PartialRFC2616Test
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test3_3_2()
|
||||
{
|
||||
try
|
||||
{
|
||||
String get = connector.getResponse("GET /R1 HTTP/1.0\n" + "Host: localhost\n" + "\n");
|
||||
checkContains(get, 0, "HTTP/1.1 200", "GET");
|
||||
checkContains(get, 0, "Content-Type: text/html", "GET _content");
|
||||
checkContains(get, 0, "<html>", "GET body");
|
||||
int cli = get.indexOf("Content-Length");
|
||||
String contentLength = get.substring(cli,get.indexOf("\r",cli));
|
||||
|
||||
String head = connector.getResponse("HEAD /R1 HTTP/1.0\n" + "Host: localhost\n" + "\n");
|
||||
checkContains(head, 0, "HTTP/1.1 200", "HEAD");
|
||||
checkContains(head, 0, "Content-Type: text/html", "HEAD _content");
|
||||
assertEquals(-1, head.indexOf("<html>"), "HEAD no body");
|
||||
checkContains(head, 0, contentLength, "3.3.2 HEAD");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
assertTrue(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test3_6_a() throws Exception
|
||||
{
|
||||
|
@ -324,12 +352,10 @@ public class PartialRFC2616Test
|
|||
"\n");
|
||||
offset = 0;
|
||||
response = endp.getResponse();
|
||||
offset = checkContains(response, offset, "HTTP/1.1 200 OK", "2. identity") + 10;
|
||||
offset = checkContains(response, offset, "/R1", "2. identity") + 3;
|
||||
offset = checkContains(response, offset, "HTTP/1.1 400 ", "2. identity") + 10;
|
||||
offset = 0;
|
||||
response = endp.getResponse();
|
||||
offset = checkContains(response, offset, "HTTP/1.1 200 OK", "2. identity") + 10;
|
||||
offset = checkContains(response, offset, "/R2", "2. identity") + 3;
|
||||
assertThat("There should be no next response as first one closed connection", response, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -361,7 +387,7 @@ public class PartialRFC2616Test
|
|||
"\n" +
|
||||
"abcdef");
|
||||
response = endp.getResponse();
|
||||
offset = checkContains(response, offset, "HTTP/1.1 400 Bad", "3. ignore c-l") + 1;
|
||||
offset = checkContains(response, offset, "HTTP/1.1 400 ", "3. ignore c-l") + 1;
|
||||
checkNotContained(response, offset, "/R2", "3. _content-length");
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,9 @@ package org.eclipse.jetty.server.handler;
|
|||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -45,17 +47,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
public class InetAccessHandlerTest
|
||||
{
|
||||
private static Server _server;
|
||||
private static ServerConnector _connector;
|
||||
private static ServerConnector _connector1;
|
||||
private static ServerConnector _connector2;
|
||||
private static InetAccessHandler _handler;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
_connector = new ServerConnector(_server);
|
||||
_connector.setName("http");
|
||||
_connector1 = new ServerConnector(_server);
|
||||
_connector1.setName("http_connector1");
|
||||
_connector2 = new ServerConnector(_server);
|
||||
_connector2.setName("http_connector2");
|
||||
_server.setConnectors(new Connector[]
|
||||
{_connector});
|
||||
{_connector1, _connector2});
|
||||
|
||||
_handler = new InetAccessHandler();
|
||||
_handler.setHandler(new AbstractHandler()
|
||||
|
@ -113,7 +118,21 @@ public class InetAccessHandlerTest
|
|||
}
|
||||
}
|
||||
|
||||
try (Socket socket = new Socket("127.0.0.1", _connector.getLocalPort());)
|
||||
List<String> codePerConnector = new ArrayList<>();
|
||||
for (String nextCode : code.split(";", -1))
|
||||
{
|
||||
if (nextCode.length() > 0)
|
||||
{
|
||||
codePerConnector.add(nextCode);
|
||||
}
|
||||
}
|
||||
|
||||
testConnector(_connector1.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
|
||||
testConnector(_connector2.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
|
||||
}
|
||||
|
||||
private void testConnector(int port, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException {
|
||||
try (Socket socket = new Socket("127.0.0.1", port);)
|
||||
{
|
||||
socket.setSoTimeout(5000);
|
||||
|
||||
|
@ -136,39 +155,68 @@ public class InetAccessHandlerTest
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for this test.
|
||||
* @return Format of data: include;exclude;includeConnectors;excludeConnectors;assertionStatusCodePerConnector
|
||||
*/
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
Object[][] data = new Object[][]
|
||||
{
|
||||
// Empty lists
|
||||
{"", "", "", "", "200"},
|
||||
// Empty lists 1
|
||||
{"", "", "", "", "200;200"},
|
||||
|
||||
// test simple filters
|
||||
{"127.0.0.1", "", "", "", "200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "", "200"},
|
||||
{"192.0.0.1", "", "", "", "403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "", "403"},
|
||||
{"127.0.0.1", "", "", "", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "", "200;200"},
|
||||
{"192.0.0.1", "", "", "", "403;403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "", "403;403"},
|
||||
|
||||
// test connector name filters
|
||||
{"127.0.0.1", "", "http", "", "200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "http", "", "200"},
|
||||
{"192.0.0.1", "", "http", "", "403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "http", "", "403"},
|
||||
// test includeConnector
|
||||
{"127.0.0.1", "", "http_connector1", "", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
|
||||
{"192.0.0.1", "", "http_connector1", "", "403;200"},
|
||||
{"192.0.0.1-192.0.0.254", "", "http_connector1", "", "403;200"},
|
||||
{"192.0.0.1", "", "http_connector2", "", "200;403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "http_connector2", "", "200;403"},
|
||||
|
||||
{"127.0.0.1", "", "nothttp", "", "403"},
|
||||
{"127.0.0.1-127.0.0.254", "", "nothttp", "", "403"},
|
||||
{"192.0.0.1", "", "nothttp", "", "403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "nothttp", "", "403"},
|
||||
// test includeConnector names where none of them match
|
||||
{"127.0.0.1", "", "nothttp", "", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
|
||||
{"192.0.0.1", "", "nothttp", "", "200;200"},
|
||||
{"192.0.0.1-192.0.0.254", "", "nothttp", "", "200;200"},
|
||||
|
||||
{"127.0.0.1", "", "", "http", "403"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "http", "403"},
|
||||
{"192.0.0.1", "", "", "http", "403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "http", "403"},
|
||||
// text excludeConnector
|
||||
{"127.0.0.1", "", "", "http_connector1", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "http_connector1", "200;200"},
|
||||
{"192.0.0.1", "", "", "http_connector1", "200;403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "http_connector1", "200;403"},
|
||||
{"192.0.0.1", "", "", "http_connector2", "403;200"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;200"},
|
||||
|
||||
{"127.0.0.1", "", "", "nothttp", "200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "nothttp", "200"},
|
||||
{"192.0.0.1", "", "", "nothttp", "403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "nothttp", "403"},
|
||||
// test excludeConnector where none of them match.
|
||||
{"127.0.0.1", "", "", "nothttp", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
|
||||
{"192.0.0.1", "", "", "nothttp", "403;403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
|
||||
|
||||
// both connectors are excluded
|
||||
{"127.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
|
||||
{"192.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
|
||||
{"192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
|
||||
|
||||
// both connectors are included
|
||||
{"127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
|
||||
{"192.0.0.1", "", "http_connector1;http_connector2", "", "403;403"},
|
||||
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "403;403"},
|
||||
|
||||
// exclude takes precedence over include
|
||||
{"127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
|
||||
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
|
||||
{"192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
|
||||
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
|
||||
};
|
||||
return Arrays.asList(data).stream().map(Arguments::of);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,12 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
|
|||
{
|
||||
return set.contains(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "CONTAINS";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,4 +233,34 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
|
|||
{
|
||||
return _includes.isEmpty() && _excludes.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Match items in combined IncludeExcludeSets.
|
||||
* @param item1 The item to match against set1
|
||||
* @param set1 A IncludeExcludeSet to match item1 against
|
||||
* @param item2 The item to match against set2
|
||||
* @param set2 A IncludeExcludeSet to match item2 against
|
||||
* @param <T1> The type of item1
|
||||
* @param <T2> The type of item2
|
||||
* @return True IFF <ul>
|
||||
* <li>Neither item is excluded from their respective sets</li>
|
||||
* <li>Both sets have no includes OR at least one of the items is included in its respective set</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static <T1,T2> boolean matchCombined(T1 item1, IncludeExcludeSet<?,T1> set1, T2 item2, IncludeExcludeSet<?,T2> set2)
|
||||
{
|
||||
Boolean match1 = set1.isIncludedAndNotExcluded(item1);
|
||||
Boolean match2 = set2.isIncludedAndNotExcluded(item2);
|
||||
|
||||
// if we are excluded from either set, then we do not match
|
||||
if (match1 == Boolean.FALSE || match2 == Boolean.FALSE)
|
||||
return false;
|
||||
|
||||
// If either set has any includes, then we must be included by one of them
|
||||
if (set1.hasIncludes() || set2.hasIncludes())
|
||||
return match1 == Boolean.TRUE || match2 == Boolean.TRUE;
|
||||
|
||||
// If not excluded and no includes, then we match
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -366,20 +366,11 @@ public abstract class RFC2616BaseTest
|
|||
req1.append("\n");
|
||||
req1.append("123\r\n");
|
||||
|
||||
req1.append("GET /echo/R2 HTTP/1.1\n");
|
||||
req1.append("Host: localhost\n");
|
||||
req1.append("Connection: close\n");
|
||||
req1.append("\n");
|
||||
|
||||
List<HttpTester.Response> responses = http.requests(req1);
|
||||
assertEquals(2, responses.size(), "Response Count");
|
||||
assertEquals(1, responses.size(), "Response Count");
|
||||
|
||||
HttpTester.Response response = responses.get(0);
|
||||
assertThat("4.4.2 Message Length / Response Code", response.getStatus(), is(HttpStatus.OK_200));
|
||||
assertThat("4.4.2 Message Length / Body", response.getContent(), Matchers.containsString("123\n"));
|
||||
response = responses.get(1);
|
||||
assertThat("4.4.2 Message Length / Response Code", response.getStatus(), is(HttpStatus.OK_200));
|
||||
assertEquals("", response.getContent(), "4.4.2 Message Length / No Body");
|
||||
assertThat("4.4.2 Message Length / Response Code", response.getStatus(), is(HttpStatus.BAD_REQUEST_400));
|
||||
|
||||
// 4.4.3 -
|
||||
// Client - do not send 'Content-Length' if entity-length
|
||||
|
|
Loading…
Reference in New Issue