Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StreamWriteConstraints with a nesting depth check #1055

Merged
merged 2 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions src/main/java/com/fasterxml/jackson/core/JsonFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,14 @@ public static int collectDefaults() {
*/
protected StreamReadConstraints _streamReadConstraints;

/**
* Write constraints to use for {@link JsonGenerator}s constructed using
* this factory.
*
* @since 2.16
*/
protected StreamWriteConstraints _streamWriteConstraints;

/**
* Optional helper object that may decorate input sources, to do
* additional processing on input during parsing.
Expand Down Expand Up @@ -350,6 +358,7 @@ public JsonFactory(ObjectCodec oc) {
_objectCodec = oc;
_quoteChar = DEFAULT_QUOTE_CHAR;
_streamReadConstraints = StreamReadConstraints.defaults();
_streamWriteConstraints = StreamWriteConstraints.defaults();
_generatorDecorators = null;
}

Expand All @@ -374,6 +383,8 @@ protected JsonFactory(JsonFactory src, ObjectCodec codec)
_generatorDecorators = _copy(src._generatorDecorators);
_streamReadConstraints = src._streamReadConstraints == null ?
StreamReadConstraints.defaults() : src._streamReadConstraints;
_streamWriteConstraints = src._streamWriteConstraints == null ?
StreamWriteConstraints.defaults() : src._streamWriteConstraints;

// JSON-specific
_characterEscapes = src._characterEscapes;
Expand Down Expand Up @@ -401,6 +412,8 @@ public JsonFactory(JsonFactoryBuilder b) {
_generatorDecorators = _copy(b._generatorDecorators);
_streamReadConstraints = b._streamReadConstraints == null ?
StreamReadConstraints.defaults() : b._streamReadConstraints;
_streamWriteConstraints = b._streamWriteConstraints == null ?
StreamWriteConstraints.defaults() : b._streamWriteConstraints;

// JSON-specific
_characterEscapes = b._characterEscapes;
Expand Down Expand Up @@ -428,6 +441,8 @@ protected JsonFactory(TSFBuilder<?,?> b, boolean bogus) {
_generatorDecorators = _copy(b._generatorDecorators);
_streamReadConstraints = b._streamReadConstraints == null ?
StreamReadConstraints.defaults() : b._streamReadConstraints;
_streamWriteConstraints = b._streamWriteConstraints == null ?
StreamWriteConstraints.defaults() : b._streamWriteConstraints;

// JSON-specific: need to assign even if not really used
_characterEscapes = null;
Expand Down Expand Up @@ -802,6 +817,11 @@ public StreamReadConstraints streamReadConstraints() {
return _streamReadConstraints;
}

@Override
public StreamWriteConstraints streamWriteConstraints() {
return _streamWriteConstraints;
}

/**
* Method for overriding {@link StreamReadConstraints} defined for
* this factory.
Expand All @@ -822,6 +842,26 @@ public JsonFactory setStreamReadConstraints(StreamReadConstraints src) {
return this;
}

/**
* Method for overriding {@link StreamWriteConstraints} defined for
* this factory.
*<p>
* NOTE: the preferred way to set constraints is by using
* {@link JsonFactoryBuilder#streamWriteConstraints}: this method is only
* provided to support older non-builder-based construction.
* In Jackson 3.x this method will not be available.
*
* @param swc Constraints
*
* @return This factory instance (to allow call chaining)
*
* @since 2.16
*/
public JsonFactory setStreamWriteConstraints(StreamWriteConstraints swc) {
_streamWriteConstraints = Objects.requireNonNull(swc);
return this;
}

/*
/**********************************************************
/* Configuration, parser configuration
Expand Down Expand Up @@ -2076,7 +2116,8 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
if (contentRef == null) {
contentRef = ContentReference.unknown();
}
return new IOContext(_streamReadConstraints, _getBufferRecycler(), contentRef, resourceManaged);
return new IOContext(_streamReadConstraints, _streamWriteConstraints,
_getBufferRecycler(), contentRef, resourceManaged);
}

/**
Expand All @@ -2091,7 +2132,8 @@ protected IOContext _createContext(ContentReference contentRef, boolean resource
*/
@Deprecated // @since 2.13
protected IOContext _createContext(Object rawContentRef, boolean resourceManaged) {
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
return new IOContext(_streamReadConstraints, _streamWriteConstraints,
_getBufferRecycler(),
_createContentReference(rawContentRef),
resourceManaged);
}
Expand All @@ -2109,7 +2151,8 @@ protected IOContext _createContext(Object rawContentRef, boolean resourceManaged
protected IOContext _createNonBlockingContext(Object srcRef) {
// [jackson-core#479]: allow recycling for non-blocking parser again
// now that access is thread-safe
return new IOContext(_streamReadConstraints, _getBufferRecycler(),
return new IOContext(_streamReadConstraints, _streamWriteConstraints,
_getBufferRecycler(),
_createContentReference(srcRef),
false);
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,15 @@ protected JsonGenerator() { }
*/
public abstract ObjectCodec getCodec();

/**
* Get the constraints to apply when performing streaming writes.
*
* @since 2.16
*/
public StreamWriteConstraints streamWriteConstraints() {
return StreamWriteConstraints.defaults();
}

/**
* Accessor for finding out version of the bundle that provided this generator instance.
*
Expand Down
157 changes: 157 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/StreamWriteConstraints.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.fasterxml.jackson.core;

import com.fasterxml.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.
*<p>
* Currently constrained aspects, with default settings, are:
* <ul>
* <li>Maximum Nesting depth: default 1000 (see {@link #DEFAULT_MAX_DEPTH})
* </li>
* </ul>
*
* @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"));
pjfanning marked this conversation as resolved.
Show resolved Hide resolved
}
}

/*
/**********************************************************************
/* 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+"()`";
}
}
21 changes: 20 additions & 1 deletion src/main/java/com/fasterxml/jackson/core/TSFBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,19 @@ public abstract class TSFBuilder<F extends JsonFactory,
protected OutputDecorator _outputDecorator;

/**
* Optional StreamReadConfig.
* Optional StreamReadConstraints.
*
* @since 2.15
*/
protected StreamReadConstraints _streamReadConstraints;

/**
* Optional StreamWriteConstraints.
*
* @since 2.16
*/
protected StreamWriteConstraints _streamWriteConstraints;

/**
* @since 2.16
*/
Expand Down Expand Up @@ -313,6 +320,18 @@ public B streamReadConstraints(StreamReadConstraints streamReadConstraints) {
return _this();
}

/**
* Sets the constraints for streaming writes.
*
* @param streamWriteConstraints constraints for streaming reads
* @return this factory
* @since 2.16
*/
public B streamWriteConstraints(StreamWriteConstraints streamWriteConstraints) {
_streamWriteConstraints = streamWriteConstraints;
return _this();
}

// // // Other methods

/**
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/TokenStreamFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ public abstract class TokenStreamFactory
*/
public abstract StreamReadConstraints streamReadConstraints();

/**
* Get the constraints to apply when performing streaming writes.
*
* @return Constraints to apply to reads done by {@link JsonGenerator}s constructed
* by this factory.
*
* @since 2.16
*/
public abstract StreamWriteConstraints streamWriteConstraints();

/*
/**********************************************************************
/* Factory methods, parsers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ protected TokenFilterContext(int type, TokenFilterContext parent,
super();
_type = type;
_parent = parent;
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
_filter = filter;
_index = -1;
_startHandled = startHandled;
Expand Down
Loading