Base64 Encode / Decode — Swift source
Encode text to Base64 or decode it back. UTF-8 safe, runs entirely in your browser, with a shareable link to your exact input.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// base64 — UTF-8 safe Base64 encode/decode.
//
// Language: Swift (5.9, standard library only)
// Source: CosmoDev polyglot showcase port of the `base64` tool, ported
// from src/lib/base64.ts (the canonical TypeScript implementation);
// algorithm and structure mirror src/tool-sources/base64/rust.rs.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Foundation's Data(base64Encoded:) quietly ignores characters outside the
// alphabet — too lenient for this tool's strict "throw on invalid input"
// contract — so, like rust.rs, this port hand-rolls the codec: a 256-entry
// lookup table with sentinels, explicit length and padding checks, and a
// UTF-8 validity gate on the way out.
//
// Swift strings are natively Unicode: `input.utf8` is already the UTF-8 byte
// sequence Base64 operates on (the TextEncoder role), and
// String(validating:as:) returns nil for invalid UTF-8 (the TextDecoder
// role).
//
// Run: swift swift.swift
/// Standard Base64 alphabet (RFC 4648). The index of each byte is its 6-bit
/// value — the same alphabet btoa emits in the browser.
let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".utf8)
/// Sentinels for the decode table (a namespace avoids any case-pattern
/// ambiguity in the comparisons below).
enum Sextet {
/// Value for "this byte is not part of the Base64 alphabet".
static let invalid: Int8 = -1
/// Value for "this byte is the '=' padding character".
static let padding: Int8 = -2
}
/// A decode failure. Messages mirror the error strings of the TS/Rust ports.
enum Base64Error: Error, CustomStringConvertible {
case illegalCharacter(at: Int)
case badLength
case invalidUtf8
var description: String {
switch self {
case .illegalCharacter(let at):
return "invalid base64: illegal character at byte \(at)"
case .badLength:
return "invalid base64: length is not a multiple of 4"
case .invalidUtf8:
return "invalid base64: decoded bytes are not valid UTF-8"
}
}
}
/// 256-entry lookup table mapping an ASCII byte to its 6-bit value or one of
/// the sentinels. Built once and reused for every decode.
let decodeTable: [Int8] = {
var table = [Int8](repeating: Sextet.invalid, count: 256)
for (value, byte) in alphabet.enumerated() {
table[Int(byte)] = Int8(value)
}
table[Int(UInt8(ascii: "="))] = Sextet.padding
return table
}()
/// Encode a Unicode string into standard, padded Base64 over its UTF-8 bytes.
///
/// Walks the bytes in 3-byte groups, emitting four 6-bit indices per group.
/// A trailing partial group (1 or 2 bytes) is padded with "=" so the output
/// length is always a multiple of 4 — the same shape as btoa in the browser.
func b64encode(_ input: String) -> String {
let bytes = Array(input.utf8)
var out: [UInt8] = []
out.reserveCapacity((bytes.count + 2) / 3 * 4)
var i = 0
while i + 3 <= bytes.count {
let triple = (UInt32(bytes[i]) << 16) | (UInt32(bytes[i + 1]) << 8)
| UInt32(bytes[i + 2])
out.append(alphabet[Int((triple >> 18) & 0x3F)])
out.append(alphabet[Int((triple >> 12) & 0x3F)])
out.append(alphabet[Int((triple >> 6) & 0x3F)])
out.append(alphabet[Int(triple & 0x3F)])
i += 3
}
// Trailing 1 or 2 bytes, padded so the output stays a multiple of 4.
let remainder = bytes.count - i
if remainder == 1 {
let triple = UInt32(bytes[i]) << 16
out.append(alphabet[Int((triple >> 18) & 0x3F)])
out.append(alphabet[Int((triple >> 12) & 0x3F)])
out.append(UInt8(ascii: "="))
out.append(UInt8(ascii: "="))
} else if remainder == 2 {
let triple = (UInt32(bytes[i]) << 16) | (UInt32(bytes[i + 1]) << 8)
out.append(alphabet[Int((triple >> 18) & 0x3F)])
out.append(alphabet[Int((triple >> 12) & 0x3F)])
out.append(alphabet[Int((triple >> 6) & 0x3F)])
out.append(UInt8(ascii: "="))
}
return String(decoding: out, as: UTF8.self)
}
/// Decode standard Base64 back into the original Unicode text.
///
/// Whitespace inside the input is stripped first (Character.isWhitespace
/// matches everything \s does), so line-wrapped Base64 decodes cleanly. Any
/// malformed input — an illegal character, a length that is not a multiple
/// of 4, or decoded bytes that are not valid UTF-8 — throws, matching the
/// TS port's "throw on invalid input" contract.
func b64decode(_ input: String) throws -> String {
// Drop every whitespace character, then collect the surviving ASCII bytes.
let cleaned = Array(input.filter { !$0.isWhitespace }.utf8)
// Standard Base64 with padding is always a multiple of 4 characters.
if cleaned.count % 4 != 0 { throw Base64Error.badLength }
// Count trailing "=" padding (0, 1, or 2 in well-formed input).
var padding = 0
while padding < 2, cleaned.count - padding > 0,
cleaned[cleaned.count - 1 - padding] == UInt8(ascii: "=")
{
padding += 1
}
var bytes: [UInt8] = []
bytes.reserveCapacity(cleaned.count * 3 / 4)
let n = cleaned.count
var i = 0
while i < n {
// Read four sextets, validating each against the lookup table.
var sextets = [UInt8](repeating: 0, count: 4)
for j in 0..<4 {
let entry = decodeTable[Int(cleaned[i + j])]
if entry == Sextet.padding {
sextets[j] = 0 // padding contributes zero bits
} else if entry == Sextet.invalid {
throw Base64Error.illegalCharacter(at: i + j)
} else {
sextets[j] = UInt8(bitPattern: entry)
}
}
let triple = (UInt32(sextets[0]) << 18) | (UInt32(sextets[1]) << 12)
| (UInt32(sextets[2]) << 6) | UInt32(sextets[3])
let isLastGroup = i + 4 == n
bytes.append(UInt8((triple >> 16) & 0xFF)) // byte 0: always present
if !(isLastGroup && padding == 2) {
bytes.append(UInt8((triple >> 8) & 0xFF)) // byte 1
}
if !(isLastGroup && padding >= 1) {
bytes.append(UInt8(triple & 0xFF)) // byte 2
}
i += 4
}
// Re-interpret the decoded bytes as UTF-8; nil means invalid.
guard let text = String(validating: bytes, as: UTF8.self) else {
throw Base64Error.invalidUtf8
}
return text
}
// Demo — `swift swift.swift`
print(b64encode("Hello, world!")) // SGVsbG8sIHdvcmxkIQ==
do {
print(try b64decode("aGVs\nbG8g d29ybGQ=")) // hello world
print(try b64decode(b64encode("héllo 🌍"))) // multi-byte UTF-8 survives
} catch {
print("unexpected failure: \(error)")
}
do {
_ = try b64decode("SGVsbG8*")
} catch {
print(error)
}
// "/w==" decodes to the single byte 0xFF, which is not valid UTF-8.
do {
_ = try b64decode("/w==")
} catch {
print(error)
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →