|
| 1 | +// This source file is part of the Swift.org open source project |
| 2 | +// |
| 3 | +// Copyright (c) 2020 Apple Inc. and the Swift project authors |
| 4 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 5 | +// |
| 6 | +// See http://swift.org/LICENSE.txt for license information |
| 7 | +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 8 | + |
| 9 | +import Foundation |
| 10 | + |
| 11 | + |
| 12 | +extension ArraySlice where Element == UInt8 { |
| 13 | + |
| 14 | + /// String representation of a Base64URL-encoded `ArraySlice<UInt8>`. |
| 15 | + public func base64URL() -> String { |
| 16 | + return base64URL(prepending: []) |
| 17 | + } |
| 18 | + |
| 19 | + public func base64URL(prepending: [UInt8]) -> String { |
| 20 | + var offset = 0 |
| 21 | + var recorded = 0 |
| 22 | + let base64URLCount = ((count * 4 / 3) + 3) & ~3 |
| 23 | + var arr = [UInt8](repeating: UInt8(ascii: "="), count: prepending.count + base64URLCount) |
| 24 | + self.withUnsafeBytes { from in |
| 25 | + arr.withUnsafeMutableBytes { to_ in |
| 26 | + var to = Base64URLAppendable(to_.baseAddress!) |
| 27 | + to.append(prepending) |
| 28 | + while true { |
| 29 | + switch self.count - offset { |
| 30 | + case let n where n >= 3: |
| 31 | + to.add = from[offset] >> 2 |
| 32 | + to.add = from[offset] << 4 | from[offset+1] >> 4 |
| 33 | + to.add = from[offset+1] << 2 | from[offset+2] >> 6 |
| 34 | + to.add = from[offset+2] |
| 35 | + offset += 3 |
| 36 | + recorded += 4 |
| 37 | + case 2: |
| 38 | + to.add = from[offset] >> 2 |
| 39 | + to.add = from[offset] << 4 | from[offset+1] >> 4 |
| 40 | + to.add = from[offset+1] << 2 |
| 41 | + recorded += 4 |
| 42 | + return |
| 43 | + case 1: |
| 44 | + to.add = from[offset] >> 2 |
| 45 | + to.add = from[offset] << 4 |
| 46 | + recorded += 4 |
| 47 | + return |
| 48 | + case 0: |
| 49 | + return |
| 50 | + default: |
| 51 | + fatalError("can't appear here (left=\(self.count-offset))") |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + assert(prepending.count + recorded == arr.count, "prepending=\(prepending.count)+recorded=\(recorded) != \(arr.count)") |
| 57 | + return String(bytes: arr, encoding: .ascii)! |
| 58 | + } |
| 59 | + |
| 60 | + fileprivate struct Base64URLAppendable { |
| 61 | + private let ptr: UnsafeMutableRawPointer |
| 62 | + private var offset_: Int = 0 |
| 63 | + |
| 64 | + private static let toBase64Table: [UInt8] = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8) |
| 65 | + |
| 66 | + var offset: Int { |
| 67 | + return offset_ |
| 68 | + } |
| 69 | + |
| 70 | + init(_ ptr: UnsafeMutableRawPointer) { |
| 71 | + self.ptr = ptr |
| 72 | + } |
| 73 | + |
| 74 | + var add: UInt8 { |
| 75 | + get { return 0 } |
| 76 | + set { |
| 77 | + let value: UInt8 = Base64URLAppendable.toBase64Table[Int(newValue & 0x3f)] |
| 78 | + ptr.storeBytes(of: value, toByteOffset: offset_, as: UInt8.self) |
| 79 | + offset_ += 1 |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + mutating func append(_ bytes: [UInt8]) { |
| 84 | + bytes.withUnsafeBytes { arg in |
| 85 | + let fromptr = arg.baseAddress! |
| 86 | + ptr.copyMemory(from: fromptr, byteCount: bytes.count) |
| 87 | + } |
| 88 | + offset_ += bytes.count |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +extension Array where Element == UInt8 { |
| 94 | + |
| 95 | + /// String representation of a Base64URL-encoded `[UInt8]`. |
| 96 | + public func base64URL() -> String { |
| 97 | + return ArraySlice(self).base64URL() |
| 98 | + } |
| 99 | + |
| 100 | + /// Base64URL encoding. Returns `nil` if the Base64URL encoding is broken. |
| 101 | + public init?(base64URL str: String, prepending: [UInt8] = []) { |
| 102 | + guard let array = [UInt8](base64URL: str[str.startIndex...]) else { |
| 103 | + return nil |
| 104 | + } |
| 105 | + self = array |
| 106 | + } |
| 107 | + |
| 108 | + public init?(base64URL str: Substring, prepending: [UInt8] = []) { |
| 109 | + var memory = [UInt8](prepending) |
| 110 | + memory.reserveCapacity(prepending.count + str.count * 3 / 4) |
| 111 | + |
| 112 | + var currentValue: UInt32 = 0 |
| 113 | + var currentBits = 0 |
| 114 | + |
| 115 | + for char in str.unicodeScalars { |
| 116 | + guard char.isASCII else { |
| 117 | + return nil |
| 118 | + } |
| 119 | + |
| 120 | + switch char.value { |
| 121 | + case let n where n >= UInt32(UInt8(ascii: "A")) && n <= UInt32(UInt8(ascii: "Z")): |
| 122 | + currentValue <<= 6 |
| 123 | + currentValue |= n - UInt32(UInt8(ascii: "A")) |
| 124 | + currentBits += 6 |
| 125 | + case let n where n >= UInt32(UInt8(ascii: "a")) && n <= UInt32(UInt8(ascii: "z")): |
| 126 | + currentValue <<= 6 |
| 127 | + currentValue |= 26 + (n - UInt32(UInt8(ascii: "a"))) |
| 128 | + currentBits += 6 |
| 129 | + case let n where n >= UInt32(UInt8(ascii: "0")) && n <= UInt32(UInt8(ascii: "9")): |
| 130 | + currentValue <<= 6 |
| 131 | + currentValue |= 52 + (n - UInt32(UInt8(ascii: "0"))) |
| 132 | + currentBits += 6 |
| 133 | + case UInt32(UInt8(ascii: "-")): |
| 134 | + currentValue <<= 6 |
| 135 | + currentValue |= 62 |
| 136 | + currentBits += 6 |
| 137 | + case UInt32(UInt8(ascii: "_")): |
| 138 | + currentValue <<= 6 |
| 139 | + currentValue |= 63 |
| 140 | + currentBits += 6 |
| 141 | + case UInt32(UInt8(ascii: "=")): |
| 142 | + guard currentValue == 0 else { |
| 143 | + return nil |
| 144 | + } |
| 145 | + currentBits = 0 |
| 146 | + continue |
| 147 | + default: |
| 148 | + return nil |
| 149 | + } |
| 150 | + |
| 151 | + if currentBits >= 8 { |
| 152 | + currentBits -= 8 |
| 153 | + assert(currentBits < 8) |
| 154 | + let byte: UInt8 = UInt8(currentValue >> currentBits) |
| 155 | + currentValue &= (1 << currentBits) - 1 |
| 156 | + memory.append(byte) |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + guard currentBits == 0 && currentValue == 0 else { |
| 161 | + return nil |
| 162 | + } |
| 163 | + |
| 164 | + self = memory |
| 165 | + } |
| 166 | + |
| 167 | +} |
0 commit comments