Skip to content

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 →