Skip to content

Commit 8a7adf2

Browse files
authored
New XML PropertyListDecoder parser accepts malformed tags (#1180)
1 parent 47e587a commit 8a7adf2

File tree

2 files changed

+46
-23
lines changed

2 files changed

+46
-23
lines changed

Sources/FoundationEssentials/PropertyList/XMLPlistScanner.swift

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -908,68 +908,80 @@ internal struct XMLPlistScanner {
908908
}
909909
}
910910

911-
func determineTag() throws -> XMLPlistTag? {
911+
mutating func readTag() throws -> XMLPlistTag? {
912912
let marker = reader.readIndex
913-
switch reader.char(at: marker) {
913+
var tag: XMLPlistTag?
914+
switch reader.peek() {
914915
case UInt8(ascii: "a"): // Array
915916
if matches(tag: .array, at: marker, until: reader.endIndex) {
916-
return .array
917+
tag = .array
917918
}
918919
case UInt8(ascii: "d"): // Dictionary, data, or date
919920
if matches(tag: .dict, at: marker, until: reader.endIndex) {
920-
return .dict
921+
tag = .dict
921922
} else if matches(tag: .data, at: marker, until: reader.endIndex) {
922-
return .data
923+
tag = .data
923924
} else if matches(tag: .date, at: marker, until: reader.endIndex) {
924-
return .date
925+
tag = .date
925926
}
926927
case UInt8(ascii: "f"): // false (boolean)
927928
if matches(tag: .false, at: marker, until: reader.endIndex) {
928-
return .false
929+
tag = .false
929930
}
930931
case UInt8(ascii: "i"): // integer
931932
if matches(tag: .integer, at: marker, until: reader.endIndex) {
932-
return .integer
933+
tag = .integer
933934
}
934935
case UInt8(ascii: "k"): // Key of a dictionary
935936
if matches(tag: .key, at: marker, until: reader.endIndex) {
936-
return .key
937+
tag = .key
937938
}
938939
case UInt8(ascii: "p"): // Plist
939940
if matches(tag: .plist, at: marker, until: reader.endIndex) {
940-
return .plist
941+
tag = .plist
941942
}
942943
case UInt8(ascii: "r"): // real
943944
if matches(tag: .real, at: marker, until: reader.endIndex) {
944-
return .real
945+
tag = .real
945946
}
946947
case UInt8(ascii: "s"): // String
947948
if matches(tag: .string, at: marker, until: reader.endIndex) {
948-
return .string
949+
tag = .string
949950
}
950951
case UInt8(ascii: "t"): // true (boolean)
951952
if matches(tag: .true, at: marker, until: reader.endIndex) {
952-
return .true
953+
tag = .true
953954
}
954-
case ._space, ._tab, ._newline, ._return, ._closeangle:
955-
throw XMLPlistError.malformedTag(line: reader.lineNumber)
956955
default:
957956
break
958957
}
959-
return nil
958+
959+
guard let tag else {
960+
return nil
961+
}
962+
963+
// Tag names must be delimited either by whitespace or a '>' or `/` character to be valid.
964+
reader.advance(tag.tagLength)
965+
switch reader.peek() {
966+
case ._closeangle, ._forwardslash, ._space, ._tab, ._newline, ._return:
967+
return tag
968+
default:
969+
return nil
970+
}
960971
}
961972

962973
mutating func peekXMLElement() throws -> (XMLPlistTag, isEmpty: Bool) {
963-
guard let tag = try determineTag() else {
964-
let badTagStart = reader.readIndex
965-
while let ch = reader.read(), ch != ._closeangle { }
966-
let badTagEnd = reader.readIndex
967-
let markerStr = String._tryFromUTF8(reader.fullBuffer[badTagStart..<badTagEnd]) ?? "<unparseable>"
974+
let tagStart = reader.readIndex
975+
guard let tag = try readTag() else {
976+
var idx = reader.readIndex
977+
while idx < reader.endIndex, reader.char(at: idx) != ._closeangle {
978+
reader.advance(&idx)
979+
}
980+
let markerStr = String._tryFromUTF8(reader.fullBuffer[tagStart..<idx]) ?? "<unparseable>"
968981
throw XMLPlistError.other("Encountered unknown tag \(markerStr) on line \(reader.lineNumber)")
969982
}
970983

971-
reader.advance(tag.tagLength)
972-
984+
// Skip past any characters (whitespace or attributes) that may follow a valid tag name.
973985
while let ch = reader.read(), ch != ._closeangle { }
974986
if reader.isAtEnd {
975987
throw XMLPlistError.malformedTag(line: reader.lineNumber)

Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,17 @@ data1 = <7465
15451545
}
15461546

15471547
}
1548+
1549+
func test_garbageCharactersAfterXMLTagName() throws {
1550+
let garbage = "<plist><dict><key>bar</key><stringGARBAGE>foo</string></dict></plist>".data(using: .utf8)!
1551+
1552+
XCTAssertThrowsError(try PropertyListDecoder().decode([String:String].self, from: garbage))
1553+
1554+
// Historical behavior allows for whitespace to immediately follow tag names
1555+
let acceptable = "<plist><dict><key>bar</key><string >foo</string></dict></plist>".data(using: .utf8)!
1556+
1557+
XCTAssertEqual(try PropertyListDecoder().decode([String:String].self, from: acceptable), ["bar":"foo"])
1558+
}
15481559
}
15491560

15501561

0 commit comments

Comments
 (0)