Basic Auth Generator — Zig source
Build HTTP Basic Access Authentication headers from a username and password, or decode an existing Authorization header back to its credentials. UTF-8 safe and fully client-side, with a shareable link.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
//! basic-auth-generator — HTTP Basic-Auth header encode/decode.
//!
//! Language: Zig (0.13, standard library only)
//! Source: CosmoDev polyglot showcase port of the Basic Auth Generator tool,
//! ported from src/lib/basic-auth.ts (the canonical TypeScript
//! implementation) and kept in lock-step with cli/basic-auth-generator.
//! License: display source — part of CosmoDev's polyglot tool pages.
//!
//! Design goals:
//! - Pure + deterministic; never panics (parseHeader returns null on
//! malformed input — the only error path is allocator failure).
//! - Functionally equivalent to the TS reference + Go twin: same inputs ->
//! same outputs, same reject behavior on malformed headers.
//! - Self-contained: std only — std.base64.standard is the RFC 4648 §4
//! codec, the direct equivalent of Go's encoding/base64 StdEncoding.
//!
//! Zig is a dependency-rich polyglot here, like the Python port: the standard
//! library ships the base64 codec, so unlike the Rust/C/C++ ports no codec is
//! hand-rolled. One quirk is compensated for below — the standard decoder
//! rejects whitespace instead of skipping it, so interior whitespace is
//! stripped first, mirroring the TS b64decode(token.replace(/\s+/g, ''))
//! helper. All returned/returned-through slices are owned by the caller's
//! allocator.
const std = @import("std");
/// Credentials hold a parsed username/password pair. Mirrors the Go twin's
/// `Credentials` struct and the TS `BasicAuthCredentials` interface. The two
/// slices are separately owned by the allocator passed to parseHeader.
pub const Credentials = struct {
user: []u8,
pass: []u8,
pub fn deinit(self: Credentials, alloc: std.mem.Allocator) void {
alloc.free(self.user);
alloc.free(self.pass);
}
};
/// Build an HTTP Basic-Auth header value: "Basic " ++ base64(user ++ ":" ++ pass).
/// Mirrors `BuildHeader` in the Go twin and `buildBasicAuth` in the TS source.
/// The encoder operates on the UTF-8 byte representation, so multibyte
/// credentials (e.g. "café:päss") encode correctly. The returned slice is
/// owned by `alloc`.
pub fn buildHeader(alloc: std.mem.Allocator, user: []const u8, pass: []const u8) ![]u8 {
const raw = try std.mem.concat(alloc, u8, &.{ user, ":", pass });
defer alloc.free(raw);
const enc = std.base64.standard.Encoder;
const out = try alloc.alloc(u8, "Basic ".len + enc.calcSize(raw.len));
@memcpy(out[0.."Basic ".len], "Basic ");
_ = enc.encode(out["Basic ".len..], raw);
return out;
}
/// Parse a "Basic <token>" header back into credentials, returning null if
/// the header is malformed or the credentials have no colon separator. Logic
/// mirrors `ParseHeader` in the Go twin exactly: trim the header, require a
/// case-insensitive "basic " prefix, trim the remaining token, base64-decode
/// it (rejecting invalid characters), then split on the FIRST colon so a
/// password containing colons round-trips.
pub fn parseHeader(alloc: std.mem.Allocator, header: []const u8) !?Credentials {
const ws = " \t\n\r\x0b\x0c";
const h = std.mem.trim(u8, header, ws);
const prefix = "basic ";
if (h.len < prefix.len) return null;
for (prefix, 0..) |c, i| {
if (std.ascii.toLower(h[i]) != c) return null;
}
const token = std.mem.trim(u8, h[prefix.len..], ws);
// Strip interior whitespace: the standard decoder rejects it, Go's
// StdEncoding and the TS helper skip it.
const buf = try alloc.alloc(u8, token.len);
defer alloc.free(buf);
var n: usize = 0;
for (token) |c| {
switch (c) {
' ', '\t', '\n', '\r' => {},
else => {
buf[n] = c;
n += 1;
},
}
}
const cleaned = buf[0..n];
// calcSizeForSlice enforces the multiple-of-4 length (error.InvalidPadding
// otherwise) and the trailing-padding arithmetic, exactly like Go.
const dec = std.base64.standard.Decoder;
const decoded_len = dec.calcSizeForSlice(cleaned) catch return null;
const decoded = try alloc.alloc(u8, decoded_len);
dec.decode(decoded, cleaned) catch {
alloc.free(decoded);
return null;
};
defer alloc.free(decoded);
// UTF-8 validation, mirroring Rust's String::from_utf8 check.
if (!std.unicode.utf8ValidateSlice(decoded)) return null;
// Split on the FIRST colon only — the rest stays in the password.
const colon = std.mem.indexOfScalar(u8, decoded, ':') orelse return null;
const user = try alloc.dupe(u8, decoded[0..colon]);
const pass = alloc.dupe(u8, decoded[colon + 1 ..]) catch |e| {
alloc.free(user);
return e;
};
return .{ .user = user, .pass = pass };
}
// ---------- tests (showcase-only; the canonical suite lives in src/lib) ----------
test "build known vector" {
const h = try buildHeader(std.testing.allocator, "user", "pass");
defer std.testing.allocator.free(h);
try std.testing.expectEqualStrings("Basic dXNlcjpwYXNz", h);
}
test "round trip (incl. empty halves and colon-bearing password)" {
const cases = [_][2][]const u8{
.{ "alice", "s3cr3t" },
.{ "", "x" },
.{ "x", "" },
.{ "u", "a:b:c" },
};
for (cases) |case| {
const h = try buildHeader(std.testing.allocator, case[0], case[1]);
defer std.testing.allocator.free(h);
const creds = (try parseHeader(std.testing.allocator, h)).?;
defer creds.deinit(std.testing.allocator);
try std.testing.expectEqualStrings(case[0], creds.user);
try std.testing.expectEqualStrings(case[1], creds.pass);
}
}
test "rejects malformed headers" {
const bad = [_][]const u8{
"Bearer xyz",
"",
"Basic !!!not-base64!!!",
"Basic bm9jb2xvbg==", // "nocolon" — no colon separator
};
for (bad) |s| {
try std.testing.expect((try parseHeader(std.testing.allocator, s)) == null);
}
}
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 →