diff --git a/build.sbt b/build.sbt index b5be2647d..31445b7dc 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,7 @@ def specs2(scalaVersion: String) = ("org.specs2" %% s"specs2-$n" % "4.20.8") % Test } -val jacksonDatabindVersion = "2.14.3" +val jacksonDatabindVersion = "2.17.2" val jacksonDatabind = Seq( "com.fasterxml.jackson.core" % "jackson-databind" % jacksonDatabindVersion ) diff --git a/play-json/jvm/src/main/mima-filters/3.1.x.backwards.excludes/PR1072.backwards.excludes b/play-json/jvm/src/main/mima-filters/3.1.x.backwards.excludes/PR1072.backwards.excludes new file mode 100644 index 000000000..b417d126f --- /dev/null +++ b/play-json/jvm/src/main/mima-filters/3.1.x.backwards.excludes/PR1072.backwards.excludes @@ -0,0 +1,2 @@ +# private final class JsonConfigImpl has changed +ProblemFilters.exclude[MissingTypesProblem]("play.api.libs.json.JsonConfigImpl$") diff --git a/play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala b/play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala index 71d21d963..a25dd3a3f 100644 --- a/play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala +++ b/play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala @@ -4,6 +4,8 @@ package play.api.libs.json +import com.fasterxml.jackson.core.StreamReadConstraints + import play.api.libs.json.JsonConfig.defaultMaxPlain import play.api.libs.json.JsonConfig.defaultMinPlain import play.api.libs.json.JsonConfig.defaultDigitsLimit @@ -103,6 +105,7 @@ private final case class DecimalSerializerSettingsImpl( sealed trait JsonConfig { def bigDecimalParseConfig: BigDecimalParseConfig def bigDecimalSerializerConfig: BigDecimalSerializerConfig + def streamReadConstraints: StreamReadConstraints } object JsonConfig { @@ -168,6 +171,11 @@ object JsonConfig { */ val maxPlainProperty: String = "play.json.serializer.maxPlain" + /** + * The system property to override the max nesting depth for JSON parsing. + */ + val maxNestingDepth: String = "play.json.parser.maxNestingDepth" + /** * The system property to override whether zero decimals (e.g. .0 or .00) are written by default. These are dropped by default. */ @@ -183,15 +191,26 @@ object JsonConfig { private[json] def loadMaxPlain: BigDecimal = prop(maxPlainProperty, defaultMaxPlain)(BigDecimal.exact) + private[json] def loadMaxNestingDepth: Int = + prop(maxNestingDepth, StreamReadConstraints.DEFAULT_MAX_DEPTH)(Integer.parseInt) + private[json] def loadPreserveZeroDecimal: Boolean = prop(preserveZeroDecimalProperty, defaultPreserveZeroDecimal)(_.toBoolean) + private[json] val defaultStreamReadConstraints: StreamReadConstraints = + StreamReadConstraints + .builder() + .maxNestingDepth(loadMaxNestingDepth) + .maxNumberLength(Int.MaxValue) // play-json has its own support for limiting number length + .build() + // Default settings, which can be controlled with system properties. // To override, call JacksonJson.setConfig() val settings: JsonConfig = JsonConfig( BigDecimalParseConfig(loadMathContext, loadScaleLimit, loadDigitsLimit), - BigDecimalSerializerConfig(loadMinPlain, loadMaxPlain, loadPreserveZeroDecimal) + BigDecimalSerializerConfig(loadMinPlain, loadMaxPlain, loadPreserveZeroDecimal), + defaultStreamReadConstraints ) def apply(): JsonConfig = apply(BigDecimalParseConfig(), BigDecimalSerializerConfig()) @@ -200,7 +219,14 @@ object JsonConfig { bigDecimalParseConfig: BigDecimalParseConfig, bigDecimalSerializerConfig: BigDecimalSerializerConfig ): JsonConfig = - JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig) + JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig, defaultStreamReadConstraints) + + def apply( + bigDecimalParseConfig: BigDecimalParseConfig, + bigDecimalSerializerConfig: BigDecimalSerializerConfig, + streamReadConstraints: StreamReadConstraints + ): JsonConfig = + JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig, streamReadConstraints) private[json] def parseMathContext(key: String): MathContext = sys.props.get(key).map(_.toLowerCase) match { case Some("decimal128") => MathContext.DECIMAL128 @@ -220,7 +246,8 @@ object JsonConfig { private final case class JsonConfigImpl( bigDecimalParseConfig: BigDecimalParseConfig, - bigDecimalSerializerConfig: BigDecimalSerializerConfig + bigDecimalSerializerConfig: BigDecimalSerializerConfig, + streamReadConstraints: StreamReadConstraints ) extends JsonConfig @deprecated("Use BigDecimalParseConfig instead", "2.9.4") @@ -241,7 +268,8 @@ final case class BigDecimalSerializerSettings( @deprecated("Use JsonConfig instead", "2.9.4") final case class JsonParserSettings( bigDecimalParseSettings: BigDecimalParseSettings, - bigDecimalSerializerSettings: BigDecimalSerializerSettings + bigDecimalSerializerSettings: BigDecimalSerializerSettings, + streamReadConstraints: StreamReadConstraints = JsonConfig.defaultStreamReadConstraints ) extends JsonConfig { override def bigDecimalParseConfig: BigDecimalParseConfig = bigDecimalParseSettings diff --git a/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala b/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala index 44f0c090d..a0a54211f 100644 --- a/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala +++ b/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala @@ -13,11 +13,7 @@ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ListBuffer -import com.fasterxml.jackson.core.JsonFactory -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonTokenId -import com.fasterxml.jackson.core.Version +import com.fasterxml.jackson.core.{ JsonFactoryBuilder, JsonGenerator, JsonParser, JsonTokenId, Version } import com.fasterxml.jackson.core.json.JsonWriteFeature import com.fasterxml.jackson.core.util.DefaultPrettyPrinter @@ -25,6 +21,7 @@ import com.fasterxml.jackson.databind.Module.SetupContext import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.`type`.TypeFactory import com.fasterxml.jackson.databind.deser.Deserializers +import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.ser.Serializers @@ -219,7 +216,7 @@ private[jackson] class JsValueDeserializer(factory: TypeFactory, klass: Class[_] case JsonTokenId.ID_FIELD_NAME => parserContext match { - case (c: ReadingMap) :: stack => (None, c.setField(jp.getCurrentName) +: stack) + case (c: ReadingMap) :: stack => (None, c.setField(jp.currentName()) +: stack) case _ => throw new RuntimeException("We should be reading map, something got wrong") } @@ -282,9 +279,13 @@ private[json] object JacksonJson { } private[json] case class JacksonJson(jsonConfig: JsonConfig) { - private val mapper = (new ObjectMapper).registerModule(new PlayJsonMapperModule(jsonConfig)) - - private val jsonFactory = new JsonFactory(mapper) + private val jsonFactory = new JsonFactoryBuilder() + .streamReadConstraints(jsonConfig.streamReadConstraints) + .build() + private val mapper = JsonMapper + .builder(jsonFactory) + .addModule(new PlayJsonMapperModule(jsonConfig)) + .build() private def stringJsonGenerator(out: java.io.StringWriter) = jsonFactory.createGenerator(out)