diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt
index 0972fa2c..f54e1035 100644
--- a/src/main/kotlin/html4tree/main.kt
+++ b/src/main/kotlin/html4tree/main.kt
@@ -236,15 +236,8 @@ fun String.escapeHtml(): String {
var sb: StringBuilder? = null
for (i in 0 until this.length) {
val c = this[i]
- val replacement = when (c) {
- '&' -> "&"
- '<' -> "<"
- '>' -> ">"
- '"' -> """
- '\'' -> "'"
- '`' -> "`"
- else -> null
- }
+ val cInt = c.toInt()
+ val replacement = if (cInt < 128) Constants.HTML_ESCAPE_TABLE[cInt] else null
if (replacement != null) {
if (sb == null) {
sb = StringBuilder(this.length + 16)
@@ -496,6 +489,16 @@ fun help() {
}
private object Constants {
+ @JvmField
+ val HTML_ESCAPE_TABLE: Array = Array(128) { null }.apply {
+ this['&'.toInt()] = "&"
+ this['<'.toInt()] = "<"
+ this['>'.toInt()] = ">"
+ this['"'.toInt()] = """
+ this['\''.toInt()] = "'"
+ this['`'.toInt()] = "`"
+ }
+
@JvmField
val defaultSensitiveFiles = listOf(".git", ".env", ".ssh", ".htpasswd", ".htaccess", "id_rsa", "id_ed25519", "secrets.yml", ".html4ignore", ".DS_Store", ".aws", ".kube", ".npmrc", ".gnupg", "config.json", "credentials.json")
diff --git a/src/test/kotlin/html4tree/EscapeHtmlLookupContractTest.kt b/src/test/kotlin/html4tree/EscapeHtmlLookupContractTest.kt
new file mode 100644
index 00000000..8df23e9d
--- /dev/null
+++ b/src/test/kotlin/html4tree/EscapeHtmlLookupContractTest.kt
@@ -0,0 +1,15 @@
+package html4tree
+
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class EscapeHtmlLookupContractTest {
+ @Test
+ fun escapeHtmlPreservesProtectedMappingAcrossAsciiAndUnicode() {
+ assertEquals("", "".escapeHtml())
+ assertEquals("plain ASCII 123", "plain ASCII 123".escapeHtml())
+ assertEquals("&<>"'`", "&<>\"'`".escapeHtml())
+ assertEquals("한글 中文 日本語 العربية עברית", "한글 中文 日本語 العربية עברית".escapeHtml())
+ assertEquals("A&한글<עברית>Z", "A&한글<עברית>Z".escapeHtml())
+ }
+}