Skip to content

Commit

Permalink
Vert.x Jackson json factory read constraint configuration should be o…
Browse files Browse the repository at this point in the history
…verridable.

Motivation:

Read constraints were added since Jackson 2.15 to provide upper bounds for input such as the maximum number of a name. These constraints can be configured per json factory, Vert.x static json factory should provide configurability for it.

Changes:

Add system properties that configure the Vert.x static Jackson json factory.
  • Loading branch information
vietj committed Feb 27, 2025
1 parent 1e27d23 commit 71d1cc8
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 8 deletions.
20 changes: 20 additions & 0 deletions vertx-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,26 @@
</systemProperties>
</configuration>
</execution>
<execution>
<id>jackson-config-override</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<includes>
<include>io/vertx/it/json/JacksonConfigOverrideTest.java</include>
</includes>
<systemProperties>
<vertx.jackson.defaultReadMaxNestingDepth>100</vertx.jackson.defaultReadMaxNestingDepth>
<vertx.jackson.defaultReadMaxDocumentLength>100</vertx.jackson.defaultReadMaxDocumentLength>
<vertx.jackson.defaultReadMaxNumberLength>100</vertx.jackson.defaultReadMaxNumberLength>
<vertx.jackson.defaultReadMaxStringLength>100</vertx.jackson.defaultReadMaxStringLength>
<vertx.jackson.defaultReadMaxNameLength>100</vertx.jackson.defaultReadMaxNameLength>
<vertx.jackson.defaultReadMaxTokenCount>100</vertx.jackson.defaultReadMaxTokenCount>
</systemProperties>
</configuration>
</execution>
<execution>
<id>no-jackson</id>
<goals>
Expand Down
18 changes: 18 additions & 0 deletions vertx-core/src/main/asciidoc/json.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,21 @@ When you are unsure of the string validity then you should use instead `{@link i
----
{@link examples.JsonExamples#example5}
----

=== Jackson configuration

==== Read constraint configuration

Since Jackson 2.15, upper bound constraints have been added to limit the bytes cumulated when parsing a JSON input.

You can override the default configuration of the underlying parsers used by Vert.x with the following system properties:

- `vertx.jackson.defaultReadMaxNestingDepth`: Maximum Nesting depth
- `vertx.jackson.defaultReadMaxDocumentLength`: Maximum Document length
- `vertx.jackson.defaultReadMaxNumberLength`: Maximum Number value length
- `vertx.jackson.defaultReadMaxStringLength`: Maximum String value length
- `vertx.jackson.defaultReadMaxNameLength`: Maximum Property name length
- `vertx.jackson.defaultMaxTokenCount`: Maximum Token count

You can refer to https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/latest/index.html[this] for more information.

25 changes: 25 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/impl/SysProps.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import io.vertx.core.internal.http.HttpHeadersInternal;

import java.io.File;
import java.util.OptionalInt;
import java.util.OptionalLong;

/**
* Vert.x known system properties.
Expand Down Expand Up @@ -86,6 +88,13 @@ public String get() {
*/
LOGGER_DELEGATE_FACTORY_CLASS_NAME("vertx.logger-delegate-factory-class-name"),

JACKSON_DEFAULT_READ_MAX_NESTING_DEPTH("vertx.jackson.defaultReadMaxNestingDepth"),
JACKSON_DEFAULT_READ_MAX_DOC_LEN("vertx.jackson.defaultReadMaxDocumentLength"),
JACKSON_DEFAULT_READ_MAX_NUM_LEN("vertx.jackson.defaultReadMaxNumberLength"),
JACKSON_DEFAULT_READ_MAX_STRING_LEN("vertx.jackson.defaultReadMaxStringLength"),
JACKSON_DEFAULT_READ_MAX_NAME_LEN("vertx.jackson.defaultReadMaxNameLength"),
JACKSON_DEFAULT_READ_MAX_TOKEN_COUNT("vertx.jackson.defaultMaxTokenCount"),

;

public final String name;
Expand All @@ -98,6 +107,22 @@ public String get() {
return System.getProperty(name);
}

public OptionalLong getAsLong() throws NumberFormatException {
String s = get();
if (s != null) {
return OptionalLong.of(Long.parseLong(s));
}
return OptionalLong.empty();
}

public OptionalInt getAsInt() throws NumberFormatException {
String s = get();
if (s != null) {
return OptionalInt.of(Integer.parseInt(s));
}
return OptionalInt.empty();
}

public boolean getBoolean() {
return Boolean.getBoolean(name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import io.netty.buffer.ByteBufInputStream;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.impl.SysProps;
import io.vertx.core.internal.buffer.BufferInternal;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.EncodeException;
import io.vertx.core.json.JsonArray;
Expand All @@ -32,10 +35,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import static io.vertx.core.json.impl.JsonUtil.BASE64_ENCODER;
import static io.vertx.core.json.impl.JsonUtil.BASE64_DECODER;
Expand All @@ -46,10 +46,71 @@
*/
public class JacksonCodec implements JsonCodec {

private static final Logger log = LoggerFactory.getLogger(JacksonCodec.class);

private static JsonFactory buildFactory() {
TSFBuilder<?, ?> builder = JsonFactory.builder();
builder.recyclerPool(HybridJacksonPool.getInstance());
return builder.build();
TSFBuilder<?, ?> tsfBuilder = JsonFactory.builder();

// Build stream read constraints
StreamReadConstraints.Builder readConstraintsBuilder = StreamReadConstraints.builder();
try {
OptionalInt override = SysProps.JACKSON_DEFAULT_READ_MAX_NESTING_DEPTH.getAsInt();
if (override.isPresent()) {
readConstraintsBuilder.maxNestingDepth(override.getAsInt());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_NESTING_DEPTH.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_DEPTH + " instead");
}
try {
OptionalLong override = SysProps.JACKSON_DEFAULT_READ_MAX_DOC_LEN.getAsLong();
if (override.isPresent()) {
readConstraintsBuilder.maxDocumentLength(override.getAsLong());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_DOC_LEN.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_DOC_LEN + " instead");
}
try {
OptionalInt override = SysProps.JACKSON_DEFAULT_READ_MAX_NUM_LEN.getAsInt();
if (override.isPresent()) {
readConstraintsBuilder.maxNumberLength(override.getAsInt());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_NUM_LEN.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_NUM_LEN + " instead");
}
try {
OptionalInt override = SysProps.JACKSON_DEFAULT_READ_MAX_STRING_LEN.getAsInt();
if (override.isPresent()) {
readConstraintsBuilder.maxStringLength(override.getAsInt());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_STRING_LEN.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_STRING_LEN + " instead");
}
try {
OptionalInt override = SysProps.JACKSON_DEFAULT_READ_MAX_NAME_LEN.getAsInt();
if (override.isPresent()) {
readConstraintsBuilder.maxNameLength(override.getAsInt());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_NAME_LEN.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_NAME_LEN + " instead");
}
try {
OptionalLong override = SysProps.JACKSON_DEFAULT_READ_MAX_TOKEN_COUNT.getAsLong();
if (override.isPresent()) {
readConstraintsBuilder.maxTokenCount(override.getAsLong());
}
} catch (IllegalArgumentException e) {
log.warn("Invalid " + SysProps.JACKSON_DEFAULT_READ_MAX_TOKEN_COUNT.name + " system property value, use " +
StreamReadConstraints.DEFAULT_MAX_TOKEN_COUNT + " instead");
}

tsfBuilder.streamReadConstraints(readConstraintsBuilder.build());
tsfBuilder.recyclerPool(HybridJacksonPool.getInstance());
return tsfBuilder.build();
}

static final JsonFactory factory = buildFactory();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/

package io.vertx.it.json;

import io.vertx.test.core.VertxTestBase;
import io.vertx.tests.json.JacksonTest;
import org.junit.Test;

/**
* @author <a href="mailto:[email protected]">Julien Viet</a>
*/
public class JacksonConfigOverrideTest extends VertxTestBase {

@Test
public void testReadConstraints() {
JacksonTest.testReadConstraints(100, 100, 100, 100);
}
}
66 changes: 65 additions & 1 deletion vertx-core/src/test/java/io/vertx/tests/json/JacksonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@

package io.vertx.tests.json;

import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.EncodeException;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.JacksonCodec;
import io.vertx.test.core.VertxTestBase;
import org.junit.Assert;
import org.junit.Test;

import static com.fasterxml.jackson.core.StreamReadConstraints.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

Expand Down Expand Up @@ -70,4 +74,64 @@ public void encodeToBuffer() {
codec.toBuffer(new RuntimeException("Unsupported"));
}

@Test
public void testDefaultConstraints() {
testReadConstraints(
DEFAULT_MAX_DEPTH,
DEFAULT_MAX_NUM_LEN,
DEFAULT_MAX_STRING_LEN,
DEFAULT_MAX_NAME_LEN);
}

public static void testReadConstraints(int defaultMaxDepth,
int maxNumberLength,
int defaultMaxStringLength,
int defaultMaxNameLength) {
testMaxNestingDepth(defaultMaxDepth);
try {
testMaxNestingDepth(defaultMaxDepth + 1);
Assert.fail();
} catch (DecodeException expected) {
}
testMaxNumberLength(maxNumberLength);
try {
testMaxNumberLength(maxNumberLength + 1);
Assert.fail();
} catch (DecodeException expected) {
}

testMaxStringLength(defaultMaxStringLength);
try {
testMaxStringLength(defaultMaxStringLength + 1);
Assert.fail();
} catch (DecodeException expected) {
}

testMaxNameLength(defaultMaxNameLength);
try {
testMaxNameLength(defaultMaxNameLength + 1);
Assert.fail();
} catch (DecodeException expected) {
}
}

private static JsonArray testMaxNestingDepth(int depth) {
String json = "[".repeat(depth) + "]".repeat(depth);
return new JsonArray(json);
}

private static JsonObject testMaxNumberLength(int len) {
String json = "{\"number\":" + "1".repeat(len) + "}";
return new JsonObject(json);
}

private static JsonObject testMaxStringLength(int len) {
String json = "{\"string\":\"" + "a".repeat(len) + "\"}";
return new JsonObject(json);
}

private static JsonObject testMaxNameLength(int len) {
String json = "{\"" + "a".repeat(len) + "\":3}";
return new JsonObject(json);
}
}

0 comments on commit 71d1cc8

Please sign in to comment.