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 support for StreamWriteConstraints checks #386

Merged
merged 9 commits into from
Jul 12, 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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum Feature
{
/**
* Feature that can be disabled to prevent Avro from buffering any more
* data then absolutely necessary.
* data than absolutely necessary.
* This affects buffering by underlying codec.
* Note that disabling buffer is likely to reduce performance if the underlying
* input/output is unbuffered.
Expand Down Expand Up @@ -157,6 +157,11 @@ public void setSchema(AvroSchema schema)
schema.getAvroSchema(), _encoder);
}

@Override
public StreamWriteConstraints streamWriteConstraints() {
return _ioContext.streamWriteConstraints();
}

/*
/**********************************************************
/* Versioned
Expand Down Expand Up @@ -374,6 +379,7 @@ public void close() throws IOException
@Override
public final void writeStartArray() throws IOException {
_avroContext = _avroContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
_complete = false;
}

Expand All @@ -392,12 +398,14 @@ public final void writeEndArray() throws IOException
@Override
public final void writeStartObject() throws IOException {
_avroContext = _avroContext.createChildObjectContext(null);
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
_complete = false;
}

@Override
public void writeStartObject(Object forValue) throws IOException {
_avroContext = _avroContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
_complete = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ protected AvroWriteContext(int type, AvroWriteContext parent,
super();
_type = type;
_parent = parent;
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
_generator = generator;
_schema = schema;
_currentValue = currValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.fasterxml.jackson.dataformat.avro.dos;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.dataformat.avro.AvroMapper;
import com.fasterxml.jackson.dataformat.avro.AvroSchema;
import com.fasterxml.jackson.dataformat.avro.AvroTestBase;

/**
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
* data that is cyclic (eg a list that contains itself).
*/
public class CyclicDataSerTest extends AvroTestBase
{

public static class Bean
{
Bean _next;
final String _name;

public Bean(Bean next, String name) {
_next = next;
_name = name;
}

public Bean getNext() { return _next; }
public String getName() { return _name; }

public void assignNext(Bean n) { _next = n; }
}

private final AvroMapper MAPPER = getMapper();

public void testCyclic() throws Exception {
Bean bean = new Bean(null, "123");
bean.assignNext(bean);
try {
AvroSchema schema = MAPPER.schemaFor(Bean.class);
MAPPER.writer(schema).writeValueAsBytes(bean);
fail("expected InvalidDefinitionException");
} catch (InvalidDefinitionException idex) {
assertTrue("InvalidDefinitionException message is as expected?",
idex.getMessage().startsWith("Direct self-reference leading to cycle"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
/**********************************************************
*/

@Override
public StreamWriteConstraints streamWriteConstraints() {
return _ioContext.streamWriteConstraints();
}

/**
* No way (or need) to indent anything, so let's block any attempts. (should
* we throw an exception instead?)
Expand Down Expand Up @@ -570,6 +575,7 @@ public final void writeFieldId(long id) throws IOException {
public final void writeStartArray() throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
Expand All @@ -581,6 +587,7 @@ public final void writeStartArray() throws IOException {
public void writeStartArray(Object forValue) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
Expand All @@ -596,6 +603,7 @@ public void writeStartArray(Object forValue) throws IOException {
public void writeStartArray(Object forValue, int elementsToWrite) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
Expand All @@ -606,6 +614,7 @@ public void writeStartArray(Object forValue, int elementsToWrite) throws IOExcep
public void writeStartArray(int elementsToWrite) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
Expand All @@ -624,6 +633,7 @@ public final void writeEndArray() throws IOException {
public final void writeStartObject() throws IOException {
_verifyValueWrite("start an object");
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
Expand All @@ -636,6 +646,7 @@ public final void writeStartObject() throws IOException {
public final void writeStartObject(Object forValue) throws IOException {
_verifyValueWrite("start an object");
CBORWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
_streamWriteContext = ctxt;
if (_elementCountsPtr > 0) {
_pushRemainingElements();
Expand All @@ -652,6 +663,7 @@ public final void writeStartObject(int elementsToWrite) throws IOException {
public void writeStartObject(Object forValue, int elementsToWrite) throws IOException {
_verifyValueWrite("start an object");
_streamWriteContext = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ protected CBORWriteContext(int type, CBORWriteContext parent, DupDetector dups,
super();
_type = type;
_parent = parent;
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
_dups = dups;
_index = -1;
_currentValue = currentValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.fasterxml.jackson.dataformat.cbor.gen.dos;

import com.fasterxml.jackson.core.StreamWriteConstraints;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;

import java.util.ArrayList;
import java.util.List;

/**
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
* data that is cyclic (eg a list that contains itself).
*/
public class CyclicDataSerTest extends CBORTestBase
{
private final ObjectMapper MAPPER = cborMapper();

public void testListWithSelfReference() throws Exception {
List<Object> list = new ArrayList<>();
list.add(list);
try {
MAPPER.writeValueAsBytes(list);
fail("expected JsonMappingException");
} catch (JsonMappingException jmex) {
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1);
assertTrue("JsonMappingException message is as expected?",
jmex.getMessage().startsWith(exceptionPrefix));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.StreamWriteConstraints;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
Expand All @@ -28,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception
}
fail("expected StreamConstraintsException");
} catch (StreamConstraintsException e) {
assertTrue("unexpected message: " + e.getMessage(),
e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed"));
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
StreamReadConstraints.DEFAULT_MAX_DEPTH + 1);
assertTrue("JsonMappingException message is as expected?",
e.getMessage().startsWith(exceptionPrefix));
}
}

Expand Down Expand Up @@ -61,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception
}
fail("expected StreamConstraintsException");
} catch (StreamConstraintsException e) {
assertTrue("unexpected message: " + e.getMessage(),
e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed"));
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
StreamReadConstraints.DEFAULT_MAX_DEPTH + 1);
assertTrue("JsonMappingException message is as expected?",
e.getMessage().startsWith(exceptionPrefix));
}
}

Expand All @@ -83,7 +88,10 @@ public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception
}

private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException {
try (JsonGenerator gen = cborGenerator(out)) {
CBORFactory cborFactory = cborFactoryBuilder()
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build())
.build();
try (JsonGenerator gen = cborGenerator(cborFactory, out)) {
for (int i = 0; i < depth; i++) {
gen.writeStartObject();
gen.writeFieldName("a");
Expand All @@ -96,7 +104,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws
}

private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException {
try (JsonGenerator gen = cborGenerator(out)) {
CBORFactory cborFactory = cborFactoryBuilder()
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build())
.build();
try (JsonGenerator gen = cborGenerator(cborFactory, out)) {
for (int i = 0; i < depth; i++) {
gen.writeStartObject();
gen.writeFieldName("a");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.StreamWriteCapability;
import com.fasterxml.jackson.core.StreamWriteConstraints;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.base.GeneratorBase;
import com.fasterxml.jackson.core.io.IOContext;
Expand Down Expand Up @@ -148,6 +149,11 @@ public Version version() {
return PackageVersion.VERSION;
}

@Override
public StreamWriteConstraints streamWriteConstraints() {
return _ioContext.streamWriteConstraints();
}

/*
/**********************************************************************
/* JsonGenerator implementation: state handling
Expand Down Expand Up @@ -524,13 +530,15 @@ protected void _writeFieldName(String value) throws IOException {
public void writeStartArray() throws IOException {
_verifyValueWrite("start an array"); // <-- copied from UTF8JsonGenerator
_writeContext = _writeContext.createChildArrayContext(); // <-- copied from UTF8JsonGenerator
streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
_writer.stepIn(IonType.LIST);
}

@Override
public void writeStartObject() throws IOException {
_verifyValueWrite("start an object"); // <-- copied from UTF8JsonGenerator
_writeContext = _writeContext.createChildObjectContext(); // <-- copied from UTF8JsonGenerator
streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
_writer.stepIn(IonType.STRUCT);
}

Expand All @@ -540,6 +548,7 @@ public void writeStartObject() throws IOException {
public void writeStartSexp() throws IOException {
_verifyValueWrite("start a sexp");
_writeContext = ((IonWriteContext) _writeContext).createChildSexpContext();
streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
_writer.stepIn(IonType.SEXP);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* context to track that. Sexp handling is modeled after arrays.
*/
public class IonWriteContext extends JsonWriteContext {
// Both contstants are in the tens instead of the ones to avoid conflict with the native
// Both constants are in the tens instead of the ones to avoid conflict with the native
// Jackson ones

// Ion-specific contexts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.fasterxml.jackson.dataformat.ion.dos;

import com.fasterxml.jackson.core.StreamWriteConstraints;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.ion.IonObjectMapper;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
* data that is cyclic (eg a list that contains itself).
*/
public class CyclicDataSerTest
{
private final ObjectMapper MAPPER = IonObjectMapper.builderForTextualWriters().build();

@Test
public void testListWithSelfReference() throws Exception {
List<Object> list = new ArrayList<>();
list.add(list);
try {
MAPPER.writeValueAsBytes(list);
fail("expected JsonMappingException");
} catch (JsonMappingException jmex) {
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1);
assertTrue("JsonMappingException message is as expected?",
jmex.getMessage().startsWith(exceptionPrefix));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ public void setSchema(ProtobufSchema schema)
_pbContext = _rootContext = ProtobufWriteContext.createRootContext(schema.getRootType());
}

@Override
public StreamWriteConstraints streamWriteConstraints() {
return _ioContext.streamWriteConstraints();
}

@Override // since 2.13
public Object currentValue() {
return _pbContext.getCurrentValue();
Expand Down Expand Up @@ -413,6 +418,7 @@ public final void writeStartArray() throws IOException
// NOTE: do NOT clear _currField; needed for actual element type

_pbContext = _pbContext.createChildArrayContext();
streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth());
_writeTag = !_currField.packed;
/* Unpacked vs packed: if unpacked, nothing special is needed, since it
* is equivalent to just replicating same field N times.
Expand Down Expand Up @@ -481,6 +487,7 @@ public final void writeStartObject() throws IOException
// but do NOT clear next field here
_inObject = true;
}
streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth());
// even if within array, object fields use tags
_writeTag = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ protected ProtobufWriteContext(int type, ProtobufWriteContext parent,
super();
_type = type;
_parent = parent;
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
_message = msg;
}

Expand Down
Loading