Skip to content

Commit 20b9a35

Browse files
committed
Fix tests
1 parent 58acf85 commit 20b9a35

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
@@ -587,10 +587,7 @@ extension Base64 {
587587
length: inout Int,
588588
options: Data.Base64DecodingOptions
589589
) throws(DecodingError) {
590-
let remaining = inBuffer.count % 4
591-
if !options.contains(.ignoreUnknownCharacters) {
592-
guard remaining == 0 else { throw DecodingError.invalidLength }
593-
}
590+
assert(options.contains(.ignoreUnknownCharacters))
594591

595592
let outputLength = ((inBuffer.count + 3) / 4) * 3
596593
guard outBuffer.count >= outputLength else {
@@ -601,7 +598,7 @@ extension Base64 {
601598
var outIndex = 0
602599
var inIndex = 0
603600

604-
while inIndex + 3 < inBuffer.count {
601+
fastLoop: while inIndex + 3 < inBuffer.count {
605602
let a0 = inBuffer[inIndex]
606603
let a1 = inBuffer[inIndex &+ 1]
607604
let a2 = inBuffer[inIndex &+ 2]
@@ -613,11 +610,6 @@ extension Base64 {
613610
break // the loop
614611
}
615612

616-
guard options.contains(.ignoreUnknownCharacters) else {
617-
// TODO: Inspect characters here better
618-
throw DecodingError.invalidCharacter(inBuffer[inIndex])
619-
}
620-
621613
// error fast path. we assume that illeagal errors are at the boundary.
622614
// lets skip them and then return to fast mode!
623615
if !self.isValidBase64Byte(a0, options: options) {
@@ -639,10 +631,45 @@ extension Base64 {
639631
continue
640632
}
641633
}
642-
fatalError()
643-
}
644634

645-
inIndex &+= 4
635+
// error slow path... the first character is valid base64
636+
let b0 = a0
637+
var b1: UInt8? = nil
638+
var b2: UInt8? = nil
639+
var b3: UInt8? = nil
640+
let startIndex = inIndex
641+
inIndex &+= 1
642+
scanForValidCharacters: while inIndex < inBuffer.count {
643+
guard self.isValidBase64Byte(inBuffer[inIndex], options: options) else {
644+
if inBuffer[inIndex] == Self.encodePaddingCharacter {
645+
inIndex = startIndex
646+
break fastLoop
647+
}
648+
inIndex &+= 1
649+
continue scanForValidCharacters
650+
}
651+
652+
defer { inIndex &+= 1 }
653+
654+
if b1 == nil {
655+
b1 = inBuffer[inIndex]
656+
} else if b2 == nil {
657+
b2 = inBuffer[inIndex]
658+
} else if b3 == nil {
659+
b3 = inBuffer[inIndex]
660+
break scanForValidCharacters
661+
}
662+
}
663+
664+
guard let b1, let b2, let b3 else {
665+
throw DecodingError.invalidLength
666+
}
667+
668+
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
669+
670+
} else {
671+
inIndex &+= 4
672+
}
646673

647674
withUnsafePointer(to: &x) { ptr in
648675
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
@@ -660,34 +687,101 @@ extension Base64 {
660687
return
661688
}
662689

663-
// TODO: check we have at least two more characters, or they are all bs
690+
guard inIndex + 3 < inBuffer.count else {
691+
if options.contains(.ignoreUnknownCharacters) {
692+
// ensure that all remaining characters are unknown
693+
while inIndex < inBuffer.count {
694+
let value = inBuffer[inIndex]
695+
if self.isValidBase64Byte(value, options: options) || value == Self.encodePaddingCharacter {
696+
throw DecodingError.invalidCharacter(inBuffer[inIndex])
697+
}
698+
inIndex &+= 1
699+
}
700+
length = outIndex
701+
return
702+
}
703+
throw DecodingError.invalidLength
704+
}
664705

665706
let a0 = inBuffer[inIndex]
666707
let a1 = inBuffer[inIndex + 1]
667-
var a2: UInt8?
668-
var a3: UInt8?
669-
if inIndex + 2 < inBuffer.count, inBuffer[inIndex + 2] != Self.encodePaddingCharacter {
708+
var a2: UInt8 = 65
709+
var a3: UInt8 = 65
710+
var padding2 = false
711+
var padding3 = false
712+
713+
if inBuffer[inIndex + 2] == Self.encodePaddingCharacter {
714+
padding2 = true
715+
} else {
670716
a2 = inBuffer[inIndex + 2]
671717
}
672-
if inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter {
718+
if inBuffer[inIndex + 3] == Self.encodePaddingCharacter {
719+
padding3 = true
720+
} else {
721+
if padding2 && self.isValidBase64Byte(inBuffer[inIndex + 3], options: options) {
722+
throw DecodingError.unexpectedPaddingCharacter
723+
}
673724
a3 = inBuffer[inIndex + 3]
674725
}
675726

676-
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2 ?? 65)] | d3[Int(a3 ?? 65)]
727+
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)]
677728
if x >= Self.badCharacter {
678-
// TODO: Inspect characters here better
679-
throw DecodingError.invalidCharacter(inBuffer[inIndex])
729+
var b0: UInt8? = nil
730+
var b1: UInt8? = nil
731+
var b2: UInt8? = nil
732+
var b3: UInt8? = nil
733+
734+
scanForValidCharacters: while inIndex < inBuffer.count {
735+
defer { inIndex &+= 1 }
736+
let value = inBuffer[inIndex]
737+
if self.isValidBase64Byte(value, options: options) {
738+
if b0 == nil {
739+
b0 = value
740+
} else if b1 == nil {
741+
b1 = value
742+
} else if b2 == nil {
743+
b2 = value
744+
} else if b3 == nil {
745+
if padding2 { throw DecodingError.unexpectedPaddingCharacter }
746+
b3 = value
747+
break scanForValidCharacters
748+
}
749+
} else if value == Self.encodePaddingCharacter {
750+
guard b0 != nil, b1 != nil else {
751+
throw DecodingError.invalidLength
752+
}
753+
if b2 == nil {
754+
padding2 = true
755+
b2 = 65
756+
} else if b3 == nil {
757+
padding3 = true
758+
b3 = 65
759+
break scanForValidCharacters
760+
}
761+
}
762+
}
763+
764+
guard let b0, let b1, let b2, let b3 else {
765+
if b0 == nil {
766+
length = outIndex
767+
return
768+
}
769+
throw DecodingError.invalidLength
770+
}
771+
772+
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
773+
assert(x < Self.badCharacter)
680774
}
681775

682776
withUnsafePointer(to: &x) { ptr in
683777
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
684778
outBuffer[outIndex] = newPtr[0]
685779
outIndex += 1
686-
if a2 != nil {
780+
if !padding2 {
687781
outBuffer[outIndex] = newPtr[1]
688782
outIndex += 1
689783
}
690-
if a3 != nil {
784+
if !padding3 {
691785
outBuffer[outIndex] = newPtr[2]
692786
outIndex += 1
693787
}

Tests/FoundationEssentialsTests/DataTests.swift

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

1989+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
1990+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
1991+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
1992+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
1993+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
1994+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
1995+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
1996+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
1997+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
1998+
1999+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
2000+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
2001+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
2002+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
2003+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
2004+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
2005+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
2006+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
2007+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
2008+
2009+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
2010+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
2011+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
2012+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
2013+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
2014+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
2015+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
2016+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
2017+
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
2018+
19892019
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
19902020
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
19912021
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
@@ -1996,6 +2026,36 @@ extension DataTests {
19962026
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
19972027
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
19982028

2029+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2030+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2031+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2032+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2033+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2034+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2035+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2036+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2037+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2038+
2039+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2040+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2041+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2042+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2043+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2044+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2045+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2046+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2047+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2048+
2049+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
2050+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
2051+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
2052+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
2053+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
2054+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
2055+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
2056+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
2057+
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
2058+
19992059
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
20002060
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
20012061
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
@@ -2005,6 +2065,36 @@ extension DataTests {
20052065
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
20062066
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
20072067
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2068+
2069+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2070+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2071+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2072+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2073+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2074+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2075+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2076+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2077+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2078+
2079+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2080+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2081+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2082+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2083+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2084+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2085+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2086+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2087+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
2088+
2089+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
2090+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
2091+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
2092+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
2093+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
2094+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
2095+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
2096+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
2097+
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
20082098
}
20092099

20102100
func test_base64Decode_test1MBDataGoing0to255OverAndOver() {

0 commit comments

Comments
 (0)