Jetty 12.0.x 9444 servlet paths fully decoded (#9479)

getServletPath and getPathInfo will never return an encoded path segment. Instead, they will throw an IllegalArgumentException if they are called when there is a URI with violations.

Signed-off-by: gregw <gregw@webtide.com>
Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Greg Wilkins 2023-03-11 14:13:09 +01:00 committed by GitHub
parent bcd175a156
commit bd0186c2f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 421 additions and 283 deletions

View File

@ -99,19 +99,29 @@ public final class UriCompliance implements ComplianceViolation.Mode
}
}
public static final Set<Violation> AMBIGUOUS_VIOLATIONS = EnumSet.of(
Violation.AMBIGUOUS_EMPTY_SEGMENT,
Violation.AMBIGUOUS_PATH_ENCODING,
Violation.AMBIGUOUS_PATH_PARAMETER,
Violation.AMBIGUOUS_PATH_SEGMENT,
Violation.AMBIGUOUS_PATH_SEPARATOR);
/**
* The default compliance mode that extends <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a> compliance with
* additional violations to avoid most ambiguous URIs.
* This mode does allow {@link Violation#AMBIGUOUS_PATH_SEPARATOR}, but disallows all out {@link Violation}s.
* Compliance mode that exactly follows <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>,
* excluding all URI Violations.
*/
public static final UriCompliance DEFAULT = new UriCompliance("DEFAULT",
of(Violation.AMBIGUOUS_PATH_SEPARATOR,
Violation.AMBIGUOUS_PATH_ENCODING));
public static final UriCompliance RFC3986 = new UriCompliance("RFC3986", noneOf(Violation.class));
/**
* The default compliance mode allows no violations from <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>
* and is equivalent to {@link #RFC3986} compliance.
*/
public static final UriCompliance DEFAULT = RFC3986;
/**
* LEGACY compliance mode that models Jetty-9.4 behavior by allowing {@link Violation#AMBIGUOUS_PATH_SEGMENT},
* {@link Violation#AMBIGUOUS_EMPTY_SEGMENT}, {@link Violation#AMBIGUOUS_PATH_SEPARATOR}, {@link Violation#AMBIGUOUS_PATH_ENCODING}
* and {@link Violation#UTF16_ENCODINGS}
* and {@link Violation#UTF16_ENCODINGS}.
*/
public static final UriCompliance LEGACY = new UriCompliance("LEGACY",
of(Violation.AMBIGUOUS_PATH_SEGMENT,
@ -121,24 +131,12 @@ public final class UriCompliance implements ComplianceViolation.Mode
Violation.UTF16_ENCODINGS));
/**
* Compliance mode that exactly follows <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>,
* including allowing all additional ambiguous URI Violations.
*/
public static final UriCompliance RFC3986 = new UriCompliance("RFC3986", allOf(Violation.class));
/**
* Compliance mode that follows <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>
* plus it does not allow any ambiguous URI {@link Violation}s.
*/
public static final UriCompliance RFC3986_UNAMBIGUOUS = new UriCompliance("RFC3986_UNAMBIGUOUS", noneOf(Violation.class));
/**
* Compliance mode that allows all URI Violations, including allowing ambiguous paths in non canonicalized form.
* Compliance mode that allows all URI Violations, including allowing ambiguous paths in non-canonical form.
*/
public static final UriCompliance UNSAFE = new UriCompliance("UNSAFE", allOf(Violation.class));
private static final AtomicInteger __custom = new AtomicInteger();
private static final List<UriCompliance> KNOWN_MODES = List.of(DEFAULT, LEGACY, RFC3986, RFC3986_UNAMBIGUOUS, UNSAFE);
private static final List<UriCompliance> KNOWN_MODES = List.of(DEFAULT, LEGACY, RFC3986, UNSAFE);
public static UriCompliance valueOf(String name)
{

View File

@ -38,7 +38,7 @@ import org.eclipse.jetty.util.resource.Resource;
public interface Context extends Attributes, Decorator, Executor
{
/**
* @return the context path of this Context
* @return the encoded context path of this Context
*/
String getContextPath();
@ -94,18 +94,46 @@ public interface Context extends Attributes, Decorator, Executor
void run(Runnable task, Request request);
/**
* <p>Returns a URI path scoped to this Context.</p>
* <p>For example, if the context path is {@code /ctx} then a
* full path of {@code /ctx/foo/bar} will return {@code /foo/bar}.</p>
*
* @param fullPath a full URI path
* <p>Returns the URI path scoped to this Context.</p>
* @see #getPathInContext(String, String)
* @param canonicallyEncodedPath a full URI path that should be canonically encoded as
* per {@link org.eclipse.jetty.util.URIUtil#canonicalPath(String)}
* @return the URI path scoped to this Context, or {@code null} if the full path does not match this Context.
* The empty string is returned if the full path is exactly the context path.
*/
String getPathInContext(String fullPath);
default String getPathInContext(String canonicallyEncodedPath)
{
return getPathInContext(getContextPath(), canonicallyEncodedPath);
}
/**
* @return a non-{@code null} temporary directory, configured either for the context, the server or the JVM
*/
File getTempDirectory();
/**
* <p>Returns the URI path scoped to the passed context path.</p>
* <p>For example, if the context path passed is {@code /ctx} then a
* path of {@code /ctx/foo/bar} will return {@code /foo/bar}.</p>
*
* @param encodedContextPath The context path that should be canonically encoded as
* per {@link org.eclipse.jetty.util.URIUtil#canonicalPath(String)}.
* @param encodedPath a full URI path that should be canonically encoded as
* per {@link org.eclipse.jetty.util.URIUtil#canonicalPath(String)}.
* @return the URI {@code encodedPath} scoped to the {@code encodedContextPath},
* or {@code null} if the {@code encodedPath} does not match the context.
* The empty string is returned if the {@code encodedPath} is exactly the {@code encodedContextPath}.
*/
static String getPathInContext(String encodedContextPath, String encodedPath)
{
if (encodedContextPath.length() == 0 || "/".equals(encodedContextPath))
return encodedPath;
if (encodedContextPath.length() > encodedPath.length() || !encodedPath.startsWith(encodedContextPath))
return null;
if (encodedPath.length() == encodedContextPath.length())
return "";
if (encodedPath.charAt(encodedContextPath.length()) != '/')
return null;
return encodedPath.substring(encodedContextPath.length());
}
}

View File

@ -35,7 +35,6 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Callback;
@ -726,14 +725,14 @@ public interface Request extends Attributes, Content.Source
* {@code newPathInContext=/newPath}, the returned HttpURI is {@code http://host/ctx/newPath?a=b}.</p>
*
* @param request The request to base the new HttpURI on.
* @param newPathInContext The new path in context for the new HttpURI
* @param newEncodedPathInContext The new path in context for the new HttpURI
* @return A new immutable HttpURI with the path in context replaced, but query string and path
* parameters retained.
*/
static HttpURI newHttpURIFrom(Request request, String newPathInContext)
static HttpURI newHttpURIFrom(Request request, String newEncodedPathInContext)
{
return HttpURI.build(request.getHttpURI())
.path(URIUtil.addPaths(getContextPath(request), newPathInContext))
.path(URIUtil.addPaths(getContextPath(request), newEncodedPathInContext))
.asImmutable();
}
}

View File

@ -914,9 +914,9 @@ public class Server extends Handler.Wrapper implements Attributes
}
@Override
public String getPathInContext(String fullPath)
public String getPathInContext(String canonicallyEncodedPath)
{
return fullPath;
return canonicallyEncodedPath;
}
@Override

View File

@ -1297,17 +1297,9 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
}
@Override
public String getPathInContext(String fullPath)
public String getPathInContext(String canonicallyEncodedPath)
{
if (_rootContext)
return fullPath;
if (!fullPath.startsWith(_contextPath))
return null;
if (fullPath.length() == _contextPath.length())
return "";
if (fullPath.charAt(_contextPath.length()) != '/')
return null;
return fullPath.substring(_contextPath.length());
return _rootContext ? canonicallyEncodedPath : Context.getPathInContext(_contextPath, canonicallyEncodedPath);
}
}

View File

@ -1390,7 +1390,7 @@ public class HttpConnectionTest
Host: whatever
""";
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986_UNAMBIGUOUS);
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
@ -1409,6 +1409,10 @@ public class HttpConnectionTest
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from(EnumSet.of(UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER)));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
}
@ -1425,7 +1429,7 @@ public class HttpConnectionTest
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
}
@Test
@ -1437,13 +1441,13 @@ public class HttpConnectionTest
\r
""";
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(new UriCompliance("Test", EnumSet.noneOf(UriCompliance.Violation.class)));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from("DEFAULT,AMBIGUOUS_PATH_SEPARATOR"));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
}
@Test
@ -1476,11 +1480,13 @@ public class HttpConnectionTest
\r
""";
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from(EnumSet.of(UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING)));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
@ -1500,12 +1506,10 @@ public class HttpConnectionTest
""";
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986_UNAMBIGUOUS);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 400"));
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
assertThat(_connector.getResponse(request), startsWith("HTTP/1.1 200"));
}

View File

@ -88,9 +88,7 @@ public class RequestTest
\r
""";
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request));
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContent(), containsString("httpURI.path=/fo%6f%2fbar"));
assertThat(response.getContent(), containsString("pathInContext=/foo%2Fbar"));
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
}
@Test

View File

@ -28,7 +28,6 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
@ -479,51 +478,6 @@ public final class URIUtil
* @see #normalizePath(String)
*/
public static String decodePath(String path, int offset, int length)
{
return decodePath(path, offset, length, Utf8StringBuilder::append);
}
/**
* Decodes a percent-encoded URI path (assuming UTF-8 characters) and strips path parameters, but leaves reserved path characters percent-encoded.
* @param path A String holding the URI path to decode
* @see #canonicalPath(String)
* @see #normalizePath(String)
*/
public static String safeDecodePath(String path)
{
return safeDecodePath(path, 0, path.length());
}
/**
* Decodes a percent-encoded URI path (assuming UTF-8 characters) and strips path parameters, but leaves reserved path characters percent-encoded.
* @param path A String holding the URI path to decode
* @param offset The start of the URI within the path string
* @param length The length of the URI within the path string
* @see #canonicalPath(String)
* @see #normalizePath(String)
*/
public static String safeDecodePath(String path, int offset, int length)
{
return decodePath(path, offset, length, URIUtil::safePathAppend);
}
private static void safePathAppend(Utf8StringBuilder builder, byte b)
{
switch (b)
{
case '/' -> builder.append("%2F");
case '%' -> builder.append("%25");
case '?' -> builder.append("%3F");
default -> builder.append(b);
}
}
/**
* Decode a URI path and strip parameters of UTF-8 path
* @see #canonicalPath(String)
* @see #normalizePath(String)
*/
public static String decodePath(String path, int offset, int length, BiConsumer<Utf8StringBuilder, Byte> decoder)
{
try
{
@ -550,13 +504,13 @@ public final class URIUtil
String str = new String(codePoints, 0, 1);
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
for (byte b: bytes)
decoder.accept(builder, b);
builder.append(b);
i += 5;
}
else
{
byte b = (byte)(0xff & (TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(path.charAt(i + 2))));
decoder.accept(builder, b);
builder.append(b);
i += 2;
}
}
@ -706,40 +660,41 @@ public final class URIUtil
* <p>
* For example the path {@code /fo %2fo/b%61r} will be normalized to {@code /fo%20%2Fo/bar},
* whilst {@link #decodePath(String)} would result in the ambiguous and URI illegal {@code /fo /o/bar}.
* @param encodedPath An encoded URI path
* @return the canonical path or null if it is non-normal
* @see #decodePath(String)
* @see #normalizePath(String)
* @see URI
*/
public static String canonicalPath(String path)
public static String canonicalPath(String encodedPath)
{
if (path == null)
if (encodedPath == null)
return null;
try
{
Utf8StringBuilder builder = null;
int end = path.length();
int end = encodedPath.length();
boolean slash = true;
boolean normal = true;
for (int i = 0; i < end; i++)
{
char c = path.charAt(i);
char c = encodedPath.charAt(i);
switch (c)
{
case '%':
if (builder == null)
{
builder = new Utf8StringBuilder(path.length());
builder.append(path, 0, i);
builder = new Utf8StringBuilder(encodedPath.length());
builder.append(encodedPath, 0, i);
}
if ((i + 2) < end)
{
char u = path.charAt(i + 1);
char u = encodedPath.charAt(i + 1);
if (u == 'u')
{
// UTF16 encoding is only supported with UriCompliance.Violation.UTF16_ENCODINGS.
int code = TypeUtil.parseInt(path, i + 2, 4, 16);
int code = TypeUtil.parseInt(encodedPath, i + 2, 4, 16);
if (isSafeElseEncode(code, builder))
{
char[] chars = Character.toChars(code);
@ -755,7 +710,7 @@ public final class URIUtil
}
else
{
int code = TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(path.charAt(i + 2));
int code = TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(encodedPath.charAt(i + 2));
if (isSafeElseEncode(code, builder))
{
builder.append((byte)(0xff & code));
@ -774,13 +729,13 @@ public final class URIUtil
case ';':
if (builder == null)
{
builder = new Utf8StringBuilder(path.length());
builder.append(path, 0, i);
builder = new Utf8StringBuilder(encodedPath.length());
builder.append(encodedPath, 0, i);
}
while (++i < end)
{
if (path.charAt(i) == '/')
if (encodedPath.charAt(i) == '/')
{
builder.append('/');
break;
@ -803,8 +758,8 @@ public final class URIUtil
default:
if (builder == null && !isSafe(c))
{
builder = new Utf8StringBuilder(path.length());
builder.append(path, 0, i);
builder = new Utf8StringBuilder(encodedPath.length());
builder.append(encodedPath, 0, i);
}
if (builder != null && isSafeElseEncode(c, builder))
@ -815,13 +770,13 @@ public final class URIUtil
slash = c == '/';
}
String canonical = (builder != null) ? builder.toString() : path;
String canonical = (builder != null) ? builder.toString() : encodedPath;
return normal ? canonical : normalizePath(canonical);
}
catch (NotUtf8Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("{} {}", path, e.toString());
LOG.debug("{} {}", encodedPath, e.toString());
throw e;
}
catch (IllegalArgumentException e)

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.util;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.Normalizer;
@ -37,8 +36,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@ -95,60 +92,79 @@ public class URIUtilTest
public static Stream<Arguments> decodePathSource()
{
List<Arguments> arguments = new ArrayList<>();
arguments.add(Arguments.of("/foo/bar", "/foo/bar", "/foo/bar"));
return Stream.of(
Arguments.of("/foo/bar", "/foo/bar", "/foo/bar"),
// Simple encoding
arguments.add(Arguments.of("/f%20%6f/b%20r", "/f%20o/b%20r", "/f o/b r"));
Arguments.of("/f%20%6f/b%20r", "/f%20o/b%20r", "/f o/b r"),
// UTF8 and unicode handling
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
arguments.add(Arguments.of("/foo/b\u00e4\u00e4", "/foo/b\u00e4\u00e4", "/foo/b\u00e4\u00e4"));
arguments.add(Arguments.of("/f%d8%a9%D8%A9/bar", "/f\u0629\u0629/bar", "/f\u0629\u0629/bar"));
Arguments.of("/foo/b\u00e4\u00e4", "/foo/bää", "/foo/bää"),
Arguments.of("/f\u20ac\u20ac/bar", "/f€€/bar", "/f€€/bar"),
Arguments.of("/f%d8%a9%D8%A9/bar", "/f\u0629\u0629/bar", "/f\u0629\u0629/bar"),
// Encoded UTF-8 unicode (euro)
Arguments.of("/f%e2%82%ac%E2%82%AC/bar", "/f€€/bar", "/f€€/bar"),
// Encoded delimiters
arguments.add(Arguments.of("/foo%2fbar", "/foo%2Fbar", "/foo/bar"));
arguments.add(Arguments.of("/foo%252fbar", "/foo%252fbar", "/foo%2fbar"));
arguments.add(Arguments.of("/foo%3bbar", "/foo%3Bbar", "/foo;bar"));
arguments.add(Arguments.of("/foo%3fbar", "/foo%3Fbar", "/foo?bar"));
Arguments.of("/foo%2fbar", "/foo%2Fbar", "/foo/bar"),
Arguments.of("/foo%252fbar", "/foo%252fbar", "/foo%2fbar"),
Arguments.of("/foo%3bbar", "/foo%3Bbar", "/foo;bar"),
Arguments.of("/foo%3fbar", "/foo%3Fbar", "/foo?bar"),
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
arguments.add(Arguments.of("/f%20o/b%20r", "/f%20o/b%20r", "/f o/b r"));
arguments.add(Arguments.of("f\u00e4\u00e4%2523%3b%2c:%3db%20a%20r%3D", "f\u00e4\u00e4%2523%3B,:=b%20a%20r=", "f\u00e4\u00e4%23;,:=b a r="));
arguments.add(Arguments.of("f%d8%a9%D8%A9%2523%3b%2c:%3db%20a%20r", "f\u0629\u0629%2523%3B,:=b%20a%20r", "f\u0629\u0629%23;,:=b a r"));
Arguments.of("/f%20o/b%20r", "/f%20o/b%20r", "/f o/b r"),
Arguments.of("f\u00e4\u00e4%2523%3b%2c:%3db%20a%20r%3D", "f\u00e4\u00e4%2523%3B,:=b%20a%20r=", "f\u00e4\u00e4%23;,:=b a r="),
Arguments.of("f%d8%a9%D8%A9%2523%3b%2c:%3db%20a%20r", "f\u0629\u0629%2523%3B,:=b%20a%20r", "f\u0629\u0629%23;,:=b a r"),
// path parameters should be ignored
arguments.add(Arguments.of("/foo;ignore/bar;ignore", "/foo/bar", "/foo/bar"));
arguments.add(Arguments.of("/f\u00e4\u00e4;ignore/bar;ignore", "/fää/bar", "/fää/bar"));
arguments.add(Arguments.of("/f%d8%a9%d8%a9%2523;ignore/bar;ignore", "/f\u0629\u0629%2523/bar", "/f\u0629\u0629%23/bar"));
arguments.add(Arguments.of("foo%2523%3b%2c:%3db%20a%20r;rubbish", "foo%2523%3B,:=b%20a%20r", "foo%23;,:=b a r"));
Arguments.of("/foo;ignore/bar;ignore", "/foo/bar", "/foo/bar"),
Arguments.of("/f\u00e4\u00e4;ignore/bar;ignore", "/fää/bar", "/fää/bar"),
Arguments.of("/f%d8%a9%d8%a9%2523;ignore/bar;ignore", "/f\u0629\u0629%2523/bar", "/f\u0629\u0629%23/bar"),
Arguments.of("foo%2523%3b%2c:%3db%20a%20r;rubbish", "foo%2523%3B,:=b%20a%20r", "foo%23;,:=b a r"),
// test for chars that are somehow already decoded, but shouldn't be
arguments.add(Arguments.of("/foo bar\n", "/foo%20bar%0A", "/foo bar\n"));
arguments.add(Arguments.of("/foo\u0000bar", "/foo%00bar", "/foo\u0000bar"));
arguments.add(Arguments.of("/foo/bär", "/foo/bär", "/foo/bär"));
arguments.add(Arguments.of("/foo/€/bar", "/foo/€/bar", "/foo/€/bar"));
arguments.add(Arguments.of("/fo %2fo/b%61r", "/fo%20%2Fo/bar", "/fo /o/bar"));
Arguments.of("/foo bar\n", "/foo%20bar%0A", "/foo bar\n"),
Arguments.of("/foo\u0000bar", "/foo%00bar", "/foo\u0000bar"),
Arguments.of("/foo/bär", "/foo/bär", "/foo/bär"),
Arguments.of("/foo/€/bar", "/foo/€/bar", "/foo/€/bar"),
Arguments.of("/fo %2fo/b%61r", "/fo%20%2Fo/bar", "/fo /o/bar"),
// Test for null character (real world ugly test case)
byte[] oddBytes = {'/', 0x00, '/'};
String odd = new String(oddBytes, StandardCharsets.ISO_8859_1);
arguments.add(Arguments.of("/%00/", "/%00/", odd));
Arguments.of("/%00/", "/%00/", "/\u0000/"),
// Deprecated Microsoft Percent-U encoding
arguments.add(Arguments.of("abc%u3040", "abc\u3040", "abc\u3040"));
Arguments.of("abc%u3040", "abc\u3040", "abc\u3040"),
// Invalid UTF-8 - replacement characters should be present on invalid sequences
// URI paths do not support ISO-8859-1, so this should not be a fallback of our decodePath implementation
/* TODO: remove ISO-8859-1 fallback mode in decodePath - Issue #9489
Arguments.of("/a%D8%2fbar", "/a<>%2Fbar", "/a<>%2Fbar"), // invalid 2 octet sequence
Arguments.of("/abc%C3%28", "/abc<62>", "/abc<62>"), // invalid 2 octet sequence
Arguments.of("/abc%A0%A1", "/abc<62><63>", "/abc<62><63>"), // invalid 2 octet sequence
Arguments.of("/abc%e2%28%a1", "/abc<62><63>", "/abc<62><63>"), // invalid 3 octet sequence
Arguments.of("/abc%e2%82%28", "/abc<62>", "/abc<62>"), // invalid 3 octet sequence
Arguments.of("/abc%f0%28%8c%bc", "/abc<62><63><EFBFBD>", "/abc<62><63><EFBFBD>"), // invalid 4 octet sequence
Arguments.of("/abc%f0%90%28%bc", "/abc<62><63>", "/abc<62><63>"), // invalid 4 octet sequence
Arguments.of("/abc%f0%28%8c%28", "/abc<62><63>(", "/abc<62><63>("), // invalid 4 octet sequence
Arguments.of("/abc%f8%a1%a1%a1%a1", "/abc<62><63><EFBFBD><EFBFBD><EFBFBD>", "/abc<62><63><EFBFBD><EFBFBD><EFBFBD>"), // valid sequence, but not unicode
Arguments.of("/abc%fc%a1%a1%a1%a1%a1", "/abc<62><63><EFBFBD><EFBFBD><EFBFBD><EFBFBD>", "/abc<62><63><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"), // valid sequence, but not unicode
Arguments.of("/abc%f8%a1%a1%a1", "/abc<62><63><EFBFBD><EFBFBD>", "/abc<62><63><EFBFBD><EFBFBD>"), // incomplete sequence
*/
// Deprecated Microsoft Percent-U encoding
Arguments.of("/abc%u3040", "/abc\u3040", "/abc\u3040"),
// Canonical paths are also normalized
arguments.add(Arguments.of("./bar", "bar", "./bar"));
arguments.add(Arguments.of("/foo/./bar", "/foo/bar", "/foo/./bar"));
arguments.add(Arguments.of("/foo/../bar", "/bar", "/foo/../bar"));
arguments.add(Arguments.of("/foo/.../bar", "/foo/.../bar", "/foo/.../bar"));
arguments.add(Arguments.of("/foo/%2e/bar", "/foo/bar", "/foo/./bar")); // Not by the RFC, but safer
arguments.add(Arguments.of("/foo/%2e%2e/bar", "/bar", "/foo/../bar")); // Not by the RFC, but safer
arguments.add(Arguments.of("/foo/%2e%2e%2e/bar", "/foo/.../bar", "/foo/.../bar"));
return arguments.stream();
Arguments.of("./bar", "bar", "./bar"),
Arguments.of("/foo/./bar", "/foo/bar", "/foo/./bar"),
Arguments.of("/foo/../bar", "/bar", "/foo/../bar"),
Arguments.of("/foo/.../bar", "/foo/.../bar", "/foo/.../bar"),
Arguments.of("/foo/%2e/bar", "/foo/bar", "/foo/./bar"), // Not by the RFC, but safer
Arguments.of("/foo/%2e%2e/bar", "/bar", "/foo/../bar"), // Not by the RFC, but safer
Arguments.of("/foo/%2e%2e%2e/bar", "/foo/.../bar", "/foo/.../bar")
);
}
@ParameterizedTest(name = "[{index}] {0}")
@ -171,14 +187,6 @@ public class URIUtilTest
{
List<Arguments> arguments = new ArrayList<>();
// Test for null character (real world ugly test case)
// TODO is this a bad decoding or a bad URI ?
// arguments.add(Arguments.of("/%00/"));
// Deprecated Microsoft Percent-U encoding
// TODO still supported for now ?
// arguments.add(Arguments.of("abc%u3040"));
// Bad %## encoding
arguments.add(Arguments.of("abc%xyz"));
@ -196,21 +204,6 @@ public class URIUtilTest
arguments.add(Arguments.of("abc%uA"));
arguments.add(Arguments.of("abc%u"));
// Invalid UTF-8 and ISO8859-1
// TODO currently ISO8859 is too forgiving to detect these
/*
arguments.add(Arguments.of("abc%C3%28")); // invalid 2 octext sequence
arguments.add(Arguments.of("abc%A0%A1")); // invalid 2 octext sequence
arguments.add(Arguments.of("abc%e2%28%a1")); // invalid 3 octext sequence
arguments.add(Arguments.of("abc%e2%82%28")); // invalid 3 octext sequence
arguments.add(Arguments.of("abc%f0%28%8c%bc")); // invalid 4 octext sequence
arguments.add(Arguments.of("abc%f0%90%28%bc")); // invalid 4 octext sequence
arguments.add(Arguments.of("abc%f0%28%8c%28")); // invalid 4 octext sequence
arguments.add(Arguments.of("abc%f8%a1%a1%a1%a1")); // valid sequence, but not unicode
arguments.add(Arguments.of("abc%fc%a1%a1%a1%a1%a1")); // valid sequence, but not unicode
arguments.add(Arguments.of("abc%f8%a1%a1%a1")); // incomplete sequence
*/
return arguments.stream();
}

View File

@ -359,18 +359,22 @@ public class DefaultServlet extends HttpServlet
{
String includedServletPath = (String)req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
boolean included = includedServletPath != null;
String pathInContext;
String encodedPathInContext;
if (included)
pathInContext = getIncludedPathInContext(req, includedServletPath, isPathInfoOnly());
encodedPathInContext = URIUtil.encodePath(getIncludedPathInContext(req, includedServletPath, isPathInfoOnly()));
else if (isPathInfoOnly())
encodedPathInContext = URIUtil.encodePath(req.getPathInfo());
else if (req instanceof ServletApiRequest apiRequest)
encodedPathInContext = Context.getPathInContext(req.getContextPath(), apiRequest.getServletContextRequest().getHttpURI().getCanonicalPath());
else
pathInContext = URIUtil.addPaths(isPathInfoOnly() ? "/" : req.getServletPath(), req.getPathInfo());
encodedPathInContext = Context.getPathInContext(req.getContextPath(), URIUtil.canonicalPath(req.getRequestURI()));
if (LOG.isDebugEnabled())
LOG.debug("doGet(req={}, resp={}) pathInContext={}, included={}", req, resp, pathInContext, included);
LOG.debug("doGet(req={}, resp={}) pathInContext={}, included={}", req, resp, encodedPathInContext, included);
try
{
HttpContent content = _resourceService.getContent(pathInContext, ServletContextRequest.getServletContextRequest(req));
HttpContent content = _resourceService.getContent(encodedPathInContext, ServletContextRequest.getServletContextRequest(req));
if (LOG.isDebugEnabled())
LOG.debug("content = {}", content);
@ -384,7 +388,7 @@ public class DefaultServlet extends HttpServlet
* If the exception isnt caught and handled, and the response
* hasnt been committed, the status code MUST be set to 500.
*/
throw new FileNotFoundException(pathInContext);
throw new FileNotFoundException(encodedPathInContext);
}
// no content
@ -432,9 +436,9 @@ public class DefaultServlet extends HttpServlet
catch (InvalidPathException e)
{
if (LOG.isDebugEnabled())
LOG.debug("InvalidPathException for pathInContext: {}", pathInContext, e);
LOG.debug("InvalidPathException for pathInContext: {}", encodedPathInContext, e);
if (included)
throw new FileNotFoundException(pathInContext);
throw new FileNotFoundException(encodedPathInContext);
resp.setStatus(404);
}
}
@ -481,9 +485,9 @@ public class DefaultServlet extends HttpServlet
if (request.getDispatcherType() == DispatcherType.REQUEST)
_uri = getWrapped().getHttpURI();
else if (included)
_uri = Request.newHttpURIFrom(getWrapped(), getIncludedPathInContext(request, includedServletPath, false));
_uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(getIncludedPathInContext(request, includedServletPath, false)));
else
_uri = Request.newHttpURIFrom(getWrapped(), URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo()));
_uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo())));
}
@Override

View File

@ -35,7 +35,6 @@ import jakarta.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.ee10.servlet.util.ServletOutputStreamWrapper;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
@ -64,30 +63,30 @@ public class Dispatcher implements RequestDispatcher
private final ServletContextHandler _contextHandler;
private final HttpURI _uri;
private final String _pathInContext;
private final String _decodedPathInContext;
private final String _named;
private final ServletHandler.MappedServlet _mappedServlet;
private final ServletHandler _servletHandler;
private final ServletPathMapping _servletPathMapping;
public Dispatcher(ServletContextHandler contextHandler, HttpURI uri, String pathInContext)
public Dispatcher(ServletContextHandler contextHandler, HttpURI uri, String decodedPathInContext)
{
_contextHandler = contextHandler;
_uri = uri.asImmutable();
_pathInContext = pathInContext;
_decodedPathInContext = decodedPathInContext;
_named = null;
_servletHandler = _contextHandler.getServletHandler();
MatchedResource<ServletHandler.MappedServlet> matchedServlet = _servletHandler.getMatchedServlet(pathInContext);
MatchedResource<ServletHandler.MappedServlet> matchedServlet = _servletHandler.getMatchedServlet(decodedPathInContext);
_mappedServlet = matchedServlet.getResource();
_servletPathMapping = _mappedServlet.getServletPathMapping(_pathInContext, matchedServlet.getMatchedPath());
_servletPathMapping = _mappedServlet.getServletPathMapping(_decodedPathInContext, matchedServlet.getMatchedPath());
}
public Dispatcher(ServletContextHandler contextHandler, String name) throws IllegalStateException
{
_contextHandler = contextHandler;
_uri = null;
_pathInContext = null;
_decodedPathInContext = null;
_named = name;
_servletHandler = _contextHandler.getServletHandler();
@ -100,7 +99,7 @@ public class Dispatcher implements RequestDispatcher
HttpServletRequest httpRequest = (request instanceof HttpServletRequest) ? (HttpServletRequest)request : new ServletRequestHttpWrapper(request);
HttpServletResponse httpResponse = (response instanceof HttpServletResponse) ? (HttpServletResponse)response : new ServletResponseHttpWrapper(response);
_mappedServlet.handle(_servletHandler, _pathInContext, new ErrorRequest(httpRequest), httpResponse);
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new ErrorRequest(httpRequest), httpResponse);
}
@Override
@ -111,7 +110,7 @@ public class Dispatcher implements RequestDispatcher
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
servletContextRequest.getResponse().resetForForward();
_mappedServlet.handle(_servletHandler, _pathInContext, new ForwardRequest(httpRequest), httpResponse);
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new ForwardRequest(httpRequest), httpResponse);
// If we are not async and not closed already, then close via the possibly wrapped response.
if (!servletContextRequest.getState().isAsync() && !servletContextRequest.getHttpOutput().isClosed())
@ -136,7 +135,7 @@ public class Dispatcher implements RequestDispatcher
try
{
_mappedServlet.handle(_servletHandler, _pathInContext, new IncludeRequest(httpRequest), new IncludeResponse(httpResponse));
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new IncludeRequest(httpRequest), new IncludeResponse(httpResponse));
}
finally
{
@ -149,7 +148,7 @@ public class Dispatcher implements RequestDispatcher
HttpServletRequest httpRequest = (request instanceof HttpServletRequest) ? (HttpServletRequest)request : new ServletRequestHttpWrapper(request);
HttpServletResponse httpResponse = (response instanceof HttpServletResponse) ? (HttpServletResponse)response : new ServletResponseHttpWrapper(response);
_mappedServlet.handle(_servletHandler, _pathInContext, new AsyncRequest(httpRequest), httpResponse);
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new AsyncRequest(httpRequest), httpResponse);
}
public class ParameterRequestWrapper extends HttpServletRequestWrapper

View File

@ -60,6 +60,7 @@ import org.eclipse.jetty.ee10.servlet.security.UserIdentity;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@ -1117,7 +1118,7 @@ public class ServletApiRequest implements HttpServletRequest
// handle relative path
if (!path.startsWith("/"))
{
String relTo = _request.getPathInContext();
String relTo = _request.getDecodedPathInContext();
int slash = relTo.lastIndexOf("/");
if (slash > 1)
relTo = relTo.substring(0, slash + 1);
@ -1195,7 +1196,7 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public HttpServletMapping getHttpServletMapping()
{
return _request._mappedServlet.getServletPathMapping(_request.getPathInContext());
return _request._mappedServlet.getServletPathMapping(_request.getDecodedPathInContext());
}
@Override
@ -1246,4 +1247,24 @@ public class ServletApiRequest implements HttpServletRequest
}
return trailersMap;
}
static class AmbiguousURI extends ServletApiRequest
{
protected AmbiguousURI(ServletContextRequest servletContextRequest)
{
super(servletContextRequest);
}
@Override
public String getPathInfo()
{
throw new HttpException.IllegalArgumentException(HttpStatus.BAD_REQUEST_400, "Ambiguous URI encoding");
}
@Override
public String getServletPath()
{
throw new HttpException.IllegalArgumentException(HttpStatus.BAD_REQUEST_400, "Ambiguous URI encoding");
}
}
}

View File

@ -461,9 +461,9 @@ public class ServletChannel
}
}
// We first worked with the core pathInContext above, but now need to convert to servlet style
pathInContext = URIUtil.safeDecodePath(pathInContext);
String decodedPathInContext = URIUtil.decodePath(pathInContext);
Dispatcher dispatcher = new Dispatcher(getContextHandler(), uri, pathInContext);
Dispatcher dispatcher = new Dispatcher(getContextHandler(), uri, decodedPathInContext);
dispatcher.async(asyncContextEvent.getSuppliedRequest(), asyncContextEvent.getSuppliedResponse());
});
break;

View File

@ -1150,10 +1150,10 @@ public class ServletContextHandler extends ContextHandler implements Graceful
protected ServletContextRequest newServletContextRequest(ServletChannel servletChannel,
Request request,
Response response,
String pathInContext,
String decodedPathInContext,
MatchedResource<ServletHandler.MappedServlet> matchedResource)
{
return new ServletContextRequest(_servletContext, servletChannel, request, response, pathInContext, matchedResource, getSessionHandler());
return new ServletContextRequest(_servletContext, servletChannel, request, response, decodedPathInContext, matchedResource, getSessionHandler());
}
@Override
@ -1161,9 +1161,9 @@ public class ServletContextHandler extends ContextHandler implements Graceful
{
// Need to ask directly to the Context for the pathInContext, rather than using
// Request.getPathInContext(), as the request is not yet wrapped in this Context.
String pathInContext = URIUtil.safeDecodePath(getContext().getPathInContext(request.getHttpURI().getCanonicalPath()));
String decodedPathInContext = URIUtil.decodePath(getContext().getPathInContext(request.getHttpURI().getCanonicalPath()));
MatchedResource<ServletHandler.MappedServlet> matchedResource = _servletHandler.getMatchedServlet(pathInContext);
MatchedResource<ServletHandler.MappedServlet> matchedResource = _servletHandler.getMatchedServlet(decodedPathInContext);
if (matchedResource == null)
return null;
ServletHandler.MappedServlet mappedServlet = matchedResource.getResource();
@ -1179,7 +1179,7 @@ public class ServletContextHandler extends ContextHandler implements Graceful
cache.setAttribute(ServletChannel.class.getName(), servletChannel);
}
ServletContextRequest servletContextRequest = newServletContextRequest(servletChannel, request, response, pathInContext, matchedResource);
ServletContextRequest servletContextRequest = newServletContextRequest(servletChannel, request, response, decodedPathInContext, matchedResource);
servletChannel.associate(servletContextRequest);
return servletContextRequest;
}
@ -1197,7 +1197,7 @@ public class ServletContextHandler extends ContextHandler implements Graceful
{
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
DispatcherType dispatch = scopedRequest.getHttpServletRequest().getDispatcherType();
if (dispatch == DispatcherType.REQUEST && isProtectedTarget(scopedRequest.getPathInContext()))
if (dispatch == DispatcherType.REQUEST && isProtectedTarget(scopedRequest.getDecodedPathInContext()))
{
Response.writeError(request, response, callback, HttpServletResponse.SC_NOT_FOUND, null);
return true;
@ -2824,16 +2824,16 @@ public class ServletContextHandler extends ContextHandler implements Graceful
String contextPath = getContextPath();
// uriInContext is canonicalized by HttpURI.
HttpURI.Mutable uri = HttpURI.build(uriInContext);
String pathInfo = uri.getCanonicalPath();
if (StringUtil.isEmpty(pathInfo))
String encodedPathInContext = uri.getCanonicalPath();
if (StringUtil.isEmpty(encodedPathInContext))
return null;
if (!StringUtil.isEmpty(contextPath))
{
uri.path(URIUtil.addPaths(contextPath, uri.getPath()));
pathInfo = uri.getCanonicalPath().substring(contextPath.length());
encodedPathInContext = uri.getCanonicalPath().substring(contextPath.length());
}
return new Dispatcher(ServletContextHandler.this, uri, pathInfo);
return new Dispatcher(ServletContextHandler.this, uri, URIUtil.decodePath(encodedPathInContext));
}
catch (Exception e)
{

View File

@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.pathmap.MatchedPath;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathSpec;
@ -39,13 +40,10 @@ import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionManager;
import org.eclipse.jetty.util.Fields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServletContextRequest extends ContextRequest
{
public static final String MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
private static final Logger LOG = LoggerFactory.getLogger(ServletContextRequest.class);
static final int INPUT_NONE = 0;
static final int INPUT_STREAM = 1;
static final int INPUT_READER = 2;
@ -78,7 +76,7 @@ public class ServletContextRequest extends ContextRequest
private final ServletContextResponse _response;
final ServletHandler.MappedServlet _mappedServlet;
private final HttpInput _httpInput;
private final String _pathInContext;
private final String _decodedPathInContext;
private final ServletChannel _servletChannel;
private final PathSpec _pathSpec;
private final SessionManager _sessionManager;
@ -93,7 +91,7 @@ public class ServletContextRequest extends ContextRequest
ServletChannel servletChannel,
Request request,
Response response,
String pathInContext,
String decodedPathInContext,
MatchedResource<ServletHandler.MappedServlet> matchedResource,
SessionManager sessionManager)
{
@ -102,7 +100,7 @@ public class ServletContextRequest extends ContextRequest
_httpServletRequest = newServletApiRequest();
_mappedServlet = matchedResource.getResource();
_httpInput = _servletChannel.getHttpInput();
_pathInContext = pathInContext;
_decodedPathInContext = decodedPathInContext;
_pathSpec = matchedResource.getPathSpec();
_matchedPath = matchedResource.getMatchedPath();
_response = newServletContextResponse(response);
@ -111,6 +109,15 @@ public class ServletContextRequest extends ContextRequest
protected ServletApiRequest newServletApiRequest()
{
if (getHttpURI().hasViolations() && !getServletChannel().getContextHandler().getServletHandler().isDecodeAmbiguousURIs())
{
for (UriCompliance.Violation violation : getHttpURI().getViolations())
{
if (UriCompliance.AMBIGUOUS_VIOLATIONS.contains(violation))
return new ServletApiRequest.AmbiguousURI(this);
}
}
return new ServletApiRequest(this);
}
@ -119,9 +126,9 @@ public class ServletContextRequest extends ContextRequest
return new ServletContextResponse(_servletChannel, this, response);
}
public String getPathInContext()
public String getDecodedPathInContext()
{
return _pathInContext;
return _decodedPathInContext;
}
public PathSpec getPathSpec()
@ -209,8 +216,8 @@ public class ServletContextRequest extends ContextRequest
{
case "o.e.j.s.s.ServletScopedRequest.request" -> _httpServletRequest;
case "o.e.j.s.s.ServletScopedRequest.response" -> _response.getHttpServletResponse();
case "o.e.j.s.s.ServletScopedRequest.servlet" -> _mappedServlet.getServletPathMapping(getPathInContext()).getServletName();
case "o.e.j.s.s.ServletScopedRequest.url-pattern" -> _mappedServlet.getServletPathMapping(getPathInContext()).getPattern();
case "o.e.j.s.s.ServletScopedRequest.servlet" -> _mappedServlet.getServletPathMapping(getDecodedPathInContext()).getServletName();
case "o.e.j.s.s.ServletScopedRequest.url-pattern" -> _mappedServlet.getServletPathMapping(getDecodedPathInContext()).getPattern();
default -> super.getAttribute(name);
};
}

View File

@ -116,6 +116,7 @@ public class ServletHandler extends Handler.Wrapper
@SuppressWarnings("unchecked")
protected final ConcurrentMap<String, FilterChain>[] _chainCache = new ConcurrentMap[FilterMapping.ALL];
private boolean _decodeAmbiguousURIs = false;
/**
* Constructor.
@ -124,6 +125,21 @@ public class ServletHandler extends Handler.Wrapper
{
}
@ManagedAttribute(value = "True if URIs with violations are decoded")
public boolean isDecodeAmbiguousURIs()
{
return _decodeAmbiguousURIs;
}
/**
* @param decodeAmbiguousURIs {@code True} if ambiguous URIs are decoded by {@link ServletApiRequest#getServletPath()}
* and {@link ServletApiRequest#getPathInfo()}.
*/
public void setDecodeAmbiguousURIs(boolean decodeAmbiguousURIs)
{
_decodeAmbiguousURIs = decodeAmbiguousURIs;
}
AutoLock lock()
{
return _lock.lock();

View File

@ -471,10 +471,10 @@ public abstract class SecurityHandler extends Handler.Wrapper implements Authent
if (authenticator != null)
authenticator.prepareRequest(request);
RoleInfo roleInfo = prepareConstraintInfo(servletContextRequest.getPathInContext(), servletApiRequest);
RoleInfo roleInfo = prepareConstraintInfo(servletContextRequest.getDecodedPathInContext(), servletApiRequest);
// Check data constraints
if (!checkUserDataPermissions(servletContextRequest.getPathInContext(), servletContextRequest, response, callback, roleInfo))
if (!checkUserDataPermissions(servletContextRequest.getDecodedPathInContext(), servletContextRequest, response, callback, roleInfo))
return true;
// is Auth mandatory?

View File

@ -225,7 +225,7 @@ public class FormAuthenticator extends LoginAuthenticator
ServletContextRequest servletContextRequest = Request.as(req, ServletContextRequest.class);
ServletApiRequest servletApiRequest = servletContextRequest.getServletApiRequest();
String pathInContext = servletContextRequest.getPathInContext();
String pathInContext = servletContextRequest.getDecodedPathInContext();
boolean jSecurityCheck = isJSecurityCheck(pathInContext);
mandatory |= jSecurityCheck;
if (!mandatory)

View File

@ -70,11 +70,15 @@ public class AsyncContextTest
_contextHandler.addServlet(new ServletHolder(new TestServlet()), "/path with spaces/servletPath");
_contextHandler.addServlet(new ServletHolder(new TestServlet2()), "/servletPath2");
ServletHolder testHolder = new ServletHolder(new TestServlet());
testHolder.setInitParameter("dispatchPath", "/test2/something%2felse");
_contextHandler.addServlet(testHolder, "/test/*");
ServletHolder encodedTestHolder = new ServletHolder(new TestServlet());
encodedTestHolder.setInitParameter("dispatchPath", "/test2/something%25else");
_contextHandler.addServlet(encodedTestHolder, "/encoded/*");
_contextHandler.addServlet(new ServletHolder(new TestServlet2()), "/test2/*");
ServletHolder ambiguousTestHolder = new ServletHolder(new TestServlet());
ambiguousTestHolder.setInitParameter("dispatchPath", "/test2/something%2Felse");
_contextHandler.addServlet(ambiguousTestHolder, "/ambiguous/*");
_contextHandler.addServlet(new ServletHolder(new SelfDispatchingServlet()), "/self/*");
_contextHandler.addServlet(new ServletHolder(new TestStartThrowServlet()), "/startthrow/*");
@ -221,11 +225,52 @@ public class AsyncContextTest
@Test
public void testDispatchAsyncContextEncodedUrl() throws Exception
{
String request = "GET /ctx/test/hello%20there?dispatch=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Connection: close\r\n" +
"\r\n";
String request = """
GET /ctx/encoded/hello%20there?dispatch=true HTTP/1.1\r
Host: localhost\r
Content-Type: application/x-www-form-urlencoded\r
Connection: close\r
\r
""";
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request));
assertThat("Response.status", response.getStatus(), is(HttpServletResponse.SC_OK));
String responseBody = response.getContent();
// initial values
assertThat("servlet gets right path", responseBody, containsString("doGet:getServletPath:/test2"));
assertThat("request uri has correct encoding", responseBody, containsString("doGet:getRequestURI:/ctx/test2/something%25else"));
assertThat("request url has correct encoding", responseBody, containsString("doGet:getRequestURL:http://localhost/ctx/test2/something%25else"));
assertThat("path info has correct encoding", responseBody, containsString("doGet:getPathInfo:/something%else"));
// async values
assertThat("async servlet gets right path", responseBody, containsString("doGet:async:getServletPath:/test2"));
assertThat("async request uri has correct encoding", responseBody, containsString("doGet:async:getRequestURI:/ctx/test2/something%25else"));
assertThat("async request url has correct encoding", responseBody, containsString("doGet:async:getRequestURL:http://localhost/ctx/test2/something%25else"));
assertThat("async path info has correct encoding", responseBody, containsString("doGet:async:getPathInfo:/something%else"));
// async run attributes
assertThat("async run attr servlet path is original", responseBody, containsString("async:run:attr:servletPath:/encoded"));
assertThat("async run attr path info has correct encoding", responseBody, containsString("async:run:attr:pathInfo:/hello there"));
assertThat("async run attr query string", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("async run context path", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/encoded/hello%20there"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:encoded"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/encoded/*"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:PATH"));
}
@Test
public void testDispatchAsyncAmbiguousUrl() throws Exception
{
String request = """
GET /ctx/ambiguous/hello%20there?dispatch=true HTTP/1.1\r
Host: localhost\r
Content-Type: application/x-www-form-urlencoded\r
Connection: close\r
\r
""";
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request));
assertThat("Response.status", response.getStatus(), is(HttpServletResponse.SC_OK));
@ -235,22 +280,22 @@ public class AsyncContextTest
assertThat("servlet gets right path", responseBody, containsString("doGet:getServletPath:/test2"));
assertThat("request uri has correct encoding", responseBody, containsString("doGet:getRequestURI:/ctx/test2/something%2Felse"));
assertThat("request url has correct encoding", responseBody, containsString("doGet:getRequestURL:http://localhost/ctx/test2/something%2Felse"));
assertThat("path info has correct encoding", responseBody, containsString("doGet:getPathInfo:/something%2Felse"));
assertThat("path info has correct encoding", responseBody, containsString("doGet:getPathInfo:/something/else"));
// async values
assertThat("async servlet gets right path", responseBody, containsString("doGet:async:getServletPath:/test2"));
assertThat("async request uri has correct encoding", responseBody, containsString("doGet:async:getRequestURI:/ctx/test2/something%2Felse"));
assertThat("async request url has correct encoding", responseBody, containsString("doGet:async:getRequestURL:http://localhost/ctx/test2/something%2Felse"));
assertThat("async path info has correct encoding", responseBody, containsString("doGet:async:getPathInfo:/something%2Felse"));
assertThat("async path info has correct encoding", responseBody, containsString("doGet:async:getPathInfo:/something/else"));
// async run attributes
assertThat("async run attr servlet path is original", responseBody, containsString("async:run:attr:servletPath:/test"));
assertThat("async run attr servlet path is original", responseBody, containsString("async:run:attr:servletPath:/ambiguous"));
assertThat("async run attr path info has correct encoding", responseBody, containsString("async:run:attr:pathInfo:/hello there"));
assertThat("async run attr query string", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("async run context path", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/test/hello%20there"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:test"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/test/*"));
assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/ambiguous/hello%20there"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:ambiguous"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/ambiguous/*"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:PATH"));
}

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.AllowedResourceAliasChecker;
@ -207,6 +208,8 @@ public class DefaultServletTest
@Test
public void testGetPercent2F() throws Exception
{
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
Path file = docRoot.resolve("file.txt");
Files.writeString(file, "How now brown cow", UTF_8);
@ -256,6 +259,7 @@ public class DefaultServletTest
assertThat(response.toString(), response.getContent(), is("In a while"));
// Attempt access of content in sub-dir of context, using "%2F" instead of "/", should be a 404
// as neither getServletPath and getPathInfo are used and thus they don't throw.
rawResponse = connector.getResponse("""
GET /context/dirFoo%2Fother.txt HTTP/1.1\r
Host: local\r

View File

@ -212,12 +212,12 @@ public class DispatcherTest
String expected = """
HTTP/1.1 200 OK\r
Content-Type: text/plain\r
Content-Length: 56\r
Content-Length: 54\r
Connection: close\r
\r
/context\r
/EchoURI\r
/x%20x\r
/x x\r
/context/EchoURI/x%20x;a=1\r
""";
assertEquals(expected, responses);

View File

@ -27,6 +27,7 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
@ -148,15 +149,24 @@ public class EncodedURITest
ServletContextHandler context2 = new ServletContextHandler();
context2.setContextPath("/context_path".replace("_", separator));
_contextCollection.addHandler(context2);
context2.addServlet(TestServlet.class, "/test_servlet/*".replace("_", separator));
context2.addServlet(TestServlet.class, URIUtil.decodePath("/test_servlet/*".replace("_", separator)));
_connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
_server.start();
String response = _connector.getResponse("GET /context_path/test_servlet/path_info HTTP/1.0\n\n".replace("_", separator));
assertThat(response, startsWith("HTTP/1.1 200 "));
assertThat(response, Matchers.containsString("requestURI=/context_path/test_servlet/path_info".replace("_", separator)));
assertThat(response, Matchers.containsString("contextPath=/context_path".replace("_", separator)));
assertThat(response, Matchers.containsString("servletPath=/test_servlet".replace("_", separator)));
assertThat(response, Matchers.containsString("pathInfo=/path_info".replace("_", separator)));
if ("%2F".equals(separator))
{
assertThat(response, Matchers.containsString("servletPath=org.eclipse.jetty.http.HttpException$IllegalArgumentException: 400: Ambiguous URI encoding"));
assertThat(response, Matchers.containsString("pathInfo=org.eclipse.jetty.http.HttpException$IllegalArgumentException: 400: Ambiguous URI encoding"));
}
else
{
assertThat(response, Matchers.containsString("servletPath=/test_servlet".replace("_", "?")));
assertThat(response, Matchers.containsString("pathInfo=/path_info".replace("_", "?")));
}
}
public static class TestServlet extends HttpServlet
@ -167,9 +177,23 @@ public class EncodedURITest
response.setContentType("text/plain");
response.getWriter().println("requestURI=" + request.getRequestURI());
response.getWriter().println("contextPath=" + request.getContextPath());
try
{
response.getWriter().println("servletPath=" + request.getServletPath());
}
catch (Throwable e)
{
response.getWriter().println("servletPath=" + e);
}
try
{
response.getWriter().println("pathInfo=" + request.getPathInfo());
}
catch (Throwable e)
{
response.getWriter().println("pathInfo=" + e);
}
}
}
public static class AsyncServlet extends HttpServlet

View File

@ -59,6 +59,7 @@ import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -173,6 +174,7 @@ public class MultiPartServletTest
}
@Test
@Tag("Flaky")
public void testManyParts() throws Exception
{
start(new HttpServlet()

View File

@ -13,6 +13,8 @@
package org.eclipse.jetty.ee10.servlet;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.servlet.http.HttpServlet;
@ -30,6 +32,8 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class RequestTest
@ -124,27 +128,70 @@ public class RequestTest
}
@Test
public void testSafeURI() throws Exception
public void testAmbiguousURI() throws Exception
{
AtomicInteger count = new AtomicInteger();
startServer(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse resp)
protected void service(HttpServletRequest request, HttpServletResponse resp) throws IOException
{
count.incrementAndGet();
String requestURI = request.getRequestURI();
String servletPath;
String pathInfo;
try
{
servletPath = request.getServletPath();
}
catch (IllegalArgumentException iae)
{
servletPath = iae.toString();
}
try
{
pathInfo = request.getPathInfo();
}
catch (IllegalArgumentException iae)
{
pathInfo = iae.toString();
}
resp.getOutputStream().println("requestURI=%s servletPath=%s pathInfo=%s".formatted(requestURI, servletPath, pathInfo));
}
});
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986_UNAMBIGUOUS);
String rawResponse = _connector.getResponse(
"""
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
String rawRequest = """
GET /test/foo%2fbar HTTP/1.1\r
Host: localhost\r
Connection: close\r
\r
""");
""";
String rawResponse = _connector.getResponse(rawRequest);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.BAD_REQUEST_400));
assertThat(count.get(), equalTo(0));
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
rawResponse = _connector.getResponse(rawRequest);
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.getContent(), containsString("requestURI=/test/foo%2fbar"));
assertThat(response.getContent(), containsString("servletPath=org.eclipse.jetty.http.HttpException$IllegalArgumentException: 400: Ambiguous URI encoding"));
assertThat(response.getContent(), containsString("pathInfo=org.eclipse.jetty.http.HttpException$IllegalArgumentException: 400: Ambiguous URI encoding"));
assertThat(count.get(), equalTo(1));
_server.getContainedBeans(ServletHandler.class).iterator().next().setDecodeAmbiguousURIs(true);
rawResponse = _connector.getResponse(rawRequest);
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.getContent(), containsString("requestURI=/test/foo%2fbar"));
assertThat(response.getContent(), containsString("servletPath= "));
assertThat(response.getContent(), containsString("pathInfo=/test/foo/bar"));
assertThat(count.get(), equalTo(2));
}
@Test
@ -165,14 +212,14 @@ public class RequestTest
String rawResponse = _connector.getResponse(
"""
GET /test/path%20info/foo%2fbar HTTP/1.1\r
GET /test/path%20info/foo%2cbar HTTP/1.1\r
Host: localhost\r
Connection: close\r
\r
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat("request.getRequestURI", resultRequestURI.get(), is("/test/path%20info/foo%2fbar"));
assertThat("request.getPathInfo", resultPathInfo.get(), is("/test/path info/foo%2Fbar"));
assertThat("request.getRequestURI", resultRequestURI.get(), is("/test/path%20info/foo%2cbar"));
assertThat("request.getPathInfo", resultPathInfo.get(), is("/test/path info/foo,bar"));
}
}

View File

@ -116,7 +116,7 @@ public class RequestURITest
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");

View File

@ -2406,9 +2406,9 @@ public class ServletContextHandlerTest
ServletContextHandler context = new ServletContextHandler()
{
@Override
protected ServletContextRequest newServletContextRequest(ServletChannel servletChannel, Request request, Response response, String pathInContext, MatchedResource<ServletHandler.MappedServlet> matchedResource)
protected ServletContextRequest newServletContextRequest(ServletChannel servletChannel, Request request, Response response, String decodedPathInContext, MatchedResource<ServletHandler.MappedServlet> matchedResource)
{
return new ServletContextRequest(getContext().getServletContext(), servletChannel, request, response, pathInContext, matchedResource, getSessionHandler())
return new ServletContextRequest(getContext().getServletContext(), servletChannel, request, response, decodedPathInContext, matchedResource, getSessionHandler())
{
@Override
protected ServletContextResponse newServletContextResponse(Response response)

View File

@ -65,6 +65,7 @@ import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
@ -329,7 +330,7 @@ public class WebAppContextTest
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
server.start();
@ -374,7 +375,8 @@ public class WebAppContextTest
server.start();
assertThat(HttpTester.parseResponse(connector.getResponse("GET " + target + " HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
assertThat(HttpTester.parseResponse(connector.getResponse("GET " + target + " HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(),
either(is(HttpStatus.NOT_FOUND_404)).or(is(HttpStatus.BAD_REQUEST_400)));
}
@ParameterizedTest

View File

@ -48,7 +48,7 @@ public class WebAppDefaultServletTest
{
server = new Server();
connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
server.addConnector(connector);
Path directoryPath = workDir.getEmptyPathDir();

View File

@ -116,7 +116,7 @@ public class RequestURITest
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");

View File

@ -325,7 +325,7 @@ public class WebAppContextTest
LocalConnector connector = new LocalConnector(server);
server.addConnector(connector);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
server.start();

View File

@ -50,7 +50,7 @@ public class WebAppDefaultServletTest
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
server = new Server();
connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE);
server.addConnector(connector);
Path directoryPath = workDir.getEmptyPathDir();