diff --git a/vertx-core/pom.xml b/vertx-core/pom.xml
index d8b7f598051..856e90afc92 100644
--- a/vertx-core/pom.xml
+++ b/vertx-core/pom.xml
@@ -402,6 +402,26 @@
+
+ jackson-config-override
+
+ integration-test
+ verify
+
+
+
+ io/vertx/it/json/JacksonConfigOverrideTest.java
+
+
+ 100
+ 100
+ 100
+ 100
+ 100
+ 100
+
+
+
no-jackson
diff --git a/vertx-core/src/main/asciidoc/json.adoc b/vertx-core/src/main/asciidoc/json.adoc
index 535d1cad5b8..ebe87eb2584 100644
--- a/vertx-core/src/main/asciidoc/json.adoc
+++ b/vertx-core/src/main/asciidoc/json.adoc
@@ -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.
+
diff --git a/vertx-core/src/main/java/io/vertx/core/impl/SysProps.java b/vertx-core/src/main/java/io/vertx/core/impl/SysProps.java
index 36c10194f0b..47220e1d15d 100644
--- a/vertx-core/src/main/java/io/vertx/core/impl/SysProps.java
+++ b/vertx-core/src/main/java/io/vertx/core/impl/SysProps.java
@@ -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.
@@ -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;
@@ -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);
}
diff --git a/vertx-core/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java b/vertx-core/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
index 76b3ca7fa9f..7f31fceeff8 100644
--- a/vertx-core/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
+++ b/vertx-core/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java
@@ -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;
@@ -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;
@@ -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();
diff --git a/vertx-core/src/test/java/io/vertx/it/json/JacksonConfigOverrideTest.java b/vertx-core/src/test/java/io/vertx/it/json/JacksonConfigOverrideTest.java
new file mode 100644
index 00000000000..dc00fb9b3db
--- /dev/null
+++ b/vertx-core/src/test/java/io/vertx/it/json/JacksonConfigOverrideTest.java
@@ -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 Julien Viet
+ */
+public class JacksonConfigOverrideTest extends VertxTestBase {
+
+ @Test
+ public void testReadConstraints() {
+ JacksonTest.testReadConstraints(100, 100, 100, 100);
+ }
+}
diff --git a/vertx-core/src/test/java/io/vertx/tests/json/JacksonTest.java b/vertx-core/src/test/java/io/vertx/tests/json/JacksonTest.java
index 6d724cff3cb..0eb85a27f46 100644
--- a/vertx-core/src/test/java/io/vertx/tests/json/JacksonTest.java
+++ b/vertx-core/src/test/java/io/vertx/tests/json/JacksonTest.java
@@ -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;
@@ -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);
+ }
}