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# 12 / .NET 8, 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 for any string input.
//   - Functionally equivalent to the TS / Go reference: same inputs -> same outputs.
//   - Self-contained: string.Replace(String, String) replaces every occurrence
//     natively — the closest one-call 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.

using System;

namespace CosmoDev.Polyglot;

/// <summary>
/// Make URLs, IPs, and emails safe to paste (and reverse it).
/// </summary>
public static class DefangRefang
{
    /// <summary>
    /// Defang text so URLs, IPs, and emails are no longer clickable or auto-linked.
    /// <para>
    /// Mirrors <c>defang()</c> in src/lib/defang.ts and <c>Defang()</c> 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 <c>[://]</c> 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.
    /// </para>
    /// </summary>
    public static string Defang(string text) =>
        text
            .Replace("https://", "hxxps[://]")
            .Replace("http://", "hxxp[://]")
            .Replace(".", "[.]")
            .Replace("@", "[@]");

    /// <summary>
    /// Reverse defanging — restore "hxxps[://]" / "hxxp[://]" to real schemes and
    /// unbracket "[.]" / "[@]": the exact inverse of <see cref="Defang"/>.
    /// <para>
    /// 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.
    /// </para>
    /// </summary>
    public static string Refang(string text) =>
        text
            .Replace("hxxps[://]", "https://")
            .Replace("hxxp[://]", "http://")
            .Replace("[.]", ".")
            .Replace("[@]", "@");
}

// ---------- tests (showcase-only; the canonical suite lives in src/lib) ----------
namespace CosmoDev.Polyglot
{
    internal static class DefangRefangShowcase
    {
        private static int failures;

        private static void Expect(string got, string want, string label)
        {
            if (got != want)
            {
                Console.Error.WriteLine($"FAIL {label}:\n  got  \"{got}\"\n  want \"{want}\"");
                failures++;
            }
        }

        private static int Main()
        {
            // defangs url, email, and ip
            Expect(DefangRefang.Defang("https://example.com"), "hxxps[://]example[.]com", "defang url");
            Expect(DefangRefang.Defang("user@example.com"), "user[@]example[.]com", "defang email");
            Expect(DefangRefang.Defang("192.168.1.1"), "192[.]168[.]1[.]1", "defang ip");

            // defangs mixed text
            Expect(
                DefangRefang.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
            string got = DefangRefang.Defang("https://a.com");
            Expect(got, "hxxps[://]a[.]com", "scheme first");
            if (got.Contains("https[://]") || got.Contains("hxxps://"))
            {
                Console.Error.WriteLine($"FAIL scheme marker corrupted: \"{got}\"");
                failures++;
            }

            // refangs and round-trips
            Expect(DefangRefang.Refang("hxxps[://]example[.]com"), "https://example.com", "refang https");
            Expect(DefangRefang.Refang("hxxp[://]single[.]com"), "http://single.com", "refang http");
            // hxxp and hxxps are distinct markers — never confused.
            Expect(DefangRefang.Refang("hxxps[://]secure[.]com"), "https://secure.com", "refang hxxps distinct");
            // Round-trip identity for typical URLs, IPs, emails, and plain text.
            foreach (string x in new[]
                {
                    "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(DefangRefang.Refang(DefangRefang.Defang(x)), x, "round trip");
            }

            // empty and passthrough
            Expect(DefangRefang.Defang(""), "", "defang empty");
            Expect(DefangRefang.Refang(""), "", "refang empty");
            Expect(DefangRefang.Defang("nothing to transform here"), "nothing to transform here", "defang passthrough");
            Expect(DefangRefang.Refang("no indicators here"), "no indicators here", "refang passthrough");

            if (failures > 0)
            {
                Console.Error.WriteLine($"{failures} check(s) failed");
                return 1;
            }
            Console.WriteLine("all checks passed");
            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 →