From e6a9027cc606ea479cab1b562c02f6a8640ecb6f Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 13 Jan 2016 15:04:01 +0100 Subject: [PATCH] Improve StringUtils.escapeForXml() --- .../smack/util/PacketParserUtils.java | 2 +- .../jivesoftware/smack/util/StringUtils.java | 152 ++++++++++++++++-- .../smack/util/XmlStringBuilder.java | 10 +- .../smack/util/StringUtilsTest.java | 26 +-- .../AbstractHttpOverXmppProvider.java | 4 +- .../smackx/si/packet/StreamInitiation.java | 4 +- .../smackx/vcardtemp/packet/VCard.java | 6 +- .../xdatalayout/packet/DataLayoutTest.java | 2 +- .../packet/xdata-layout-sample.xml | 2 +- .../smackx/workgroup/ext/macros/Macros.java | 2 +- .../smackx/workgroup/util/MetaDataUtils.java | 2 +- 11 files changed, 169 insertions(+), 43 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index 813abca62..401ea80c9 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -497,7 +497,7 @@ public class PacketParserUtils { CharSequence text = parser.getText(); if (event == XmlPullParser.TEXT) { // TODO the toString() can be removed in Smack 4.2. - text = StringUtils.escapeForXML(text.toString()); + text = StringUtils.escapeForXmlText(text.toString()); } sb.append(text); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index a0a70e19f..4398e836f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2016 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,74 @@ public class StringUtils { public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + /** + * Escape input for XML. + * + * @param input the input to escape. + * @return the XML escaped variant of input. + * @deprecated use {@link #escapeForXml(CharSequence)} instead. + */ + // Remove in 4.3. + @Deprecated + public static CharSequence escapeForXML(CharSequence input) { + return escapeForXml(input); + } + + /** + * Escape input for XML. + * + * @param input the input to escape. + * @return the XML escaped variant of input. + */ + public static CharSequence escapeForXml(CharSequence input) { + return escapeForXml(input, XmlEscapeMode.safe); + } + + /** + * Escape input for XML. + * + * @param input the input to escape. + * @return the XML escaped variant of input. + * @since 4.2 + */ + public static CharSequence escapeForXmlAttribute(CharSequence input) { + return escapeForXml(input, XmlEscapeMode.forAttribute); + } + + /** + * Escape input for XML. + *

+ * This is an optimized variant of {@link #escapeForXmlAttribute(CharSequence)} for XML where the + * XML attribute is quoted using ''' (Apos). + *

+ * + * @param input the input to escape. + * @return the XML escaped variant of input. + * @since 4.2 + */ + public static CharSequence escapeForXmlAttributeApos(CharSequence input) { + return escapeForXml(input, XmlEscapeMode.forAttributeApos); + } + + /** + * Escape input for XML. + * + * @param input the input to escape. + * @return the XML escaped variant of input. + * @since 4.2 + */ + public static CharSequence escapeForXmlText(CharSequence input) { + return escapeForXml(input, XmlEscapeMode.forText); + } + + private enum XmlEscapeMode { + safe, + forAttribute, + forAttributeApos, + forText, + ; + }; + /** * Escapes all necessary characters in the CharSequence so that it can be used * in an XML doc. @@ -48,7 +116,7 @@ public class StringUtils { * @param input the CharSequence to escape. * @return the string with appropriate characters escaped. */ - public static CharSequence escapeForXML(final CharSequence input) { + private static CharSequence escapeForXml(final CharSequence input, final XmlEscapeMode xmlEscapeMode) { if (input == null) { return null; } @@ -61,23 +129,75 @@ public class StringUtils { while (i < len) { toAppend = null; ch = input.charAt(i); - switch(ch) { - case '<': - toAppend = LT_ENCODE; + switch (xmlEscapeMode) { + case safe: + switch (ch) { + case '<': + toAppend = LT_ENCODE; + break; + case '>': + toAppend = GT_ENCODE; + break; + case '&': + toAppend = AMP_ENCODE; + break; + case '"': + toAppend = QUOTE_ENCODE; + break; + case '\'': + toAppend = APOS_ENCODE; + break; + default: + break; + } break; - case '>': - toAppend = GT_ENCODE; + case forAttribute: + // No need to escape '>' for attributes. + switch(ch) { + case '<': + toAppend = LT_ENCODE; + break; + case '&': + toAppend = AMP_ENCODE; + break; + case '"': + toAppend = QUOTE_ENCODE; + break; + case '\'': + toAppend = APOS_ENCODE; + break; + default: + break; + } break; - case '&': - toAppend = AMP_ENCODE; + case forAttributeApos: + // No need to escape '>' and '"' for attributes using '\'' as quote. + switch(ch) { + case '<': + toAppend = LT_ENCODE; + break; + case '&': + toAppend = AMP_ENCODE; + break; + case '\'': + toAppend = APOS_ENCODE; + break; + default: + break; + } break; - case '"': - toAppend = QUOTE_ENCODE; - break; - case '\'': - toAppend = APOS_ENCODE; - break; - default: + case forText: + // No need to escape '"', '\'', and '>' for text. + switch(ch) { + case '<': + toAppend = LT_ENCODE; + break; + case '&': + toAppend = AMP_ENCODE; + break; + default: + break; + } break; } if (toAppend != null) { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 9dbec85b6..127382740 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -233,7 +233,7 @@ public class XmlStringBuilder implements Appendable, CharSequence { public XmlStringBuilder attribute(String name, String value) { assert value != null; sb.append(' ').append(name).append("='"); - escape(value); + escapeAttributeValue(value); sb.append('\''); return this; } @@ -357,7 +357,13 @@ public class XmlStringBuilder implements Appendable, CharSequence { public XmlStringBuilder escape(String text) { assert text != null; - sb.append(StringUtils.escapeForXML(text)); + sb.append(StringUtils.escapeForXml(text)); + return this; + } + + public XmlStringBuilder escapeAttributeValue(String value) { + assert value != null; + sb.append(StringUtils.escapeForXmlAttributeApos(value)); return this; } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java index 86df0b2cb..bd0d5ad05 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java @@ -28,43 +28,43 @@ import org.junit.Test; */ public class StringUtilsTest { @Test - public void testEscapeForXML() { + public void testEscapeForXml() { String input = null; - assertNull(StringUtils.escapeForXML(null)); + assertNull(StringUtils.escapeForXml(null)); input = ""; - assertCharSequenceEquals("<b>", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("<b>", StringUtils.escapeForXml(input)); input = "\""; - assertCharSequenceEquals(""", StringUtils.escapeForXML(input)); + assertCharSequenceEquals(""", StringUtils.escapeForXml(input)); input = "&"; - assertCharSequenceEquals("&", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("&", StringUtils.escapeForXml(input)); input = "\n\t\r"; - assertCharSequenceEquals("<b>\n\t\r</b>", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("<b>\n\t\r</b>", StringUtils.escapeForXml(input)); input = " & "; - assertCharSequenceEquals(" & ", StringUtils.escapeForXML(input)); + assertCharSequenceEquals(" & ", StringUtils.escapeForXml(input)); input = " \" "; - assertCharSequenceEquals(" " ", StringUtils.escapeForXML(input)); + assertCharSequenceEquals(" " ", StringUtils.escapeForXml(input)); input = "> of me <"; - assertCharSequenceEquals("> of me <", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("> of me <", StringUtils.escapeForXml(input)); input = "> of me & you<"; - assertCharSequenceEquals("> of me & you<", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("> of me & you<", StringUtils.escapeForXml(input)); input = "& <"; - assertCharSequenceEquals("& <", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("& <", StringUtils.escapeForXml(input)); input = "&"; - assertCharSequenceEquals("&", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("&", StringUtils.escapeForXml(input)); input = "It's a good day today"; - assertCharSequenceEquals("It's a good day today", StringUtils.escapeForXML(input)); + assertCharSequenceEquals("It's a good day today", StringUtils.escapeForXml(input)); } public static void assertCharSequenceEquals(CharSequence expected, CharSequence actual) { diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java index 05bf8c813..b3cbccb6d 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java @@ -186,7 +186,7 @@ public abstract class AbstractHttpOverXmppProvider'); startClosed = true; } - builder.append(StringUtils.escapeForXML(parser.getText())); + builder.append(StringUtils.escapeForXmlText(parser.getText())); } else { throw new IllegalArgumentException("unexpected eventType: " + eventType); } @@ -206,7 +206,7 @@ public abstract class AbstractHttpOverXmppProvider 0) { @@ -350,7 +350,7 @@ public class StreamInitiation extends IQ { if ((desc != null && desc.length() > 0) || isRanged) { buffer.append('>'); if (getDesc() != null && desc.length() > 0) { - buffer.append("").append(StringUtils.escapeForXML(getDesc())).append(""); + buffer.append("").append(StringUtils.escapeForXmlText(getDesc())).append(""); } if (isRanged()) { buffer.append(""); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/vcardtemp/packet/VCard.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/vcardtemp/packet/VCard.java index 00349600b..433b5ea4c 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/vcardtemp/packet/VCard.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/vcardtemp/packet/VCard.java @@ -525,13 +525,13 @@ public class VCard extends IQ { private void updateFN() { StringBuilder sb = new StringBuilder(); if (firstName != null) { - sb.append(StringUtils.escapeForXML(firstName)).append(' '); + sb.append(StringUtils.escapeForXml(firstName)).append(' '); } if (middleName != null) { - sb.append(StringUtils.escapeForXML(middleName)).append(' '); + sb.append(StringUtils.escapeForXml(middleName)).append(' '); } if (lastName != null) { - sb.append(StringUtils.escapeForXML(lastName)); + sb.append(StringUtils.escapeForXml(lastName)); } setField("FN", sb.toString()); } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdatalayout/packet/DataLayoutTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdatalayout/packet/DataLayoutTest.java index 51d0a1de1..10787d66d 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdatalayout/packet/DataLayoutTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdatalayout/packet/DataLayoutTest.java @@ -58,7 +58,7 @@ public class DataLayoutTest { + "SectionText - & \u00E9 \u00E1 " + "" + "PageText - & \u00E9 \u00E1 " - + "
" + + "
" + "" + "
" + "<html><font color='red'><em>DO NOT DELAY</em></font><br/>supply further information</html>" diff --git a/smack-extensions/src/test/resources/org/jivesoftware/smackx/xdatalayout/packet/xdata-layout-sample.xml b/smack-extensions/src/test/resources/org/jivesoftware/smackx/xdatalayout/packet/xdata-layout-sample.xml index cbb0262b0..f667a3913 100644 --- a/smack-extensions/src/test/resources/org/jivesoftware/smackx/xdatalayout/packet/xdata-layout-sample.xml +++ b/smack-extensions/src/test/resources/org/jivesoftware/smackx/xdatalayout/packet/xdata-layout-sample.xml @@ -7,7 +7,7 @@
PageText - & é á
+ label='<html>Number of Persons by<br/> Nationality and Status</html>'>
DO NOT DELAY
supply further information]]>
diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java index d98397371..7f8b64426 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java @@ -85,7 +85,7 @@ public class Macros extends IQ { if (getPersonalMacroGroup() != null) { // CHECKSTYLE:OFF buf.append(""); - buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML())); + buf.append(StringUtils.escapeForXmlText(getPersonalMacroGroup().toXML())); buf.append(""); // CHECKSTYLE:ON } diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java index 2a378b40c..8f6664508 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java @@ -97,7 +97,7 @@ public class MetaDataUtils { for (Iterator it = value.iterator(); it.hasNext();) { String v = it.next(); buf.append(""); - buf.append(StringUtils.escapeForXML(v)); + buf.append(StringUtils.escapeForXmlText(v)); buf.append(""); } }