Skip to content

Commit

Permalink
TextFormat decoding options to skip unknown fields/extensions.
Browse files Browse the repository at this point in the history
Modeled after the upstream C++, provide two new decoding options to skip
unknown fields while decoding TextFormat.
  • Loading branch information
thomasvl committed Apr 29, 2024
1 parent 1881999 commit b73920e
Show file tree
Hide file tree
Showing 8 changed files with 1,100 additions and 68 deletions.
14 changes: 13 additions & 1 deletion Sources/SwiftProtobuf/DoubleParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import Foundation
internal class DoubleParser {
// Temporary buffer so we can null-terminate the UTF-8 string
// before calling the C standard library to parse it.
//
// In theory, JSON writers should be able to represent any IEEE Double
// in at most 25 bytes, but many writers will emit more digits than
// necessary, so we size this generously.
// necessary, so we size this generously; but we could still fail to
// parse if someone crafts something really long (especially for
// TextFormat due to overflows (see below)).
private var work =
UnsafeMutableBufferPointer<Int8>.allocate(capacity: 128)

Expand Down Expand Up @@ -49,6 +52,15 @@ internal class DoubleParser {

// Fail if strtod() did not consume everything we expected
// or if strtod() thought the number was out of range.
//
// NOTE: TextFormat specifically calls out handling for overflows
// for float/double fields:
// https://protobuf.dev/reference/protobuf/textformat-spec/#value
//
// > Overflows are treated as infinity or -infinity.
//
// But the JSON protobuf spec doesn't mention anything:
// https://protobuf.dev/programming-guides/proto3/#json
if e != work.baseAddress! + bytes.count || !d.isFinite {
return nil
}
Expand Down
55 changes: 35 additions & 20 deletions Sources/SwiftProtobuf/TextFormatDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ internal struct TextFormatDecoder: Decoder {
private var fieldNameMap: _NameMap?
private var messageType: any Message.Type

internal var options: TextFormatDecodingOptions {
return scanner.options
}

internal var complete: Bool {
mutating get {
return scanner.complete
Expand Down Expand Up @@ -63,27 +67,17 @@ internal struct TextFormatDecoder: Decoder {
}

mutating func nextFieldNumber() throws -> Int? {
// Per https://protobuf.dev/reference/protobuf/textformat-spec/#fields, every field can be
// followed by a field separator, so if we've seen a field, remove the separator before
// checking for the terminator.
if fieldCount > 0 {
scanner.skipOptionalSeparator()
}
if let terminator = terminator,
scanner.skipOptionalObjectEnd(terminator) {
return nil
}
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!, messageType: messageType) {
if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!,
messageType: messageType,
terminator: terminator) {
fieldCount += 1
return fieldNumber
} else if terminator == nil {
return nil
} else {
// If this decoder is looking for at a terminator, then if the scanner failed to
// find a field number, something went wrong (end of stream).
throw TextFormatDecodingError.truncated
return nil
}

}

mutating func decodeSingularFloatField(value: inout Float) throws {
Expand Down Expand Up @@ -559,6 +553,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType.BaseType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -568,14 +563,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try ValueType.decodeSingular(value: &valueField, from: &self)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down Expand Up @@ -608,6 +609,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -617,14 +619,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularEnumField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down Expand Up @@ -657,6 +665,7 @@ internal struct TextFormatDecoder: Decoder {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
let ignoreExtensionFields = options.ignoreUnknownExtensionFields
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
Expand All @@ -666,14 +675,20 @@ internal struct TextFormatDecoder: Decoder {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
if let key = try scanner.nextKey(allowExtensions: ignoreExtensionFields) {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularMessageField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
if ignoreExtensionFields && key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else if options.ignoreUnknownFields && !key.hasPrefix("[") {
try scanner.skipUnknownFieldValue()
} else {
throw TextFormatDecodingError.unknownField
}
}
scanner.skipOptionalSeparator()
} else {
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftProtobuf/TextFormatDecodingOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,19 @@ public struct TextFormatDecodingOptions: Sendable {
/// while parsing.
public var messageDepthLimit: Int = 100

/// If unknown fields in the TextFormat should be ignored. If they aren't
/// ignored, an error will be raised if one is encountered.
///
/// Note: This is a lossy option, enabling it means part of the TextFormat
/// is silently skipped.
public var ignoreUnknownFields: Bool = false

/// If unknown extension fields in the TextFormat should be ignored. If they
/// aren't ignored, an error will be raised if one is encountered.
///
/// Note: This is a lossy option, enabling it means part of the TextFormat
/// is silently skipped.
public var ignoreUnknownExtensionFields: Bool = false

public init() {}
}
Loading

0 comments on commit b73920e

Please sign in to comment.