Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Greg Wilkins 2021-09-23 17:37:26 +10:00
commit 8c283a733d
8 changed files with 198 additions and 28 deletions

View File

@ -58,6 +58,12 @@
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-rewrite</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>

View File

@ -26,6 +26,8 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.VirtualHostRuleContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
@ -35,6 +37,9 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.junit.jupiter.api.Assertions.assertEquals;
public class BalancerServletTest
@ -106,12 +111,17 @@ public class BalancerServletTest
return server.getURI().getPort();
}
protected byte[] sendRequestToBalancer(String path) throws Exception
protected ContentResponse getBalancedResponse(String path) throws Exception
{
ContentResponse response = client.newRequest("localhost", getServerPort(balancer))
return client.newRequest("localhost", getServerPort(balancer))
.path(CONTEXT_PATH + SERVLET_PATH + path)
.timeout(5, TimeUnit.SECONDS)
.send();
}
protected byte[] sendRequestToBalancer(String path) throws Exception
{
ContentResponse response = getBalancedResponse(path);
return response.getContent();
}
@ -155,12 +165,44 @@ public class BalancerServletTest
assertEquals("success", msg);
}
@Test
public void testRewrittenBalancerWithEncodedURI() throws Exception
{
startBalancer(DumpServlet.class);
balancer.stop();
RewriteHandler rewrite = new RewriteHandler();
rewrite.setHandler(balancer.getHandler());
balancer.setHandler(rewrite);
rewrite.setRewriteRequestURI(true);
rewrite.addRule(new VirtualHostRuleContainer());
balancer.start();
ContentResponse response = getBalancedResponse("/test/%0A");
assertThat(response.getStatus(), is(200));
assertThat(response.getContentAsString(), containsString("requestURI='/context/mapping/test/%0A'"));
assertThat(response.getContentAsString(), containsString("servletPath='/mapping'"));
assertThat(response.getContentAsString(), containsString("pathInfo='/test/\n'"));
}
private String readFirstLine(byte[] responseBytes) throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
return reader.readLine();
}
public static final class DumpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
resp.getWriter().printf("requestURI='%s'%n", req.getRequestURI());
resp.getWriter().printf("servletPath='%s'%n", req.getServletPath());
resp.getWriter().printf("pathInfo='%s'%n", req.getPathInfo());
resp.getWriter().flush();
}
}
public static final class CounterServlet extends HttpServlet
{
private final AtomicInteger counter = new AtomicInteger();

View File

@ -389,23 +389,20 @@ public class ResourceService
// Redirect to directory
if (!endsWithSlash)
{
StringBuffer buf = request.getRequestURL();
synchronized (buf)
StringBuilder buf = new StringBuilder(request.getRequestURI());
int param = buf.lastIndexOf(";");
if (param < 0 || buf.lastIndexOf("/", param) > 0)
buf.append('/');
else
buf.insert(param, '/');
String q = request.getQueryString();
if (q != null && q.length() != 0)
{
int param = buf.lastIndexOf(";");
if (param < 0)
buf.append('/');
else
buf.insert(param, '/');
String q = request.getQueryString();
if (q != null && q.length() != 0)
{
buf.append('?');
buf.append(q);
}
response.setContentLength(0);
response.sendRedirect(response.encodeRedirectURL(buf.toString()));
buf.append('?');
buf.append(q);
}
response.setContentLength(0);
response.sendRedirect(response.encodeRedirectURL(buf.toString()));
return;
}

View File

@ -74,6 +74,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.containsHeader;
import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.containsHeaderValue;
import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.headerValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
@ -881,20 +882,25 @@ public class DefaultServletTest
rawResponse = connector.getResponse("GET /context/dir/ HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, containsHeaderValue("Location", "http://0.0.0.0/context/dir/index.html"));
assertThat(response, headerValue("Location", "http://0.0.0.0/context/dir/index.html"));
createFile(inde, "<h1>Hello Inde</h1>");
rawResponse = connector.getResponse("GET /context/dir HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, headerValue("Location", "http://0.0.0.0/context/dir/"));
rawResponse = connector.getResponse("GET /context/dir/ HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, containsHeaderValue("Location", "http://0.0.0.0/context/dir/index.html"));
assertThat(response, headerValue("Location", "http://0.0.0.0/context/dir/index.html"));
if (deleteFile(index))
{
rawResponse = connector.getResponse("GET /context/dir/ HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, containsHeaderValue("Location", "http://0.0.0.0/context/dir/index.htm"));
assertThat(response, headerValue("Location", "http://0.0.0.0/context/dir/index.htm"));
if (deleteFile(inde))
{
@ -905,6 +911,46 @@ public class DefaultServletTest
}
}
@Test
public void testRelativeRedirect() throws Exception
{
Path dir = docRoot.resolve("dir");
FS.ensureDirExists(dir);
Path index = dir.resolve("index.html");
createFile(index, "<h1>Hello Index</h1>");
context.addAliasCheck((p, r) -> true);
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setRelativeRedirectAllowed(true);
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("dirAllowed", "false");
defholder.setInitParameter("redirectWelcome", "true");
defholder.setInitParameter("welcomeServlets", "false");
defholder.setInitParameter("gzip", "false");
defholder.setInitParameter("maxCacheSize", "1024000");
defholder.setInitParameter("maxCachedFileSize", "512000");
defholder.setInitParameter("maxCachedFiles", "100");
String rawResponse;
HttpTester.Response response;
rawResponse = connector.getResponse("GET /context/dir HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, headerValue("Location", "/context/dir/"));
rawResponse = connector.getResponse("GET /context/dir/ HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, headerValue("Location", "/context/dir/index.html"));
rawResponse = connector.getResponse("GET /context/dir/index.html/ HTTP/1.0\r\n\r\n");
response = HttpTester.parseResponse(rawResponse);
assertThat(response.toString(), response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
assertThat(response, headerValue("Location", "/context/dir/index.html"));
}
/**
* Ensure that oddball directory names are served with proper escaping
*/
@ -1477,8 +1523,7 @@ public class DefaultServletTest
assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "" + body.length()));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "text/plain"));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "charset="));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "text/plain;charset=UTF-8"));
assertThat(body, containsString("Extra Info"));
rawResponse = connector.getResponse("GET /context/image.jpg HTTP/1.0\r\n\r\n");
@ -1486,8 +1531,7 @@ public class DefaultServletTest
assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "" + body.length()));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "image/jpeg"));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "charset=utf-8"));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "image/jpeg;charset=utf-8"));
assertThat(body, containsString("Extra Info"));
server.stop();
@ -1501,7 +1545,6 @@ public class DefaultServletTest
assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_TYPE, "text/plain"));
assertThat(response, not(containsHeaderValue(HttpHeader.CONTENT_TYPE, "charset=")));
assertThat(body, containsString("Extra Info"));
}

View File

@ -115,7 +115,7 @@ public class URIUtil
buf = new StringBuilder(path.length() * 2);
break loop;
default:
if (c > 127)
if (c < 0x20 || c >= 0x7f)
{
bytes = path.getBytes(URIUtil.__CHARSET);
buf = new StringBuilder(path.length() * 2);
@ -188,7 +188,7 @@ public class URIUtil
continue;
default:
if (c > 127)
if (c < 0x20 || c >= 0x7f)
{
bytes = path.getBytes(URIUtil.__CHARSET);
break loop;
@ -256,7 +256,7 @@ public class URIUtil
buf.append("%7D");
continue;
default:
if (c < 0)
if (c < 0x20 || c >= 0x7f)
{
buf.append('%');
TypeUtil.toHex(c, buf);

View File

@ -64,6 +64,7 @@ public class URIUtilTest
{
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
return Stream.of(
Arguments.of("/foo/\n/bar", "/foo/%0A/bar"),
Arguments.of("/foo%23+;,:=/b a r/?info ", "/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20"),
Arguments.of("/context/'list'/\"me\"/;<script>window.alert('xss');</script>",
"/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E"),
@ -727,4 +728,25 @@ public class URIUtilTest
{
assertThat(URIUtil.addQueries(param1, param2), matcher);
}
@Test
public void testEncodeDecodeVisibleOnly()
{
StringBuilder builder = new StringBuilder();
builder.append('/');
for (char i = 0; i < 0x7FFF; i++)
builder.append(i);
String path = builder.toString();
String encoded = URIUtil.encodePath(path);
// Check endoded is visible
for (char c : encoded.toCharArray())
{
assertTrue(c > 0x20 && c < 0x80);
assertFalse(Character.isWhitespace(c));
assertFalse(Character.isISOControl(c));
}
// check decode to original
String decoded = URIUtil.decodePath(encoded);
assertEquals(path, decoded);
}
}

View File

@ -0,0 +1,55 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.http.tools.matchers;
import java.util.Objects;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class HttpFieldsHeaderValue extends TypeSafeMatcher<HttpFields>
{
private final String keyName;
private final String value;
public HttpFieldsHeaderValue(String keyName, String value)
{
this.keyName = keyName;
this.value = value;
}
public HttpFieldsHeaderValue(HttpHeader header, String value)
{
this(header.asString(), value);
}
@Override
public void describeTo(Description description)
{
description.appendText("expecting http header ").appendValue(keyName).appendText(" with value ").appendValue(value);
}
@Override
protected boolean matchesSafely(HttpFields fields)
{
HttpField field = fields.getField(this.keyName);
if (field == null)
return false;
return Objects.equals(this.value, field.getValue());
}
}

View File

@ -29,6 +29,11 @@ public class HttpFieldsMatchers
return new HttpFieldsContainsHeaderKey(header);
}
public static Matcher<HttpFields> headerValue(String keyName, String value)
{
return new HttpFieldsHeaderValue(keyName, value);
}
public static Matcher<HttpFields> containsHeaderValue(String keyName, String value)
{
return new HttpFieldsContainsHeaderValue(keyName, value);