File: Base64.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (216 lines) | stat: -rw-r--r-- 8,841 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// This is a simplified vendored version from:
// https://github.com/fabianfett/swift-base64-kit

public extension String {

    /// Base64 encode a collection of UInt8 to a string, without the use of Foundation.
    @inlinable
    init<Buffer: Collection>(base64Encoding bytes: Buffer) where Buffer.Element == UInt8 {
        self = Base64.encode(bytes: bytes)
    }

    @inlinable
    func base64Decoded() throws -> [UInt8] {
        return try Base64.decode(string: self)
    }
}

public enum Base64Error: Error {
    case invalidLength
    case invalidCharacter
}

@usableFromInline
internal struct Base64 {

    @inlinable
    static func encode<Buffer: Collection>(bytes: Buffer) -> String where Buffer.Element == UInt8 {
        guard !bytes.isEmpty else {
            return ""
        }

        // In Base64, 3 bytes become 4 output characters, and we pad to the
        // nearest multiple of four.
        let base64StringLength = ((bytes.count + 2) / 3) * 4
        let alphabet = Base64.encodeBase64

        return String(customUnsafeUninitializedCapacity: base64StringLength) { backingStorage in
            var input = bytes.makeIterator()
            var offset = 0
            while let firstByte = input.next() {
                let secondByte = input.next()
                let thirdByte = input.next()

                backingStorage[offset] = Base64.encode(alphabet: alphabet, firstByte: firstByte)
                backingStorage[offset + 1] = Base64.encode(alphabet: alphabet, firstByte: firstByte, secondByte: secondByte)
                backingStorage[offset + 2] = Base64.encode(alphabet: alphabet, secondByte: secondByte, thirdByte: thirdByte)
                backingStorage[offset + 3] = Base64.encode(alphabet: alphabet, thirdByte: thirdByte)
                offset += 4
            }
            return offset
        }
    }

    @inlinable
    static func decode(string: String) throws -> [UInt8] {
        guard string.count % 4 == 0 else {
            throw Base64Error.invalidLength
        }

        let bytes = string.utf8.map { $0 }
        var decoded = [UInt8]()

        // Go over the encoded string in groups of 4 characters,
        // and build groups of 3 bytes from them.
        for i in stride(from: 0, to: bytes.count, by: 4) {
            guard let byte0Index = Base64.encodeBase64.firstIndex(of: bytes[i]),
                  let byte1Index = Base64.encodeBase64.firstIndex(of: bytes[i+1]) else {
                throw Base64Error.invalidCharacter
            }

            let byte0 = (UInt8(byte0Index) << 2 | UInt8(byte1Index) >> 4)
            decoded.append(byte0)

            // Check if the 3rd char is not a padding character, and decode the 2nd byte
            if bytes[i+2] != Base64.encodePaddingCharacter {
                guard let byte2Index = Base64.encodeBase64.firstIndex(of: bytes[i+2]) else {
                    throw Base64Error.invalidCharacter
                }

                let second = (UInt8(byte1Index) << 4 | UInt8(byte2Index) >> 2)
                decoded.append(second)
            }

            // Check if the 4th character is not a padding, and decode the 3rd byte
            if bytes[i+3] != Base64.encodePaddingCharacter {
                guard let byte3Index = Base64.encodeBase64.firstIndex(of: bytes[i+3]),
                      let byte2Index = Base64.encodeBase64.firstIndex(of: bytes[i+2]) else {
                    throw Base64Error.invalidCharacter
                }
                let third = (UInt8(byte2Index) << 6 | UInt8(byte3Index))
                decoded.append(third)
            }
        }
        return decoded
    }


    // MARK: Internal

    // The base64 unicode table.
    @usableFromInline
    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: "/"),
    ]

    @usableFromInline
    static let encodePaddingCharacter: UInt8 = UInt8(ascii: "=")

    @usableFromInline
    static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 {
        let index = firstByte >> 2
        return alphabet[Int(index)]
    }

    @usableFromInline
    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)]
    }

    @usableFromInline
    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)]
    }

    @usableFromInline
    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)]
    }
}

extension String {
    /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory.
    ///
    /// 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.
    @inlinable
    init(backportUnsafeUninitializedCapacity capacity: Int,
         initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer<UInt8>) throws -> Int) rethrows {

        // The buffer will store zero terminated C string
        let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: capacity + 1)
        defer {
            buffer.deallocate()
        }

        let initializedCount = try initializer(buffer)
        precondition(initializedCount <= capacity, "Overran buffer in initializer!")

        // add zero termination
        buffer[initializedCount] = 0

        self = String(cString: buffer.baseAddress!)
    }
}

// Frustratingly, Swift 5.3 shipped before the macOS 11 SDK did, so we cannot gate the availability of
// this declaration on having the 5.3 compiler. This has caused a number of build issues. While updating
// to newer Xcodes does work, we can save ourselves some hassle and just wait until 5.4 to get this
// enhancement on Apple platforms.
extension String {

    @inlinable
    init(customUnsafeUninitializedCapacity capacity: Int,
         initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer<UInt8>) throws -> Int) rethrows {
        if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) {
            try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer)
        } else {
            try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer)
        }
    }
}