Skip to content
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
9 changes: 9 additions & 0 deletions protobuf/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 5.1.0-wip

* Update default size limit of `CodedBufferReader` from 67,108,864 bytes to
2,147,483,647 bytes, and default recursion limit from 64 to 100.

The new limits are consistent with the Java and C++ implementations. ([#1060])

[#1060]: https://github.com/google/protobuf.dart/pull/1060

## 5.0.0

* Improve performance of `GeneratedMessage.deepCopy`. ([#742])
Expand Down
5 changes: 3 additions & 2 deletions protobuf/lib/src/protobuf/coded_buffer_reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ part of 'internal.dart';
/// [GeneratedMessage]s.
class CodedBufferReader {
// ignore: constant_identifier_names
static const int DEFAULT_RECURSION_LIMIT = 64;
static const int DEFAULT_RECURSION_LIMIT = 100;
// Maximum value of a 32-bit signed integer.
// ignore: constant_identifier_names
static const int DEFAULT_SIZE_LIMIT = 64 << 20;
static const int DEFAULT_SIZE_LIMIT = (1 << 31) - 1;

final Uint8List _buffer;

Expand Down
8 changes: 4 additions & 4 deletions protobuf/lib/src/protobuf/coded_buffer_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ class CodedBufferWriter {
_commitChunk(true);
}

void writeField(int fieldNumber, int fieldType, Object? fieldValue) {
void writeField(int fieldNumber, int fieldType, dynamic fieldValue) {
final valueType = PbFieldType.baseType(fieldType);

if ((fieldType & PbFieldType.PACKED_BIT) != 0) {
final list = fieldValue as List;
final List list = fieldValue;
if (list.isNotEmpty) {
_writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
final mark = _startLengthDelimited();
Expand All @@ -81,7 +81,7 @@ class CodedBufferWriter {
}

if ((fieldType & PbFieldType.MAP_BIT) != 0) {
final map = fieldValue as PbMap;
final PbMap map = fieldValue;
final keyWireFormat = _wireTypes[_valueTypeIndex(map.keyFieldType)];
final valueWireFormat = _wireTypes[_valueTypeIndex(map.valueFieldType)];

Expand All @@ -108,7 +108,7 @@ class CodedBufferWriter {
final wireFormat = _wireTypes[_valueTypeIndex(valueType)];

if ((fieldType & PbFieldType.REPEATED_BIT) != 0) {
final list = fieldValue as List;
final List list = fieldValue;
for (var i = 0; i < list.length; i++) {
_writeValue(fieldNumber, valueType, list[i], wireFormat);
}
Expand Down
3 changes: 2 additions & 1 deletion protobuf/lib/src/protobuf/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class InvalidProtocolBufferException implements Exception {
InvalidProtocolBufferException.recursionLimitExceeded()
: this._('''
Protocol message had too many levels of nesting. May be malicious.
Use CodedBufferReader.setRecursionLimit() to increase the depth limit.
Use a CodedBufferReader with a defined recursion depth limit if you need to
parse deeply nested messages.
''');

InvalidProtocolBufferException.truncatedMessage()
Expand Down
18 changes: 7 additions & 11 deletions protobuf/lib/src/protobuf/field_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,18 @@ part of 'internal.dart';

/// Defines constants and functions for dealing with fieldType bits.
class PbFieldType {
static bool isRepeated(int fieldType) =>
(fieldType & PbFieldType.REPEATED_BIT) != 0;
static bool isRepeated(int fieldType) => (fieldType & REPEATED_BIT) != 0;

static bool isRequired(int fieldType) =>
(fieldType & PbFieldType.REQUIRED_BIT) != 0;
static bool isRequired(int fieldType) => (fieldType & REQUIRED_BIT) != 0;

static bool isEnum(int fieldType) =>
PbFieldType.baseType(fieldType) == PbFieldType.ENUM_BIT;
static bool isEnum(int fieldType) => baseType(fieldType) == ENUM_BIT;

static bool isBytes(int fieldType) =>
PbFieldType.baseType(fieldType) == PbFieldType.BYTES_BIT;
static bool isBytes(int fieldType) => baseType(fieldType) == BYTES_BIT;

static bool isGroupOrMessage(int fieldType) =>
(fieldType & (PbFieldType.GROUP_BIT | PbFieldType.MESSAGE_BIT)) != 0;
(fieldType & (GROUP_BIT | MESSAGE_BIT)) != 0;

static bool isMapField(int fieldType) =>
(fieldType & PbFieldType.MAP_BIT) != 0;
static bool isMapField(int fieldType) => (fieldType & MAP_BIT) != 0;

/// Returns the base field type without any of the required, repeated
/// and packed bits.
Expand Down Expand Up @@ -176,6 +171,7 @@ class PbFieldType {
static const int PACKED_SFIXED64 = REPEATED_BIT | PACKED_BIT | SFIXED64_BIT;

static const int MAP = MAP_BIT | MESSAGE_BIT;

// Short names for use in generated code.

// _O_ptional.
Expand Down
49 changes: 11 additions & 38 deletions protobuf/lib/src/protobuf/proto3_json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,33 +191,6 @@ Object? _writeToProto3Json(FieldSet fs, TypeRegistry typeRegistry) {
return result;
}

int _tryParse32BitProto3(String s, JsonParsingContext context) {
return int.tryParse(s) ??
(throw context.parseException('expected integer', s));
}

int _check32BitSignedProto3(int n, JsonParsingContext context) {
if (n < -2147483648 || n > 2147483647) {
throw context.parseException('expected 32 bit signed integer', n);
}
return n;
}

int _check32BitUnsignedProto3(int n, JsonParsingContext context) {
if (n < 0 || n > 0xFFFFFFFF) {
throw context.parseException('expected 32 bit unsigned integer', n);
}
return n;
}

Int64 _tryParse64BitProto3(Object? json, String s, JsonParsingContext context) {
try {
return Int64.parseInt(s);
} on FormatException {
throw context.parseException('expected integer', json);
}
}

/// TODO(paulberry): find a better home for this?
extension _FindFirst<E> on Iterable<E> {
E? findFirst(bool Function(E) test) {
Expand Down Expand Up @@ -384,36 +357,36 @@ void _mergeFromProto3JsonWithContext(
if (value is int) {
result = value;
} else if (value is String) {
result = _tryParse32BitProto3(value, context);
result = Proto3ParseUtils.tryParse32Bit(value, context);
} else {
throw context.parseException(
'Expected int or stringified int',
value,
);
}
return _check32BitUnsignedProto3(result, context);
return Proto3ParseUtils.check32BitUnsigned(result, context);
case PbFieldType.INT32_BIT:
case PbFieldType.SINT32_BIT:
case PbFieldType.SFIXED32_BIT:
int result;
if (value is int) {
result = value;
} else if (value is String) {
result = _tryParse32BitProto3(value, context);
result = Proto3ParseUtils.tryParse32Bit(value, context);
} else {
throw context.parseException(
'Expected int or stringified int',
value,
);
}
_check32BitSignedProto3(result, context);
Proto3ParseUtils.check32BitSigned(result, context);
return result;
case PbFieldType.UINT64_BIT:
Int64 result;
if (value is int) {
result = Int64(value);
} else if (value is String) {
result = _tryParse64BitProto3(json, value, context);
result = Proto3ParseUtils.tryParse64Bit(json, value, context);
} else {
throw context.parseException(
'Expected int or stringified int',
Expand Down Expand Up @@ -469,23 +442,23 @@ void _mergeFromProto3JsonWithContext(
case PbFieldType.UINT64_BIT:
// TODO(sigurdm): We do not throw on negative values here.
// That would probably require going via bignum.
return _tryParse64BitProto3(json, key, context);
return Proto3ParseUtils.tryParse64Bit(json, key, context);
case PbFieldType.INT64_BIT:
case PbFieldType.SINT64_BIT:
case PbFieldType.SFIXED64_BIT:
case PbFieldType.FIXED64_BIT:
return _tryParse64BitProto3(json, key, context);
return Proto3ParseUtils.tryParse64Bit(json, key, context);
case PbFieldType.INT32_BIT:
case PbFieldType.SINT32_BIT:
case PbFieldType.FIXED32_BIT:
case PbFieldType.SFIXED32_BIT:
return _check32BitSignedProto3(
_tryParse32BitProto3(key, context),
return Proto3ParseUtils.check32BitSigned(
Proto3ParseUtils.tryParse32Bit(key, context),
context,
);
case PbFieldType.UINT32_BIT:
return _check32BitUnsignedProto3(
_tryParse32BitProto3(key, context),
return Proto3ParseUtils.check32BitUnsigned(
Proto3ParseUtils.tryParse32Bit(key, context),
context,
);
default:
Expand Down
36 changes: 36 additions & 0 deletions protobuf/lib/src/protobuf/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:fixnum/fixnum.dart' show Int64;

import 'internal.dart';
import 'json_parsing_context.dart';

// TODO(antonm): reconsider later if PbList should take care of equality.
bool deepEquals(Object lhs, Object rhs) {
Expand Down Expand Up @@ -53,3 +56,36 @@ class HashUtils {
static int hash2(dynamic a, dynamic b) =>
_finish(combine(combine(0, a.hashCode), b.hashCode));
}

class Proto3ParseUtils {
static int tryParse32Bit(String s, JsonParsingContext context) {
return int.tryParse(s) ??
(throw context.parseException('expected integer', s));
}

static int check32BitSigned(int n, JsonParsingContext context) {
if (n < -2147483648 || n > 2147483647) {
throw context.parseException('expected 32 bit signed integer', n);
}
return n;
}

static int check32BitUnsigned(int n, JsonParsingContext context) {
if (n < 0 || n > 0xFFFFFFFF) {
throw context.parseException('expected 32 bit unsigned integer', n);
}
return n;
}

static Int64 tryParse64Bit(
Object? json,
String s,
JsonParsingContext context,
) {
try {
return Int64.parseInt(s);
} on FormatException {
throw context.parseException('expected integer', json);
}
}
}
2 changes: 1 addition & 1 deletion protobuf/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: protobuf
version: 5.0.0
version: 5.1.0-wip
description: >-
Runtime library for protocol buffers support. Use with package:protoc_plugin
to generate Dart code for your '.proto' files.
Expand Down
23 changes: 17 additions & 6 deletions protoc_plugin/test/generated_message_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,27 @@ void main() {
}
}

final List<int> data64 = makeRecursiveMessage(64).writeToBuffer();
final List<int> data65 = makeRecursiveMessage(65).writeToBuffer();

assertMessageDepth(TestRecursiveMessage.fromBuffer(data64), 64);
// Message with exactly `DEFAULT_RECURSION_LIMIT` levels of nesting.
final List<int> dataShallow =
makeRecursiveMessage(
CodedBufferReader.DEFAULT_RECURSION_LIMIT,
).writeToBuffer();
// Message with more than `DEFAULT_RECURSION_LIMIT` levels of nesting.
final List<int> dataDeep =
makeRecursiveMessage(
CodedBufferReader.DEFAULT_RECURSION_LIMIT + 1,
).writeToBuffer();

assertMessageDepth(
TestRecursiveMessage.fromBuffer(dataShallow),
CodedBufferReader.DEFAULT_RECURSION_LIMIT,
);

expect(() {
TestRecursiveMessage.fromBuffer(data65);
TestRecursiveMessage.fromBuffer(dataDeep);
}, throwsInvalidProtocolBufferException);

final input = CodedBufferReader(data64, recursionLimit: 8);
final input = CodedBufferReader(dataShallow, recursionLimit: 8);
expect(() {
// Uncomfortable alternative to below...
TestRecursiveMessage().mergeFromCodedBufferReader(input);
Expand Down