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()) + } +}