Skip to content

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 →