UTM Link Builder — C source
Build campaign tracking URLs with utm_source, utm_medium and utm_campaign parameters. Bulk mode processes a whole list, presets and import round-trip existing tracking URLs, and a validator flags attribution-breaking values — runs entirely in your browser.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* utm-link-builder — campaign URL builder with canonical utm_* ordering. Language: C (C11, stdlib only). Port of src/lib/utm.ts build(); lint/bulk/preset helpers live in the TypeScript/Go sources. No URL parser in the stdlib: query pairs pass through raw (values must be pre-encoded). */
#include <stdio.h>
#include <string.h>
typedef struct { const char *field, *value; } utm_param;
static const char *const CANON[5][2] = { /* field -> canonical query key */
{"source", "utm_source"}, {"medium", "utm_medium"}, {"campaign", "utm_campaign"},
{"term", "utm_term"}, {"content", "utm_content"},
};
static const char *lookup(const utm_param *params, size_t n, const char *field) {
for (size_t i = 0; i < n; i++)
if (strcmp(params[i].field, field) == 0) return params[i].value;
return NULL;
}
static char *pushn(char *cur, char *end, const char *s, size_t n) {
if ((size_t)(end - cur) < n) n = (size_t)(end - cur); /* clamp to buffer */
memcpy(cur, s, n);
return cur + n;
}
char *utm_build(const char *base, const utm_param *params, size_t n, char *out, size_t outsz) {
char *cur = out, *end = out + outsz - 1;
const char *scheme = strstr(base, "://"), *rest;
if (scheme) { cur = pushn(cur, end, base, (size_t)(scheme - base) + 3); rest = scheme + 3; }
else { cur = pushn(cur, end, "https://", 8); rest = base; } /* coerce scheme-less */
size_t auth_len = strcspn(rest, "/?#"); /* authority ends at '/', '?' or '#' */
if (auth_len == 0) return NULL; /* empty host: unparseable */
cur = pushn(cur, end, rest, auth_len);
const char *path = rest + auth_len;
cur = pushn(cur, end, path, strcspn(path, "?#"));
const char *query = path + strcspn(path, "?#");
int wrote = 0;
if (*query == '?') { /* keep unrelated pairs raw; drop stale utm_* ones */
const char *q = query + 1, *frag = strchr(q, '#');
const char *qend = frag ? frag : q + strlen(q);
const char *pair = q;
while (pair < qend) {
const char *amp = memchr(pair, '&', (size_t)(qend - pair));
const char *next = amp ? amp : qend;
size_t plen = (size_t)(next - pair);
const char *eq = memchr(pair, '=', plen);
size_t key_len = eq ? (size_t)(eq - pair) : plen;
if (key_len > 0 && !(key_len >= 4 && strncmp(pair, "utm_", 4) == 0)) {
cur = pushn(cur, end, wrote ? "&" : "?", 1); wrote = 1;
cur = pushn(cur, end, pair, plen);
wrote = 1;
}
pair = next + 1;
}
}
for (size_t c = 0; c < 5; c++) { /* append in canonical order; empty values skipped */
const char *value = lookup(params, n, CANON[c][0]);
if (!value || !*value) continue;
cur = pushn(cur, end, wrote ? "&" : "?", 1); wrote = 1;
cur = pushn(cur, end, CANON[c][1], strlen(CANON[c][1]));
cur = pushn(cur, end, "=", 1);
cur = pushn(cur, end, value, strlen(value));
wrote = 1;
}
const char *frag = strchr(query, '#');
if (frag) cur = pushn(cur, end, frag, strlen(frag)); /* "#..." verbatim */
*cur = '\0';
return out;
}
int main(void) {
utm_param params[] = {{"source", "twitter"}, {"medium", "social"}, {"campaign", ""}};
char out[512];
printf("%s\n", utm_build("example.com/page?utm_source=stale&id=7", params, 3, out, sizeof out));
/* -> https://example.com/page?id=7&utm_source=twitter&utm_medium=social */
return 0;
}
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 →