Defang / Refang — Kotlin 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 Kotlin 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: Kotlin (Kotlin 1.9, 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; never throws.
// - Functionally equivalent to the TS / Go reference: same inputs -> same outputs.
// - Self-contained: String.replace(String, String) replaces every occurrence
// literally — the direct equivalent of the TS replaceAll.
//
// 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.
/**
* 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.
*/
fun defang(text: String): String =
text
.replace("https://", "hxxps[://]")
.replace("http://", "hxxp[://]")
.replace(".", "[.]")
.replace("@", "[@]")
/**
* 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.
*/
fun refang(text: String): String =
text
.replace("hxxps[://]", "https://")
.replace("hxxp[://]", "http://")
.replace("[.]", ".")
.replace("[@]", "@")
// ---------- tests (showcase-only; the canonical suite lives in src/lib) ----------
private fun expect(got: String, want: String, label: String) {
check(got == want) { "FAIL $label:\n got \"$got\"\n want \"$want\"" }
}
fun main() {
// 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
val got = defang("https://a.com")
expect(got, "hxxps[://]a[.]com", "scheme first")
check("https[://]" !in got && "hxxps://" !in got) { "FAIL scheme marker corrupted: \"$got\"" }
// 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 (x in listOf(
"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")
println("all checks passed")
}
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 →