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++ (C++17, 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:
// - Pure + deterministic; the only failure mode is std::bad_alloc from a
// string allocation (never a logic error).
// - Functionally equivalent to the TS / Go reference: same inputs -> same outputs.
// - Self-contained: std only — std::string::find + replace power the
// replace-all (the standard library has no one-call replace_all;
// boost::algorithm::replace_all is the ecosystem equivalent).
//
// 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.
#include <iostream>
#include <string>
#include <utility>
namespace defang_refang {
namespace {
// Return `str` with every occurrence of `needle` replaced by `replacement`.
// The find + replace loop is the standard-library-only equivalent of the TS
// replaceAll; `pos` advances past each inserted replacement so replacement
// text is never itself rescanned.
std::string replace_all(std::string str, const std::string& needle,
const std::string& replacement) {
for (std::string::size_type pos = 0;
(pos = str.find(needle, pos)) != std::string::npos;
pos += replacement.size()) {
str.replace(pos, needle.size(), replacement);
}
return str;
}
} // namespace
// 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.
std::string defang(const std::string& text) {
std::string out = replace_all(text, "https://", "hxxps[://]");
out = replace_all(std::move(out), "http://", "hxxp[://]");
out = replace_all(std::move(out), ".", "[.]");
return replace_all(std::move(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.
std::string refang(const std::string& text) {
std::string out = replace_all(text, "hxxps[://]", "https://");
out = replace_all(std::move(out), "hxxp[://]", "http://");
out = replace_all(std::move(out), "[.]", ".");
return replace_all(std::move(out), "[@]", "@");
}
} // namespace defang_refang
// ---------- tests (showcase-only; the canonical suite lives in src/lib) ----------
namespace {
int failures = 0;
void expect(const std::string& got, const std::string& want, const char* label) {
if (got != want) {
std::cerr << "FAIL " << label << ":\n got \"" << got << "\"\n want \"" << want
<< "\"\n";
++failures;
}
}
} // namespace
int main() {
using defang_refang::defang;
using defang_refang::refang;
// defangs url, email, and ip
expect(defang("https://example.com"), "hxxps[://]example[.]com", "defang url");
expect(defang("user@example.com"), "user[@]example[.]com", "defang email");
expect(defang("192.168.1.1"), "192[.]168[.]1[.]1", "defang ip");
// defangs mixed text
expect(defang("contact admin@site.co.uk or visit http://site.co.uk"),
"contact admin[@]site[.]co[.]uk or visit hxxp[://]site[.]co[.]uk",
"defang mixed");
// applies scheme replacement before dot bracketing
const std::string got = defang("https://a.com");
expect(got, "hxxps[://]a[.]com", "scheme first");
if (got.find("https[://]") != std::string::npos ||
got.find("hxxps://") != std::string::npos) {
std::cerr << "FAIL scheme marker corrupted: \"" << got << "\"\n";
++failures;
}
// refangs and round-trips
expect(refang("hxxps[://]example[.]com"), "https://example.com", "refang https");
expect(refang("hxxp[://]single[.]com"), "http://single.com", "refang http");
// hxxp and hxxps are distinct markers — never confused.
expect(refang("hxxps[://]secure[.]com"), "https://secure.com", "refang hxxps distinct");
// Round-trip identity for typical URLs, IPs, emails, and plain text.
for (const char* x : {
"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",
}) {
expect(refang(defang(x)), x, "round trip");
}
// empty and passthrough
expect(defang(""), "", "defang empty");
expect(refang(""), "", "refang empty");
expect(defang("nothing to transform here"), "nothing to transform here", "defang passthrough");
expect(refang("no indicators here"), "no indicators here", "refang passthrough");
if (failures > 0) {
std::cerr << failures << " check(s) failed\n";
return 1;
}
std::cout << "all checks passed\n";
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 →