@@ -35,6 +35,7 @@ private let asciiSingleQuote = UInt8(ascii: "\'")
3535private let asciiBackslash = UInt8 ( ascii: " \\ " )
3636private let asciiForwardSlash = UInt8 ( ascii: " / " )
3737private let asciiHash = UInt8 ( ascii: " # " )
38+ private let asciiEqualSign = UInt8 ( ascii: " = " )
3839private let asciiUnderscore = UInt8 ( ascii: " _ " )
3940private let asciiQuestionMark = UInt8 ( ascii: " ? " )
4041private let asciiSpace = UInt8 ( ascii: " " )
@@ -79,14 +80,15 @@ private func fromHexDigit(_ c: UnicodeScalar) -> UInt32? {
7980 }
8081}
8182
82- // Decode the RFC 4648 section 4 Base 64 encoding.
83+ // Decode both the RFC 4648 section 4 Base 64 encoding and the
84+ // RFC 4648 section 5 Base 64 variant.
8385let base64Values : [ Int ] = [
8486/* 0x00 */ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
8587/* 0x10 */ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
86- /* 0x20 */ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , 62 , - 1 , - 1 , - 1 , 63 ,
88+ /* 0x20 */ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , 62 , - 1 , 62 , - 1 , 63 ,
8789/* 0x30 */ 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
8890/* 0x40 */ - 1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
89- /* 0x50 */ 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , - 1 , - 1 , - 1 , - 1 , - 1 ,
91+ /* 0x50 */ 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , - 1 , - 1 , - 1 , - 1 , 63 ,
9092/* 0x60 */ - 1 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 ,
9193/* 0x70 */ 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , - 1 , - 1 , - 1 , - 1 , - 1 ,
9294/* 0x80 */ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
@@ -107,12 +109,9 @@ let base64Values: [Int] = [
107109/// mixed in with the base-64 characters
108110/// * Google's C++ implementation ignores missing '=' characters
109111/// but if present, there must be the exact correct number of them.
112+ /// * The conformance test requires us to accept both standard RFC4648
113+ /// Base 64 encoding and the "URL and Filename Safe Alphabet" variant.
110114///
111- /// Note: Google's C++ code seems to allow many base-64 extensions
112- /// (including websafe and '.' as padding), but the Java version just
113- /// uses uses Guava's BaseEncoding.base64() (which only supports the
114- /// RFC4648 standard encoding) and the conformance test explicitly
115- /// requires us to reject '-' and '_' characters.
116115private func parseBytes(
117116 source: UnsafeBufferPointer < UInt8 > ,
118117 index: inout UnsafeBufferPointer < UInt8 > . Index ,
@@ -125,12 +124,20 @@ private func parseBytes(
125124 source. formIndex ( after: & index)
126125
127126 // Count the base-64 digits
127+ // Ignore unrecognized characters in this first pass,
128+ // stop at the closing double quote.
128129 let digitsStart = index
129130 var rawChars = 0
131+ var sawSection4Characters = false
132+ var sawSection5Characters = false
130133 while index != end {
131134 let digit = source [ index]
132135 if digit == asciiDoubleQuote {
133136 break
137+ } else if digit == asciiPlus || digit == asciiForwardSlash {
138+ sawSection4Characters = true
139+ } else if digit == asciiMinus || digit == asciiUnderscore {
140+ sawSection5Characters = true
134141 }
135142 let k = base64Values [ Int ( digit) ]
136143 if k >= 0 {
@@ -143,11 +150,19 @@ private func parseBytes(
143150 if index == end {
144151 throw JSONDecodingError . malformedString
145152 }
153+ // Reject mixed encodings.
154+ if sawSection4Characters && sawSection5Characters {
155+ throw JSONDecodingError . malformedString
156+ }
146157
147158 // Allocate a Data object of exactly the right size
148159 var value = Data ( count: rawChars * 3 / 4 )
149160
150- // Scan the digits again and populate the Data object
161+ // Scan the digits again and populate the Data object.
162+ // In this pass, we check for (and fail) if there are
163+ // unexpected characters. But we don't check for end-of-input,
164+ // because the loop above already verified that there was
165+ // a closing double quote.
151166 index = digitsStart
152167 try value. withUnsafeMutableBytes {
153168 ( dataPointer: UnsafeMutablePointer < UInt8 > ) in
@@ -177,7 +192,7 @@ private func parseBytes(
177192 break digits
178193 case asciiSpace:
179194 break
180- case 61 : // Count padding
195+ case asciiEqualSign : // Count padding
181196 while true {
182197 switch source [ index] {
183198 case asciiDoubleQuote:
@@ -202,12 +217,12 @@ private func parseBytes(
202217 case 3 :
203218 p [ 0 ] = UInt8 ( extendingOrTruncating: n >> 10 )
204219 p [ 1 ] = UInt8 ( extendingOrTruncating: n >> 2 )
205- if padding == 1 {
220+ if padding == 1 || padding == 0 {
206221 return
207222 }
208223 case 2 :
209224 p [ 0 ] = UInt8 ( extendingOrTruncating: n >> 4 )
210- if padding == 2 {
225+ if padding == 2 || padding == 0 {
211226 return
212227 }
213228 case 0 :
0 commit comments