Skip to content

Commit fbdbcbc

Browse files
committed
Used vendored base64 encoding implementation rather than Foundation.Data
1 parent 972bcdd commit fbdbcbc

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

Sources/AsyncHTTPClient/Base64.swift

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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

Sources/AsyncHTTPClient/HTTPHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ extension HTTPClient {
279279
}
280280

281281
public static func basic(username: String, password: String) -> HTTPClient.Authorization {
282-
return .basic(credentials: Data("\(username):\(password)".utf8).base64EncodedString())
282+
return .basic(credentials: Base64.encode(bytes: "\(username):\(password)".utf8))
283283
}
284284

285285
public static func basic(credentials: String) -> HTTPClient.Authorization {

0 commit comments

Comments
 (0)