Improve StringUtils.escapeForXml()

This commit is contained in:
Florian Schmaus 2016-01-13 15:04:01 +01:00
parent f2a748db9a
commit e6a9027cc6
11 changed files with 169 additions and 43 deletions

View File

@ -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);
}

View File

@ -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 <code>input</code> for XML.
*
* @param input the input to escape.
* @return the XML escaped variant of <code>input</code>.
* @deprecated use {@link #escapeForXml(CharSequence)} instead.
*/
// Remove in 4.3.
@Deprecated
public static CharSequence escapeForXML(CharSequence input) {
return escapeForXml(input);
}
/**
* Escape <code>input</code> for XML.
*
* @param input the input to escape.
* @return the XML escaped variant of <code>input</code>.
*/
public static CharSequence escapeForXml(CharSequence input) {
return escapeForXml(input, XmlEscapeMode.safe);
}
/**
* Escape <code>input</code> for XML.
*
* @param input the input to escape.
* @return the XML escaped variant of <code>input</code>.
* @since 4.2
*/
public static CharSequence escapeForXmlAttribute(CharSequence input) {
return escapeForXml(input, XmlEscapeMode.forAttribute);
}
/**
* Escape <code>input</code> for XML.
* <p>
* This is an optimized variant of {@link #escapeForXmlAttribute(CharSequence)} for XML where the
* XML attribute is quoted using ''' (Apos).
* </p>
*
* @param input the input to escape.
* @return the XML escaped variant of <code>input</code>.
* @since 4.2
*/
public static CharSequence escapeForXmlAttributeApos(CharSequence input) {
return escapeForXml(input, XmlEscapeMode.forAttributeApos);
}
/**
* Escape <code>input</code> for XML.
*
* @param input the input to escape.
* @return the XML escaped variant of <code>input</code>.
* @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) {

View File

@ -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;
}

View File

@ -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 = "<b>";
assertCharSequenceEquals("&lt;b&gt;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&lt;b&gt;", StringUtils.escapeForXml(input));
input = "\"";
assertCharSequenceEquals("&quot;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&quot;", StringUtils.escapeForXml(input));
input = "&";
assertCharSequenceEquals("&amp;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&amp;", StringUtils.escapeForXml(input));
input = "<b>\n\t\r</b>";
assertCharSequenceEquals("&lt;b&gt;\n\t\r&lt;/b&gt;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&lt;b&gt;\n\t\r&lt;/b&gt;", StringUtils.escapeForXml(input));
input = " & ";
assertCharSequenceEquals(" &amp; ", StringUtils.escapeForXML(input));
assertCharSequenceEquals(" &amp; ", StringUtils.escapeForXml(input));
input = " \" ";
assertCharSequenceEquals(" &quot; ", StringUtils.escapeForXML(input));
assertCharSequenceEquals(" &quot; ", StringUtils.escapeForXml(input));
input = "> of me <";
assertCharSequenceEquals("&gt; of me &lt;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&gt; of me &lt;", StringUtils.escapeForXml(input));
input = "> of me & you<";
assertCharSequenceEquals("&gt; of me &amp; you&lt;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&gt; of me &amp; you&lt;", StringUtils.escapeForXml(input));
input = "& <";
assertCharSequenceEquals("&amp; &lt;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&amp; &lt;", StringUtils.escapeForXml(input));
input = "&";
assertCharSequenceEquals("&amp;", StringUtils.escapeForXML(input));
assertCharSequenceEquals("&amp;", StringUtils.escapeForXml(input));
input = "It's a good day today";
assertCharSequenceEquals("It&apos;s a good day today", StringUtils.escapeForXML(input));
assertCharSequenceEquals("It&apos;s a good day today", StringUtils.escapeForXml(input));
}
public static void assertCharSequenceEquals(CharSequence expected, CharSequence actual) {

View File

@ -186,7 +186,7 @@ public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmp
builder.append('>');
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<H extends AbstractHttpOverXmp
builder.append(' ');
builder.append(parser.getAttributeName(i));
builder.append("=\"");
builder.append(StringUtils.escapeForXML(parser.getAttributeValue(i)));
builder.append(StringUtils.escapeForXml(parser.getAttributeValue(i)));
builder.append('"');
}
}

View File

@ -332,7 +332,7 @@ public class StreamInitiation extends IQ {
.append(getNamespace()).append("\" ");
if (getName() != null) {
buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" ");
buffer.append("name=\"").append(StringUtils.escapeForXmlAttribute(getName())).append("\" ");
}
if (getSize() > 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("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>");
buffer.append("<desc>").append(StringUtils.escapeForXmlText(getDesc())).append("</desc>");
}
if (isRanged()) {
buffer.append("<range/>");

View File

@ -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());
}

View File

@ -58,7 +58,7 @@ public class DataLayoutTest {
+ "<text>SectionText - &amp; \u00E9 \u00E1 </text>"
+ "</section>"
+ "<text>PageText - &amp; \u00E9 \u00E1 </text>"
+ "<section label='&lt;html&gt;Number of Persons by&lt;br/&gt; Nationality and Status&lt;/html&gt;'>"
+ "<section label='&lt;html>Number of Persons by&lt;br/> Nationality and Status&lt;/html>'>"
+ "<reportedref/>"
+ "</section>"
+ "<text>&lt;html&gt;&lt;font color=&apos;red&apos;&gt;&lt;em&gt;DO NOT DELAY&lt;/em&gt;&lt;/font&gt;&lt;br/&gt;supply further information&lt;/html&gt;</text>"

View File

@ -7,7 +7,7 @@
</section>
<text>PageText - &amp; é á </text>
<section
label='&lt;html&gt;Number of Persons by&lt;br/&gt; Nationality and Status&lt;/html&gt;'>
label='&lt;html>Number of Persons by&lt;br/> Nationality and Status&lt;/html>'>
<reportedref />
</section>
<text><![CDATA[<html><font color='red'><em>DO NOT DELAY</em></font><br/>supply further information</html>]]></text>

View File

@ -85,7 +85,7 @@ public class Macros extends IQ {
if (getPersonalMacroGroup() != null) {
// CHECKSTYLE:OFF
buf.append("<personalMacro>");
buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML()));
buf.append(StringUtils.escapeForXmlText(getPersonalMacroGroup().toXML()));
buf.append("</personalMacro>");
// CHECKSTYLE:ON
}

View File

@ -97,7 +97,7 @@ public class MetaDataUtils {
for (Iterator<String> it = value.iterator(); it.hasNext();) {
String v = it.next();
buf.append("<value name=\"").append(key).append("\">");
buf.append(StringUtils.escapeForXML(v));
buf.append(StringUtils.escapeForXmlText(v));
buf.append("</value>");
}
}