Defang / Refang — C source
Make URLs, IPs, and emails safe to paste by defanging indicators (https:// → hxxps[://], . → [.], @ → [@]) - and reverse refanged text back to its original form. Runs entirely in your browser, with a shareable link to your exact input and mode.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/*
* defang-refang — make URLs, IPs, and emails safe to paste (and reverse it).
*
* Language: C (C11, standard library only)
* Source: CosmoDev polyglot showcase port of the Defang/Refang tool, ported
* from src/lib/defang.ts (the canonical TypeScript implementation)
* and held in lock-step with cli/defang-refang/defang-refang.go.
* License: display source — part of CosmoDev's polyglot tool pages.
*
* Design goals:
* - Deterministic; the only failure mode is allocation failure (NULL return).
* - Functionally equivalent to the TS / Go reference: same inputs -> same outputs.
* - Self-contained: stdlib only (strstr + memcpy power the replace-all).
*
* Algorithm: defang() replaces "https://" / "http://" schemes with "hxxps[://]"
* / "hxxp[://]", then brackets EVERY dot as "[.]" and every at-sign as "[@]" —
* the standard threat-intel indicator-defanging convention. refang() is its
* exact inverse: scheme markers are restored first, then "[.]" / "[@]" are
* unbracketed, so refang(defang(x)) == x for typical URLs, IPs, emails, and
* plain text.
*
* Ordering note: scheme replacements run BEFORE dot/at bracketing so the dots
* and colon inside the resulting "[://]" marker are not themselves re-bracketed
* (which would corrupt the scheme marker). Note that EVERY dot is bracketed,
* not just the domain separator — so defang is NOT idempotent by design: apply
* it once to fresh text. replace_all() replaces every occurrence, matching the
* replaceAll semantics of the TS source one for one.
*
* Memory: defang() / refang() return a fresh malloc'd buffer the caller owns
* and must free(); NULL is returned only when malloc fails.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Return a fresh heap buffer holding `haystack` with every occurrence of
* `needle` (which must be non-empty) replaced by `replacement`.
* Returns NULL only when malloc fails.
*/
static char *replace_all(const char *haystack, const char *needle,
const char *replacement) {
const size_t needle_len = strlen(needle);
const size_t repl_len = strlen(replacement);
/* Count matches first so the output buffer is sized exactly once. */
size_t matches = 0;
const char *scan = haystack;
while ((scan = strstr(scan, needle)) != NULL) {
matches++;
scan += needle_len;
}
const size_t hay_len = strlen(haystack);
char *out = malloc(hay_len - matches * needle_len + matches * repl_len + 1);
if (out == NULL) {
return NULL;
}
char *dst = out;
scan = haystack;
for (;;) {
const char *hit = strstr(scan, needle);
if (hit == NULL) {
break;
}
memcpy(dst, scan, (size_t)(hit - scan));
dst += hit - scan;
memcpy(dst, replacement, repl_len);
dst += repl_len;
scan = hit + needle_len;
}
strcpy(dst, scan); /* trailing remainder */
return out;
}
/*
* Chain one replacement onto a buffer this pipeline already owns, freeing the
* intermediate result so callers never track it.
*/
static char *replace_owned(char *owned, const char *needle,
const char *replacement) {
char *out = replace_all(owned, needle, replacement);
free(owned);
return out;
}
/*
* Defang text so URLs, IPs, and emails are no longer clickable or auto-linked.
*
* Mirrors defang() in src/lib/defang.ts and Defang() in the Go twin, and must
* agree with both on every shared vector. Scheme replacements run first so the
* dots and colon inside the resulting "[://]" marker are not themselves
* re-bracketed. EVERY dot is bracketed, not just the domain separator. Empty
* input returns empty; text with no indicators passes through unchanged.
*/
char *defang(const char *text) {
char *out = replace_all(text, "https://", "hxxps[://]");
if (out == NULL) return NULL;
out = replace_owned(out, "http://", "hxxp[://]");
if (out == NULL) return NULL;
out = replace_owned(out, ".", "[.]");
if (out == NULL) return NULL;
return replace_owned(out, "@", "[@]");
}
/*
* Reverse defanging — restore "hxxps[://]" / "hxxp[://]" to real schemes and
* unbracket "[.]" / "[@]". Mirrors refang() in src/lib/defang.ts and Refang()
* in the Go twin: the exact inverse of defang().
*
* Scheme markers are restored before "[.]" / "[@]" are unbracketed, so a
* marker like "hxxps[://]" reassembles into "https://" rather than being
* split by an earlier dot/at substitution. Empty input returns empty.
*/
char *refang(const char *text) {
char *out = replace_all(text, "hxxps[://]", "https://");
if (out == NULL) return NULL;
out = replace_owned(out, "hxxp[://]", "http://");
if (out == NULL) return NULL;
out = replace_owned(out, "[.]", ".");
if (out == NULL) return NULL;
return replace_owned(out, "[@]", "@");
}
/* ---------- tests (showcase-only; the canonical suite lives in src/lib) ---------- */
static int checks_failed = 0;
static void expect_str(const char *got, const char *want, const char *label) {
if (strcmp(got, want) != 0) {
fprintf(stderr, "FAIL %s:\n got \"%s\"\n want \"%s\"\n", label, got, want);
checks_failed = 1;
}
}
static void expect_absent(const char *haystack, const char *needle, const char *label) {
if (strstr(haystack, needle) != NULL) {
fprintf(stderr, "FAIL %s: unexpectedly contains \"%s\"\n", label, needle);
checks_failed = 1;
}
}
int main(void) {
char *s;
/* defangs url, email, and ip */
s = defang("https://example.com");
expect_str(s, "hxxps[://]example[.]com", "defang url");
free(s);
s = defang("user@example.com");
expect_str(s, "user[@]example[.]com", "defang email");
free(s);
s = defang("192.168.1.1");
expect_str(s, "192[.]168[.]1[.]1", "defang ip");
free(s);
/* defangs mixed text */
s = defang("contact admin@site.co.uk or visit http://site.co.uk");
expect_str(s, "contact admin[@]site[.]co[.]uk or visit hxxp[://]site[.]co[.]uk",
"defang mixed");
free(s);
/* applies scheme replacement before dot bracketing */
s = defang("https://a.com");
expect_str(s, "hxxps[://]a[.]com", "scheme first");
expect_absent(s, "https[://]", "scheme marker not split");
expect_absent(s, "hxxps://", "scheme marker not split");
free(s);
/* refangs and round-trips */
s = refang("hxxps[://]example[.]com");
expect_str(s, "https://example.com", "refang https");
free(s);
s = refang("hxxp[://]single[.]com");
expect_str(s, "http://single.com", "refang http");
free(s);
s = refang("hxxps[://]secure[.]com"); /* hxxp and hxxps are distinct markers */
expect_str(s, "https://secure.com", "refang hxxps distinct");
free(s);
static const char *const round_trips[] = {
"https://sub.example.com/path?q=1&a=b",
"first.last@sub.example.co.uk",
"172.16.254.1",
"just a sentence with no indicators",
};
for (size_t i = 0; i < sizeof round_trips / sizeof round_trips[0]; i++) {
char *defanged = defang(round_trips[i]);
char *refanged = refang(defanged);
expect_str(refanged, round_trips[i], "round trip");
free(defanged);
free(refanged);
}
/* empty and passthrough */
s = defang("");
expect_str(s, "", "defang empty");
free(s);
s = refang("");
expect_str(s, "", "refang empty");
free(s);
s = defang("nothing to transform here");
expect_str(s, "nothing to transform here", "defang passthrough");
free(s);
s = refang("no indicators here");
expect_str(s, "no indicators here", "refang passthrough");
free(s);
if (checks_failed) {
fputs("at least one check failed\n", stderr);
return EXIT_FAILURE;
}
puts("all checks passed");
return EXIT_SUCCESS;
}
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 →