diff --git a/NOTICE.txt b/NOTICE.txt index 7ec6b52565..ee54bdf41d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -77,3 +77,12 @@ This product contains "ifaddrs-android.c" and "ifaddrs-android.h" from dxr.mozil * https://webrtc.googlesource.com/src/+/master/LICENSE * HOMEPAGE: * https://webrtc.googlesource.com/ + +--- + +This product contains a derivation of Fabian Fett's 'Base64.swift'. + + * LICENSE (Apache License 2.0): + * https://github.com/fabianfett/swift-base64-kit/blob/master/LICENSE + * HOMEPAGE: + * https://github.com/fabianfett/swift-base64-kit diff --git a/Sources/NIOWebSocket/Base64.swift b/Sources/NIOWebSocket/Base64.swift index f72e1cb9a0..b1d3ff9fb1 100644 --- a/Sources/NIOWebSocket/Base64.swift +++ b/Sources/NIOWebSocket/Base64.swift @@ -12,75 +12,112 @@ // //===----------------------------------------------------------------------===// -// The base64 unicode table. -private let base64Table: [Unicode.Scalar] = [ - "A", "B", "C", "D", "E", "F", "G", "H", - "I", "J", "K", "L", "M", "N", "O", "P", - "Q", "R", "S", "T", "U", "V", "W", "X", - "Y", "Z", "a", "b", "c", "d", "e", "f", - "g", "h", "i", "j", "k", "l", "m", "n", - "o", "p", "q", "r", "s", "t", "u", "v", - "w", "x", "y", "z", "0", "1", "2", "3", - "4", "5", "6", "7", "8", "9", "+", "/", -] +// This is a simplified vendored version from: +// https://github.com/fabianfett/swift-base64-kit extension String { - /// Base64 encode an array of UInt8 to a string, without the use of Foundation. - /// - /// This function performs the world's most naive Base64 encoding: no attempts to use a larger - /// lookup table or anything intelligent like that, just shifts and masks. This works fine, for - /// now: the purpose of this encoding is to avoid round-tripping through Data, and the perf gain - /// from avoiding that is more than enough to outweigh the silliness of this code. - init(base64Encoding array: Array) { - // In Base64, 3 bytes become 4 output characters, and we pad to the nearest multiple - // of four. - var outputString = String() - outputString.reserveCapacity(((array.count + 2) / 3) * 4) - var bytes = array.makeIterator() - while let firstByte = bytes.next() { - let secondByte = bytes.next() - let thirdByte = bytes.next() - outputString.unicodeScalars.append(String.encode(firstByte: firstByte)) - outputString.unicodeScalars.append(String.encode(firstByte: firstByte, secondByte: secondByte)) - outputString.unicodeScalars.append(String.encode(secondByte: secondByte, thirdByte: thirdByte)) - outputString.unicodeScalars.append(String.encode(thirdByte: thirdByte)) - } + /// Base64 encode a collection of UInt8 to a string, without the use of Foundation. + init(base64Encoding bytes: Buffer) + where Buffer.Element == UInt8 + { + self = Base64.encode(bytes: bytes) + } +} - self = outputString - } - private static func encode(firstByte: UInt8) -> Unicode.Scalar { - let index = firstByte >> 2 - return base64Table[Int(index)] +fileprivate struct Base64 { + + static func encode(bytes: Buffer) + -> String where Buffer.Element == UInt8 + { + // In Base64, 3 bytes become 4 output characters, and we pad to the + // nearest multiple of four. + let newCapacity = ((bytes.count + 2) / 3) * 4 + let alphabet = Base64.encodeBase64 + + // NOTE: Once SE-263 lands we should replace this implementation with one + // that makes use of the non copy initializer. For more information + // please see: + // https://github.com/apple/swift-evolution/blob/master/proposals/0263-string-uninitialized-initializer.md + var outputBytes = [UInt8]() + outputBytes.reserveCapacity(newCapacity) + + var input = bytes.makeIterator() + + while let firstByte = input.next() { + let secondByte = input.next() + let thirdByte = input.next() + + let firstChar = Base64.encode(alphabet: alphabet, firstByte: firstByte) + let secondChar = Base64.encode(alphabet: alphabet, firstByte: firstByte, secondByte: secondByte) + let thirdChar = Base64.encode(alphabet: alphabet, secondByte: secondByte, thirdByte: thirdByte) + let forthChar = Base64.encode(alphabet: alphabet, thirdByte: thirdByte) + + outputBytes.append(firstChar) + outputBytes.append(secondChar) + outputBytes.append(thirdChar) + outputBytes.append(forthChar) } - private static func encode(firstByte: UInt8, secondByte: UInt8?) -> Unicode.Scalar { - var index = (firstByte & 0b00000011) << 4 - if let secondByte = secondByte { - index += (secondByte & 0b11110000) >> 4 - } - return base64Table[Int(index)] + return String(decoding: outputBytes, as: Unicode.UTF8.self) + } + + // MARK: Internal + + // The base64 unicode table. + static let encodeBase64: [UInt8] = [ + UInt8(ascii: "A"), UInt8(ascii: "B"), UInt8(ascii: "C"), UInt8(ascii: "D"), + UInt8(ascii: "E"), UInt8(ascii: "F"), UInt8(ascii: "G"), UInt8(ascii: "H"), + UInt8(ascii: "I"), UInt8(ascii: "J"), UInt8(ascii: "K"), UInt8(ascii: "L"), + UInt8(ascii: "M"), UInt8(ascii: "N"), UInt8(ascii: "O"), UInt8(ascii: "P"), + UInt8(ascii: "Q"), UInt8(ascii: "R"), UInt8(ascii: "S"), UInt8(ascii: "T"), + UInt8(ascii: "U"), UInt8(ascii: "V"), UInt8(ascii: "W"), UInt8(ascii: "X"), + UInt8(ascii: "Y"), UInt8(ascii: "Z"), UInt8(ascii: "a"), UInt8(ascii: "b"), + UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), + UInt8(ascii: "g"), UInt8(ascii: "h"), UInt8(ascii: "i"), UInt8(ascii: "j"), + UInt8(ascii: "k"), UInt8(ascii: "l"), UInt8(ascii: "m"), UInt8(ascii: "n"), + UInt8(ascii: "o"), UInt8(ascii: "p"), UInt8(ascii: "q"), UInt8(ascii: "r"), + UInt8(ascii: "s"), UInt8(ascii: "t"), UInt8(ascii: "u"), UInt8(ascii: "v"), + UInt8(ascii: "w"), UInt8(ascii: "x"), UInt8(ascii: "y"), UInt8(ascii: "z"), + UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), + UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), + UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "+"), UInt8(ascii: "/"), + ] + + static let encodePaddingCharacter: UInt8 = UInt8(ascii: "=") + + static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 { + let index = firstByte >> 2 + return alphabet[Int(index)] + } + + static func encode(alphabet: [UInt8], firstByte: UInt8, secondByte: UInt8?) -> UInt8 { + var index = (firstByte & 0b00000011) << 4 + if let secondByte = secondByte { + index += (secondByte & 0b11110000) >> 4 } + return alphabet[Int(index)] + } - private static func encode(secondByte: UInt8?, thirdByte: UInt8?) -> Unicode.Scalar { - guard let secondByte = secondByte else { - // No second byte means we are just emitting padding. - return "=" - } - var index = (secondByte & 0b00001111) << 2 - if let thirdByte = thirdByte { - index += (thirdByte & 0b11000000) >> 6 - } - return base64Table[Int(index)] + static func encode(alphabet: [UInt8], secondByte: UInt8?, thirdByte: UInt8?) -> UInt8 { + guard let secondByte = secondByte else { + // No second byte means we are just emitting padding. + return Base64.encodePaddingCharacter + } + var index = (secondByte & 0b00001111) << 2 + if let thirdByte = thirdByte { + index += (thirdByte & 0b11000000) >> 6 } + return alphabet[Int(index)] + } - private static func encode(thirdByte: UInt8?) -> Unicode.Scalar { - guard let thirdByte = thirdByte else { - // No third byte means just padding. - return "=" - } - let index = thirdByte & 0b00111111 - return base64Table[Int(index)] + static func encode(alphabet: [UInt8], thirdByte: UInt8?) -> UInt8 { + guard let thirdByte = thirdByte else { + // No third byte means just padding. + return Base64.encodePaddingCharacter } + let index = thirdByte & 0b00111111 + return alphabet[Int(index)] + } }