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:
Greg Wilkins 2019-10-19 10:29:01 +11:00
commit 203eef4029
12 changed files with 404 additions and 96 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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())
{

View File

@ -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");

View File

@ -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)

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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