diff options
Diffstat (limited to 'java')
4 files changed, 110 insertions, 11 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java index e461fa28..14169dc4 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedInputStream.java @@ -60,7 +60,8 @@ import java.util.List; public abstract class CodedInputStream { private static final int DEFAULT_BUFFER_SIZE = 4096; private static final int DEFAULT_RECURSION_LIMIT = 100; - private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB + // Integer.MAX_VALUE == 0x7FFFFFF == INT_MAX from limits.h + private static final int DEFAULT_SIZE_LIMIT = Integer.MAX_VALUE; /** Visible for subclasses. See setRecursionLimit() */ int recursionDepth; @@ -2762,9 +2763,9 @@ public abstract class CodedInputStream { throw InvalidProtocolBufferException.negativeSize(); } - // Verify that the message size so far has not exceeded sizeLimit. + // Integer-overflow-conscious check that the message size so far has not exceeded sizeLimit. int currentMessageSize = totalBytesRetired + pos + size; - if (currentMessageSize > sizeLimit) { + if (currentMessageSize - sizeLimit > 0) { throw InvalidProtocolBufferException.sizeLimitExceeded(); } diff --git a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java index e2ab0df9..e440c7db 100644 --- a/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java +++ b/java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java @@ -30,8 +30,6 @@ package com.google.protobuf; -import static org.junit.Assert.assertArrayEquals; - import protobuf_unittest.UnittestProto.BoolMessage; import protobuf_unittest.UnittestProto.Int32Message; import protobuf_unittest.UnittestProto.Int64Message; @@ -445,6 +443,82 @@ public class CodedInputStreamTest extends TestCase { } } + /** + * Test we can do messages that are up to CodedInputStream#DEFAULT_SIZE_LIMIT + * in size (2G or Integer#MAX_SIZE). + * @throws IOException + */ + public void testParseMessagesCloseTo2G() throws IOException { + byte[] serializedMessage = getBigSerializedMessage(); + // How many of these big messages do we need to take us near our 2G limit? + int count = Integer.MAX_VALUE / serializedMessage.length; + // Now make an inputstream that will fake a near 2G message of messages + // returning our big serialized message 'count' times. + InputStream is = new RepeatingInputStream(serializedMessage, count); + // Parse should succeed! + TestAllTypes.parseFrom(is); + } + + /** + * Test there is an exception if a message exceeds + * CodedInputStream#DEFAULT_SIZE_LIMIT in size (2G or Integer#MAX_SIZE). + * @throws IOException + */ + public void testParseMessagesOver2G() throws IOException { + byte[] serializedMessage = getBigSerializedMessage(); + // How many of these big messages do we need to take us near our 2G limit? + int count = Integer.MAX_VALUE / serializedMessage.length; + // Now add one to take us over the limit + count++; + // Now make an inputstream that will fake a near 2G message of messages + // returning our big serialized message 'count' times. + InputStream is = new RepeatingInputStream(serializedMessage, count); + try { + TestAllTypes.parseFrom(is); + fail("Should have thrown an exception!"); + } catch (InvalidProtocolBufferException e) { + assertTrue(e.getMessage().contains("too large")); + } + } + + /* + * @return A serialized big message. + */ + private static byte[] getBigSerializedMessage() { + byte[] value = new byte[16 * 1024 * 1024]; + ByteString bsValue = ByteString.wrap(value); + return TestAllTypes.newBuilder().setOptionalBytes(bsValue).build().toByteArray(); + } + + /* + * An input stream that repeats a byte arrays' content a number of times. + * Simulates really large input without consuming loads of memory. Used above + * to test the parsing behavior when the input size exceeds 2G or close to it. + */ + private static class RepeatingInputStream extends InputStream { + private final byte[] serializedMessage; + private final int count; + private int index = 0; + private int offset = 0; + + RepeatingInputStream(byte[] serializedMessage, int count) { + this.serializedMessage = serializedMessage; + this.count = count; + } + + @Override + public int read() throws IOException { + if (this.offset == this.serializedMessage.length) { + this.index++; + this.offset = 0; + } + if (this.index == this.count) { + return -1; + } + return this.serializedMessage[offset++]; + } + } + private TestRecursiveMessage makeRecursiveMessage(int depth) { if (depth == 0) { return TestRecursiveMessage.newBuilder().setI(5).build(); diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java index 7f6c8aea..ac712c94 100644 --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -49,6 +49,7 @@ import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.DoubleValue; import com.google.protobuf.Duration; import com.google.protobuf.DynamicMessage; @@ -782,12 +783,18 @@ public class JsonFormat { if (includingDefaultValueFields) { fieldsToPrint = new TreeMap<FieldDescriptor, Object>(); for (FieldDescriptor field : message.getDescriptorForType().getFields()) { - if (field.isOptional() - && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE - && !message.hasField(field)) { - // Always skip empty optional message fields. If not we will recurse indefinitely if - // a message has itself as a sub-field. - continue; + if (field.isOptional()) { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE + && !message.hasField(field)){ + // Always skip empty optional message fields. If not we will recurse indefinitely if + // a message has itself as a sub-field. + continue; + } + OneofDescriptor oneof = field.getContainingOneof(); + if (oneof != null && !message.hasField(field)) { + // Skip all oneof fields except the one that is actually set + continue; + } } fieldsToPrint.put(field, message.getField(field)); } diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java index 164ee54b..dafd9bb5 100644 --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -1267,6 +1267,23 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", JsonFormat.printer().includingDefaultValueFields().print(mapMessage)); + + TestOneof oneofMessage = TestOneof.getDefaultInstance(); + assertEquals("{\n}", JsonFormat.printer().print(oneofMessage)); + assertEquals("{\n}", JsonFormat.printer().includingDefaultValueFields().print(oneofMessage)); + + oneofMessage = TestOneof.newBuilder().setOneofInt32(42).build(); + assertEquals("{\n \"oneofInt32\": 42\n}", + JsonFormat.printer().print(oneofMessage)); + assertEquals("{\n \"oneofInt32\": 42\n}", + JsonFormat.printer().includingDefaultValueFields().print(oneofMessage)); + + TestOneof.Builder oneofBuilder = TestOneof.newBuilder(); + mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", oneofBuilder); + oneofMessage = oneofBuilder.build(); + assertEquals("{\n \"oneofNullValue\": null\n}", JsonFormat.printer().print(oneofMessage)); + assertEquals("{\n \"oneofNullValue\": null\n}", + JsonFormat.printer().includingDefaultValueFields().print(oneofMessage)); } public void testPreservingProtoFieldNames() throws Exception { |