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); + } }