Every language
12 langages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
function escapeHtml(s) {
return s.replace(/[&<>"']/g, (c) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
}[c]));
}
// safe in element text AND in attribute values — the five cover both:
div.innerHTML = `<option value="${escapeHtml(city)}">${escapeHtml(city)}</option>`;Escape in the RIGHT context — attribute values need " and ' escaped too, and the five-character map covers body text and attributes. But a URL in href needs percent-encoding, not entity-encoding. innerHTML with escaped text is fine; innerHTML with unescaped text never is.
TSTypeScript
const ENTITIES: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
export function escapeHtml(s: string): string {
return s.replace(/[&<>"']/g, (c) => ENTITIES[c]);
}One map pass beats chained .replace() calls — when hand-chaining, the ORDER is load-bearing: & must be replaced FIRST, or the '<' emitted by the < pass gets re-escaped into '&lt;'. Record<string, string> keeps the lookup typed with no index-signature gymnastics.
GoGo
import "html"
// HTML text contexts — escapes & < > ' " :
func renderComment(body string) string {
return "<p>" + html.EscapeString(body) + "</p>"
}html.EscapeString is for HTML TEXT, NOT URLs — url.PathEscape / url.QueryEscape handle href and query sinks, and a <script> block needs JS-string escaping, not entities. Better still: html/template escapes interpolations automatically; reaching for text/template is the classic Go XSS bug.
RsRust
use html_escape::encode_text;
fn render_comment(body: &str) -> String {
format!("<p>{}</p>", encode_text(body)) // & < > " ' — text context
}Askama and handlebars-rust auto-escape {{ }} interpolations in HTML templates — the XSS bug is format!()-ing untrusted text into raw HTML strings, or opting out with the `safe` filter. Outside a template, the html_escape crate's encode_text is the maintained escaper.
PHPPHP
function e(string $s): string {
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// the one-letter helper every template defines:
<?= e($userComment) ?>ENT_QUOTES is mandatory — without it " and ' pass through raw and a single-quoted attribute pops open. ENT_SUBSTITUTE turns invalid UTF-8 into U+FFFD instead of an empty string. strip_tags is NOT escaping: it deletes complete tags and keeps every other dangerous byte.
PyPython
import html
def render_comment(body: str) -> str:
return f"<p>{html.escape(body, quote=True)}</p>" # & < > " 'quote=True (the default) is what makes it attribute-safe — " and ' are encoded too; quote=False leaves them raw. In Flask/Jinja, MarkupSafe integrates with autoescape, and Markup() opts OUT of it exactly like Rails' raw() — audit those call sites.
C#C#
using System.Net;
using System.Text.Encodings.Web;
string body = WebUtility.HtmlEncode(input); // & < > " only
string attr = HtmlEncoder.Default.Encode(input); // all five — attribute-safeWebUtility.HtmlEncode does NOT encode the single quote — fine for element text, a hole inside single-quoted attributes. HtmlEncoder.Default (System.Text.Encodings.Web, successor to the retired Microsoft.AntiXSS) covers all five for attribute-safe output.
JvJava
import org.owasp.encoder.Encode;
String body = Encode.forHtml(comment); // & < > " '
String attr = Encode.forHtmlAttribute(comment); // the context-typed spellingSpring's HtmlUtils.htmlEscape is the no-extra-dependency option for HTML body text; OWASP Encoder gives per-sink spellings (forHtml, forHtmlAttribute, forJavaScript). commons-lang StringEscapeUtils targets Java/JSON string contexts — it is not an HTML escaper.
SwSwift
import Foundation
func escapeHtml(_ s: String) -> String {
s.replacingOccurrences(of: "&", with: "&") // & FIRST — or every
.replacingOccurrences(of: "<", with: "<") // entity you just
.replacingOccurrences(of: ">", with: ">") // wrote gets
.replacingOccurrences(of: "\"", with: """) // re-escaped
.replacingOccurrences(of: "'", with: "'")
}Swift ships no stdlib HTML escaper — the mapped-replace hand-roll is the answer, and its ORDER is load-bearing: & first, or '&' becomes '&amp;'. NSAttributedString is a markup parser, not an escaper — do not reach for it here.
KtKotlin
import org.owasp.encoder.Encode
fun String.escapeHtml(): String = Encode.forHtml(this)
val safe = comment.escapeHtml() // same JVM escapers as Java, one line shorterSame JVM options as Java (OWASP Encode, Spring HtmlUtils) behind a one-line extension — and the same trap: pick the escaper for the SINK (forJavaScript for <script> blocks), never one function for every context.
RbRuby
require 'erb'
ERB::Util.html_escape(comment) # & < > " '
CGI.escapeHTML(comment) # same five — stdlib, no require in RailsRails and Erubi escape <%= %> by default — the leaks are raw() and .html_safe opting OUT; audit every one of those call sites. ERB::Util.html_escape and CGI.escapeHTML encode the same five characters.
ZigZig
const std = @import("std");
fn escapeHtml(writer: anytype, s: []const u8) !void {
for (s) |c| switch (c) {
'&' => try writer.writeAll("&"),
'<' => try writer.writeAll("<"),
'>' => try writer.writeAll(">"),
'"' => try writer.writeAll("""),
'\'' => try writer.writeAll("'"),
else => try writer.writeByte(c),
};
}No std library ships HTML escaping — hand-roll it into a writer (std.Io.Writer or an ArrayList(u8) you own). The single-pass switch makes the & -first rule structural: emitted entities are never re-read. If you instead run successive mem.replace passes over a buffer, & MUST go first or & becomes &amp;. UTF-8 bytes >= 0x80 hit `else` and copy through untouched.