Skip to content

Commit

Permalink
Add semicolonIsNormalChar and maxParams properties to the server conf…
Browse files Browse the repository at this point in the history
…ig (#11416)

* add semicolonIsNormalChar and maxParams properties to the server configurations

fixes #11411

Co-authored-by: Jonas Konrad <[email protected]>

---------

Co-authored-by: Jonas Konrad <[email protected]>
  • Loading branch information
glorrian and yawkat authored Dec 30, 2024
1 parent ecfbe01 commit 5b17a13
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.micronaut.http.netty.stream.StreamedHttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.DefaultAttributeMap;
Expand Down Expand Up @@ -191,14 +192,28 @@ public String getPath() {
*/
protected abstract Charset initCharset(Charset characterEncoding);

/**
* @return the maximum number of parameters.
*/
protected abstract int getMaxParams();

/**
* @return {@code true} if yes, {@code false} otherwise.
*/
protected abstract boolean isSemicolonIsNormalChar();

/**
* @param uri The URI
* @return The query string decoder
*/
@SuppressWarnings("ConstantConditions")
protected final QueryStringDecoder createDecoder(URI uri) {
Charset cs = getCharacterEncoding();
return cs != null ? new QueryStringDecoder(uri, cs) : new QueryStringDecoder(uri);
boolean semicolonIsNormalChar = isSemicolonIsNormalChar();
int maxParams = getMaxParams();
return cs != null ?
new QueryStringDecoder(uri, cs, maxParams, semicolonIsNormalChar) :
new QueryStringDecoder(uri, HttpConstants.DEFAULT_CHARSET, maxParams, semicolonIsNormalChar);
}

private NettyHttpParameters decodeParameters() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,16 @@ protected Charset initCharset(Charset characterEncoding) {
return characterEncoding == null ? serverConfiguration.getDefaultCharset() : characterEncoding;
}

@Override
protected int getMaxParams() {
return serverConfiguration.getMaxParams();
}

@Override
protected boolean isSemicolonIsNormalChar() {
return serverConfiguration.isSemicolonIsNormalChar();
}

/**
* @return Return true if the request is form data.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ public class HttpServerConfiguration implements ServerContextPathProvider {
*/
@SuppressWarnings("WeakerAccess")
public static final boolean DEFAULT_DISPATCH_OPTIONS_REQUESTS = false;

@SuppressWarnings("WeakerAccess")
public static final boolean DEFAULT_SEMICOLON_IS_NORMAL_CHAR = false;

@SuppressWarnings("WeakerAccess")
public static final int DEFAULT_MAX_PARAMS = 1024;
private Integer port;
private String host;
private Integer readTimeout;
Expand Down Expand Up @@ -155,6 +161,8 @@ public class HttpServerConfiguration implements ServerContextPathProvider {
private ThreadSelection threadSelection = ThreadSelection.MANUAL;
private boolean validateUrl = true;
private boolean notFoundOnMissingBody = true;
private boolean semicolonIsNormalChar = DEFAULT_SEMICOLON_IS_NORMAL_CHAR;
private int maxParams = DEFAULT_MAX_PARAMS;

/**
* Default constructor.
Expand Down Expand Up @@ -598,6 +606,45 @@ public void setNotFoundOnMissingBody(boolean notFoundOnMissingBody) {
this.notFoundOnMissingBody = notFoundOnMissingBody;
}

/**
* Returns whether the semicolon is considered a normal character in the query.
* A "normal" semicolon is one that is not used as a parameter separator.
*
* @return {@code true} if the semicolon is a normal character, {@code false} otherwise.
* @since 4.8
*/
public boolean isSemicolonIsNormalChar() {
return semicolonIsNormalChar;
}

/**
* Sets whether the semicolon should be considered a normal character in the query.
* A "normal" semicolon is one that is not used as a parameter separator.
*
* @param semicolonIsNormalChar {@code true} if the semicolon should be a normal character, {@code false} otherwise.
* @since 4.8
*/
public void setSemicolonIsNormalChar(boolean semicolonIsNormalChar) {
this.semicolonIsNormalChar = semicolonIsNormalChar;
}

/**
* @return the maximum parameter count.
* @since 4.8
*/
public int getMaxParams() {
return maxParams;
}

/**
* @param maxParams the maximum parameter count.
* @since 4.8
*/
public void setMaxParams(int maxParams) {
this.maxParams = maxParams;
}


/**
* Configuration for multipart handling.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ public interface FormConfiguration {
* @return default maximum of decoded key value parameters. It defaults to 1024.
*/
int getMaxDecodedKeyValueParameters();

/**
* @return true if the semicolon handle as a normal character, false otherwise.
*/
default boolean isSemicolonIsNormalChar() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ final class FormConfigurationProperties implements FormConfiguration {
@SuppressWarnings("WeakerAccess")
private static final int DEFAULT_MAX_DECODED_KEY_VALUE_PARAMETERS = 1024;

/**
* Default value indicating whether the semicolon is treated as a normal character
* used in {@link io.micronaut.http.form.FormUrlEncodedDecoder}.
*/
@SuppressWarnings("WeakerAccess")
private static final boolean DEFAULT_SEMICOLON_IS_NORMAL_CHAR = false;

private int maxDecodedKeyValueParameters = DEFAULT_MAX_DECODED_KEY_VALUE_PARAMETERS;
private boolean semicolonIsNormalChar = DEFAULT_SEMICOLON_IS_NORMAL_CHAR;

/**
*
Expand All @@ -45,11 +53,27 @@ public int getMaxDecodedKeyValueParameters() {
return maxDecodedKeyValueParameters;
}


/**
* @return true if the semicolon is treated as a normal character, false otherwise
*/
@Override
public boolean isSemicolonIsNormalChar() {
return semicolonIsNormalChar;
}

/**
* default maximum of decoded key value parameters. Default value {@link #DEFAULT_MAX_DECODED_KEY_VALUE_PARAMETERS}.
* @param maxDecodedKeyValueParameters default maximum of decoded key value parameters
*/
public void setMaxDecodedKeyValueParameters(int maxDecodedKeyValueParameters) {
this.maxDecodedKeyValueParameters = maxDecodedKeyValueParameters;
}

/**
* @param semicolonIsNormalChar true if the semicolon should be treated as a normal character, false otherwise
*/
public void setSemicolonIsNormalChar(boolean semicolonIsNormalChar) {
this.semicolonIsNormalChar = semicolonIsNormalChar;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ final class DefaultFormUrlEncodedDecoder implements FormUrlEncodedDecoder {
@NonNull
public Map<String, Object> decode(@NonNull String formUrlEncodedString,
@NonNull Charset charset) {
QueryStringDecoder decoder = new QueryStringDecoder(formUrlEncodedString, charset, false, formConfiguration.getMaxDecodedKeyValueParameters());
QueryStringDecoder decoder = new QueryStringDecoder(formUrlEncodedString, charset, false,
formConfiguration.getMaxDecodedKeyValueParameters(),
formConfiguration.isSemicolonIsNormalChar());
return flatten(decoder.parameters());
}
}
20 changes: 19 additions & 1 deletion http/src/main/java/io/micronaut/http/uri/QueryStringDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ final class QueryStringDecoder {
private final Charset charset;
private final String uri;
private final int maxParams;
private final boolean semicolonIsNormalChar;
private int pathEndIdx;
private String path;
private Map<String, List<String>> params;
Expand Down Expand Up @@ -121,9 +122,14 @@ final class QueryStringDecoder {
* @param maxParams The maximum number of params
*/
QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) {
this(uri, charset, hasPath, maxParams, false);
}

QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams, boolean semicolonIsNormalChar) {
this.uri = Objects.requireNonNull(uri, "uri");
this.charset = Objects.requireNonNull(charset, "charset");
this.maxParams = maxParams;
this.semicolonIsNormalChar = semicolonIsNormalChar;

// `-1` means that path end index will be initialized lazily
pathEndIdx = hasPath ? -1 : 0;
Expand Down Expand Up @@ -159,13 +165,18 @@ final class QueryStringDecoder {
* @param maxParams The maximum number of params
*/
QueryStringDecoder(URI uri, Charset charset, int maxParams) {
this(uri, charset, maxParams, false);
}

QueryStringDecoder(URI uri, Charset charset, int maxParams, boolean semicolonIsNormalChar) {
String rawPath = uri.getRawPath();
if (rawPath == null) {
rawPath = EMPTY_STRING;
}
this.uri = uriToString(uri);
this.charset = Objects.requireNonNull(charset, "charset");
this.maxParams = ArgumentUtils.requirePositive("maxParams", maxParams);
this.semicolonIsNormalChar = semicolonIsNormalChar;
pathEndIdx = rawPath.length();
}

Expand Down Expand Up @@ -196,7 +207,7 @@ public String path() {
*/
public Map<String, List<String>> parameters() {
if (params == null) {
params = decodeParams(uri, pathEndIdx(), charset, maxParams);
params = decodeParams(uri, pathEndIdx(), charset, maxParams, semicolonIsNormalChar);
}
return params;
}
Expand Down Expand Up @@ -260,6 +271,10 @@ private static String uriToString(URI uri) {
}

private static Map<String, List<String>> decodeParams(String s, int from, Charset charset, int paramsLimit) {
return decodeParams(s, from, charset, paramsLimit, false);
}

private static Map<String, List<String>> decodeParams(String s, int from, Charset charset, int paramsLimit, boolean semicolonIsNormalChar) {
int len = s.length();
if (from >= len) {
return Collections.emptyMap();
Expand All @@ -283,6 +298,9 @@ private static Map<String, List<String>> decodeParams(String s, int from, Charse
break;
case '&':
case ';':
if (semicolonIsNormalChar) {
continue;
}
if (addParam(s, nameStart, valueStart, i, params, charset)) {
paramsLimit--;
if (paramsLimit == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

@MicronautTest(startApplication = false)
class FormConfigurationTest {
Expand All @@ -17,4 +18,9 @@ class FormConfigurationTest {
void defaultMaxParams() {
assertEquals(1024, formConfiguration.getMaxDecodedKeyValueParameters());
}

@Test
void defaultSemicolonIsNormalChar() {
assertFalse(formConfiguration.isSemicolonIsNormalChar());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@Property(name = "micronaut.http.forms.max-decoded-key-value-parameters", value = "512")
@Property(name = "micronaut.http.forms.semicolon-is-normal-char", value = "true")
@MicronautTest(startApplication = false)
class FormConfigurationViaPropertyTest {

Expand All @@ -19,4 +21,9 @@ class FormConfigurationViaPropertyTest {
void maxParamCanBeSetViaProperty() {
assertEquals(512, formConfiguration.getMaxDecodedKeyValueParameters());
}

@Test
void semicolonIsNormalCharCanBeSetViaProperty() {
assertTrue(formConfiguration.isSemicolonIsNormalChar());
}
}

0 comments on commit 5b17a13

Please sign in to comment.