|
|
|
@ -23,6 +23,7 @@ package org.eclipse.jetty.server;
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.StringReader;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.EnumSet;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
@ -39,6 +40,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|
|
|
|
import org.eclipse.jetty.http.HttpParser;
|
|
|
|
|
import org.eclipse.jetty.http.HttpStatus;
|
|
|
|
|
import org.eclipse.jetty.http.HttpTester;
|
|
|
|
|
import org.eclipse.jetty.http.HttpVersion;
|
|
|
|
|
import org.eclipse.jetty.http.MimeTypes;
|
|
|
|
|
import org.eclipse.jetty.http.UriCompliance;
|
|
|
|
|
import org.eclipse.jetty.io.Content;
|
|
|
|
@ -67,6 +69,7 @@ import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|
|
|
|
import static org.hamcrest.Matchers.not;
|
|
|
|
|
import static org.hamcrest.Matchers.startsWith;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
|
|
|
|
|
public class HttpConnectionTest
|
|
|
|
@ -90,7 +93,6 @@ public class HttpConnectionTest
|
|
|
|
|
_connector.setIdleTimeout(5000);
|
|
|
|
|
_server.addConnector(_connector);
|
|
|
|
|
_server.setHandler(new DumpHandler());
|
|
|
|
|
_server.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
@ -103,6 +105,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testFragmentedChunk() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
@ -152,6 +155,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHttp09NoVersion() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
|
|
|
|
|
String request = "GET / HTTP/0.9\r\n\r\n";
|
|
|
|
|
String response = _connector.getResponse(request);
|
|
|
|
@ -171,6 +175,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHttp09NoHeaders() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
|
|
|
|
|
// header looking like another request is ignored
|
|
|
|
|
String request = "GET /one\r\nGET :/two\r\n\r\n";
|
|
|
|
@ -185,6 +190,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHttp09MultipleRequests() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
|
|
|
|
|
|
|
|
|
|
// Verify that pipelining does not work with HTTP/0.9.
|
|
|
|
@ -202,6 +208,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHttp11ChunkedBodyTruncation() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = "POST /?id=123 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: local\r\n" +
|
|
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
@ -251,6 +258,7 @@ public class HttpConnectionTest
|
|
|
|
|
@MethodSource("contentLengths")
|
|
|
|
|
public void testHttp11MultipleContentLength(int[] clen) throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
LOG.info("badMessage: 400 Bad messages EXPECTED...");
|
|
|
|
|
StringBuilder request = new StringBuilder();
|
|
|
|
|
request.append("POST / HTTP/1.1\r\n");
|
|
|
|
@ -298,6 +306,7 @@ public class HttpConnectionTest
|
|
|
|
|
@MethodSource("http11ContentLengthAndChunkedData")
|
|
|
|
|
public void testHttp11ContentLengthAndChunk(int[] contentLengths) throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
LOG.info("badMessage: 400 Bad messages EXPECTED...");
|
|
|
|
|
|
|
|
|
|
StringBuilder request = new StringBuilder();
|
|
|
|
@ -354,6 +363,7 @@ public class HttpConnectionTest
|
|
|
|
|
@MethodSource("http11TransferEncodingChunked")
|
|
|
|
|
public void testHttp11TransferEncodingChunked(List<String> tokens) throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
StringBuilder request = new StringBuilder();
|
|
|
|
|
request.append("POST / HTTP/1.1\r\n");
|
|
|
|
|
request.append("Host: local\r\n");
|
|
|
|
@ -405,6 +415,7 @@ public class HttpConnectionTest
|
|
|
|
|
@MethodSource("http11TransferEncodingInvalidChunked")
|
|
|
|
|
public void testHttp11TransferEncodingInvalidChunked(List<String> tokens) throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
LOG.info("badMessage: 400 Bad messages EXPECTED...");
|
|
|
|
|
StringBuilder request = new StringBuilder();
|
|
|
|
|
request.append("POST / HTTP/1.1\r\n");
|
|
|
|
@ -424,6 +435,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testNoPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET http://localhost:80 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost:80\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -437,6 +449,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testDate() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET / HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost:80\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -451,6 +464,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testSetDate() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /?date=1+Jan+1970 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost:80\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -465,6 +479,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadNoPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET http://localhost:80/../cheat HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost:80\r\n" +
|
|
|
|
|
"\r\n");
|
|
|
|
@ -474,6 +489,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testOKPathDotDotPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /ooops/../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
|
|
|
|
checkContains(response, 0, "HTTP/1.1 200 OK");
|
|
|
|
|
checkContains(response, 0, "pathInContext=/path");
|
|
|
|
@ -482,6 +498,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadPathDotDotPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /ooops/../../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
|
|
|
|
checkContains(response, 0, "HTTP/1.1 400 ");
|
|
|
|
|
checkContains(response, 0, "<th>MESSAGE:</th><td>Bad Request</td>");
|
|
|
|
@ -490,6 +507,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadDotDotPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET ../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
|
|
|
|
checkContains(response, 0, "HTTP/1.1 400 ");
|
|
|
|
|
checkContains(response, 0, "<th>MESSAGE:</th><td>Bad Request</td>");
|
|
|
|
@ -498,14 +516,16 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadSlashDotDotPath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
|
|
|
|
checkContains(response, 0, "HTTP/1.1 400 ");
|
|
|
|
|
checkContains(response, 0, "<th>MESSAGE:</th><td>Bad Request</td>");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void test09()
|
|
|
|
|
public void test09() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616_LEGACY);
|
|
|
|
|
LocalConnector.LocalEndPoint endp = _connector.executeRequest("GET /R1\n");
|
|
|
|
|
endp.waitUntilClosed();
|
|
|
|
@ -521,6 +541,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testSimple() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -535,6 +556,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyNotPersistent() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("""
|
|
|
|
|
GET /R1?empty=true HTTP/1.0\r
|
|
|
|
|
Host: localhost\r
|
|
|
|
@ -558,6 +580,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyPersistent() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("""
|
|
|
|
|
GET /R1?empty=true HTTP/1.0\r
|
|
|
|
|
Host: localhost\r
|
|
|
|
@ -585,6 +608,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyChunk() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
@ -602,6 +626,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testChunk() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
@ -622,6 +647,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testChunkTrailer() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
@ -643,6 +669,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testChunkNoTrailer() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
// Expect TimeoutException logged
|
|
|
|
|
_connector.setIdleTimeout(1000);
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
@ -663,6 +690,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHead() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String responsePOST = _connector.getResponse("POST /R1 HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -714,6 +742,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHeadChunked() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String responsePOST = _connector.getResponse("POST /R1?no-content-length=true HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"\r\n", false, 1, TimeUnit.SECONDS);
|
|
|
|
@ -763,6 +792,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testHostOnlyPort() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
|
response = _connector.getResponse("""
|
|
|
|
@ -777,6 +807,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadHostPort() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
|
response = _connector.getResponse("GET http://localhost:EXPECTED_NUMBER_FORMAT_EXCEPTION/ HTTP/1.1\r\n" +
|
|
|
|
@ -789,6 +820,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testNoHost() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
|
response = _connector.getResponse("""
|
|
|
|
@ -801,6 +833,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyHost() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
|
response = _connector.getResponse("GET / HTTP/1.1\r\n" +
|
|
|
|
@ -812,6 +845,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyHostAbsolute() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
|
response = _connector.getResponse("GET scheme:/// HTTP/1.1\r\n" +
|
|
|
|
@ -823,6 +857,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadURIencoding() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = _connector.getResponse("GET /bad/encoding%x HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
@ -834,6 +869,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Disabled("review this test. seems a security issue to fallback from utf-8 to iso-1, was there a reason to do that?")
|
|
|
|
|
public void testBadUTF8FallsbackTo8859() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
LOG.info("badMessage: bad encoding expected ...");
|
|
|
|
|
String response;
|
|
|
|
|
|
|
|
|
@ -853,6 +889,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAutoFlush() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
|
|
|
|
|
String response = _connector.getResponse("GET /R1 HTTP/1.1\r\n" +
|
|
|
|
@ -874,7 +911,6 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testEmptyFlush() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.stop();
|
|
|
|
|
_server.setHandler(new Handler.Abstract.NonBlocking()
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
@ -898,6 +934,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testUnconsumed() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
String requests =
|
|
|
|
|
"GET /R1?read=4 HTTP/1.1\r\n" +
|
|
|
|
@ -935,6 +972,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testUnconsumedTimeout() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
_connector.setIdleTimeout(500);
|
|
|
|
|
int offset = 0;
|
|
|
|
|
String requests =
|
|
|
|
@ -959,6 +997,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testUnconsumedErrorRead() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
String requests =
|
|
|
|
|
"GET /R1?read=1&error=499 HTTP/1.1\r\n" +
|
|
|
|
@ -992,6 +1031,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testUnconsumedErrorStream() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
String requests =
|
|
|
|
|
"GET /R1?error=599 HTTP/1.1\r\n" +
|
|
|
|
@ -1028,6 +1068,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testUnconsumedException() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
String requests = "GET /R1?read=1&ISE=true HTTP/1.1\r\n" +
|
|
|
|
|
"Host: localhost\r\n" +
|
|
|
|
@ -1059,6 +1100,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testConnection() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
@ -1091,6 +1133,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testOversizedBuffer() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
@ -1123,6 +1166,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testExcessiveHeader() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
|
|
|
|
|
StringBuilder request = new StringBuilder();
|
|
|
|
@ -1150,7 +1194,6 @@ public class HttpConnectionTest
|
|
|
|
|
}
|
|
|
|
|
final String longstr = str;
|
|
|
|
|
final CountDownLatch checkError = new CountDownLatch(1);
|
|
|
|
|
_server.stop();
|
|
|
|
|
_server.setHandler(new Handler.Abstract.NonBlocking()
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
@ -1189,7 +1232,6 @@ public class HttpConnectionTest
|
|
|
|
|
Arrays.fill(bytes, (byte)'X');
|
|
|
|
|
final String longstr = "thisisastringthatshouldreachover12kbytes-" + new String(bytes, StandardCharsets.ISO_8859_1) + "_Z_";
|
|
|
|
|
final CountDownLatch checkError = new CountDownLatch(1);
|
|
|
|
|
_server.stop();
|
|
|
|
|
_server.setHandler(new Handler.Abstract.NonBlocking()
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
@ -1224,6 +1266,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAsterisk() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = null;
|
|
|
|
|
try (StacklessLogging stackless = new StacklessLogging(HttpParser.class))
|
|
|
|
|
{
|
|
|
|
@ -1278,6 +1321,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testCONNECT() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String response = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
@ -1302,7 +1346,6 @@ public class HttpConnectionTest
|
|
|
|
|
String chunk1 = "0123456789ABCDEF";
|
|
|
|
|
String chunk2 = IntStream.range(0, 64).mapToObj(i -> chunk1).collect(Collectors.joining());
|
|
|
|
|
long dataLength = chunk1.length() + chunk2.length();
|
|
|
|
|
_server.stop();
|
|
|
|
|
_server.setHandler(new Handler.Abstract()
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
@ -1386,6 +1429,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testBadURI() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/doubleSlash// HTTP/1.0
|
|
|
|
|
Host: whatever
|
|
|
|
@ -1400,6 +1444,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousParameters() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/..;/path HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1420,6 +1465,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousSegments() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/%2e%2e/path HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1436,6 +1482,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousSeparators() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/%2f/path HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1454,6 +1501,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousPaths() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /unnormal/.././path/ambiguous%2f%2e%2e/%2e;/info HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1475,6 +1523,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousEncoding() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/encoded/%25/path HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1500,6 +1549,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testAmbiguousDoubleSlash() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET /ambiguous/doubleSlash// HTTP/1.0
|
|
|
|
|
Host: whatever
|
|
|
|
@ -1518,6 +1568,7 @@ public class HttpConnectionTest
|
|
|
|
|
@Test
|
|
|
|
|
public void testRelativePath() throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.start();
|
|
|
|
|
String request = """
|
|
|
|
|
GET foo/bar HTTP/1.0\r
|
|
|
|
|
Host: whatever\r
|
|
|
|
@ -1538,4 +1589,143 @@ public class HttpConnectionTest
|
|
|
|
|
{
|
|
|
|
|
assertThat(s.substring(offset), Matchers.not(Matchers.containsString(c)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Stream<Arguments> connectionBehavior()
|
|
|
|
|
{
|
|
|
|
|
List<Arguments> cases = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
// --- VALID HTTP/1.0 SPEC REQUESTS ---
|
|
|
|
|
|
|
|
|
|
// Request does not send connection header.
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, null, null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "keep-alive", null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Keep-Alive", null));
|
|
|
|
|
|
|
|
|
|
// Request sends "keep-alive"
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", null, "keep-alive"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", "keep-alive", "keep-alive"));
|
|
|
|
|
|
|
|
|
|
// --- INVALID HTTP/1.0 SPEC REQUESTS ---
|
|
|
|
|
|
|
|
|
|
// Request sends invalid value "close" (on HTTP/1.0)
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "close", null, null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "close", "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "close", "keep-alive", null));
|
|
|
|
|
|
|
|
|
|
// --- INVALID HTTP RESPONSE HEADERS ---
|
|
|
|
|
// this is when the servlet is setting headers that are in conflict with
|
|
|
|
|
// the spec and each other.
|
|
|
|
|
|
|
|
|
|
// Request does not set "connection" header.
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Request sends invalid value "close" (on HTTP/1.0)
|
|
|
|
|
// keep-alive is forbidden when not persistent
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "close", "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "close", "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Request sends "keep-alive"
|
|
|
|
|
// connection lists are preserved per hop-by-hop rules
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Other connection values
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Other, Fields", "Other, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Other, close, Fields", "Other, close, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Other, keep-alive, Fields", "Other, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, null, "Other, close, keep-alive, Fields", "Other, close, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_0, "keep-alive", "Other, keep-alive, Fields", "Other, keep-alive, Fields"));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- VALID HTTP/1.1 SPEC REQUESTS ---
|
|
|
|
|
|
|
|
|
|
// Request does not send connection header.
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, null, null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "keep-alive", null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Keep-Alive", null));
|
|
|
|
|
|
|
|
|
|
// Request sends value "close"
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", null, "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", "keep-alive", "close"));
|
|
|
|
|
|
|
|
|
|
// --- INVALID HTTP/1.0 SPEC REQUESTS ---
|
|
|
|
|
|
|
|
|
|
// Request sends invalid "keep-alive" header value (on HTTP/1.1)
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "keep-alive", null, null));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "keep-alive", "close", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "keep-alive", "keep-alive", null));
|
|
|
|
|
|
|
|
|
|
// --- INVALID HTTP RESPONSE HEADERS ---
|
|
|
|
|
// this is when the servlet is setting headers that are in conflict with
|
|
|
|
|
// the spec and each other.
|
|
|
|
|
|
|
|
|
|
// Request does not set "connection" header.
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Request sends value "close"
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Request sends invalid "keep-alive" header value (on HTTP/1.1)
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "keep-alive", "close, keep-alive", "close"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "keep-alive", "keep-alive, close", "close"));
|
|
|
|
|
|
|
|
|
|
// Other connection values
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Other, Fields", "Other, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Other, close, Fields", "Other, close, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Other, keep-alive, Fields", "Other, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, null, "Other, close, keep-alive, Fields", "Other, close, Fields"));
|
|
|
|
|
cases.add(Arguments.of(HttpVersion.HTTP_1_1, "close", "Other, Fields", "Other, Fields|close"));
|
|
|
|
|
|
|
|
|
|
return cases.stream();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
|
@MethodSource("connectionBehavior")
|
|
|
|
|
public void testConnectionBehavior(HttpVersion version, String requestConnectionHeader, String responseConnectionHeader, String expectedConnectionHeader) throws Exception
|
|
|
|
|
{
|
|
|
|
|
_server.setHandler(new Handler.Abstract()
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
|
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
|
|
|
|
{
|
|
|
|
|
if (responseConnectionHeader != null)
|
|
|
|
|
response.getHeaders().put(HttpHeader.CONNECTION, responseConnectionHeader);
|
|
|
|
|
|
|
|
|
|
callback.succeeded();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_server.start();
|
|
|
|
|
|
|
|
|
|
StringBuilder rawRequest = new StringBuilder();
|
|
|
|
|
rawRequest.append("GET /nothing %s\r\n".formatted(version.asString()));
|
|
|
|
|
rawRequest.append("Host: test\r\n");
|
|
|
|
|
if (requestConnectionHeader != null)
|
|
|
|
|
rawRequest.append("Connection: ").append(requestConnectionHeader).append("\r\n");
|
|
|
|
|
rawRequest.append("\r\n");
|
|
|
|
|
|
|
|
|
|
String rawResponse = _connector.getResponse(rawRequest.toString());
|
|
|
|
|
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
|
|
|
|
|
|
|
|
|
assertThat(response.getStatus(), is(200));
|
|
|
|
|
assertThat(response.getVersion(), is(HttpVersion.HTTP_1_1));
|
|
|
|
|
if (expectedConnectionHeader == null)
|
|
|
|
|
assertFalse(response.getMetaData().getHttpFields().contains(HttpHeader.CONNECTION));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
List<String> actual = response.getValuesList(HttpHeader.CONNECTION);
|
|
|
|
|
for (String expected : expectedConnectionHeader.split("\\|"))
|
|
|
|
|
assertThat(actual.remove(0), is(expected));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|