Skip to content

Commit c743d97

Browse files
committed
Fix tests
1 parent aad373f commit c743d97

File tree

2 files changed

+207
-23
lines changed

2 files changed

+207
-23
lines changed

Sources/FoundationEssentials/Data/Data+Base64.swift

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -576,10 +576,7 @@ extension Base64 {
576576
length: inout Int,
577577
options: Data.Base64DecodingOptions
578578
) throws(DecodingError) {
579-
let remaining = inBuffer.count % 4
580-
if !options.contains(.ignoreUnknownCharacters) {
581-
guard remaining == 0 else { throw DecodingError.invalidLength }
582-
}
579+
assert(options.contains(.ignoreUnknownCharacters))
583580

584581
let outputLength = ((inBuffer.count + 3) / 4) * 3
585582
guard outBuffer.count >= outputLength else {
@@ -590,7 +587,7 @@ extension Base64 {
590587
var outIndex = 0
591588
var inIndex = 0
592589

593-
while inIndex + 3 < inBuffer.count {
590+
fastLoop: while inIndex + 3 < inBuffer.count {
594591
let a0 = inBuffer[inIndex]
595592
let a1 = inBuffer[inIndex &+ 1]
596593
let a2 = inBuffer[inIndex &+ 2]
@@ -602,11 +599,6 @@ extension Base64 {
602599
break // the loop
603600
}
604601

605-
guard options.contains(.ignoreUnknownCharacters) else {
606-
// TODO: Inspect characters here better
607-
throw DecodingError.invalidCharacter(inBuffer[inIndex])
608-
}
609-
610602
// error fast path. we assume that illeagal errors are at the boundary.
611603
// lets skip them and then return to fast mode!
612604
if !self.isValidBase64Byte(a0, options: options) {
@@ -628,10 +620,45 @@ extension Base64 {
628620
continue
629621
}
630622
}
631-
fatalError()
632-
}
633623

634-
inIndex &+= 4
624+
// error slow path... the first character is valid base64
625+
let b0 = a0
626+
var b1: UInt8? = nil
627+
var b2: UInt8? = nil
628+
var b3: UInt8? = nil
629+
let startIndex = inIndex
630+
inIndex &+= 1
631+
scanForValidCharacters: while inIndex < inBuffer.count {
632+
guard self.isValidBase64Byte(inBuffer[inIndex], options: options) else {
633+
if inBuffer[inIndex] == Self.encodePaddingCharacter {
634+
inIndex = startIndex
635+
break fastLoop
636+
}
637+
inIndex &+= 1
638+
continue scanForValidCharacters
639+
}
640+
641+
defer { inIndex &+= 1 }
642+
643+
if b1 == nil {
644+
b1 = inBuffer[inIndex]
645+
} else if b2 == nil {
646+
b2 = inBuffer[inIndex]
647+
} else if b3 == nil {
648+
b3 = inBuffer[inIndex]
649+
break scanForValidCharacters
650+
}
651+
}
652+
653+
guard let b1, let b2, let b3 else {
654+
throw DecodingError.invalidLength
655+
}
656+
657+
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
658+
659+
} else {
660+
inIndex &+= 4
661+
}
635662

636663
withUnsafePointer(to: &x) { ptr in
637664
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
@@ -649,34 +676,101 @@ extension Base64 {
649676
return
650677
}
651678

652-
// TODO: check we have at least two more characters, or they are all bs
679+
guard inIndex + 3 < inBuffer.count else {
680+
if options.contains(.ignoreUnknownCharacters) {
681+
// ensure that all remaining characters are unknown
682+
while inIndex < inBuffer.count {
683+
let value = inBuffer[inIndex]
684+
if self.isValidBase64Byte(value, options: options) || value == Self.encodePaddingCharacter {
685+
throw DecodingError.invalidCharacter(inBuffer[inIndex])
686+
}
687+
inIndex &+= 1
688+
}
689+
length = outIndex
690+
return
691+
}
692+
throw DecodingError.invalidLength
693+
}
653694

654695
let a0 = inBuffer[inIndex]
655696
let a1 = inBuffer[inIndex + 1]
656-
var a2: UInt8?
657-
var a3: UInt8?
658-
if inIndex + 2 < inBuffer.count, inBuffer[inIndex + 2] != Self.encodePaddingCharacter {
697+
var a2: UInt8 = 65
698+
var a3: UInt8 = 65
699+
var padding2 = false
700+
var padding3 = false
701+
702+
if inBuffer[inIndex + 2] == Self.encodePaddingCharacter {
703+
padding2 = true
704+
} else {
659705
a2 = inBuffer[inIndex + 2]
660706
}
661-
if inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter {
707+
if inBuffer[inIndex + 3] == Self.encodePaddingCharacter {
708+
padding3 = true
709+
} else {
710+
if padding2 && self.isValidBase64Byte(inBuffer[inIndex + 3], options: options) {
711+
throw DecodingError.unexpectedPaddingCharacter
712+
}
662713
a3 = inBuffer[inIndex + 3]
663714
}
664715

665-
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2 ?? 65)] | d3[Int(a3 ?? 65)]
716+
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)]
666717
if x >= Self.badCharacter {
667-
// TODO: Inspect characters here better
668-
throw DecodingError.invalidCharacter(inBuffer[inIndex])
718+
var b0: UInt8? = nil
719+
var b1: UInt8? = nil
720+
var b2: UInt8? = nil
721+
var b3: UInt8? = nil
722+
723+
scanForValidCharacters: while inIndex < inBuffer.count {
724+
defer { inIndex &+= 1 }
725+
let value = inBuffer[inIndex]
726+
if self.isValidBase64Byte(value, options: options) {
727+
if b0 == nil {
728+
b0 = value
729+
} else if b1 == nil {
730+
b1 = value
731+
} else if b2 == nil {
732+
b2 = value
733+
} else if b3 == nil {
734+
if padding2 { throw DecodingError.unexpectedPaddingCharacter }
735+
b3 = value
736+
break scanForValidCharacters
737+
}
738+
} else if value == Self.encodePaddingCharacter {
739+
guard b0 != nil, b1 != nil else {
740+
throw DecodingError.invalidLength
741+
}
742+
if b2 == nil {
743+
padding2 = true
744+
b2 = 65
745+
} else if b3 == nil {
746+
padding3 = true
747+
b3 = 65
748+
break scanForValidCharacters
749+
}
750+
}
751+
}
752+
753+
guard let b0, let b1, let b2, let b3 else {
754+
if b0 == nil {
755+
length = outIndex
756+
return
757+
}
758+
throw DecodingError.invalidLength
759+
}
760+
761+
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
762+
assert(x < Self.badCharacter)
669763
}
670764

671765
withUnsafePointer(to: &x) { ptr in
672766
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
673767
outBuffer[outIndex] = newPtr[0]
674768
outIndex += 1
675-
if a2 != nil {
769+
if !padding2 {
676770
outBuffer[outIndex] = newPtr[1]
677771
outIndex += 1
678772
}
679-
if a3 != nil {
773+
if !padding3 {
680774
outBuffer[outIndex] = newPtr[2]
681775
outIndex += 1
682776
}

Tests/FoundationEssentialsTests/DataTests.swift

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,36 @@ extension DataTests {
19561956
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
19571957
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
19581958

1959+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
1960+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
1961+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
1962+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
1963+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
1964+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
1965+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
1966+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
1967+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
1968+
1969+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
1970+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
1971+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
1972+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
1973+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
1974+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
1975+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
1976+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
1977+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
1978+
1979+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
1980+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
1981+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
1982+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
1983+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
1984+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
1985+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
1986+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
1987+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
1988+
19591989
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
19601990
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
19611991
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
@@ -1966,6 +1996,36 @@ extension DataTests {
19661996
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
19671997
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
19681998

1999+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2000+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2001+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2002+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2003+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2004+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2005+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2006+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2007+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2008+
2009+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2010+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2011+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2012+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2013+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2014+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2015+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2016+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2017+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2018+
2019+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2020+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2021+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2022+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2023+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2024+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2025+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2026+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2027+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2028+
19692029
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
19702030
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
19712031
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
@@ -1975,6 +2035,36 @@ extension DataTests {
19752035
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
19762036
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
19772037
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2038+
2039+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2040+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2041+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2042+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2043+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2044+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2045+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2046+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2047+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2048+
2049+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2050+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2051+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2052+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2053+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2054+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2055+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2056+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2057+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2058+
2059+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2060+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2061+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2062+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2063+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2064+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2065+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2066+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2067+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
19782068
}
19792069

19802070
func test_base64Decode_test1MBDataGoing0to255OverAndOver() {

0 commit comments

Comments
 (0)