Skip to content

Commit cdd70bb

Browse files
authored
Add support for StreamWriteConstraints checks (#386)
1 parent 8ffc8b6 commit cdd70bb

File tree

17 files changed

+242
-17
lines changed

17 files changed

+242
-17
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public enum Feature
2525
{
2626
/**
2727
* Feature that can be disabled to prevent Avro from buffering any more
28-
* data then absolutely necessary.
28+
* data than absolutely necessary.
2929
* This affects buffering by underlying codec.
3030
* Note that disabling buffer is likely to reduce performance if the underlying
3131
* input/output is unbuffered.
@@ -157,6 +157,11 @@ public void setSchema(AvroSchema schema)
157157
schema.getAvroSchema(), _encoder);
158158
}
159159

160+
@Override
161+
public StreamWriteConstraints streamWriteConstraints() {
162+
return _ioContext.streamWriteConstraints();
163+
}
164+
160165
/*
161166
/**********************************************************
162167
/* Versioned
@@ -374,6 +379,7 @@ public void close() throws IOException
374379
@Override
375380
public final void writeStartArray() throws IOException {
376381
_avroContext = _avroContext.createChildArrayContext(null);
382+
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
377383
_complete = false;
378384
}
379385

@@ -392,12 +398,14 @@ public final void writeEndArray() throws IOException
392398
@Override
393399
public final void writeStartObject() throws IOException {
394400
_avroContext = _avroContext.createChildObjectContext(null);
401+
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
395402
_complete = false;
396403
}
397404

398405
@Override
399406
public void writeStartObject(Object forValue) throws IOException {
400407
_avroContext = _avroContext.createChildObjectContext(forValue);
408+
streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth());
401409
_complete = false;
402410
}
403411

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ protected AvroWriteContext(int type, AvroWriteContext parent,
5252
super();
5353
_type = type;
5454
_parent = parent;
55+
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
5556
_generator = generator;
5657
_schema = schema;
5758
_currentValue = currValue;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.dataformat.avro.dos;
2+
3+
import com.fasterxml.jackson.databind.JsonMappingException;
4+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
5+
import com.fasterxml.jackson.dataformat.avro.AvroMapper;
6+
import com.fasterxml.jackson.dataformat.avro.AvroSchema;
7+
import com.fasterxml.jackson.dataformat.avro.AvroTestBase;
8+
9+
/**
10+
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
11+
* data that is cyclic (eg a list that contains itself).
12+
*/
13+
public class CyclicDataSerTest extends AvroTestBase
14+
{
15+
16+
public static class Bean
17+
{
18+
Bean _next;
19+
final String _name;
20+
21+
public Bean(Bean next, String name) {
22+
_next = next;
23+
_name = name;
24+
}
25+
26+
public Bean getNext() { return _next; }
27+
public String getName() { return _name; }
28+
29+
public void assignNext(Bean n) { _next = n; }
30+
}
31+
32+
private final AvroMapper MAPPER = getMapper();
33+
34+
public void testCyclic() throws Exception {
35+
Bean bean = new Bean(null, "123");
36+
bean.assignNext(bean);
37+
try {
38+
AvroSchema schema = MAPPER.schemaFor(Bean.class);
39+
MAPPER.writer(schema).writeValueAsBytes(bean);
40+
fail("expected InvalidDefinitionException");
41+
} catch (InvalidDefinitionException idex) {
42+
assertTrue("InvalidDefinitionException message is as expected?",
43+
idex.getMessage().startsWith("Direct self-reference leading to cycle"));
44+
}
45+
}
46+
}

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,11 @@ public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
379379
/**********************************************************
380380
*/
381381

382+
@Override
383+
public StreamWriteConstraints streamWriteConstraints() {
384+
return _ioContext.streamWriteConstraints();
385+
}
386+
382387
/**
383388
* No way (or need) to indent anything, so let's block any attempts. (should
384389
* we throw an exception instead?)
@@ -570,6 +575,7 @@ public final void writeFieldId(long id) throws IOException {
570575
public final void writeStartArray() throws IOException {
571576
_verifyValueWrite("start an array");
572577
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
578+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
573579
if (_elementCountsPtr > 0) {
574580
_pushRemainingElements();
575581
}
@@ -581,6 +587,7 @@ public final void writeStartArray() throws IOException {
581587
public void writeStartArray(Object forValue) throws IOException {
582588
_verifyValueWrite("start an array");
583589
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
590+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
584591
if (_elementCountsPtr > 0) {
585592
_pushRemainingElements();
586593
}
@@ -596,6 +603,7 @@ public void writeStartArray(Object forValue) throws IOException {
596603
public void writeStartArray(Object forValue, int elementsToWrite) throws IOException {
597604
_verifyValueWrite("start an array");
598605
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
606+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
599607
_pushRemainingElements();
600608
_currentRemainingElements = elementsToWrite;
601609
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
@@ -606,6 +614,7 @@ public void writeStartArray(Object forValue, int elementsToWrite) throws IOExcep
606614
public void writeStartArray(int elementsToWrite) throws IOException {
607615
_verifyValueWrite("start an array");
608616
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
617+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
609618
_pushRemainingElements();
610619
_currentRemainingElements = elementsToWrite;
611620
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
@@ -624,6 +633,7 @@ public final void writeEndArray() throws IOException {
624633
public final void writeStartObject() throws IOException {
625634
_verifyValueWrite("start an object");
626635
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
636+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
627637
if (_elementCountsPtr > 0) {
628638
_pushRemainingElements();
629639
}
@@ -636,6 +646,7 @@ public final void writeStartObject() throws IOException {
636646
public final void writeStartObject(Object forValue) throws IOException {
637647
_verifyValueWrite("start an object");
638648
CBORWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue);
649+
streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
639650
_streamWriteContext = ctxt;
640651
if (_elementCountsPtr > 0) {
641652
_pushRemainingElements();
@@ -652,6 +663,7 @@ public final void writeStartObject(int elementsToWrite) throws IOException {
652663
public void writeStartObject(Object forValue, int elementsToWrite) throws IOException {
653664
_verifyValueWrite("start an object");
654665
_streamWriteContext = _streamWriteContext.createChildObjectContext(forValue);
666+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
655667
_pushRemainingElements();
656668
_currentRemainingElements = elementsToWrite;
657669
_writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite);

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ protected CBORWriteContext(int type, CBORWriteContext parent, DupDetector dups,
6565
super();
6666
_type = type;
6767
_parent = parent;
68+
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
6869
_dups = dups;
6970
_index = -1;
7071
_currentValue = currentValue;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.fasterxml.jackson.dataformat.cbor.gen.dos;
2+
3+
import com.fasterxml.jackson.core.StreamWriteConstraints;
4+
import com.fasterxml.jackson.databind.JsonMappingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
/**
12+
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
13+
* data that is cyclic (eg a list that contains itself).
14+
*/
15+
public class CyclicDataSerTest extends CBORTestBase
16+
{
17+
private final ObjectMapper MAPPER = cborMapper();
18+
19+
public void testListWithSelfReference() throws Exception {
20+
List<Object> list = new ArrayList<>();
21+
list.add(list);
22+
try {
23+
MAPPER.writeValueAsBytes(list);
24+
fail("expected JsonMappingException");
25+
} catch (JsonMappingException jmex) {
26+
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
27+
StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1);
28+
assertTrue("JsonMappingException message is as expected?",
29+
jmex.getMessage().startsWith(exceptionPrefix));
30+
}
31+
}
32+
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.core.JsonParser;
55
import com.fasterxml.jackson.core.JsonToken;
66
import com.fasterxml.jackson.core.StreamReadConstraints;
7+
import com.fasterxml.jackson.core.StreamWriteConstraints;
78
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
89
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
910
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
@@ -28,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception
2829
}
2930
fail("expected StreamConstraintsException");
3031
} catch (StreamConstraintsException e) {
31-
assertTrue("unexpected message: " + e.getMessage(),
32-
e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed"));
32+
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
33+
StreamReadConstraints.DEFAULT_MAX_DEPTH + 1);
34+
assertTrue("JsonMappingException message is as expected?",
35+
e.getMessage().startsWith(exceptionPrefix));
3336
}
3437
}
3538

@@ -61,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception
6164
}
6265
fail("expected StreamConstraintsException");
6366
} catch (StreamConstraintsException e) {
64-
assertTrue("unexpected message: " + e.getMessage(),
65-
e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed"));
67+
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
68+
StreamReadConstraints.DEFAULT_MAX_DEPTH + 1);
69+
assertTrue("JsonMappingException message is as expected?",
70+
e.getMessage().startsWith(exceptionPrefix));
6671
}
6772
}
6873

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

8590
private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException {
86-
try (JsonGenerator gen = cborGenerator(out)) {
91+
CBORFactory cborFactory = cborFactoryBuilder()
92+
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build())
93+
.build();
94+
try (JsonGenerator gen = cborGenerator(cborFactory, out)) {
8795
for (int i = 0; i < depth; i++) {
8896
gen.writeStartObject();
8997
gen.writeFieldName("a");
@@ -96,7 +104,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws
96104
}
97105

98106
private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException {
99-
try (JsonGenerator gen = cborGenerator(out)) {
107+
CBORFactory cborFactory = cborFactoryBuilder()
108+
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build())
109+
.build();
110+
try (JsonGenerator gen = cborGenerator(cborFactory, out)) {
100111
for (int i = 0; i < depth; i++) {
101112
gen.writeStartObject();
102113
gen.writeFieldName("a");

ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.core.JsonGenerator;
2727
import com.fasterxml.jackson.core.ObjectCodec;
2828
import com.fasterxml.jackson.core.StreamWriteCapability;
29+
import com.fasterxml.jackson.core.StreamWriteConstraints;
2930
import com.fasterxml.jackson.core.Version;
3031
import com.fasterxml.jackson.core.base.GeneratorBase;
3132
import com.fasterxml.jackson.core.io.IOContext;
@@ -148,6 +149,11 @@ public Version version() {
148149
return PackageVersion.VERSION;
149150
}
150151

152+
@Override
153+
public StreamWriteConstraints streamWriteConstraints() {
154+
return _ioContext.streamWriteConstraints();
155+
}
156+
151157
/*
152158
/**********************************************************************
153159
/* JsonGenerator implementation: state handling
@@ -524,13 +530,15 @@ protected void _writeFieldName(String value) throws IOException {
524530
public void writeStartArray() throws IOException {
525531
_verifyValueWrite("start an array"); // <-- copied from UTF8JsonGenerator
526532
_writeContext = _writeContext.createChildArrayContext(); // <-- copied from UTF8JsonGenerator
533+
streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
527534
_writer.stepIn(IonType.LIST);
528535
}
529536

530537
@Override
531538
public void writeStartObject() throws IOException {
532539
_verifyValueWrite("start an object"); // <-- copied from UTF8JsonGenerator
533540
_writeContext = _writeContext.createChildObjectContext(); // <-- copied from UTF8JsonGenerator
541+
streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
534542
_writer.stepIn(IonType.STRUCT);
535543
}
536544

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

ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* context to track that. Sexp handling is modeled after arrays.
2727
*/
2828
public class IonWriteContext extends JsonWriteContext {
29-
// Both contstants are in the tens instead of the ones to avoid conflict with the native
29+
// Both constants are in the tens instead of the ones to avoid conflict with the native
3030
// Jackson ones
3131

3232
// Ion-specific contexts
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.fasterxml.jackson.dataformat.ion.dos;
2+
3+
import com.fasterxml.jackson.core.StreamWriteConstraints;
4+
import com.fasterxml.jackson.databind.JsonMappingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.dataformat.ion.IonObjectMapper;
7+
import org.junit.Test;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import static org.junit.Assert.assertTrue;
13+
import static org.junit.Assert.fail;
14+
15+
/**
16+
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
17+
* data that is cyclic (eg a list that contains itself).
18+
*/
19+
public class CyclicDataSerTest
20+
{
21+
private final ObjectMapper MAPPER = IonObjectMapper.builderForTextualWriters().build();
22+
23+
@Test
24+
public void testListWithSelfReference() throws Exception {
25+
List<Object> list = new ArrayList<>();
26+
list.add(list);
27+
try {
28+
MAPPER.writeValueAsBytes(list);
29+
fail("expected JsonMappingException");
30+
} catch (JsonMappingException jmex) {
31+
String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed",
32+
StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1);
33+
assertTrue("JsonMappingException message is as expected?",
34+
jmex.getMessage().startsWith(exceptionPrefix));
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)