Issue #6473 - canonicalPath refactor & fix alias check in PathResource * Reverted %-escape handling for URI query parts. * Performing canonicalization in ServletContext.getResource(), and improving alias checking in ContextHandler.getResource(). * Performing canonicalization checks in Resource.addPath() to avoid navigation above of the root. * Test added and fixed. * Various cleanups. * Improved javadoc and comments * Compliance mode HttpURI uses UriCompliance.Violation Signed-off-by: Lachlan Roberts <lachlan@webtide.com> Signed-off-by: Simone Bordet <simone.bordet@gmail.com> Co-authored-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
parent
c753ca0db5
commit
3c32afa05c
|
@ -15,8 +15,11 @@ package org.eclipse.jetty.http;
|
|||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.eclipse.jetty.http.UriCompliance.Violation;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.Index;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -31,56 +34,48 @@ import org.eclipse.jetty.util.UrlEncoded;
|
|||
* via the static methods such as {@link #build()} and {@link #from(String)}.
|
||||
*
|
||||
* A URI such as
|
||||
* <code>http://user@host:port/path;ignored/info;param?query#ignored</code>
|
||||
* is split into the following undecoded elements:<ul>
|
||||
* {@code http://user@host:port/path;param1/%2e/info;param2?query#fragment}
|
||||
* is split into the following optional elements:<ul>
|
||||
* <li>{@link #getScheme()} - http:</li>
|
||||
* <li>{@link #getAuthority()} - //name@host:port</li>
|
||||
* <li>{@link #getHost()} - host</li>
|
||||
* <li>{@link #getPort()} - port</li>
|
||||
* <li>{@link #getPath()} - /path/info</li>
|
||||
* <li>{@link #getParam()} - param</li>
|
||||
* <li>{@link #getPath()} - /path;param1/%2e/info;param2</li>
|
||||
* <li>{@link #getDecodedPath()} - /path/info</li>
|
||||
* <li>{@link #getParam()} - param2</li>
|
||||
* <li>{@link #getQuery()} - query</li>
|
||||
* <li>{@link #getFragment()} - fragment</li>
|
||||
* </ul>
|
||||
* <p>Any parameters will be returned from {@link #getPath()}, but are excluded from the
|
||||
* return value of {@link #getDecodedPath()}. If there are multiple parameters, the
|
||||
* {@link #getParam()} method returns only the last one.
|
||||
*/
|
||||
* <p>The path part of the URI is provided in both raw form ({@link #getPath()}) and
|
||||
* decoded form ({@link #getDecodedPath}), which has: path parameters removed,
|
||||
* percent encoded characters expanded and relative segments resolved. This approach
|
||||
* is somewhat contrary to <a href="https://tools.ietf.org/html/rfc3986#section-3.3">RFC3986</a>
|
||||
* which no longer defines path parameters (removed after
|
||||
* <a href="https://tools.ietf.org/html/rfc2396#section-3.3">RFC2396</a>) and specifies
|
||||
* that relative segment normalization should take place before percent encoded character
|
||||
* expansion. A literal interpretation of the RFC can result in URI paths with ambiguities
|
||||
* when viewed as strings. For example, a URI of {@code /foo%2f..%2fbar} is technically a single
|
||||
* segment of "/foo/../bar", but could easily be misinterpreted as 3 segments resolving to "/bar"
|
||||
* by a file system.
|
||||
* </p>
|
||||
* <p>
|
||||
* Thus this class avoid and/or detects such ambiguities. Furthermore, by decoding characters and
|
||||
* removing parameters before relative path normalization, ambiguous paths will be resolved in such
|
||||
* a way to be non-standard-but-non-ambiguous to down stream interpretation of the decoded path string.
|
||||
* The violations are recorded and available by API such as {@link #hasAmbiguousSegment()} so that requests
|
||||
* containing them may be rejected in case the non-standard-but-non-ambiguous interpretations
|
||||
* are not satisfactory for a given compliance configuration.
|
||||
* </p>
|
||||
* <p>
|
||||
* Implementations that wish to process ambiguous URI paths must configure the compliance modes
|
||||
* to accept them and then perform their own decoding of {@link #getPath()}.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there are multiple path parameters, only the last one is returned by {@link #getParam()}.
|
||||
* </p>
|
||||
**/
|
||||
public interface HttpURI
|
||||
{
|
||||
enum Violation
|
||||
{
|
||||
/**
|
||||
* URI contains ambiguous path segments e.g. {@code /foo/%2e%2e/bar}
|
||||
*/
|
||||
SEGMENT,
|
||||
|
||||
/**
|
||||
* URI contains ambiguous empty segments e.g. {@code /foo//bar} or {@code /foo/;param/}, but not {@code /foo/}
|
||||
*/
|
||||
EMPTY,
|
||||
|
||||
/**
|
||||
* URI contains ambiguous path separator within a URI segment e.g. {@code /foo/b%2fr}
|
||||
*/
|
||||
SEPARATOR,
|
||||
|
||||
/**
|
||||
* URI contains ambiguous path encoding within a URI segment e.g. {@code /%2557EB-INF}
|
||||
*/
|
||||
ENCODING,
|
||||
|
||||
/**
|
||||
* URI contains ambiguous path parameters within a URI segment e.g. {@code /foo/..;/bar}
|
||||
*/
|
||||
PARAM,
|
||||
|
||||
/**
|
||||
* Contains UTF16 encodings
|
||||
*/
|
||||
UTF16
|
||||
}
|
||||
|
||||
static Mutable build()
|
||||
{
|
||||
return new Mutable();
|
||||
|
@ -147,6 +142,11 @@ public interface HttpURI
|
|||
|
||||
String getHost();
|
||||
|
||||
/**
|
||||
* Get a URI path parameter. Multiple and in segment parameters are ignored and only
|
||||
* the last trailing parameter is returned.
|
||||
* @return The last path parameter or null
|
||||
*/
|
||||
String getParam();
|
||||
|
||||
String getPath();
|
||||
|
@ -166,41 +166,73 @@ public interface HttpURI
|
|||
boolean isAbsolute();
|
||||
|
||||
/**
|
||||
* @return True if the URI has either an {@link #hasAmbiguousParameter()}, {@link #hasAmbiguousSegment()} or {@link #hasAmbiguousSeparator()}.
|
||||
* @return True if the URI has any ambiguous {@link Violation}s.
|
||||
*/
|
||||
boolean isAmbiguous();
|
||||
|
||||
/**
|
||||
* @return True if the URI has any Violations.
|
||||
* @return True if the URI has any {@link Violation}s.
|
||||
*/
|
||||
boolean hasViolations();
|
||||
|
||||
/**
|
||||
* @param violation the violation to check.
|
||||
* @return true if the URI has the passed violation.
|
||||
*/
|
||||
boolean hasViolation(Violation violation);
|
||||
|
||||
/**
|
||||
* @return Set of violations in the URI.
|
||||
*/
|
||||
Collection<Violation> getViolations();
|
||||
|
||||
/**
|
||||
* @return True if the URI has a possibly ambiguous segment like '..;' or '%2e%2e'
|
||||
*/
|
||||
boolean hasAmbiguousSegment();
|
||||
default boolean hasAmbiguousSegment()
|
||||
{
|
||||
return hasViolation(Violation.AMBIGUOUS_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the URI empty segment that is ambiguous like '//' or '/;param/'.
|
||||
*/
|
||||
boolean hasAmbiguousEmptySegment();
|
||||
default boolean hasAmbiguousEmptySegment()
|
||||
{
|
||||
return hasViolation(Violation.AMBIGUOUS_EMPTY_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the URI has a possibly ambiguous separator of %2f
|
||||
*/
|
||||
boolean hasAmbiguousSeparator();
|
||||
default boolean hasAmbiguousSeparator()
|
||||
{
|
||||
return hasViolation(Violation.AMBIGUOUS_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the URI has a possibly ambiguous path parameter like '..;'
|
||||
*/
|
||||
boolean hasAmbiguousParameter();
|
||||
default boolean hasAmbiguousParameter()
|
||||
{
|
||||
return hasViolation(Violation.AMBIGUOUS_PATH_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the URI has an encoded '%' character.
|
||||
*/
|
||||
boolean hasAmbiguousEncoding();
|
||||
default boolean hasAmbiguousEncoding()
|
||||
{
|
||||
return hasViolation(Violation.AMBIGUOUS_PATH_ENCODING);
|
||||
}
|
||||
|
||||
boolean hasUtf16Encoding();
|
||||
/**
|
||||
* @return True if the URI has UTF16 '%u' encodings.
|
||||
*/
|
||||
default boolean hasUtf16Encoding()
|
||||
{
|
||||
return hasViolation(Violation.UTF16_ENCODINGS);
|
||||
}
|
||||
|
||||
default URI toURI()
|
||||
{
|
||||
|
@ -408,7 +440,7 @@ public interface HttpURI
|
|||
@Override
|
||||
public boolean isAmbiguous()
|
||||
{
|
||||
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16));
|
||||
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16_ENCODINGS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -418,39 +450,15 @@ public interface HttpURI
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousSegment()
|
||||
public boolean hasViolation(Violation violation)
|
||||
{
|
||||
return _violations.contains(Violation.SEGMENT);
|
||||
return _violations.contains(violation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousEmptySegment()
|
||||
public Collection<Violation> getViolations()
|
||||
{
|
||||
return _violations.contains(Violation.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousSeparator()
|
||||
{
|
||||
return _violations.contains(Violation.SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousParameter()
|
||||
{
|
||||
return _violations.contains(Violation.PARAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousEncoding()
|
||||
{
|
||||
return _violations.contains(Violation.ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUtf16Encoding()
|
||||
{
|
||||
return _violations.contains(Violation.UTF16);
|
||||
return Collections.unmodifiableCollection(_violations);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -781,7 +789,7 @@ public interface HttpURI
|
|||
@Override
|
||||
public boolean isAmbiguous()
|
||||
{
|
||||
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16));
|
||||
return !_violations.isEmpty() && !(_violations.size() == 1 && _violations.contains(Violation.UTF16_ENCODINGS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -791,39 +799,15 @@ public interface HttpURI
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousSegment()
|
||||
public boolean hasViolation(Violation violation)
|
||||
{
|
||||
return _violations.contains(Violation.SEGMENT);
|
||||
return _violations.contains(violation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousEmptySegment()
|
||||
public Collection<Violation> getViolations()
|
||||
{
|
||||
return _violations.contains(Violation.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousSeparator()
|
||||
{
|
||||
return _violations.contains(Violation.SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousParameter()
|
||||
{
|
||||
return _violations.contains(Violation.PARAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmbiguousEncoding()
|
||||
{
|
||||
return _violations.contains(Violation.ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUtf16Encoding()
|
||||
{
|
||||
return _violations.contains(Violation.UTF16);
|
||||
return Collections.unmodifiableCollection(_violations);
|
||||
}
|
||||
|
||||
public Mutable normalize()
|
||||
|
@ -928,9 +912,9 @@ public interface HttpURI
|
|||
_uri = null;
|
||||
_decodedPath = uri.getDecodedPath();
|
||||
if (uri.hasAmbiguousSeparator())
|
||||
_violations.add(Violation.SEPARATOR);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_SEPARATOR);
|
||||
if (uri.hasAmbiguousSegment())
|
||||
_violations.add(Violation.SEGMENT);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_SEGMENT);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1205,7 +1189,7 @@ public interface HttpURI
|
|||
{
|
||||
if (encodedCharacters == 2 && c == 'u' && !encodedUtf16)
|
||||
{
|
||||
_violations.add(Violation.UTF16);
|
||||
_violations.add(Violation.UTF16_ENCODINGS);
|
||||
encodedUtf16 = true;
|
||||
encodedCharacters = 4;
|
||||
continue;
|
||||
|
@ -1216,11 +1200,15 @@ public interface HttpURI
|
|||
{
|
||||
switch (encodedValue)
|
||||
{
|
||||
case 0:
|
||||
// Byte 0 cannot be present in a UTF-8 sequence in any position
|
||||
// other than as the NUL ASCII byte which we do not wish to allow.
|
||||
throw new IllegalArgumentException("Illegal character in path");
|
||||
case '/':
|
||||
_violations.add(Violation.SEPARATOR);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_SEPARATOR);
|
||||
break;
|
||||
case '%':
|
||||
_violations.add(Violation.ENCODING);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_ENCODING);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1301,26 +1289,11 @@ public interface HttpURI
|
|||
}
|
||||
case QUERY:
|
||||
{
|
||||
switch (c)
|
||||
if (c == '#')
|
||||
{
|
||||
case '%':
|
||||
encodedCharacters = 2;
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
if (encodedCharacters == 1)
|
||||
_violations.add(Violation.UTF16);
|
||||
encodedCharacters = 0;
|
||||
break;
|
||||
case '#':
|
||||
_query = uri.substring(mark, i);
|
||||
mark = i + 1;
|
||||
state = State.FRAGMENT;
|
||||
encodedCharacters = 0;
|
||||
break;
|
||||
default:
|
||||
encodedCharacters = 0;
|
||||
break;
|
||||
_query = uri.substring(mark, i);
|
||||
mark = i + 1;
|
||||
state = State.FRAGMENT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1335,7 +1308,9 @@ public interface HttpURI
|
|||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1387,10 +1362,12 @@ public interface HttpURI
|
|||
}
|
||||
else if (_path != null)
|
||||
{
|
||||
String canonical = URIUtil.canonicalPath(_path);
|
||||
if (canonical == null)
|
||||
throw new BadMessageException("Bad URI");
|
||||
_decodedPath = URIUtil.decodePath(canonical);
|
||||
// The RFC requires this to be canonical before decoding, but this can leave dot segments and dot dot segments
|
||||
// which are not canonicalized and could be used in an attempt to bypass security checks.
|
||||
String decodedNonCanonical = URIUtil.decodePath(_path);
|
||||
_decodedPath = URIUtil.canonicalPath(decodedNonCanonical);
|
||||
if (_decodedPath == null)
|
||||
throw new IllegalArgumentException("Bad URI");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1409,9 +1386,10 @@ public interface HttpURI
|
|||
// This method is called once for every segment parsed.
|
||||
// A URI like "/foo/" has two segments: "foo" and an empty segment.
|
||||
// Empty segments are only ambiguous if they are not the last segment
|
||||
// So if this method is called for any segment and we have previously seen an empty segment, then it was ambiguous
|
||||
// So if this method is called for any segment and we have previously
|
||||
// seen an empty segment, then it was ambiguous.
|
||||
if (_emptySegment)
|
||||
_violations.add(Violation.EMPTY);
|
||||
_violations.add(Violation.AMBIGUOUS_EMPTY_SEGMENT);
|
||||
|
||||
if (end == segment)
|
||||
{
|
||||
|
@ -1422,7 +1400,7 @@ public interface HttpURI
|
|||
// If this empty segment is the first segment then it is ambiguous.
|
||||
if (segment == 0)
|
||||
{
|
||||
_violations.add(Violation.EMPTY);
|
||||
_violations.add(Violation.AMBIGUOUS_EMPTY_SEGMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1439,12 +1417,12 @@ public interface HttpURI
|
|||
if (ambiguous == Boolean.TRUE)
|
||||
{
|
||||
// The segment is always ambiguous.
|
||||
_violations.add(Violation.SEGMENT);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_SEGMENT);
|
||||
}
|
||||
else if (param && ambiguous == Boolean.FALSE)
|
||||
{
|
||||
// The segment is ambiguous only when followed by a parameter.
|
||||
_violations.add(Violation.PARAM);
|
||||
_violations.add(Violation.AMBIGUOUS_PATH_PARAMETER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.slf4j.LoggerFactory;
|
|||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.EnumSet.allOf;
|
||||
import static java.util.EnumSet.complementOf;
|
||||
import static java.util.EnumSet.noneOf;
|
||||
import static java.util.EnumSet.of;
|
||||
|
||||
|
@ -67,10 +66,6 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
|||
* Allow ambiguous path encoding within a URI segment e.g. <code>/%2557EB-INF</code>
|
||||
*/
|
||||
AMBIGUOUS_PATH_ENCODING("https://tools.ietf.org/html/rfc3986#section-3.3", "Ambiguous URI path encoding"),
|
||||
/**
|
||||
* Allow Non canonical ambiguous paths. eg <code>/foo/x%2f%2e%2e%/bar</code> provided to applications as <code>/foo/x/../bar</code>
|
||||
*/
|
||||
NON_CANONICAL_AMBIGUOUS_PATHS("https://tools.ietf.org/html/rfc3986#section-3.3", "Non canonical ambiguous paths"),
|
||||
/**
|
||||
* Allow UTF-16 encoding eg <code>/foo%u2192bar</code>.
|
||||
*/
|
||||
|
@ -125,10 +120,9 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
|||
|
||||
/**
|
||||
* Compliance mode that exactly follows <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>,
|
||||
* including allowing all additional ambiguous URI Violations,
|
||||
* except {@link Violation#NON_CANONICAL_AMBIGUOUS_PATHS}, thus ambiguous paths are canonicalized for safety.
|
||||
* including allowing all additional ambiguous URI Violations.
|
||||
*/
|
||||
public static final UriCompliance RFC3986 = new UriCompliance("RFC3986", complementOf(of(Violation.NON_CANONICAL_AMBIGUOUS_PATHS)));
|
||||
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>
|
||||
|
@ -231,6 +225,11 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
|||
boolean exclude = element.startsWith("-");
|
||||
if (exclude)
|
||||
element = element.substring(1);
|
||||
|
||||
// Ignore removed name. TODO: remove in future release.
|
||||
if (element.equals("NON_CANONICAL_AMBIGUOUS_PATHS"))
|
||||
continue;
|
||||
|
||||
Violation section = Violation.valueOf(element);
|
||||
if (exclude)
|
||||
violations.remove(section);
|
||||
|
@ -330,4 +329,14 @@ public final class UriCompliance implements ComplianceViolation.Mode
|
|||
return EnumSet.noneOf(Violation.class);
|
||||
return EnumSet.copyOf(violations);
|
||||
}
|
||||
|
||||
public static String checkUriCompliance(UriCompliance compliance, HttpURI uri)
|
||||
{
|
||||
for (UriCompliance.Violation violation : UriCompliance.Violation.values())
|
||||
{
|
||||
if (uri.hasViolation(violation) && (compliance == null || !compliance.allows(violation)))
|
||||
return violation.getDescription();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import java.util.Arrays;
|
|||
import java.util.EnumSet;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.HttpURI.Violation;
|
||||
import org.eclipse.jetty.http.UriCompliance.Violation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
|
@ -36,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
||||
public class HttpURITest
|
||||
{
|
||||
@Test
|
||||
|
@ -146,6 +147,12 @@ public class HttpURITest
|
|||
uri = builder.asImmutable();
|
||||
assertThat(uri.getHost(), is("foo"));
|
||||
assertThat(uri.getPath(), is("/bar"));
|
||||
|
||||
// We do allow nulls if not encoded. This can be used for testing 2nd line of defence.
|
||||
builder.uri("http://fo\000/bar");
|
||||
uri = builder.asImmutable();
|
||||
assertThat(uri.getHost(), is("fo\000"));
|
||||
assertThat(uri.getPath(), is("/bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -350,7 +357,9 @@ public class HttpURITest
|
|||
|
||||
// encoded paths
|
||||
{"/f%6f%6F/bar", "/foo/bar", EnumSet.noneOf(Violation.class)},
|
||||
{"/f%u006f%u006F/bar", "/foo/bar", EnumSet.of(Violation.UTF16)},
|
||||
{"/f%u006f%u006F/bar", "/foo/bar", EnumSet.of(Violation.UTF16_ENCODINGS)},
|
||||
{"/f%u0001%u0001/bar", "/f\001\001/bar", EnumSet.of(Violation.UTF16_ENCODINGS)},
|
||||
{"/foo/%u20AC/bar", "/foo/\u20AC/bar", EnumSet.of(Violation.UTF16_ENCODINGS)},
|
||||
|
||||
// illegal paths
|
||||
{"//host/../path/info", null, EnumSet.noneOf(Violation.class)},
|
||||
|
@ -360,75 +369,81 @@ public class HttpURITest
|
|||
{"/path/%2/F/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/%/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/%u000X/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/Fo%u0000/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/Fo%00/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/Foo/info%u0000", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/Foo/info%00", null, EnumSet.noneOf(Violation.class)},
|
||||
{"/path/%U20AC", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%2e%2e/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%u002e%u002e/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%2e%2e;/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%u002e%u002e;/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%2e.", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%u002e.", null, EnumSet.noneOf(Violation.class)},
|
||||
{".%2e", null, EnumSet.noneOf(Violation.class)},
|
||||
{".%u002e", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%2e%2e", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%u002e%u002e", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%2e%u002e", null, EnumSet.noneOf(Violation.class)},
|
||||
{"%u002e%2e", null, EnumSet.noneOf(Violation.class)},
|
||||
{"..;/info", null, EnumSet.noneOf(Violation.class)},
|
||||
{"..;param/info", null, EnumSet.noneOf(Violation.class)},
|
||||
|
||||
// ambiguous dot encodings
|
||||
{"scheme://host/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"scheme:/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"path/%2e/info/", "path/./info/", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e/info", "./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e/info", "./info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e%2e/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e%u002e/info", "../info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e%2e;/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e%u002e;/info", "../info", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e", ".", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e", ".", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e.", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e.", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{".%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{".%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%u002e%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%2e%u002e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"%u002e%2e", "..", EnumSet.of(Violation.SEGMENT, Violation.UTF16)},
|
||||
{"scheme://host/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"scheme:/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"path/%2e/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;param;other/info;other", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"%2e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"%u002e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)},
|
||||
|
||||
{"%2e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"%u002e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)},
|
||||
|
||||
// empty segment treated as ambiguous
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||
{"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||
{";/bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{";?n=v", "", EnumSet.of(Violation.EMPTY)},
|
||||
{";/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{";?n=v", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"?n=v", "", EnumSet.noneOf(Violation.class)},
|
||||
{"#n=v", "", EnumSet.noneOf(Violation.class)},
|
||||
{"", "", EnumSet.noneOf(Violation.class)},
|
||||
{"http:/foo", "/foo", EnumSet.noneOf(Violation.class)},
|
||||
|
||||
// ambiguous parameter inclusions
|
||||
{"/path/.;/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/.;param/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/..;/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/..;param/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||
{".;/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||
{".;param/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||
{"..;/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||
{"..;param/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/.;/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/.;param/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/..;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/..;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{".;/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{".;param/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
|
||||
// ambiguous segment separators
|
||||
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"%2f/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"%2F/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"/path/%2f../info", "/path//../info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"%2f/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"/path/%2f../info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
|
||||
// ambiguous encoding
|
||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.ENCODING)},
|
||||
{"/path/%u0025/info", "/path/%/info", EnumSet.of(Violation.ENCODING, Violation.UTF16)},
|
||||
{"%25/info", "%/info", EnumSet.of(Violation.ENCODING)},
|
||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.ENCODING)},
|
||||
{"/path/%u0025../info", "/path/%../info", EnumSet.of(Violation.ENCODING, Violation.UTF16)},
|
||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
{"/path/%u0025/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)},
|
||||
{"%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
{"/path/%u0025../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)},
|
||||
|
||||
// combinations
|
||||
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM)},
|
||||
{"/path/%u002f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.UTF16)},
|
||||
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT)},
|
||||
{"/path/%2f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/%u002f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.UTF16_ENCODINGS)},
|
||||
{"/path/%2f/..;/%2e/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
|
||||
// Non ascii characters
|
||||
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
||||
|
@ -447,15 +462,15 @@ public class HttpURITest
|
|||
HttpURI uri = HttpURI.from(input);
|
||||
assertThat(uri.getDecodedPath(), is(decodedPath));
|
||||
EnumSet<Violation> ambiguous = EnumSet.copyOf(expected);
|
||||
ambiguous.retainAll(EnumSet.complementOf(EnumSet.of(Violation.UTF16)));
|
||||
ambiguous.retainAll(EnumSet.complementOf(EnumSet.of(Violation.UTF16_ENCODINGS)));
|
||||
|
||||
assertThat(uri.isAmbiguous(), is(!ambiguous.isEmpty()));
|
||||
assertThat(uri.hasAmbiguousSegment(), is(ambiguous.contains(Violation.SEGMENT)));
|
||||
assertThat(uri.hasAmbiguousSeparator(), is(ambiguous.contains(Violation.SEPARATOR)));
|
||||
assertThat(uri.hasAmbiguousParameter(), is(ambiguous.contains(Violation.PARAM)));
|
||||
assertThat(uri.hasAmbiguousEncoding(), is(ambiguous.contains(Violation.ENCODING)));
|
||||
assertThat(uri.hasAmbiguousSegment(), is(ambiguous.contains(Violation.AMBIGUOUS_PATH_SEGMENT)));
|
||||
assertThat(uri.hasAmbiguousSeparator(), is(ambiguous.contains(Violation.AMBIGUOUS_PATH_SEPARATOR)));
|
||||
assertThat(uri.hasAmbiguousParameter(), is(ambiguous.contains(Violation.AMBIGUOUS_PATH_PARAMETER)));
|
||||
assertThat(uri.hasAmbiguousEncoding(), is(ambiguous.contains(Violation.AMBIGUOUS_PATH_ENCODING)));
|
||||
|
||||
assertThat(uri.hasUtf16Encoding(), is(expected.contains(Violation.UTF16)));
|
||||
assertThat(uri.hasUtf16Encoding(), is(expected.contains(Violation.UTF16_ENCODINGS)));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -483,78 +498,78 @@ public class HttpURITest
|
|||
{"../path/info", null, null},
|
||||
{"/path/%XX/info", null, null},
|
||||
{"/path/%2/F/info", null, null},
|
||||
{"%2e%2e/info", null, null},
|
||||
{"%2e%2e;/info", null, null},
|
||||
{"%2e.", null, null},
|
||||
{".%2e", null, null},
|
||||
{"%2e%2e", null, null},
|
||||
{"..;/info", null, null},
|
||||
{"..;param/info", null, null},
|
||||
|
||||
// ambiguous dot encodings
|
||||
{"/path/%2e/info", "/path/./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"path/%2e/info/", "path/./info/", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;param/info", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e%2e;param;other/info;other", "/path/../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e/info", "./info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e%2e/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e%2e;/info", "../info", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e", ".", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e.", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{".%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{"%2e%2e", "..", EnumSet.of(Violation.SEGMENT)},
|
||||
{"/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"path/%2e/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2e%2e;param;other/info;other", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"%2e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"%2e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
|
||||
// empty segment treated as ambiguous
|
||||
{"/", "/", EnumSet.noneOf(Violation.class)},
|
||||
{"/#", "/", EnumSet.noneOf(Violation.class)},
|
||||
{"/path", "/path", EnumSet.noneOf(Violation.class)},
|
||||
{"/path/", "/path/", EnumSet.noneOf(Violation.class)},
|
||||
{"//", "//", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//", "/foo//", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"//foo/bar", "//foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"//", "//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//", "/foo//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"//foo/bar", "//foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo?bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo#bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo;bar", "/foo", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo/?bar", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo/#bar", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo/;param", "/foo/", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo/;param/bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//bar//", "/foo//bar//", EnumSet.of(Violation.EMPTY)},
|
||||
{"//foo//bar//", "//foo//bar//", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo/;param/bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//bar//", "/foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"//foo//bar//", "//foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo//../bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo///../../../bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)},
|
||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.EMPTY)},
|
||||
{"/foo//./bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||
{"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)},
|
||||
{";/bar", "/bar", EnumSet.of(Violation.EMPTY)},
|
||||
{";?n=v", "", EnumSet.of(Violation.EMPTY)},
|
||||
{";/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{";?n=v", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
{"?n=v", "", EnumSet.noneOf(Violation.class)},
|
||||
{"#n=v", "", EnumSet.noneOf(Violation.class)},
|
||||
{"", "", EnumSet.noneOf(Violation.class)},
|
||||
|
||||
// ambiguous parameter inclusions
|
||||
{"/path/.;/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/.;param/info", "/path/./info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/..;/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/..;param/info", "/path/../info", EnumSet.of(Violation.PARAM)},
|
||||
{".;/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||
{".;param/info", "./info", EnumSet.of(Violation.PARAM)},
|
||||
{"..;/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||
{"..;param/info", "../info", EnumSet.of(Violation.PARAM)},
|
||||
{"/path/.;/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/.;param/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/..;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/..;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{".;/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{".;param/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
|
||||
// ambiguous segment separators
|
||||
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"%2f/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"%2F/info", "//info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"/path/%2f../info", "/path//../info", EnumSet.of(Violation.SEPARATOR)},
|
||||
{"/path/%2f/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"%2f/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
{"/path/%2f../info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)},
|
||||
|
||||
// ambiguous encoding
|
||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.ENCODING)},
|
||||
{"%25/info", "%/info", EnumSet.of(Violation.ENCODING)},
|
||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.ENCODING)},
|
||||
{"/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
{"%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
{"/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)},
|
||||
|
||||
// combinations
|
||||
{"/path/%2f/..;/info", "/path///../info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM)},
|
||||
{"/path/%2f/..;/%2e/info", "/path///.././info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT)},
|
||||
{"/path/%2f/%25/..;/%2e//info", "/path///%/.././/info", EnumSet.of(Violation.SEPARATOR, Violation.PARAM, Violation.SEGMENT, Violation.ENCODING, Violation.EMPTY)},
|
||||
{"/path/%2f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)},
|
||||
{"/path/%2f/..;/%2e/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)},
|
||||
{"/path/%2f/%25/..;/%2e//info", "/path////info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_ENCODING, Violation.AMBIGUOUS_EMPTY_SEGMENT)},
|
||||
}).map(Arguments::of);
|
||||
}
|
||||
|
||||
|
@ -572,11 +587,11 @@ public class HttpURITest
|
|||
HttpURI uri = HttpURI.build().pathQuery(input);
|
||||
assertThat(uri.getDecodedPath(), is(decodedPath));
|
||||
assertThat(uri.isAmbiguous(), is(!expected.isEmpty()));
|
||||
assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Violation.EMPTY)));
|
||||
assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Violation.SEGMENT)));
|
||||
assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Violation.SEPARATOR)));
|
||||
assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Violation.PARAM)));
|
||||
assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Violation.ENCODING)));
|
||||
assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Violation.AMBIGUOUS_EMPTY_SEGMENT)));
|
||||
assertThat(uri.hasAmbiguousSegment(), is(expected.contains(Violation.AMBIGUOUS_PATH_SEGMENT)));
|
||||
assertThat(uri.hasAmbiguousSeparator(), is(expected.contains(Violation.AMBIGUOUS_PATH_SEPARATOR)));
|
||||
assertThat(uri.hasAmbiguousParameter(), is(expected.contains(Violation.AMBIGUOUS_PATH_PARAMETER)));
|
||||
assertThat(uri.hasAmbiguousEncoding(), is(expected.contains(Violation.AMBIGUOUS_PATH_ENCODING)));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> parseData()
|
||||
|
@ -793,4 +808,20 @@ public class HttpURITest
|
|||
assertThat("[" + input + "] .fragment", httpUri.getFragment(), is(javaUri.getFragment()));
|
||||
assertThat("[" + input + "] .toString", httpUri.toString(), is(javaUri.toASCIIString()));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> queryData()
|
||||
{
|
||||
return Stream.of(
|
||||
new String[]{"/path?p=%U20AC", "p=%U20AC"},
|
||||
new String[]{"/path?p=%u20AC", "p=%u20AC"}
|
||||
).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("queryData")
|
||||
public void testEncodedQuery(String input, String expectedQuery)
|
||||
{
|
||||
HttpURI httpURI = HttpURI.build(input);
|
||||
assertThat("[" + input + "] .query", httpURI.getQuery(), is(expectedQuery));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -361,6 +361,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
// /WEB-INF/classes
|
||||
if ((resource == null || !resource.exists()) && uriInContext != null && _classes != null)
|
||||
{
|
||||
// Canonicalize again to look for the resource inside /WEB-INF subdirectories.
|
||||
String uri = URIUtil.canonicalPath(uriInContext);
|
||||
if (uri == null)
|
||||
return null;
|
||||
|
|
|
@ -40,20 +40,20 @@ public final class RedirectUtil
|
|||
if (location.startsWith("/"))
|
||||
{
|
||||
// absolute in context
|
||||
location = URIUtil.canonicalEncodedPath(location);
|
||||
location = URIUtil.canonicalURI(location);
|
||||
}
|
||||
else
|
||||
{
|
||||
// relative to request
|
||||
String path = request.getRequestURI();
|
||||
String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
|
||||
location = URIUtil.canonicalPath(URIUtil.addEncodedPaths(parent, location));
|
||||
if (!location.startsWith("/"))
|
||||
location = URIUtil.canonicalURI(URIUtil.addEncodedPaths(parent, location));
|
||||
if (location != null && !location.startsWith("/"))
|
||||
url.append('/');
|
||||
}
|
||||
|
||||
if (location == null)
|
||||
throw new IllegalStateException("path cannot be above root");
|
||||
throw new IllegalStateException("redirect path cannot be above root");
|
||||
url.append(location);
|
||||
|
||||
location = url.toString();
|
||||
|
|
|
@ -16,11 +16,11 @@ package org.eclipse.jetty.rewrite.handler;
|
|||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -62,7 +62,7 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
|
|||
{
|
||||
_rule.setCode("405");
|
||||
_rule.setMessage("foo");
|
||||
_request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/%00/"));
|
||||
_request.setHttpURI(HttpURI.from("/%01/"));
|
||||
|
||||
String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
|
||||
|
||||
|
@ -72,10 +72,17 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
|
|||
|
||||
@Test
|
||||
public void testInvalidJsp() throws Exception
|
||||
{
|
||||
assertThrows(IllegalArgumentException.class, () -> HttpURI.build(_request.getHttpURI(), "/jsp/bean1.jsp%00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidJspWithNullByte() throws Exception
|
||||
{
|
||||
_rule.setCode("405");
|
||||
_rule.setMessage("foo");
|
||||
_request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/jsp/bean1.jsp%00"));
|
||||
|
||||
_request.setHttpURI(HttpURI.from("/jsp/bean1.jsp\000"));
|
||||
|
||||
String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
|
||||
|
||||
|
@ -86,14 +93,7 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
|
|||
@Test
|
||||
public void testInvalidShamrock() throws Exception
|
||||
{
|
||||
_rule.setCode("405");
|
||||
_rule.setMessage("foo");
|
||||
_request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/jsp/shamrock-%00%E2%98%98.jsp"));
|
||||
|
||||
String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
|
||||
|
||||
assertEquals(405, _response.getStatus());
|
||||
assertEquals("foo", _request.getAttribute(Dispatcher.ERROR_MESSAGE));
|
||||
assertThrows(IllegalArgumentException.class, () -> HttpURI.build(_request.getHttpURI(), "/jsp/shamrock-%00%E2%98%98.jsp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -119,4 +119,3 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
|
|||
//@checkstyle-enable-check : IllegalTokenText
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.server;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
|
@ -76,7 +78,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
@Override
|
||||
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Request baseRequest = Request.getBaseRequest(request);
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
||||
|
||||
if (!(request instanceof HttpServletRequest))
|
||||
request = new ServletRequestHttpWrapper(request);
|
||||
|
@ -98,6 +100,10 @@ public class Dispatcher implements RequestDispatcher
|
|||
}
|
||||
else
|
||||
{
|
||||
Objects.requireNonNull(_uri);
|
||||
// Check any URI violations against the compliance for this request
|
||||
checkUriViolations(_uri, baseRequest);
|
||||
|
||||
IncludeAttributes attr = new IncludeAttributes(
|
||||
old_attr,
|
||||
baseRequest,
|
||||
|
@ -131,7 +137,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
|
||||
{
|
||||
Request baseRequest = Request.getBaseRequest(request);
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
||||
Response baseResponse = baseRequest.getResponse();
|
||||
baseResponse.resetForForward();
|
||||
|
||||
|
@ -159,6 +165,10 @@ public class Dispatcher implements RequestDispatcher
|
|||
}
|
||||
else
|
||||
{
|
||||
Objects.requireNonNull(_uri);
|
||||
// Check any URI violations against the compliance for this request
|
||||
checkUriViolations(_uri, baseRequest);
|
||||
|
||||
// If we have already been forwarded previously, then keep using the established
|
||||
// original value. Otherwise, this is the first forward and we need to establish the values.
|
||||
// Note: the established value on the original request for pathInfo and
|
||||
|
@ -230,6 +240,18 @@ public class Dispatcher implements RequestDispatcher
|
|||
}
|
||||
}
|
||||
|
||||
private static void checkUriViolations(HttpURI uri, Request baseRequest)
|
||||
{
|
||||
if (uri.hasViolations())
|
||||
{
|
||||
HttpChannel channel = baseRequest.getHttpChannel();
|
||||
UriCompliance compliance = channel == null || channel.getHttpConfiguration() == null ? null : channel.getHttpConfiguration().getUriCompliance();
|
||||
String illegalState = UriCompliance.checkUriCompliance(compliance, uri);
|
||||
if (illegalState != null)
|
||||
throw new IllegalStateException(illegalState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -67,6 +67,7 @@ import javax.servlet.http.WebConnection;
|
|||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.ComplianceViolation;
|
||||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
import org.eclipse.jetty.http.HttpCompliance;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -1687,28 +1688,13 @@ public class Request implements HttpServletRequest
|
|||
_method = request.getMethod();
|
||||
_httpFields = request.getFields();
|
||||
final HttpURI uri = request.getURI();
|
||||
boolean ambiguous = false;
|
||||
UriCompliance compliance = null;
|
||||
if (uri.hasViolations())
|
||||
{
|
||||
ambiguous = uri.isAmbiguous();
|
||||
compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
|
||||
if (uri.hasUtf16Encoding() && (compliance == null || !compliance.allows(UriCompliance.Violation.UTF16_ENCODINGS)))
|
||||
throw new BadMessageException("UTF16 % encoding not supported");
|
||||
|
||||
if (ambiguous)
|
||||
{
|
||||
if (uri.hasAmbiguousSegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT)))
|
||||
throw new BadMessageException("Ambiguous segment in URI");
|
||||
if (uri.hasAmbiguousEmptySegment() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT)))
|
||||
throw new BadMessageException("Ambiguous empty segment in URI");
|
||||
if (uri.hasAmbiguousSeparator() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR)))
|
||||
throw new BadMessageException("Ambiguous segment in URI");
|
||||
if (uri.hasAmbiguousParameter() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER)))
|
||||
throw new BadMessageException("Ambiguous path parameter in URI");
|
||||
if (uri.hasAmbiguousEncoding() && (compliance == null || !compliance.allows(UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING)))
|
||||
throw new BadMessageException("Ambiguous path encoding in URI");
|
||||
}
|
||||
String badMessage = UriCompliance.checkUriCompliance(compliance, uri);
|
||||
if (badMessage != null)
|
||||
throw new BadMessageException(badMessage);
|
||||
}
|
||||
|
||||
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
|
||||
|
@ -1740,7 +1726,6 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
_uri = builder.asImmutable();
|
||||
}
|
||||
|
||||
setSecure(HttpScheme.HTTPS.is(_uri.getScheme()));
|
||||
|
||||
String encoded = _uri.getPath();
|
||||
|
@ -1751,17 +1736,15 @@ public class Request implements HttpServletRequest
|
|||
else if (encoded.startsWith("/"))
|
||||
{
|
||||
path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath();
|
||||
// Strictly speaking if a URI is legal and encodes ambiguous segments, then they should be
|
||||
// reflected in the decoded string version. However, it can be ambiguous to provide a decoded path as
|
||||
// a string, so we normalize again. If an application wishes to see ambiguous URIs, then they must
|
||||
// set the {@link UriCompliance.Violation#NON_CANONICAL_AMBIGUOUS_PATHS} compliance.
|
||||
if (ambiguous && (compliance == null || !compliance.allows(UriCompliance.Violation.NON_CANONICAL_AMBIGUOUS_PATHS)))
|
||||
path = URIUtil.canonicalPath(path);
|
||||
}
|
||||
else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod()))
|
||||
{
|
||||
path = encoded;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = null;
|
||||
}
|
||||
|
||||
if (path == null || path.isEmpty())
|
||||
{
|
||||
|
|
|
@ -568,14 +568,14 @@ public class Response implements HttpServletResponse
|
|||
if (location.startsWith("/"))
|
||||
{
|
||||
// absolute in context
|
||||
location = URIUtil.canonicalEncodedPath(location);
|
||||
location = URIUtil.canonicalURI(location);
|
||||
}
|
||||
else
|
||||
{
|
||||
// relative to request
|
||||
String path = _channel.getRequest().getRequestURI();
|
||||
String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
|
||||
location = URIUtil.canonicalEncodedPath(URIUtil.addEncodedPaths(parent, location));
|
||||
location = URIUtil.canonicalURI(URIUtil.addEncodedPaths(parent, location));
|
||||
if (location != null && !location.startsWith("/"))
|
||||
buf.append('/');
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import java.util.Enumeration;
|
|||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -1490,7 +1489,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
return false;
|
||||
|
||||
if (target.startsWith("//"))
|
||||
// ignore empty segments which may be discard by file system
|
||||
target = URIUtil.compactPath(target);
|
||||
|
||||
ProtectedTargetType type = _protectedTargets.getBest(target);
|
||||
|
@ -1930,7 +1928,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
|
||||
try
|
||||
{
|
||||
path = URIUtil.canonicalPath(path);
|
||||
// addPath with accept non-canonical paths that don't go above the root,
|
||||
// but will treat them as aliases. So unless allowed by an AliasChecker
|
||||
// they will be rejected below.
|
||||
Resource resource = _baseResource.addPath(path);
|
||||
|
||||
if (checkAlias(path, resource))
|
||||
|
@ -1959,9 +1959,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
LOG.debug("Aliased resource: {}~={}", resource, resource.getAlias());
|
||||
|
||||
// alias checks
|
||||
for (Iterator<AliasCheck> i = getAliasChecks().iterator(); i.hasNext(); )
|
||||
for (AliasCheck check : getAliasChecks())
|
||||
{
|
||||
AliasCheck check = i.next();
|
||||
if (check.check(path, resource))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -2014,7 +2013,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
{
|
||||
try
|
||||
{
|
||||
path = URIUtil.canonicalPath(path);
|
||||
Resource resource = getResource(path);
|
||||
|
||||
if (resource != null && resource.exists())
|
||||
|
@ -2214,6 +2212,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String uriInContext)
|
||||
{
|
||||
// uriInContext is encoded, potentially with query.
|
||||
if (uriInContext == null)
|
||||
return null;
|
||||
|
||||
|
@ -2223,10 +2222,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
try
|
||||
{
|
||||
String contextPath = getContextPath();
|
||||
String pathInfo;
|
||||
|
||||
// uriInContext is canonicalized by HttpURI.
|
||||
HttpURI.Mutable uri = HttpURI.build(uriInContext);
|
||||
pathInfo = URIUtil.canonicalPath(uri.getDecodedPath());
|
||||
String pathInfo = uri.getDecodedPath();
|
||||
if (StringUtil.isEmpty(pathInfo))
|
||||
return null;
|
||||
|
||||
|
@ -2247,6 +2245,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
@Override
|
||||
public String getRealPath(String path)
|
||||
{
|
||||
// This is an API call from the application which may have arbitrary non canonical paths passed
|
||||
// Thus we canonicalize here, to avoid the enforcement of only canonical paths in
|
||||
// ContextHandler.this.getResource(path).
|
||||
path = URIUtil.canonicalPath(path);
|
||||
if (path == null)
|
||||
return null;
|
||||
if (path.length() == 0)
|
||||
|
@ -2275,6 +2277,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
@Override
|
||||
public URL getResource(String path) throws MalformedURLException
|
||||
{
|
||||
// This is an API call from the application which may have arbitrary non canonical paths passed
|
||||
// Thus we canonicalize here, to avoid the enforcement of only canonical paths in
|
||||
// ContextHandler.this.getResource(path).
|
||||
path = URIUtil.canonicalPath(path);
|
||||
if (path == null)
|
||||
return null;
|
||||
Resource resource = ContextHandler.this.getResource(path);
|
||||
if (resource != null && resource.exists())
|
||||
return resource.getURI().toURL();
|
||||
|
@ -2305,6 +2313,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
@Override
|
||||
public Set<String> getResourcePaths(String path)
|
||||
{
|
||||
// This is an API call from the application which may have arbitrary non canonical paths passed
|
||||
// Thus we canonicalize here, to avoid the enforcement of only canonical paths in
|
||||
// ContextHandler.this.getResource(path).
|
||||
path = URIUtil.canonicalPath(path);
|
||||
if (path == null)
|
||||
return null;
|
||||
return ContextHandler.this.getResourcePaths(path);
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,6 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
|
|||
|
||||
if (_baseResource != null)
|
||||
{
|
||||
path = URIUtil.canonicalPath(path);
|
||||
r = _baseResource.addPath(path);
|
||||
|
||||
if (r.isAlias() && (_context == null || !_context.checkAlias(path, r)))
|
||||
|
@ -169,8 +168,6 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
|
|||
else if (_context != null)
|
||||
{
|
||||
r = _context.getResource(path);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
|
||||
|
|
|
@ -504,7 +504,7 @@ public class HttpConnectionTest
|
|||
{
|
||||
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, "reason: Bad URI");
|
||||
checkContains(response, 0, "reason: Bad Request");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -512,7 +512,7 @@ public class HttpConnectionTest
|
|||
{
|
||||
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, "reason: Bad URI");
|
||||
checkContains(response, 0, "reason: Bad Request");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -520,7 +520,7 @@ public class HttpConnectionTest
|
|||
{
|
||||
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, "reason: Bad URI");
|
||||
checkContains(response, 0, "reason: Bad Request");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -827,7 +827,7 @@ public class HttpConnectionTest
|
|||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n");
|
||||
checkContains(response, 0, "HTTP/1.1 200"); //now fallback to iso-8859-1
|
||||
checkContains(response, 0, "HTTP/1.1 400");
|
||||
|
||||
response = connector.getResponse("GET /bad/utf8%c1 HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
|
|
|
@ -1713,15 +1713,6 @@ public class RequestTest
|
|||
assertThat(_connector.getResponse(request), Matchers.allOf(
|
||||
startsWith("HTTP/1.1 200"),
|
||||
containsString("pathInfo=/path/info")));
|
||||
|
||||
_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.from(EnumSet.of(
|
||||
UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR,
|
||||
UriCompliance.Violation.AMBIGUOUS_PATH_SEGMENT,
|
||||
UriCompliance.Violation.AMBIGUOUS_PATH_PARAMETER,
|
||||
UriCompliance.Violation.NON_CANONICAL_AMBIGUOUS_PATHS)));
|
||||
assertThat(_connector.getResponse(request), Matchers.allOf(
|
||||
startsWith("HTTP/1.1 200"),
|
||||
containsString("pathInfo=/path/ambiguous/.././info")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.junit.jupiter.api.BeforeAll;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
@ -218,24 +220,39 @@ public class ContextHandlerGetResourceTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testNormalize() throws Exception
|
||||
public void testDoesNotExistResource() throws Exception
|
||||
{
|
||||
final String path = "/down/.././index.html";
|
||||
Resource resource = context.getResource(path);
|
||||
assertEquals("index.html", resource.getFile().getName());
|
||||
assertEquals(docroot, resource.getFile().getParentFile());
|
||||
assertTrue(resource.exists());
|
||||
|
||||
URL url = context.getServletContext().getResource(path);
|
||||
assertEquals(docroot, new File(url.toURI()).getParentFile());
|
||||
Resource resource = context.getResource("/doesNotExist.html");
|
||||
assertNotNull(resource);
|
||||
assertFalse(resource.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooNormal() throws Exception
|
||||
public void testAlias() throws Exception
|
||||
{
|
||||
final String path = "/down/.././../";
|
||||
String path = "/./index.html";
|
||||
Resource resource = context.getResource(path);
|
||||
assertNull(resource);
|
||||
URL resourceURL = context.getServletContext().getResource(path);
|
||||
assertFalse(resourceURL.getPath().contains("/./"));
|
||||
|
||||
path = "/down/../index.html";
|
||||
resource = context.getResource(path);
|
||||
assertNull(resource);
|
||||
resourceURL = context.getServletContext().getResource(path);
|
||||
assertFalse(resourceURL.getPath().contains("/../"));
|
||||
|
||||
path = "//index.html";
|
||||
resource = context.getResource(path);
|
||||
assertNull(resource);
|
||||
resourceURL = context.getServletContext().getResource(path);
|
||||
assertNull(resourceURL);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"/down/.././../", "/../down/"})
|
||||
public void testNormalize(String path) throws Exception
|
||||
{
|
||||
URL url = context.getServletContext().getResource(path);
|
||||
assertNull(url);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
@ -420,8 +419,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
|
|||
}
|
||||
else
|
||||
{
|
||||
URL u = _servletContext.getResource(pathInContext);
|
||||
r = _contextHandler.newResource(u);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -57,11 +57,13 @@ public class RequestURITest
|
|||
ret.add(Arguments.of("/hello?type=wo&rld", "/hello", "type=wo&rld"));
|
||||
ret.add(Arguments.of("/hello?type=wo%20rld", "/hello", "type=wo%20rld"));
|
||||
ret.add(Arguments.of("/hello?type=wo+rld", "/hello", "type=wo+rld"));
|
||||
ret.add(Arguments.of("/hello?type=/a/../b/", "/hello", "type=/a/../b/"));
|
||||
ret.add(Arguments.of("/It%27s%20me%21", "/It%27s%20me%21", null));
|
||||
// try some slash encoding (with case preservation tests)
|
||||
ret.add(Arguments.of("/hello%2fworld", "/hello%2fworld", null));
|
||||
ret.add(Arguments.of("/hello%2Fworld", "/hello%2Fworld", null));
|
||||
ret.add(Arguments.of("/%2f%2Fhello%2Fworld", "/%2f%2Fhello%2Fworld", null));
|
||||
|
||||
// try some "?" encoding (should not see as query string)
|
||||
ret.add(Arguments.of("/hello%3Fworld", "/hello%3Fworld", null));
|
||||
// try some strange encodings (should preserve them)
|
||||
|
@ -69,8 +71,13 @@ public class RequestURITest
|
|||
ret.add(Arguments.of("/hello%u0025world", "/hello%u0025world", null));
|
||||
ret.add(Arguments.of("/hello-euro-%E2%82%AC", "/hello-euro-%E2%82%AC", null));
|
||||
ret.add(Arguments.of("/hello-euro?%E2%82%AC", "/hello-euro", "%E2%82%AC"));
|
||||
// test the ascii control characters (just for completeness)
|
||||
for (int i = 0x0; i < 0x1f; i++)
|
||||
|
||||
ret.add(Arguments.of("/hello/..;/world", "/hello/..;/world", null));
|
||||
ret.add(Arguments.of("/hello/..;?/world", "/hello/..;", "/world"));
|
||||
|
||||
// Test the ascii control characters (just for completeness).
|
||||
// Zero is not allowed in UTF-8 sequences so start from 1.
|
||||
for (int i = 0x1; i < 0x1f; i++)
|
||||
{
|
||||
String raw = String.format("/hello%%%02Xworld", i);
|
||||
ret.add(Arguments.of(raw, raw, null));
|
||||
|
@ -194,7 +201,6 @@ public class RequestURITest
|
|||
// Read the response.
|
||||
String response = readResponse(client);
|
||||
|
||||
// TODO: is HTTP/1.1 response appropriate for an HTTP/1.0 request?
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
|
||||
assertThat(response, Matchers.containsString("RequestURI: " + expectedReqUri));
|
||||
assertThat(response, Matchers.containsString("QueryString: " + expectedQuery));
|
||||
|
@ -221,4 +227,43 @@ public class RequestURITest
|
|||
assertThat(response, Matchers.containsString("QueryString: " + expectedQuery));
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> badData()
|
||||
{
|
||||
List<Arguments> ret = new ArrayList<>();
|
||||
ret.add(Arguments.of("/hello\000"));
|
||||
ret.add(Arguments.of("/hello%00"));
|
||||
ret.add(Arguments.of("/hello%u0000"));
|
||||
ret.add(Arguments.of("/hello\000/world"));
|
||||
ret.add(Arguments.of("/hello%00world"));
|
||||
ret.add(Arguments.of("/hello%u0000world"));
|
||||
ret.add(Arguments.of("/hello%GG"));
|
||||
ret.add(Arguments.of("/hello%;/world"));
|
||||
ret.add(Arguments.of("/hello/../../world"));
|
||||
ret.add(Arguments.of("/hello/%#x/../world"));
|
||||
ret.add(Arguments.of("/../hello/world"));
|
||||
ret.add(Arguments.of("/hello%u00u00/world"));
|
||||
ret.add(Arguments.of("hello"));
|
||||
|
||||
return ret.stream();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("badData")
|
||||
public void testGetBadRequestsURIHTTP10(String rawpath) throws Exception
|
||||
{
|
||||
try (Socket client = newSocket(serverURI.getHost(), serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
|
||||
String request = String.format("GET %s HTTP/1.0\r\n\r\n", rawpath);
|
||||
os.write(request.getBytes(StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
|
||||
// Read the response.
|
||||
String response = readResponse(client);
|
||||
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 400 "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.eclipse.jetty.server.HttpOutput;
|
|||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
|
||||
/**
|
||||
* A servlet that uses the Servlet 3.1 asynchronous IO API to server
|
||||
* A demonstration servlet that uses the Servlet 3.1 asynchronous IO API to server
|
||||
* static content at a limited data rate.
|
||||
* <p>
|
||||
* Two implementations are supported: <ul>
|
||||
|
@ -42,8 +42,7 @@ import org.eclipse.jetty.util.ProcessorUtils;
|
|||
* APIs, but produces more garbage due to the byte[] nature of the API.
|
||||
* <li>the <code>JettyDataStream</code> impl uses a Jetty API to write a ByteBuffer
|
||||
* and thus allow the efficient use of file mapped buffers without any
|
||||
* temporary buffer copies (I did tell the JSR that this was a good idea to
|
||||
* have in the standard!).
|
||||
* temporary buffer copies.
|
||||
* </ul>
|
||||
* <p>
|
||||
* The data rate is controlled by setting init parameters:
|
||||
|
@ -53,7 +52,9 @@ import org.eclipse.jetty.util.ProcessorUtils;
|
|||
* <dt>pool</dt><dd>The size of the thread pool used to service the writes (defaults to available processors)</dd>
|
||||
* </dl>
|
||||
* Thus if buffersize = 1024 and pause = 100, the data rate will be limited to 10KB per second.
|
||||
* @deprecated this is intended as a demonstration and not production quality.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DataRateLimitedServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = -4771757707068097025L;
|
||||
|
|
|
@ -57,6 +57,7 @@ import org.eclipse.jetty.util.URIUtil;
|
|||
* <li><b>putAtomic</b> - boolean, if true PUT files are written to a temp location and moved into place.
|
||||
* </ul>
|
||||
*/
|
||||
@Deprecated
|
||||
public class PutFilter implements Filter
|
||||
{
|
||||
public static final String __PUT = "PUT";
|
||||
|
@ -80,7 +81,8 @@ public class PutFilter implements Filter
|
|||
|
||||
_tmpdir = (File)_context.getAttribute("javax.servlet.context.tempdir");
|
||||
|
||||
if (_context.getRealPath("/") == null)
|
||||
String realPath = _context.getRealPath("/");
|
||||
if (realPath == null)
|
||||
throw new UnavailableException("Packed war");
|
||||
|
||||
String b = config.getInitParameter("baseURI");
|
||||
|
@ -90,7 +92,7 @@ public class PutFilter implements Filter
|
|||
}
|
||||
else
|
||||
{
|
||||
File base = new File(_context.getRealPath("/"));
|
||||
File base = new File(realPath);
|
||||
_baseURI = base.toURI().toString();
|
||||
}
|
||||
|
||||
|
@ -284,7 +286,7 @@ public class PutFilter implements Filter
|
|||
public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file)
|
||||
throws ServletException, IOException, URISyntaxException
|
||||
{
|
||||
String newPath = URIUtil.canonicalEncodedPath(request.getHeader("new-uri"));
|
||||
String newPath = URIUtil.canonicalURI(request.getHeader("new-uri"));
|
||||
if (newPath == null)
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
|
|
|
@ -471,6 +471,7 @@ public class URIUtil
|
|||
if (u == 'u')
|
||||
{
|
||||
// UTF16 encoding is only supported with UriCompliance.Violation.UTF16_ENCODINGS.
|
||||
// This is wrong. This is a codepoint not a char
|
||||
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
|
||||
i += 5;
|
||||
}
|
||||
|
@ -532,7 +533,6 @@ public class URIUtil
|
|||
{
|
||||
throw new IllegalArgumentException("cannot decode URI", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Decode a URI path and strip parameters of ISO-8859-1 path
|
||||
|
@ -776,143 +776,138 @@ public class URIUtil
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert an encoded path to a canonical form.
|
||||
* Convert a partial URI to a canonical form.
|
||||
* <p>
|
||||
* All instances of "." and ".." are factored out.
|
||||
* All segments of "." and ".." are factored out.
|
||||
* Null is returned if the path tries to .. above its root.
|
||||
* </p>
|
||||
*
|
||||
* @param path the path to convert, decoded, with path separators '/' and no queries.
|
||||
* @param uri the encoded URI from the path onwards, which may contain query strings and/or fragments
|
||||
* @return the canonical path, or null if path traversal above root.
|
||||
* @see #canonicalPath(String)
|
||||
*/
|
||||
public static String canonicalPath(String path)
|
||||
public static String canonicalURI(String uri)
|
||||
{
|
||||
// See https://tools.ietf.org/html/rfc3986#section-5.2.4
|
||||
if (uri == null || uri.isEmpty())
|
||||
return uri;
|
||||
|
||||
if (path == null || path.isEmpty())
|
||||
return path;
|
||||
|
||||
int end = path.length();
|
||||
boolean slash = true;
|
||||
int end = uri.length();
|
||||
int i = 0;
|
||||
int dots = 0;
|
||||
|
||||
// Initially just loop looking if we may need to normalize
|
||||
loop: while (i < end)
|
||||
{
|
||||
char c = path.charAt(i);
|
||||
char c = uri.charAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '/':
|
||||
dots = 0;
|
||||
slash = true;
|
||||
break;
|
||||
|
||||
case '.':
|
||||
if (dots == 0)
|
||||
{
|
||||
dots = 1;
|
||||
if (slash)
|
||||
break loop;
|
||||
}
|
||||
dots = -1;
|
||||
slash = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
case '#':
|
||||
// Nothing to normalize so return original path
|
||||
return uri;
|
||||
|
||||
default:
|
||||
dots = -1;
|
||||
slash = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Nothing to normalize so return original path
|
||||
if (i == end)
|
||||
return path;
|
||||
return uri;
|
||||
|
||||
StringBuilder canonical = new StringBuilder(path.length());
|
||||
canonical.append(path, 0, i);
|
||||
// We probably need to normalize, so copy to path so far into builder
|
||||
StringBuilder canonical = new StringBuilder(uri.length());
|
||||
canonical.append(uri, 0, i);
|
||||
|
||||
// Loop looking for single and double dot segments
|
||||
int dots = 1;
|
||||
i++;
|
||||
while (i <= end)
|
||||
loop : while (i < end)
|
||||
{
|
||||
char c = i < end ? path.charAt(i) : '\0';
|
||||
char c = uri.charAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
if (dots == 2)
|
||||
{
|
||||
if (canonical.length() < 2)
|
||||
return null;
|
||||
canonical.setLength(canonical.length() - 1);
|
||||
canonical.setLength(canonical.lastIndexOf("/") + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case '/':
|
||||
switch (dots)
|
||||
{
|
||||
case 1:
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (canonical.length() < 2)
|
||||
return null;
|
||||
canonical.setLength(canonical.length() - 1);
|
||||
canonical.setLength(canonical.lastIndexOf("/") + 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
canonical.append(c);
|
||||
}
|
||||
if (doDotsSlash(canonical, dots))
|
||||
return null;
|
||||
slash = true;
|
||||
dots = 0;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
case '#':
|
||||
// finish normalization at a query
|
||||
break loop;
|
||||
|
||||
case '.':
|
||||
switch (dots)
|
||||
{
|
||||
case 0:
|
||||
dots = 1;
|
||||
break;
|
||||
case 1:
|
||||
dots = 2;
|
||||
break;
|
||||
case 2:
|
||||
canonical.append("...");
|
||||
dots = -1;
|
||||
break;
|
||||
default:
|
||||
canonical.append('.');
|
||||
}
|
||||
// Count dots only if they are leading in the segment
|
||||
if (dots > 0)
|
||||
dots++;
|
||||
else if (slash)
|
||||
dots = 1;
|
||||
else
|
||||
canonical.append('.');
|
||||
slash = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (dots)
|
||||
{
|
||||
case 1:
|
||||
canonical.append('.');
|
||||
break;
|
||||
case 2:
|
||||
canonical.append("..");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
// Add leading dots to the path
|
||||
while (dots-- > 0)
|
||||
canonical.append('.');
|
||||
canonical.append(c);
|
||||
dots = -1;
|
||||
dots = 0;
|
||||
slash = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// process any remaining dots
|
||||
if (doDots(canonical, dots))
|
||||
return null;
|
||||
|
||||
// append any query
|
||||
if (i < end)
|
||||
canonical.append(uri, i, end);
|
||||
|
||||
return canonical.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a path to a cananonical form.
|
||||
* <p>
|
||||
* All instances of "." and ".." are factored out.
|
||||
* </p>
|
||||
* @param path the encoded URI from the path onwards, which may contain query strings and/or fragments
|
||||
* @return the canonical path, or null if path traversal above root.
|
||||
* @deprecated Use {@link #canonicalURI(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String canonicalEncodedPath(String path)
|
||||
{
|
||||
return canonicalURI(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a decoded URI path to a canonical form.
|
||||
* <p>
|
||||
* All segments of "." and ".." are factored out.
|
||||
* Null is returned if the path tries to .. above its root.
|
||||
* </p>
|
||||
*
|
||||
* @param path the path to convert (expects URI/URL form, encoded, and with path separators '/')
|
||||
* @param path the decoded URI path to convert. Any special characters (e.g. '?', "#") are assumed to be part of
|
||||
* the path segments.
|
||||
* @return the canonical path, or null if path traversal above root.
|
||||
* @see #canonicalURI(String)
|
||||
*/
|
||||
public static String canonicalEncodedPath(String path)
|
||||
public static String canonicalPath(String path)
|
||||
{
|
||||
if (path == null || path.isEmpty())
|
||||
return path;
|
||||
|
@ -921,8 +916,8 @@ public class URIUtil
|
|||
int end = path.length();
|
||||
int i = 0;
|
||||
|
||||
loop:
|
||||
while (i < end)
|
||||
// Initially just loop looking if we may need to normalize
|
||||
loop: while (i < end)
|
||||
{
|
||||
char c = path.charAt(i);
|
||||
switch (c)
|
||||
|
@ -937,9 +932,6 @@ public class URIUtil
|
|||
slash = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return path;
|
||||
|
||||
default:
|
||||
slash = false;
|
||||
}
|
||||
|
@ -947,56 +939,31 @@ public class URIUtil
|
|||
i++;
|
||||
}
|
||||
|
||||
// Nothing to normalize so return original path
|
||||
if (i == end)
|
||||
return path;
|
||||
|
||||
// We probably need to normalize, so copy to path so far into builder
|
||||
StringBuilder canonical = new StringBuilder(path.length());
|
||||
canonical.append(path, 0, i);
|
||||
|
||||
// Loop looking for single and double dot segments
|
||||
int dots = 1;
|
||||
i++;
|
||||
while (i <= end)
|
||||
while (i < end)
|
||||
{
|
||||
char c = i < end ? path.charAt(i) : '\0';
|
||||
char c = path.charAt(i);
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
case '/':
|
||||
case '?':
|
||||
switch (dots)
|
||||
{
|
||||
case 0:
|
||||
if (c != '\0')
|
||||
canonical.append(c);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (c == '?')
|
||||
canonical.append(c);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (canonical.length() < 2)
|
||||
return null;
|
||||
canonical.setLength(canonical.length() - 1);
|
||||
canonical.setLength(canonical.lastIndexOf("/") + 1);
|
||||
if (c == '?')
|
||||
canonical.append(c);
|
||||
break;
|
||||
default:
|
||||
while (dots-- > 0)
|
||||
{
|
||||
canonical.append('.');
|
||||
}
|
||||
if (c != '\0')
|
||||
canonical.append(c);
|
||||
}
|
||||
|
||||
if (doDotsSlash(canonical, dots))
|
||||
return null;
|
||||
slash = true;
|
||||
dots = 0;
|
||||
break;
|
||||
|
||||
case '.':
|
||||
// Count dots only if they are leading in the segment
|
||||
if (dots > 0)
|
||||
dots++;
|
||||
else if (slash)
|
||||
|
@ -1007,20 +974,66 @@ public class URIUtil
|
|||
break;
|
||||
|
||||
default:
|
||||
// Add leading dots to the path
|
||||
while (dots-- > 0)
|
||||
{
|
||||
canonical.append('.');
|
||||
}
|
||||
canonical.append(c);
|
||||
dots = 0;
|
||||
slash = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// process any remaining dots
|
||||
if (doDots(canonical, dots))
|
||||
return null;
|
||||
|
||||
return canonical.toString();
|
||||
}
|
||||
|
||||
private static boolean doDots(StringBuilder canonical, int dots)
|
||||
{
|
||||
switch (dots)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
if (canonical.length() < 2)
|
||||
return true;
|
||||
canonical.setLength(canonical.length() - 1);
|
||||
canonical.setLength(canonical.lastIndexOf("/") + 1);
|
||||
break;
|
||||
default:
|
||||
while (dots-- > 0)
|
||||
canonical.append('.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean doDotsSlash(StringBuilder canonical, int dots)
|
||||
{
|
||||
switch (dots)
|
||||
{
|
||||
case 0:
|
||||
canonical.append('/');
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
if (canonical.length() < 2)
|
||||
return true;
|
||||
canonical.setLength(canonical.length() - 1);
|
||||
canonical.setLength(canonical.lastIndexOf("/") + 1);
|
||||
break;
|
||||
default:
|
||||
while (dots-- > 0)
|
||||
canonical.append('.');
|
||||
canonical.append('/');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a path to a compact form.
|
||||
* All instances of "//" and "///" etc. are factored out to single "/"
|
||||
|
|
|
@ -213,7 +213,6 @@ public abstract class Utf8Appendable
|
|||
|
||||
protected void appendByte(byte b) throws IOException
|
||||
{
|
||||
|
||||
if (b > 0 && _state == UTF8_ACCEPT)
|
||||
{
|
||||
_appendable.append((char)(b & 0xFF));
|
||||
|
|
|
@ -56,7 +56,7 @@ public class PathResource extends Resource
|
|||
private final URI uri;
|
||||
private final boolean belongsToDefaultFileSystem;
|
||||
|
||||
private final Path checkAliasPath()
|
||||
private Path checkAliasPath()
|
||||
{
|
||||
Path abs = path;
|
||||
|
||||
|
@ -68,7 +68,6 @@ public class PathResource extends Resource
|
|||
* we will just use the original URI to construct the
|
||||
* alias reference Path.
|
||||
*/
|
||||
|
||||
if (!URIUtil.equalsIgnoreEncodings(uri, path.toUri()))
|
||||
{
|
||||
try
|
||||
|
@ -85,9 +84,14 @@ public class PathResource extends Resource
|
|||
}
|
||||
|
||||
if (!abs.isAbsolute())
|
||||
{
|
||||
abs = path.toAbsolutePath();
|
||||
}
|
||||
|
||||
// Any normalization difference means it's an alias,
|
||||
// and we don't want to bother further to follow
|
||||
// symlinks as it's an alias anyway.
|
||||
Path normal = path.normalize();
|
||||
if (!isSameName(abs, normal))
|
||||
return normal;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -96,11 +100,8 @@ public class PathResource extends Resource
|
|||
if (Files.exists(path))
|
||||
{
|
||||
Path real = abs.toRealPath(FOLLOW_LINKS);
|
||||
|
||||
if (!isSameName(abs, real))
|
||||
{
|
||||
return real;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
|
@ -233,8 +234,7 @@ public class PathResource extends Resource
|
|||
LOG.debug("Unable to get real/canonical path for {}", path, e);
|
||||
}
|
||||
|
||||
// cleanup any lingering relative path nonsense (like "/./" and "/../")
|
||||
this.path = absPath.normalize();
|
||||
this.path = absPath;
|
||||
|
||||
assertValidPath(path);
|
||||
this.uri = this.path.toUri();
|
||||
|
@ -254,7 +254,7 @@ public class PathResource extends Resource
|
|||
// Calculate the URI and the path separately, so that any aliasing done by
|
||||
// FileSystem.getPath(path,childPath) is visible as a difference to the URI
|
||||
// obtained via URIUtil.addDecodedPath(uri,childPath)
|
||||
|
||||
// The checkAliasPath normalization checks will only work correctly if the getPath implementation here does not normalize.
|
||||
this.path = parent.path.getFileSystem().getPath(parent.path.toString(), childPath);
|
||||
if (isDirectory() && !childPath.endsWith("/"))
|
||||
childPath += "/";
|
||||
|
@ -348,22 +348,23 @@ public class PathResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public Resource addPath(final String subpath) throws IOException
|
||||
public Resource addPath(final String subPath) throws IOException
|
||||
{
|
||||
String cpath = URIUtil.canonicalPath(subpath);
|
||||
// Check that the path is within the root,
|
||||
// but use the original path to create the
|
||||
// resource, to preserve aliasing.
|
||||
if (URIUtil.canonicalPath(subPath) == null)
|
||||
throw new MalformedURLException(subPath);
|
||||
|
||||
if ((cpath == null) || (cpath.length() == 0))
|
||||
throw new MalformedURLException(subpath);
|
||||
|
||||
if ("/".equals(cpath))
|
||||
if ("/".equals(subPath))
|
||||
return this;
|
||||
|
||||
// subpaths are always under PathResource
|
||||
// compensate for input subpaths like "/subdir"
|
||||
// Sub-paths are always under PathResource
|
||||
// compensate for input sub-paths like "/subdir"
|
||||
// where default resolve behavior would be
|
||||
// to treat that like an absolute path
|
||||
|
||||
return new PathResource(this, subpath);
|
||||
return new PathResource(this, subPath);
|
||||
}
|
||||
|
||||
private void assertValidPath(Path path)
|
||||
|
|
|
@ -419,10 +419,12 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
* Returns the resource contained inside the current resource with the
|
||||
* given name, which may or may not exist.
|
||||
*
|
||||
* @param path The path segment to add, which is not encoded
|
||||
* @param path The path segment to add, which is not encoded. The path may be non canonical, but if so then
|
||||
* the resulting Resource will return true from {@link #isAlias()}.
|
||||
* @return the Resource for the resolved path within this Resource, never null
|
||||
* @throws IOException if unable to resolve the path
|
||||
* @throws MalformedURLException if the resolution of the path fails because the input path parameter is malformed.
|
||||
* @throws MalformedURLException if the resolution of the path fails because the input path parameter is malformed, or
|
||||
* a relative path attempts to access above the root resource.
|
||||
*/
|
||||
public abstract Resource addPath(String path)
|
||||
throws IOException, MalformedURLException;
|
||||
|
@ -477,6 +479,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
*/
|
||||
public String getListHTML(String base, boolean parent, String query) throws IOException
|
||||
{
|
||||
// This method doesn't check aliases, so it is OK to canonicalize here.
|
||||
base = URIUtil.canonicalPath(base);
|
||||
if (base == null || !isDirectory())
|
||||
return null;
|
||||
|
|
|
@ -280,9 +280,11 @@ public class URLResource extends Resource
|
|||
public Resource addPath(String path)
|
||||
throws IOException
|
||||
{
|
||||
Objects.requireNonNull(path, "Path may not be null");
|
||||
|
||||
path = URIUtil.canonicalPath(path);
|
||||
// Check that the path is within the root,
|
||||
// but use the original path to create the
|
||||
// resource, to preserve aliasing.
|
||||
if (URIUtil.canonicalPath(path) == null)
|
||||
throw new MalformedURLException(path);
|
||||
|
||||
return newResource(URIUtil.addEncodedPaths(_url.toExternalForm(), URIUtil.encodePath(path)), _useCaches);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class URIUtilCanonicalPathTest
|
||||
{
|
||||
public static Stream<Arguments> data()
|
||||
public static Stream<Arguments> paths()
|
||||
{
|
||||
String[][] canonical =
|
||||
{
|
||||
|
@ -83,6 +84,7 @@ public class URIUtilCanonicalPathTest
|
|||
{"/foo/../bar//", "/bar//"},
|
||||
{"/ctx/../bar/../ctx/all/index.txt", "/ctx/all/index.txt"},
|
||||
{"/down/.././index.html", "/index.html"},
|
||||
{"/aaa/bbb/ccc/..", "/aaa/bbb/"},
|
||||
|
||||
// Path traversal up past root
|
||||
{"..", null},
|
||||
|
@ -95,10 +97,8 @@ public class URIUtilCanonicalPathTest
|
|||
{"a/../..", null},
|
||||
{"/foo/../../bar", null},
|
||||
|
||||
// Query parameter specifics
|
||||
{"/ctx/dir?/../index.html", "/ctx/index.html"},
|
||||
{"/get-files?file=/etc/passwd", "/get-files?file=/etc/passwd"},
|
||||
{"/get-files?file=../../../../../passwd", null},
|
||||
// Encoded ?
|
||||
{"/ctx/dir%3f/../index.html", "/ctx/index.html"},
|
||||
|
||||
// Known windows shell quirks
|
||||
{"file.txt ", "file.txt "}, // with spaces
|
||||
|
@ -120,7 +120,6 @@ public class URIUtilCanonicalPathTest
|
|||
{"/%2e%2e/", "/%2e%2e/"},
|
||||
|
||||
// paths with parameters are not elided
|
||||
// canonicalPath() is not responsible for decoding characters
|
||||
{"/foo/.;/bar", "/foo/.;/bar"},
|
||||
{"/foo/..;/bar", "/foo/..;/bar"},
|
||||
{"/foo/..;/..;/bar", "/foo/..;/..;/bar"},
|
||||
|
@ -139,9 +138,43 @@ public class URIUtilCanonicalPathTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
@MethodSource("paths")
|
||||
public void testCanonicalPath(String input, String expectedResult)
|
||||
{
|
||||
// Check canonicalPath
|
||||
assertThat(URIUtil.canonicalPath(input), is(expectedResult));
|
||||
|
||||
// Check canonicalURI
|
||||
if (expectedResult == null)
|
||||
{
|
||||
assertThat(URIUtil.canonicalURI(input), nullValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
// mostly encodedURI will be the same
|
||||
assertThat(URIUtil.canonicalURI(input), is(expectedResult));
|
||||
// but will terminate on fragments and queries
|
||||
assertThat(URIUtil.canonicalURI(input + "?/foo/../bar/."), is(expectedResult + "?/foo/../bar/."));
|
||||
assertThat(URIUtil.canonicalURI(input + "#/foo/../bar/."), is(expectedResult + "#/foo/../bar/."));
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> queries()
|
||||
{
|
||||
String[][] data =
|
||||
{
|
||||
{"/ctx/../dir?/../index.html", "/dir?/../index.html"},
|
||||
{"/get-files?file=/etc/passwd", "/get-files?file=/etc/passwd"},
|
||||
{"/get-files?file=../../../../../passwd", "/get-files?file=../../../../../passwd"}
|
||||
};
|
||||
return Stream.of(data).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("queries")
|
||||
public void testQuery(String input, String expectedPath)
|
||||
{
|
||||
String actual = URIUtil.canonicalURI(input);
|
||||
assertThat(actual, is(expectedPath));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,35 @@ public class Utf8AppendableTest
|
|||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("implementations")
|
||||
public void testInvalidZeroUTF8(Class<Utf8Appendable> impl) throws UnsupportedEncodingException
|
||||
{
|
||||
// From https://datatracker.ietf.org/doc/html/rfc3629#section-10
|
||||
assertThrows(Utf8Appendable.NotUtf8Exception.class, () ->
|
||||
{
|
||||
Utf8Appendable buffer = impl.getDeclaredConstructor().newInstance();
|
||||
buffer.append((byte)0xC0);
|
||||
buffer.append((byte)0x80);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("implementations")
|
||||
public void testInvalidAlternateDotEncodingUTF8(Class<Utf8Appendable> impl) throws UnsupportedEncodingException
|
||||
{
|
||||
// From https://datatracker.ietf.org/doc/html/rfc3629#section-10
|
||||
assertThrows(Utf8Appendable.NotUtf8Exception.class, () ->
|
||||
{
|
||||
Utf8Appendable buffer = impl.getDeclaredConstructor().newInstance();
|
||||
buffer.append((byte)0x2f);
|
||||
buffer.append((byte)0xc0);
|
||||
buffer.append((byte)0xae);
|
||||
buffer.append((byte)0x2e);
|
||||
buffer.append((byte)0x2f);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("implementations")
|
||||
public void testFastFail1(Class<Utf8Appendable> impl) throws Exception
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.util.resource;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
|
@ -40,6 +41,8 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ResourceTest
|
||||
{
|
||||
|
@ -320,4 +323,19 @@ public class ResourceTest
|
|||
|
||||
assertEquals(rb, ra);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClimbAboveBase() throws Exception
|
||||
{
|
||||
Resource resource = Resource.newResource("/foo/bar");
|
||||
assertThrows(MalformedURLException.class, () -> resource.addPath(".."));
|
||||
|
||||
Resource same = resource.addPath(".");
|
||||
assertNotNull(same);
|
||||
assertTrue(same.isAlias());
|
||||
|
||||
assertThrows(MalformedURLException.class, () -> resource.addPath("./.."));
|
||||
|
||||
assertThrows(MalformedURLException.class, () -> resource.addPath("./../bar"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
if (uriInContext == null || !uriInContext.startsWith(URIUtil.SLASH))
|
||||
throw new MalformedURLException(uriInContext);
|
||||
|
||||
IOException ioe = null;
|
||||
MalformedURLException mue = null;
|
||||
Resource resource = null;
|
||||
int loop = 0;
|
||||
while (uriInContext != null && loop++ < 100)
|
||||
|
@ -413,16 +413,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
|
||||
uriInContext = getResourceAlias(uriInContext);
|
||||
}
|
||||
catch (IOException e)
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
LOG.trace("IGNORED", e);
|
||||
if (ioe == null)
|
||||
ioe = e;
|
||||
if (mue == null)
|
||||
mue = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (ioe instanceof MalformedURLException)
|
||||
throw (MalformedURLException)ioe;
|
||||
if (mue != null)
|
||||
throw mue;
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
@ -1438,6 +1438,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
@Override
|
||||
public URL getResource(String path) throws MalformedURLException
|
||||
{
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
Resource resource = WebAppContext.this.getResource(path);
|
||||
if (resource == null || !resource.exists())
|
||||
return null;
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
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;
|
||||
|
||||
|
@ -328,6 +329,45 @@ public class WebAppContextTest
|
|||
assertThat(HttpTester.parseResponse(connector.getResponse("GET //WEB-INF/test.xml 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 /WEB-INF%2ftest.xml HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"/WEB-INF",
|
||||
"/WEB-INF/",
|
||||
"/WEB-INF/test.xml",
|
||||
"/web-inf/test.xml",
|
||||
"/%2e/WEB-INF/test.xml",
|
||||
"/%2e/%2e/WEB-INF/test.xml",
|
||||
"/foo/%2e%2e/WEB-INF/test.xml",
|
||||
"/%2E/WEB-INF/test.xml",
|
||||
"//WEB-INF/test.xml",
|
||||
"/WEB-INF%2ftest.xml",
|
||||
"/.%00/WEB-INF/test.xml",
|
||||
"/WEB-INF%00/test.xml"
|
||||
})
|
||||
public void testProtectedTargetFailure(String path) throws Exception
|
||||
{
|
||||
Server server = newServer();
|
||||
|
||||
LocalConnector connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.LEGACY);
|
||||
|
||||
HandlerList handlers = new HandlerList();
|
||||
ContextHandlerCollection contexts = new ContextHandlerCollection();
|
||||
WebAppContext context = new WebAppContext();
|
||||
Path testWebapp = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
context.setBaseResource(new PathResource(testWebapp));
|
||||
context.setContextPath("/");
|
||||
server.setHandler(handlers);
|
||||
handlers.addHandler(contexts);
|
||||
contexts.addHandler(context);
|
||||
|
||||
server.start();
|
||||
|
||||
assertThat(HttpTester.parseResponse(connector.getResponse("GET " + path + " HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n")).getStatus(),
|
||||
Matchers.anyOf(is(HttpStatus.NOT_FOUND_404), is(HttpStatus.BAD_REQUEST_400)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPath() throws Exception
|
||||
|
|
|
@ -22,8 +22,12 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.security.HashLoginService;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
|
@ -56,13 +60,21 @@ public class JspAndDefaultWithoutAliasesTest
|
|||
|
||||
data.add(Arguments.of("/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp/"));
|
||||
data.add(Arguments.of("/dump.jsp%00"));
|
||||
data.add(Arguments.of("/dump.jsp%00x"));
|
||||
data.add(Arguments.of("/dump.jsp%00x/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp%00/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp%00/index.html"));
|
||||
data.add(Arguments.of("/dump.jsp%00/"));
|
||||
data.add(Arguments.of("/dump.jsp%00x/"));
|
||||
data.add(Arguments.of("/dump.jsp%1e"));
|
||||
data.add(Arguments.of("/dump.jsp%1ex"));
|
||||
data.add(Arguments.of("/dump.jsp%1ex/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp%1e/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp%1e/index.html"));
|
||||
data.add(Arguments.of("/dump.jsp%1e/"));
|
||||
data.add(Arguments.of("/dump.jsp%1ex/"));
|
||||
// The _00_ is later replaced with a real null character in a customizer
|
||||
data.add(Arguments.of("/dump.jsp_00_"));
|
||||
data.add(Arguments.of("/dump.jsp_00_"));
|
||||
data.add(Arguments.of("/dump.jsp_00_/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp_00_/dump.jsp"));
|
||||
data.add(Arguments.of("/dump.jsp_00_/index.html"));
|
||||
data.add(Arguments.of("/dump.jsp_00_/"));
|
||||
data.add(Arguments.of("/dump.jsp_00_/"));
|
||||
|
||||
return data.stream();
|
||||
}
|
||||
|
@ -98,6 +110,17 @@ public class JspAndDefaultWithoutAliasesTest
|
|||
// add context
|
||||
server.setHandler(context);
|
||||
|
||||
// Add customizer to convert "_00_" to a real null
|
||||
server.getContainedBeans(HttpConfiguration.class).forEach(config ->
|
||||
{
|
||||
config.addCustomizer((connector, channelConfig, request) ->
|
||||
{
|
||||
HttpURI uri = request.getHttpURI();
|
||||
if (uri.getPath().contains("_00_"))
|
||||
request.setHttpURI(HttpURI.build(uri, uri.getPath().replace("_00_", "\000"), uri.getParam(), uri.getQuery()));
|
||||
});
|
||||
});
|
||||
|
||||
server.start();
|
||||
|
||||
int port = ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
|
||||
|
|
Loading…
Reference in New Issue