|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the AsyncHTTPClient open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors |
| 6 | +// Licensed under Apache License v2.0 |
| 7 | +// |
| 8 | +// See LICENSE.txt for license information |
| 9 | +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors |
| 10 | +// |
| 11 | +// SPDX-License-Identifier: Apache-2.0 |
| 12 | +// |
| 13 | +//===----------------------------------------------------------------------===// |
| 14 | + |
| 15 | +// This is a simplified vendored version from: |
| 16 | +// https://github.com/fabianfett/swift-base64-kit |
| 17 | + |
| 18 | +// swiftformat:disable all |
| 19 | + |
| 20 | +extension String { |
| 21 | + |
| 22 | + /// Base64 encode a collection of UInt8 to a string, without the use of Foundation. |
| 23 | + @inlinable |
| 24 | + init<Buffer: Collection>(base64Encoding bytes: Buffer) |
| 25 | + where Buffer.Element == UInt8 |
| 26 | + { |
| 27 | + self = Base64.encode(bytes: bytes) |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +@usableFromInline |
| 32 | +internal struct Base64 { |
| 33 | + |
| 34 | + @inlinable |
| 35 | + static func encode<Buffer: Collection>(bytes: Buffer) |
| 36 | + -> String where Buffer.Element == UInt8 |
| 37 | + { |
| 38 | + guard !bytes.isEmpty else { |
| 39 | + return "" |
| 40 | + } |
| 41 | + // In Base64, 3 bytes become 4 output characters, and we pad to the |
| 42 | + // nearest multiple of four. |
| 43 | + let base64StringLength = ((bytes.count + 2) / 3) * 4 |
| 44 | + let alphabet = Base64.encodeBase64 |
| 45 | + |
| 46 | + return String(customUnsafeUninitializedCapacity: base64StringLength) { backingStorage in |
| 47 | + var input = bytes.makeIterator() |
| 48 | + var offset = 0 |
| 49 | + while let firstByte = input.next() { |
| 50 | + let secondByte = input.next() |
| 51 | + let thirdByte = input.next() |
| 52 | + |
| 53 | + backingStorage[offset] = Base64.encode(alphabet: alphabet, firstByte: firstByte) |
| 54 | + backingStorage[offset + 1] = Base64.encode(alphabet: alphabet, firstByte: firstByte, secondByte: secondByte) |
| 55 | + backingStorage[offset + 2] = Base64.encode(alphabet: alphabet, secondByte: secondByte, thirdByte: thirdByte) |
| 56 | + backingStorage[offset + 3] = Base64.encode(alphabet: alphabet, thirdByte: thirdByte) |
| 57 | + offset += 4 |
| 58 | + } |
| 59 | + return offset |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + // MARK: Internal |
| 64 | + |
| 65 | + // The base64 unicode table. |
| 66 | + @usableFromInline |
| 67 | + static let encodeBase64: [UInt8] = [ |
| 68 | + UInt8(ascii: "A"), UInt8(ascii: "B"), UInt8(ascii: "C"), UInt8(ascii: "D"), |
| 69 | + UInt8(ascii: "E"), UInt8(ascii: "F"), UInt8(ascii: "G"), UInt8(ascii: "H"), |
| 70 | + UInt8(ascii: "I"), UInt8(ascii: "J"), UInt8(ascii: "K"), UInt8(ascii: "L"), |
| 71 | + UInt8(ascii: "M"), UInt8(ascii: "N"), UInt8(ascii: "O"), UInt8(ascii: "P"), |
| 72 | + UInt8(ascii: "Q"), UInt8(ascii: "R"), UInt8(ascii: "S"), UInt8(ascii: "T"), |
| 73 | + UInt8(ascii: "U"), UInt8(ascii: "V"), UInt8(ascii: "W"), UInt8(ascii: "X"), |
| 74 | + UInt8(ascii: "Y"), UInt8(ascii: "Z"), UInt8(ascii: "a"), UInt8(ascii: "b"), |
| 75 | + UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), |
| 76 | + UInt8(ascii: "g"), UInt8(ascii: "h"), UInt8(ascii: "i"), UInt8(ascii: "j"), |
| 77 | + UInt8(ascii: "k"), UInt8(ascii: "l"), UInt8(ascii: "m"), UInt8(ascii: "n"), |
| 78 | + UInt8(ascii: "o"), UInt8(ascii: "p"), UInt8(ascii: "q"), UInt8(ascii: "r"), |
| 79 | + UInt8(ascii: "s"), UInt8(ascii: "t"), UInt8(ascii: "u"), UInt8(ascii: "v"), |
| 80 | + UInt8(ascii: "w"), UInt8(ascii: "x"), UInt8(ascii: "y"), UInt8(ascii: "z"), |
| 81 | + UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), |
| 82 | + UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), |
| 83 | + UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "+"), UInt8(ascii: "/"), |
| 84 | + ] |
| 85 | + |
| 86 | + static let encodePaddingCharacter: UInt8 = UInt8(ascii: "=") |
| 87 | + |
| 88 | + @usableFromInline |
| 89 | + static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 { |
| 90 | + let index = firstByte >> 2 |
| 91 | + return alphabet[Int(index)] |
| 92 | + } |
| 93 | + |
| 94 | + @usableFromInline |
| 95 | + static func encode(alphabet: [UInt8], firstByte: UInt8, secondByte: UInt8?) -> UInt8 { |
| 96 | + var index = (firstByte & 0b00000011) << 4 |
| 97 | + if let secondByte = secondByte { |
| 98 | + index += (secondByte & 0b11110000) >> 4 |
| 99 | + } |
| 100 | + return alphabet[Int(index)] |
| 101 | + } |
| 102 | + |
| 103 | + @usableFromInline |
| 104 | + static func encode(alphabet: [UInt8], secondByte: UInt8?, thirdByte: UInt8?) -> UInt8 { |
| 105 | + guard let secondByte = secondByte else { |
| 106 | + // No second byte means we are just emitting padding. |
| 107 | + return Base64.encodePaddingCharacter |
| 108 | + } |
| 109 | + var index = (secondByte & 0b00001111) << 2 |
| 110 | + if let thirdByte = thirdByte { |
| 111 | + index += (thirdByte & 0b11000000) >> 6 |
| 112 | + } |
| 113 | + return alphabet[Int(index)] |
| 114 | + } |
| 115 | + |
| 116 | + @usableFromInline |
| 117 | + static func encode(alphabet: [UInt8], thirdByte: UInt8?) -> UInt8 { |
| 118 | + guard let thirdByte = thirdByte else { |
| 119 | + // No third byte means just padding. |
| 120 | + return Base64.encodePaddingCharacter |
| 121 | + } |
| 122 | + let index = thirdByte & 0b00111111 |
| 123 | + return alphabet[Int(index)] |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +extension String { |
| 128 | + /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory. |
| 129 | + /// |
| 130 | + /// As this API does not exist prior to 5.3 on Linux, or on older Apple platforms, we fake it out with a pointer and accept the extra copy. |
| 131 | + @inlinable |
| 132 | + init(backportUnsafeUninitializedCapacity capacity: Int, |
| 133 | + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer<UInt8>) throws -> Int) rethrows { |
| 134 | + // The buffer will store zero terminated C string |
| 135 | + let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: capacity + 1) |
| 136 | + defer { |
| 137 | + buffer.deallocate() |
| 138 | + } |
| 139 | + |
| 140 | + let initializedCount = try initializer(buffer) |
| 141 | + precondition(initializedCount <= capacity, "Overran buffer in initializer!") |
| 142 | + // add zero termination |
| 143 | + buffer[initializedCount] = 0 |
| 144 | + |
| 145 | + self = String(cString: buffer.baseAddress!) |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +// Frustratingly, Swift 5.3 shipped before the macOS 11 SDK did, so we cannot gate the availability of |
| 150 | +// this declaration on having the 5.3 compiler. This has caused a number of build issues. While updating |
| 151 | +// to newer Xcodes does work, we can save ourselves some hassle and just wait until 5.4 to get this |
| 152 | +// enhancement on Apple platforms. |
| 153 | +#if (compiler(>=5.3) && !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))) || compiler(>=5.4) |
| 154 | +extension String { |
| 155 | + |
| 156 | + @inlinable |
| 157 | + init(customUnsafeUninitializedCapacity capacity: Int, |
| 158 | + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer<UInt8>) throws -> Int) rethrows { |
| 159 | + if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { |
| 160 | + try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) |
| 161 | + } else { |
| 162 | + try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) |
| 163 | + } |
| 164 | + } |
| 165 | +} |
| 166 | +#else |
| 167 | +extension String { |
| 168 | + @inlinable |
| 169 | + init(customUnsafeUninitializedCapacity capacity: Int, |
| 170 | + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer<UInt8>) throws -> Int) rethrows { |
| 171 | + try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) |
| 172 | + } |
| 173 | +} |
| 174 | +#endif |
0 commit comments