Skip to content

Commit b73920e

Browse files
committed
TextFormat decoding options to skip unknown fields/extensions.
Modeled after the upstream C++, provide two new decoding options to skip unknown fields while decoding TextFormat.
1 parent 1881999 commit b73920e

8 files changed

+1100
-68
lines changed

Sources/SwiftProtobuf/DoubleParser.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import Foundation
1818
internal class DoubleParser {
1919
// Temporary buffer so we can null-terminate the UTF-8 string
2020
// before calling the C standard library to parse it.
21+
//
2122
// In theory, JSON writers should be able to represent any IEEE Double
2223
// in at most 25 bytes, but many writers will emit more digits than
23-
// necessary, so we size this generously.
24+
// necessary, so we size this generously; but we could still fail to
25+
// parse if someone crafts something really long (especially for
26+
// TextFormat due to overflows (see below)).
2427
private var work =
2528
UnsafeMutableBufferPointer<Int8>.allocate(capacity: 128)
2629

@@ -49,6 +52,15 @@ internal class DoubleParser {
4952

5053
// Fail if strtod() did not consume everything we expected
5154
// or if strtod() thought the number was out of range.
55+
//
56+
// NOTE: TextFormat specifically calls out handling for overflows
57+
// for float/double fields:
58+
// https://protobuf.dev/reference/protobuf/textformat-spec/#value
59+
//
60+
// > Overflows are treated as infinity or -infinity.
61+
//
62+
// But the JSON protobuf spec doesn't mention anything:
63+
// https://protobuf.dev/programming-guides/proto3/#json
5264
if e != work.baseAddress! + bytes.count || !d.isFinite {
5365
return nil
5466
}

Sources/SwiftProtobuf/TextFormatDecoder.swift

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ internal struct TextFormatDecoder: Decoder {
2727
private var fieldNameMap: _NameMap?
2828
private var messageType: any Message.Type
2929

30+
internal var options: TextFormatDecodingOptions {
31+
return scanner.options
32+
}
33+
3034
internal var complete: Bool {
3135
mutating get {
3236
return scanner.complete
@@ -63,27 +67,17 @@ internal struct TextFormatDecoder: Decoder {
6367
}
6468

6569
mutating func nextFieldNumber() throws -> Int? {
66-
// Per https://protobuf.dev/reference/protobuf/textformat-spec/#fields, every field can be
67-
// followed by a field separator, so if we've seen a field, remove the separator before
68-
// checking for the terminator.
6970
if fieldCount > 0 {
7071
scanner.skipOptionalSeparator()
7172
}
72-
if let terminator = terminator,
73-
scanner.skipOptionalObjectEnd(terminator) {
74-
return nil
75-
}
76-
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!, messageType: messageType) {
73+
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!,
74+
messageType: messageType,
75+
terminator: terminator) {
7776
fieldCount += 1
7877
return fieldNumber
79-
} else if terminator == nil {
80-
return nil
8178
} else {
82-
// If this decoder is looking for at a terminator, then if the scanner failed to
83-
// find a field number, something went wrong (end of stream).
84-
throw TextFormatDecodingError.truncated
79+
return nil
8580
}
86-
8781
}
8882

8983
mutating func decodeSingularFloatField(value: inout Float) throws {
@@ -559,6 +553,7 @@ internal struct TextFormatDecoder: Decoder {
559553
var keyField: KeyType.BaseType?
560554
var valueField: ValueType.BaseType?
561555
let terminator = try scanner.skipObjectStart()
556+
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
562557
while true {
563558
if scanner.skipOptionalObjectEnd(terminator) {
564559
if let keyField = keyField, let valueField = valueField {
@@ -568,14 +563,20 @@ internal struct TextFormatDecoder: Decoder {
568563
throw TextFormatDecodingError.malformedText
569564
}
570565
}
571-
if let key = try scanner.nextKey() {
566+
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
572567
switch key {
573568
case "key", "1":
574569
try KeyType.decodeSingular(value: &keyField, from: &self)
575570
case "value", "2":
576571
try ValueType.decodeSingular(value: &valueField, from: &self)
577572
default:
578-
throw TextFormatDecodingError.unknownField
573+
if ignoreExtensionFields && key.hasPrefix("[") {
574+
try scanner.skipUnknownFieldValue()
575+
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
576+
try scanner.skipUnknownFieldValue()
577+
} else {
578+
throw TextFormatDecodingError.unknownField
579+
}
579580
}
580581
scanner.skipOptionalSeparator()
581582
} else {
@@ -608,6 +609,7 @@ internal struct TextFormatDecoder: Decoder {
608609
var keyField: KeyType.BaseType?
609610
var valueField: ValueType?
610611
let terminator = try scanner.skipObjectStart()
612+
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
611613
while true {
612614
if scanner.skipOptionalObjectEnd(terminator) {
613615
if let keyField = keyField, let valueField = valueField {
@@ -617,14 +619,20 @@ internal struct TextFormatDecoder: Decoder {
617619
throw TextFormatDecodingError.malformedText
618620
}
619621
}
620-
if let key = try scanner.nextKey() {
622+
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
621623
switch key {
622624
case "key", "1":
623625
try KeyType.decodeSingular(value: &keyField, from: &self)
624626
case "value", "2":
625627
try decodeSingularEnumField(value: &valueField)
626628
default:
627-
throw TextFormatDecodingError.unknownField
629+
if ignoreExtensionFields && key.hasPrefix("[") {
630+
try scanner.skipUnknownFieldValue()
631+
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
632+
try scanner.skipUnknownFieldValue()
633+
} else {
634+
throw TextFormatDecodingError.unknownField
635+
}
628636
}
629637
scanner.skipOptionalSeparator()
630638
} else {
@@ -657,6 +665,7 @@ internal struct TextFormatDecoder: Decoder {
657665
var keyField: KeyType.BaseType?
658666
var valueField: ValueType?
659667
let terminator = try scanner.skipObjectStart()
668+
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
660669
while true {
661670
if scanner.skipOptionalObjectEnd(terminator) {
662671
if let keyField = keyField, let valueField = valueField {
@@ -666,14 +675,20 @@ internal struct TextFormatDecoder: Decoder {
666675
throw TextFormatDecodingError.malformedText
667676
}
668677
}
669-
if let key = try scanner.nextKey() {
678+
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
670679
switch key {
671680
case "key", "1":
672681
try KeyType.decodeSingular(value: &keyField, from: &self)
673682
case "value", "2":
674683
try decodeSingularMessageField(value: &valueField)
675684
default:
676-
throw TextFormatDecodingError.unknownField
685+
if ignoreExtensionFields && key.hasPrefix("[") {
686+
try scanner.skipUnknownFieldValue()
687+
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
688+
try scanner.skipUnknownFieldValue()
689+
} else {
690+
throw TextFormatDecodingError.unknownField
691+
}
677692
}
678693
scanner.skipOptionalSeparator()
679694
} else {

Sources/SwiftProtobuf/TextFormatDecodingOptions.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,19 @@ public struct TextFormatDecodingOptions: Sendable {
2121
/// while parsing.
2222
public var messageDepthLimit: Int = 100
2323

24+
/// If unknown fields in the TextFormat should be ignored. If they aren't
25+
/// ignored, an error will be raised if one is encountered.
26+
///
27+
/// Note: This is a lossy option, enabling it means part of the TextFormat
28+
/// is silently skipped.
29+
public var ignoreUnknownFields: Bool = false
30+
31+
/// If unknown extension fields in the TextFormat should be ignored. If they
32+
/// aren't ignored, an error will be raised if one is encountered.
33+
///
34+
/// Note: This is a lossy option, enabling it means part of the TextFormat
35+
/// is silently skipped.
36+
public var ignoreUnknownExtensionFields: Bool = false
37+
2438
public init() {}
2539
}

0 commit comments

Comments
 (0)