This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 792a5569e StringEscapeUtils.escapeHtml4/3 apostrophe gap:
single-quoted and unquoted HTML attribute contexts are trivially breakable, and
the javadoc discloses the gap only as an HTML4 entity-legality footnote (f022).
792a5569e is described below
commit 792a5569e5bc41dec9ae669c9b8fe03049215abe
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Sep 5 15:06:49 2026 -0400
StringEscapeUtils.escapeHtml4/3 apostrophe gap: single-quoted and
unquoted HTML attribute contexts are trivially breakable, and the
javadoc discloses the gap only as an HTML4 entity-legality footnote
(f022).
---
src/changes/changes.xml | 1 +
.../apache/commons/lang3/StringEscapeUtils.java | 27 ++++++++++++++--------
.../commons/lang3/text/translate/EntityArrays.java | 5 +++-
.../commons/lang3/StringEscapeUtilsTest.java | 20 +++++++++++++++-
.../lang3/text/translate/EntityArraysTest.java | 7 ++++++
5 files changed, 49 insertions(+), 11 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 846bae61c..9c23421e4 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -271,6 +271,7 @@ java.lang.NullPointerException: Cannot invoke
<action type="fix" dev="ggregory" due-to="Gary
Gregory">BitField long-mask fields silently mis-answer int/short/byte
accessors: shifts masked mod 32, and sign extension makes above-bit-31 flags
read as set on any negative holder (f019).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">ClassUtils.getClass() name normalization (whitespace stripping +
dot-to-dollar rewriting) defeats host-side class-name string checks
(f020).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Fraction.getFraction(String) throws undeclared ArithmeticException on
crafted numeric strings ('9999999999.5', '1/0', mixed-number overflow)
(f021).</action>
+ <action type="fix" dev="ggregory" due-to="Gary
Gregory">StringEscapeUtils.escapeHtml4/3 apostrophe gap: single-quoted and
unquoted HTML attribute contexts are trivially breakable, and the javadoc
discloses the gap only as an HTML4 entity-legality footnote (f022).</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add JavaVersion.JAVA_27.</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add SystemUtils.IS_JAVA_27.</action>
diff --git a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
index cf35295a5..c45e3469b 100644
--- a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java
@@ -175,8 +175,8 @@ public int translate(final CharSequence input, final int
index, final Writer out
@Deprecated
public static final CharSequenceTranslator ESCAPE_XML =
new AggregateTranslator(
- new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
- new LookupTranslator(EntityArrays.APOS_ESCAPE())
+ new LookupTranslator(EntityArrays.APOS_ESCAPE()),
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE())
);
/**
@@ -190,8 +190,8 @@ public int translate(final CharSequence input, final int
index, final Writer out
*/
public static final CharSequenceTranslator ESCAPE_XML10 =
new AggregateTranslator(
- new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
new LookupTranslator(EntityArrays.APOS_ESCAPE()),
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
new LookupTranslator(
new String[][] {
{ "\u0000", StringUtils.EMPTY },
@@ -242,8 +242,8 @@ public int translate(final CharSequence input, final int
index, final Writer out
*/
public static final CharSequenceTranslator ESCAPE_XML11 =
new AggregateTranslator(
- new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
new LookupTranslator(EntityArrays.APOS_ESCAPE()),
+ new LookupTranslator(EntityArrays.BASIC_ESCAPE()),
new LookupTranslator(
new String[][] {
{ "\u0000", StringUtils.EMPTY },
@@ -476,7 +476,13 @@ public static final String escapeEcmaScript(final String
input) {
/**
* Escapes the characters in a {@link String} using HTML entities.
- * <p>Supports only the HTML 3.0 entities.</p>
+ *
+ * <p>
+ * Supports only the HTML 3.0 entities. Apostrophes are escaped as the
numeric reference {@code '}.
+ * </p>
+ * <p>
+ * HTML attribute values must be quoted. This method does not escape all
characters that delimit unquoted attribute values.
+ * </p>
*
* @param input The {@link String} to escape, may be null
* @return A new escaped {@link String}, {@code null} if null string input
@@ -497,10 +503,13 @@ public static final String escapeHtml3(final String
input) {
* <p>
* {@code &quot;bread&quot; &amp; &quot;butter&quot;}.
* </p>
- *
- * <p>Supports all known HTML 4.0 entities, including funky accents.
- * Note that the commonly used apostrophe escape character (&apos;)
- * is not a legal entity and so is not supported).</p>
+ * <p>
+ * Supports all known HTML 4.0 entities, including funky accents.
+ * Apostrophes are escaped as the numeric reference {@code '} because
{@code '} is not a legal HTML 4.0 entity.
+ * </p>
+ * <p>
+ * HTML attribute values must be quoted. This method does not escape all
characters that delimit unquoted attribute values.
+ * </p>
*
* @param input The {@link String} to escape, may be null
* @return A new escaped {@link String}, {@code null} if null string input
diff --git
a/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java
b/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java
index b021204c7..cf8a51c06 100644
--- a/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java
+++ b/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java
@@ -330,6 +330,7 @@ public class EntityArrays {
private static final String[][] HTML40_EXTENDED_UNESCAPE =
invert(HTML40_EXTENDED_ESCAPE);
private static final String[][] BASIC_ESCAPE = {
+ {"'", "'"}, // ' - apostrophe
{"\"", """}, // " - double-quote
{"&", "&"}, // & - ampersand
{"<", "<"}, // < - less-than
@@ -375,7 +376,9 @@ public static String[][] APOS_UNESCAPE() {
/**
* Mapping to escape the basic XML and HTML character entities.
*
- * Namely: {@code " & < >}
+ * <p>
+ * Namely: {@code " ' & < >}. The apostrophe is escaped as the numeric
reference {@code '}.
+ * </p>
*
* @return The mapping table.
*/
diff --git a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
index 4af256cd8..132885b3b 100644
--- a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java
@@ -53,7 +53,7 @@ class StringEscapeUtilsTest extends AbstractLangTest {
{"quotes", ""bread" & butter", "\"bread\" & butter"},
{"final character only", "greater than >", "greater than >"},
{"first character only", "< less than", "< less than"},
- {"apostrophe", "Huntington's chorea", "Huntington's chorea"},
+ {"apostrophe", "Huntington's chorea", "Huntington's chorea"},
{"languages", "English,Français,\u65E5\u672C\u8A9E (nihongo)",
"English,Fran\u00E7ais,\u65E5\u672C\u8A9E (nihongo)"},
{"8-bit ascii shouldn't number-escape", "\u0080\u009F",
"\u0080\u009F"},
};
@@ -216,6 +216,24 @@ void testEscapeHtml() throws IOException {
}
}
+ @Test
+ void testEscapeHtmlApostrophes() throws IOException {
+ final String input = "' autofocus onfocus=alert(1) x='";
+ final String expected = "' autofocus onfocus=alert(1) x='";
+ assertEquals(expected, StringEscapeUtils.escapeHtml3(input));
+ assertEquals(expected, StringEscapeUtils.escapeHtml4(input));
+ for (final CharSequenceTranslator translator : new
CharSequenceTranslator[] {
+ StringEscapeUtils.ESCAPE_HTML3, StringEscapeUtils.ESCAPE_HTML4
}) {
+ final StringWriter writer = new StringWriter();
+ translator.translate(input, writer);
+ assertEquals(expected, writer.toString());
+ assertEquals("''", translator.translate("''"));
+ assertEquals("&#39;", translator.translate("'"));
+ }
+ assertEquals(input, StringEscapeUtils.unescapeHtml3(expected));
+ assertEquals(input, StringEscapeUtils.unescapeHtml4(expected));
+ }
+
/**
* Tests // https://issues.apache.org/jira/browse/LANG-480
*/
diff --git
a/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
b/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
index a9cc9e3a1..91fdebd1c 100644
---
a/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
+++
b/src/test/java/org/apache/commons/lang3/text/translate/EntityArraysTest.java
@@ -17,6 +17,7 @@
package org.apache.commons.lang3.text.translate;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashSet;
@@ -31,6 +32,12 @@
@Deprecated
class EntityArraysTest extends AbstractLangTest {
+ @Test
+ void testBasicApostrophe() {
+ assertEquals("'", new
LookupTranslator(EntityArrays.BASIC_ESCAPE()).translate("'"));
+ assertEquals("'", new
LookupTranslator(EntityArrays.BASIC_UNESCAPE()).translate("'"));
+ }
+
@Test
void testConstructorExists() {
new EntityArrays();