Basic Auth Generator — C 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 C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/*
* basic-auth-generator — HTTP Basic-Auth header encode/decode.
*
* Language: C (C11, 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 crashes (parse returns NULL on malformed input).
* - Functionally equivalent to the TS reference + Go twin: same inputs ->
* same outputs, same reject behavior on malformed headers.
* - Self-contained: std only (no external base64 library).
*
* Unicode note: C has no UTF-8-aware strings — a char* is its byte sequence,
* so the codec below operates on raw bytes and multibyte UTF-8 credentials
* (e.g. "café:päss") encode correctly, mirroring the TS TextEncoder() path.
* C11 ships no Unicode tables, so unlike the Rust/Go twins the decoder cannot
* UTF-8-validate; decoded bytes pass through unchanged, which is byte-for-byte
* compatible with the other ports for every valid input. All heap results are
* malloc'd and must be released by the caller (see the *_free helpers).
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
/* RFC 4648 §4 standard base64 alphabet. */
static const char B64_ALPHABET[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/* Reverse lookup: value of one base64 digit, or -1 if not in the alphabet. */
static int b64_value(unsigned char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
/*
* Encode arbitrary bytes into standard base64 (with '=' padding), matching
* Go's base64.StdEncoding.EncodeToString. Processes input in 3-byte groups,
* emitting 4 characters per group; a trailing 1- or 2-byte group emits 2 or 3
* characters plus '=' padding. Returns a malloc'd NUL-terminated string.
*/
static char *b64_encode(const unsigned char *input, size_t len) {
size_t out_len = (len + 2) / 3 * 4;
char *out = malloc(out_len + 1);
if (out == NULL) return NULL;
size_t o = 0;
for (size_t i = 0; i < len; i += 3) {
unsigned b0 = input[i];
unsigned b1 = (i + 1 < len) ? input[i + 1] : 0u;
unsigned b2 = (i + 2 < len) ? input[i + 2] : 0u;
unsigned n = (b0 << 16) | (b1 << 8) | b2;
out[o++] = B64_ALPHABET[(n >> 18) & 0x3Fu];
out[o++] = B64_ALPHABET[(n >> 12) & 0x3Fu];
out[o++] = (i + 1 < len) ? B64_ALPHABET[(n >> 6) & 0x3Fu] : '=';
out[o++] = (i + 2 < len) ? B64_ALPHABET[n & 0x3Fu] : '=';
}
out[o] = '\0';
return out;
}
/*
* Decode a standard base64 string into bytes. Whitespace inside the token is
* ignored; the cleaned length must be a multiple of 4; any character outside
* the alphabet (other than '=' padding) is rejected. Mirrors Go's
* base64.StdEncoding.DecodeString (error -> NULL). Sets *out_len and returns
* a malloc'd buffer (always at least 1 byte, never NULL-terminated).
*/
static unsigned char *b64_decode(const char *input, size_t *out_len) {
size_t cleaned_len = 0;
for (const unsigned char *p = (const unsigned char *)input; *p != '\0'; p++) {
if (!isspace(*p)) cleaned_len++;
}
if (cleaned_len % 4 != 0) return NULL;
unsigned char *out = malloc(cleaned_len / 4 * 3 + 1);
if (out == NULL) return NULL;
size_t o = 0;
unsigned idx[4] = {0, 0, 0, 0};
size_t group_filled = 0;
int pads_in_group = 0;
for (const unsigned char *p = (const unsigned char *)input; *p != '\0'; p++) {
if (isspace(*p)) continue;
if (*p == '=') {
idx[group_filled++] = 0;
pads_in_group++;
} else {
int v = b64_value(*p);
if (v < 0) {
free(out); /* invalid character */
return NULL;
}
idx[group_filled++] = (unsigned)v;
}
if (group_filled == 4) {
unsigned n = (idx[0] << 18) | (idx[1] << 12) | (idx[2] << 6) | idx[3];
out[o++] = (unsigned char)(n >> 16);
if (pads_in_group < 2) out[o++] = (unsigned char)(n >> 8);
if (pads_in_group < 1) out[o++] = (unsigned char)n;
group_filled = 0;
pads_in_group = 0;
}
}
*out_len = o;
return out;
}
/*
* Credentials hold a parsed username/password pair. Mirrors the Go twin's
* Credentials struct and the TS BasicAuthCredentials interface. Both strings
* and the struct itself are malloc'd; release with basic_auth_credentials_free.
*/
typedef struct {
char *user;
char *pass;
} basic_auth_credentials;
void basic_auth_credentials_free(basic_auth_credentials *c) {
if (c == NULL) return;
free(c->user);
free(c->pass);
free(c);
}
/*
* Build an HTTP Basic-Auth header value: "Basic " + base64(user:pass).
* Mirrors BuildHeader in the Go twin and buildBasicAuth in the TS source.
* Returns a malloc'd string.
*/
char *basic_auth_build_header(const char *user, const char *pass) {
size_t ulen = strlen(user);
size_t plen = strlen(pass);
char *raw = malloc(ulen + 1 + plen + 1);
if (raw == NULL) return NULL;
memcpy(raw, user, ulen);
raw[ulen] = ':';
memcpy(raw + ulen + 1, pass, plen + 1);
char *token = b64_encode((const unsigned char *)raw, ulen + 1 + plen);
free(raw);
if (token == NULL) return NULL;
char *header = malloc(6 + strlen(token) + 1);
if (header == NULL) {
free(token);
return NULL;
}
memcpy(header, "Basic ", 6);
strcpy(header + 6, token);
free(token);
return header;
}
static int starts_with_basic_prefix(const char *s, size_t len) {
static const char prefix[] = "basic ";
if (len < 6) return 0;
for (size_t i = 0; i < 6; i++) {
if (tolower((unsigned char)s[i]) != prefix[i]) return 0;
}
return 1;
}
/*
* Parse a "Basic <token>" header back into credentials, or 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.
*/
basic_auth_credentials *basic_auth_parse_header(const char *header) {
while (isspace((unsigned char)*header)) header++;
const char *end = header + strlen(header);
while (end > header && isspace((unsigned char)end[-1])) end--;
if (!starts_with_basic_prefix(header, (size_t)(end - header))) return NULL;
const char *tok = header + 6;
while (tok < end && isspace((unsigned char)*tok)) tok++;
while (end > tok && isspace((unsigned char)end[-1])) end--;
size_t tok_len = (size_t)(end - tok);
char *token = malloc(tok_len + 1);
if (token == NULL) return NULL;
memcpy(token, tok, tok_len);
token[tok_len] = '\0';
size_t decoded_len = 0;
unsigned char *decoded = b64_decode(token, &decoded_len);
free(token);
if (decoded == NULL) return NULL;
unsigned char *colon = memchr(decoded, ':', decoded_len);
if (colon == NULL) {
free(decoded);
return NULL;
}
basic_auth_credentials *creds = malloc(sizeof *creds);
if (creds == NULL) {
free(decoded);
return NULL;
}
size_t user_len = (size_t)(colon - decoded);
size_t pass_len = decoded_len - user_len - 1;
creds->user = malloc(user_len + 1);
creds->pass = malloc(pass_len + 1);
if (creds->user == NULL || creds->pass == NULL) {
free(creds->user);
free(creds->pass);
free(creds);
free(decoded);
return NULL;
}
memcpy(creds->user, decoded, user_len);
creds->user[user_len] = '\0';
memcpy(creds->pass, colon + 1, pass_len);
creds->pass[pass_len] = '\0';
free(decoded);
return creds;
}
/* ---------- showcase-only demo (canonical suite lives in src/lib) ----------
* Compile with -DBASIC_AUTH_DEMO to run the same vectors as the Rust port. */
#ifdef BASIC_AUTH_DEMO
#include <assert.h>
#include <stdio.h>
int main(void) {
char *h = basic_auth_build_header("user", "pass");
assert(h != NULL && strcmp(h, "Basic dXNlcjpwYXNz") == 0);
puts(h);
free(h);
const char *round_trips[4][2] = {
{"alice", "s3cr3t"}, {"", "x"}, {"x", ""}, {"u", "a:b:c"},
};
for (size_t i = 0; i < 4; i++) {
char *hdr = basic_auth_build_header(round_trips[i][0], round_trips[i][1]);
assert(hdr != NULL);
basic_auth_credentials *c = basic_auth_parse_header(hdr);
assert(c != NULL);
assert(strcmp(c->user, round_trips[i][0]) == 0);
assert(strcmp(c->pass, round_trips[i][1]) == 0); /* colon-bearing pw intact */
basic_auth_credentials_free(c);
free(hdr);
}
const char *malformed[] = {
"Bearer xyz", "", "Basic !!!not-base64!!!", "Basic bm9jb2xvbg==", /* "nocolon" */
};
for (size_t i = 0; i < 4; i++) {
assert(basic_auth_parse_header(malformed[i]) == NULL);
}
puts("all basic-auth-generator demo checks passed");
return 0;
}
#endif
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 →