Skip to content

Defang / Refang — Java 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 Java 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: Java (Java 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; never throws.
//   - Functionally equivalent to the TS / Go reference: same inputs -> same outputs.
//   - Self-contained: String.replace(CharSequence, CharSequence) replaces
//     every occurrence — 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.

/**
 * Make URLs, IPs, and emails safe to paste (and reverse it).
 *
 * <p>The class is package-private so this showcase file compiles standalone
 * under its snippet filename ({@code java.java}); in a project, drop it in as
 * {@code DefangRefang.java} and mark it {@code public} to export it.</p>
 */
final class DefangRefang {

    private DefangRefang() {
    }

    /**
     * Defang text so URLs, IPs, and emails are no longer clickable or auto-linked.
     *
     * <p>Mirrors {@code defang()} in src/lib/defang.ts and {@code 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 {@code [://]} 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.</p>
     *
     * @param text the text to defang
     * @return the defanged text
     */
    static String defang(String text) {
        return text.replace("https://", "hxxps[://]")
                   .replace("http://", "hxxp[://]")
                   .replace(".", "[.]")
                   .replace("@", "[@]");
    }

    /**
     * Reverse defanging — restore {@code hxxps[://]} / {@code hxxp[://]} to real
     * schemes and unbracket {@code [.]} / {@code [@]}. Mirrors {@code refang()}
     * in src/lib/defang.ts and {@code Refang()} in the Go twin: the exact inverse
     * of {@link #defang(String)}.
     *
     * <p>Scheme markers are restored before {@code [.]} / {@code [@]} are
     * unbracketed, so a marker like {@code hxxps[://]} reassembles into
     * {@code https://} rather than being split by an earlier dot/at substitution.
     * Empty input returns empty.</p>
     *
     * @param text the defanged text
     * @return the text with real schemes and separators restored
     */
    static String refang(String text) {
        return text.replace("hxxps[://]", "https://")
                   .replace("hxxp[://]", "http://")
                   .replace("[.]", ".")
                   .replace("[@]", "@");
    }

    // ---------- tests (showcase-only; the canonical suite lives in src/lib) ----------

    private static int failures = 0;

    private static void expect(String got, String want, String label) {
        if (!got.equals(want)) {
            System.err.printf("FAIL %s:%n  got  \"%s\"%n  want \"%s\"%n", label, got, want);
            failures++;
        }
    }

    /** Runs the showcase checks mirrored from the Rust/Python reference snippets. */
    public static void main(String[] args) {
        // 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
        String got = defang("https://a.com");
        expect(got, "hxxps[://]a[.]com", "scheme first");
        if (got.contains("https[://]") || got.contains("hxxps://")) {
            System.err.println("FAIL scheme marker corrupted: \"" + got + "\"");
            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.
        String[] roundTrips = {
            "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 (String x : roundTrips) {
            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) {
            System.err.println(failures + " check(s) failed");
            System.exit(1);
        }
        System.out.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 →