diff --git a/src/main/java/tools/jackson/core/JsonGenerator.java b/src/main/java/tools/jackson/core/JsonGenerator.java index 6409483e08..c107a954f8 100644 --- a/src/main/java/tools/jackson/core/JsonGenerator.java +++ b/src/main/java/tools/jackson/core/JsonGenerator.java @@ -50,6 +50,19 @@ protected JsonGenerator() { } @Override public abstract Version version(); + /* + /********************************************************************** + /* Constraints violation checking + /********************************************************************** + */ + + /** + * Get the constraints to apply when performing streaming writes. + */ + public StreamWriteConstraints streamWriteConstraints() { + return StreamWriteConstraints.defaults(); + } + /* /********************************************************************** /* Public API, output configuration, state access diff --git a/src/main/java/tools/jackson/core/StreamWriteConstraints.java b/src/main/java/tools/jackson/core/StreamWriteConstraints.java new file mode 100644 index 0000000000..cb25f32ac4 --- /dev/null +++ b/src/main/java/tools/jackson/core/StreamWriteConstraints.java @@ -0,0 +1,157 @@ +package tools.jackson.core; + +import tools.jackson.core.exc.StreamConstraintsException; + +/** + * The constraints to use for streaming writes: used to guard against problematic + * output by preventing processing of "too big" output constructs (values, + * structures). + * Constraints are registered with {@code TokenStreamFactory} (such as + * {@code JsonFactory}); if nothing explicitly specified, default + * constraints are used. + *

+ * Currently constrained aspects, with default settings, are: + *

+ * + * @since 2.16 + */ +public class StreamWriteConstraints + implements java.io.Serializable +{ + private static final long serialVersionUID = 1L; + + /** + * Default setting for maximum depth: see {@link Builder#maxNestingDepth(int)} for details. + */ + public static final int DEFAULT_MAX_DEPTH = 1000; + + protected final int _maxNestingDepth; + + private static final StreamWriteConstraints DEFAULT = + new StreamWriteConstraints(DEFAULT_MAX_DEPTH); + + public static final class Builder { + private int maxNestingDepth; + + /** + * Sets the maximum nesting depth. The depth is a count of objects and arrays that have not + * been closed, `{` and `[` respectively. + * + * @param maxNestingDepth the maximum depth + * + * @return this builder + * @throws IllegalArgumentException if the maxNestingDepth is set to a negative value + */ + public Builder maxNestingDepth(final int maxNestingDepth) { + if (maxNestingDepth < 0) { + throw new IllegalArgumentException("Cannot set maxNestingDepth to a negative value"); + } + this.maxNestingDepth = maxNestingDepth; + return this; + } + + Builder() { + this(DEFAULT_MAX_DEPTH); + } + + Builder(final int maxNestingDepth) { + this.maxNestingDepth = maxNestingDepth; + } + + Builder(StreamWriteConstraints src) { + maxNestingDepth = src._maxNestingDepth; + } + + public StreamWriteConstraints build() { + return new StreamWriteConstraints(maxNestingDepth); + } + } + + /* + /********************************************************************** + /* Life-cycle + /********************************************************************** + */ + + protected StreamWriteConstraints(final int maxNestingDepth) { + _maxNestingDepth = maxNestingDepth; + } + + public static Builder builder() { + return new Builder(); + } + + public static StreamWriteConstraints defaults() { + return DEFAULT; + } + + /** + * @return New {@link Builder} initialized with settings of this constraints + * instance + */ + public Builder rebuild() { + return new Builder(this); + } + + /* + /********************************************************************** + /* Accessors + /********************************************************************** + */ + + /** + * Accessor for maximum depth. + * see {@link Builder#maxNestingDepth(int)} for details. + * + * @return Maximum allowed depth + */ + public int getMaxNestingDepth() { + return _maxNestingDepth; + } + + /* + /********************************************************************** + /* Convenience methods for validation, document limits + /********************************************************************** + */ + + /** + * Convenience method that can be used to verify that the + * nesting depth does not exceed the maximum specified by this + * constraints object: if it does, a + * {@link StreamConstraintsException} + * is thrown. + * + * @param depth count of unclosed objects and arrays + * + * @throws StreamConstraintsException If depth exceeds maximum + */ + public void validateNestingDepth(int depth) throws StreamConstraintsException + { + if (depth > _maxNestingDepth) { + throw _constructException( + "Document nesting depth (%d) exceeds the maximum allowed (%d, from %s)", + depth, _maxNestingDepth, + _constrainRef("getMaxNestingDepth")); + } + } + + /* + /********************************************************************** + /* Error reporting + /********************************************************************** + */ + + // @since 2.16 + protected StreamConstraintsException _constructException(String msgTemplate, Object... args) throws StreamConstraintsException { + throw new StreamConstraintsException(String.format(msgTemplate, args)); + } + + // @since 2.16 + protected String _constrainRef(String method) { + return "`StreamWriteConstraints."+method+"()`"; + } +} diff --git a/src/main/java/tools/jackson/core/TSFBuilder.java b/src/main/java/tools/jackson/core/TSFBuilder.java index 19dc9b61d6..56cf460d71 100644 --- a/src/main/java/tools/jackson/core/TSFBuilder.java +++ b/src/main/java/tools/jackson/core/TSFBuilder.java @@ -37,11 +37,18 @@ public abstract class TSFBuilder baseBuilder) { _streamReadConstraints = baseBuilder._streamReadConstraints; + _streamWriteConstraints = baseBuilder._streamWriteConstraints; _factoryFeatures = baseBuilder.factoryFeaturesMask(); _streamReadFeatures = baseBuilder.streamReadFeaturesMask(); _streamWriteFeatures = baseBuilder.streamWriteFeaturesMask(); @@ -287,6 +295,7 @@ protected TokenStreamFactory(TSFBuilder baseBuilder) protected TokenStreamFactory(TokenStreamFactory src) { _streamReadConstraints = src._streamReadConstraints; + _streamWriteConstraints = src._streamWriteConstraints; _factoryFeatures = src._factoryFeatures; _streamReadFeatures = src._streamReadFeatures; _streamWriteFeatures = src._streamWriteFeatures; @@ -495,6 +504,8 @@ public final int getStreamWriteFeatures() { public StreamReadConstraints streamReadConstraints() { return _streamReadConstraints; } + public StreamWriteConstraints streamWriteConstraints() { return _streamWriteConstraints; } + /* /********************************************************************** /* Factory methods for helper objects @@ -1200,7 +1211,8 @@ public BufferRecycler _getBufferRecycler() * @return Context constructed */ protected IOContext _createContext(ContentReference contentRef, boolean resourceManaged) { - return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, + return new IOContext(_streamReadConstraints, _streamWriteConstraints, + _getBufferRecycler(), contentRef, resourceManaged, null); } @@ -1216,7 +1228,8 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource */ protected IOContext _createContext(ContentReference contentRef, boolean resourceManaged, JsonEncoding enc) { - return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, + return new IOContext(_streamReadConstraints, _streamWriteConstraints, + _getBufferRecycler(), contentRef, resourceManaged, enc); } diff --git a/src/main/java/tools/jackson/core/base/BinaryTSFactory.java b/src/main/java/tools/jackson/core/base/BinaryTSFactory.java index 0ab9bb40a6..cb820bdddd 100644 --- a/src/main/java/tools/jackson/core/base/BinaryTSFactory.java +++ b/src/main/java/tools/jackson/core/base/BinaryTSFactory.java @@ -26,8 +26,9 @@ public abstract class BinaryTSFactory */ protected BinaryTSFactory(StreamReadConstraints src, + StreamWriteConstraints swc, int formatPF, int formatGF) { - super(src, formatPF, formatGF); + super(src, swc, formatPF, formatGF); } /** diff --git a/src/main/java/tools/jackson/core/base/DecorableTSFactory.java b/src/main/java/tools/jackson/core/base/DecorableTSFactory.java index 69eb19a4a7..58b9ea9654 100644 --- a/src/main/java/tools/jackson/core/base/DecorableTSFactory.java +++ b/src/main/java/tools/jackson/core/base/DecorableTSFactory.java @@ -46,8 +46,9 @@ public abstract static class DecorableTSFBuilder