Skip to content

Commit

Permalink
Add attributes to Context to control decoding RequestDispatcher path
Browse files Browse the repository at this point in the history
Adds encodedReverseSolidusHandling and encodedSolidusHandling. Both
default to DECODE (the current behaviour)
  • Loading branch information
markt-asf committed Jan 23, 2025
1 parent b19df5d commit ad08420
Show file tree
Hide file tree
Showing 8 changed files with 405 additions and 15 deletions.
68 changes: 68 additions & 0 deletions java/org/apache/catalina/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Map;
import java.util.Set;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
Expand All @@ -36,6 +37,7 @@
import org.apache.tomcat.ContextBind;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.util.buf.EncodedSolidusHandling;
import org.apache.tomcat.util.descriptor.web.ApplicationParameter;
import org.apache.tomcat.util.descriptor.web.ErrorPage;
import org.apache.tomcat.util.descriptor.web.FilterDef;
Expand Down Expand Up @@ -1887,4 +1889,70 @@ default Resource findConfigFileResource(String name) throws IOException {
*/
@Deprecated
void setUseBloomFilterForArchives(boolean useBloomFilterForArchives);


/**
* Obtain the current configuration for the handling of encoded reverse solidus (%5c - \) characters in paths used
* to obtain {@link RequestDispatcher} instances for this {@link Context}.
*
* @return Obtain the current configuration for the handling of encoded reverse solidus characters
*/
default String getEncodedReverseSolidusHandling() {
return EncodedSolidusHandling.DECODE.getValue();
}


/**
* Configure the handling for encoded reverse solidus (%5c - \) characters in paths used to obtain
* {@link RequestDispatcher} instances for this {@link Context}.
*
* @param encodedReverseSolidusHandling One of the values of {@link EncodedSolidusHandling}
*/
default void setEncodedReverseSolidusHandling(String encodedReverseSolidusHandling) {
throw new UnsupportedOperationException();
}


/**
* Obtain the current configuration for the handling of encoded reverse solidus (%5c - \) characters in paths used
* to obtain {@link RequestDispatcher} instances for this {@link Context}.
*
* @return Obtain the current configuration for the handling of encoded reverse solidus characters
*/
default EncodedSolidusHandling getEncodedReverseSolidusHandlingEnum() {
return EncodedSolidusHandling.DECODE;
}


/**
* Obtain the current configuration for the handling of encoded solidus (%2f - /) characters in paths used to obtain
* {@link RequestDispatcher} instances for this {@link Context}.
*
* @return Obtain the current configuration for the handling of encoded solidus characters
*/
default String getEncodedSolidusHandling() {
return EncodedSolidusHandling.DECODE.getValue();
}


/**
* Configure the handling for encoded solidus (%2f - /) characters in paths used to obtain {@link RequestDispatcher}
* instances for this {@link Context}.
*
* @param encodedSolidusHandling One of the values of {@link EncodedSolidusHandling}
*/
default void setEncodedSolidusHandling(String encodedSolidusHandling) {
throw new UnsupportedOperationException();
}


/**
* Obtain the current configuration for the handling of encoded solidus (%2f - /) characters in paths used to obtain
* {@link RequestDispatcher} instances for this {@link Context}.
*
* @return Obtain the current configuration for the handling of encoded solidus characters
*/
default EncodedSolidusHandling getEncodedSolidusHandlingEnum() {
return EncodedSolidusHandling.DECODE;
}
}
3 changes: 2 additions & 1 deletion java/org/apache/catalina/core/ApplicationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@ public RequestDispatcher getRequestDispatcher(final String path) {

// Decode only if the uri derived from the provided path is expected to be encoded
if (getContext().getDispatchersUseEncodedPaths()) {
uriToMap = UDecoder.URLDecode(uriToMap, StandardCharsets.UTF_8);
uriToMap = UDecoder.URLDecode(uriToMap, StandardCharsets.UTF_8, context.getEncodedSolidusHandlingEnum(),
context.getEncodedReverseSolidusHandlingEnum());
}

// Then normalize
Expand Down
41 changes: 41 additions & 0 deletions java/org/apache/catalina/core/StandardContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
import org.apache.tomcat.InstanceManagerBindings;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.EncodedSolidusHandling;
import org.apache.tomcat.util.buf.StringUtils;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.descriptor.XmlIdentifiers;
Expand Down Expand Up @@ -806,9 +807,49 @@ public void unbind() {

private int notFoundClassResourceCacheSize = 1000;

private EncodedSolidusHandling encodedReverseSolidusHandling = EncodedSolidusHandling.DECODE;

private EncodedSolidusHandling encodedSolidusHandling = EncodedSolidusHandling.DECODE;


// ----------------------------------------------------- Context Properties

@Override
public String getEncodedReverseSolidusHandling() {
return encodedReverseSolidusHandling.getValue();
}


@Override
public void setEncodedReverseSolidusHandling(String encodedReverseSolidusHandling) {
this.encodedReverseSolidusHandling = EncodedSolidusHandling.fromString(encodedReverseSolidusHandling);
}


@Override
public EncodedSolidusHandling getEncodedReverseSolidusHandlingEnum() {
return encodedReverseSolidusHandling;
}


@Override
public String getEncodedSolidusHandling() {
return encodedSolidusHandling.getValue();
}


@Override
public void setEncodedSolidusHandling(String encodedSolidusHandling) {
this.encodedSolidusHandling = EncodedSolidusHandling.fromString(encodedSolidusHandling);
}


@Override
public EncodedSolidusHandling getEncodedSolidusHandlingEnum() {
return encodedSolidusHandling;
}


public int getNotFoundClassResourceCacheSize() {
return notFoundClassResourceCacheSize;
}
Expand Down
2 changes: 2 additions & 0 deletions java/org/apache/tomcat/util/buf/LocalStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ uDecoder.noBackslash=The encoded backslash character is not allowed
uDecoder.noSlash=The encoded slash character is not allowed
uDecoder.urlDecode.conversionError=Failed to decode [{0}] using character set [{1}]
uDecoder.urlDecode.missingDigit=Failed to decode [{0}] because the % character must be followed by two hexadecimal digits
uDecoder.urlDecode.rejectEncodedReverseSolidus=Failed to decode [{0}] because it contained a %5c sequence the decoder is configured to reject
uDecoder.urlDecode.rejectEncodedSolidus=Failed to decode [{0}] because it contained a %2f sequence the decoder is configured to reject
99 changes: 85 additions & 14 deletions java/org/apache/tomcat/util/buf/UDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,20 @@ private void convert(ByteChunk mb, boolean query, EncodedSolidusHandling encoded
}
}
} else if (res == '\\') {
switch (encodedReverseSolidusHandling) {
case DECODE: {
buff[idx] = (byte) res;
break;
}
case REJECT: {
throw EXCEPTION_BACKSLASH;
}
case PASS_THROUGH: {
buff[idx++] = buff[j - 2];
buff[idx++] = buff[j - 1];
buff[idx] = buff[j];
}
switch (encodedReverseSolidusHandling) {
case DECODE: {
buff[idx] = (byte) res;
break;
}
case REJECT: {
throw EXCEPTION_BACKSLASH;
}
case PASS_THROUGH: {
buff[idx++] = buff[j - 2];
buff[idx++] = buff[j - 1];
buff[idx] = buff[j];
}
}
} else if (res == '%') {
/*
* If encoded '/' or '\' is going to be left encoded then so must encoded '%' else the subsequent
Expand Down Expand Up @@ -218,6 +218,24 @@ private void convert(ByteChunk mb, boolean query, EncodedSolidusHandling encoded
* @exception IllegalArgumentException if a '%' character is not followed by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, Charset charset) {
return URLDecode(str, charset, EncodedSolidusHandling.DECODE, EncodedSolidusHandling.DECODE);
}


/**
* Decode and return the specified URL-encoded String. It is assumed the string is not a query string.
*
* @param str The url-encoded string
* @param charset The character encoding to use; if null, UTF-8 is used.
* @param encodedSolidusHandling The required handling of encoded solidus (%2f - /)
* @param encodedReverseSolidusHandling The required handling of encoded reverse solidus (%5c - \)
*
* @return the decoded string
*
* @exception IllegalArgumentException if a '%' character is not followed by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, Charset charset, EncodedSolidusHandling encodedSolidusHandling,
EncodedSolidusHandling encodedReverseSolidusHandling) {
if (str == null) {
return null;
}
Expand Down Expand Up @@ -266,7 +284,60 @@ public static String URLDecode(String str, Charset charset) {
char c1 = sourceChars[ix++];
char c2 = sourceChars[ix++];
if (isHexDigit(c1) && isHexDigit(c2)) {
baos.write(x2c(c1, c2));
int decoded = x2c(c1, c2);
switch (decoded) {
case '/': {
switch (encodedSolidusHandling) {
case DECODE: {
osw.append('/');
break;
}
case PASS_THROUGH: {
osw.append(c);
osw.append(c1);
osw.append(c2);
break;
}
case REJECT: {
throw new IllegalArgumentException(
sm.getString("uDecoder.urlDecode.rejectEncodedSolidus", str));
}
}
break;
}
case '\\': {
switch (encodedReverseSolidusHandling) {
case DECODE: {
osw.append('\\');
break;
}
case PASS_THROUGH: {
osw.append(c);
osw.append(c1);
osw.append(c2);
break;
}
case REJECT: {
throw new IllegalArgumentException(
sm.getString("uDecoder.urlDecode.rejectEncodedReverseSolidus", str));
}
}
break;
}
case '%': {
if (encodedReverseSolidusHandling == EncodedSolidusHandling.PASS_THROUGH ||
encodedSolidusHandling == EncodedSolidusHandling.PASS_THROUGH) {
osw.append(c);
osw.append(c1);
osw.append(c2);
} else {
baos.write('%');
}
break;
}
default:
baos.write(decoded);
}
} else {
throw new IllegalArgumentException(sm.getString("uDecoder.urlDecode.missingDigit", str));
}
Expand Down
Loading

0 comments on commit ad08420

Please sign in to comment.