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(); CharSequence text = parser.getText();
if (event == XmlPullParser.TEXT) { if (event == XmlPullParser.TEXT) {
// TODO the toString() can be removed in Smack 4.2. // TODO the toString() can be removed in Smack 4.2.
text = StringUtils.escapeForXML(text.toString()); text = StringUtils.escapeForXmlText(text.toString());
} }
sb.append(text); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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(); 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 * Escapes all necessary characters in the CharSequence so that it can be used
* in an XML doc. * in an XML doc.
@ -48,7 +116,7 @@ public class StringUtils {
* @param input the CharSequence to escape. * @param input the CharSequence to escape.
* @return the string with appropriate characters escaped. * @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) { if (input == null) {
return null; return null;
} }
@ -61,23 +129,75 @@ public class StringUtils {
while (i < len) { while (i < len) {
toAppend = null; toAppend = null;
ch = input.charAt(i); ch = input.charAt(i);
switch(ch) { switch (xmlEscapeMode) {
case '<': case safe:
toAppend = LT_ENCODE; 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; break;
case '>': case forAttribute:
toAppend = GT_ENCODE; // 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; break;
case '&': case forAttributeApos:
toAppend = AMP_ENCODE; // 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; break;
case '"': case forText:
toAppend = QUOTE_ENCODE; // No need to escape '"', '\'', and '>' for text.
break; switch(ch) {
case '\'': case '<':
toAppend = APOS_ENCODE; toAppend = LT_ENCODE;
break; break;
default: case '&':
toAppend = AMP_ENCODE;
break;
default:
break;
}
break; break;
} }
if (toAppend != null) { if (toAppend != null) {

View File

@ -233,7 +233,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
public XmlStringBuilder attribute(String name, String value) { public XmlStringBuilder attribute(String name, String value) {
assert value != null; assert value != null;
sb.append(' ').append(name).append("='"); sb.append(' ').append(name).append("='");
escape(value); escapeAttributeValue(value);
sb.append('\''); sb.append('\'');
return this; return this;
} }
@ -357,7 +357,13 @@ public class XmlStringBuilder implements Appendable, CharSequence {
public XmlStringBuilder escape(String text) { public XmlStringBuilder escape(String text) {
assert text != null; 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; return this;
} }

View File

@ -28,43 +28,43 @@ import org.junit.Test;
*/ */
public class StringUtilsTest { public class StringUtilsTest {
@Test @Test
public void testEscapeForXML() { public void testEscapeForXml() {
String input = null; String input = null;
assertNull(StringUtils.escapeForXML(null)); assertNull(StringUtils.escapeForXml(null));
input = "<b>"; input = "<b>";
assertCharSequenceEquals("&lt;b&gt;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&lt;b&gt;", StringUtils.escapeForXml(input));
input = "\""; input = "\"";
assertCharSequenceEquals("&quot;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&quot;", StringUtils.escapeForXml(input));
input = "&"; input = "&";
assertCharSequenceEquals("&amp;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&amp;", StringUtils.escapeForXml(input));
input = "<b>\n\t\r</b>"; 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 = " & "; input = " & ";
assertCharSequenceEquals(" &amp; ", StringUtils.escapeForXML(input)); assertCharSequenceEquals(" &amp; ", StringUtils.escapeForXml(input));
input = " \" "; input = " \" ";
assertCharSequenceEquals(" &quot; ", StringUtils.escapeForXML(input)); assertCharSequenceEquals(" &quot; ", StringUtils.escapeForXml(input));
input = "> of me <"; input = "> of me <";
assertCharSequenceEquals("&gt; of me &lt;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&gt; of me &lt;", StringUtils.escapeForXml(input));
input = "> of me & you<"; 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 = "& <"; input = "& <";
assertCharSequenceEquals("&amp; &lt;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&amp; &lt;", StringUtils.escapeForXml(input));
input = "&"; input = "&";
assertCharSequenceEquals("&amp;", StringUtils.escapeForXML(input)); assertCharSequenceEquals("&amp;", StringUtils.escapeForXml(input));
input = "It's a good day today"; 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) { public static void assertCharSequenceEquals(CharSequence expected, CharSequence actual) {

View File

@ -186,7 +186,7 @@ public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmp
builder.append('>'); builder.append('>');
startClosed = true; startClosed = true;
} }
builder.append(StringUtils.escapeForXML(parser.getText())); builder.append(StringUtils.escapeForXmlText(parser.getText()));
} else { } else {
throw new IllegalArgumentException("unexpected eventType: " + eventType); throw new IllegalArgumentException("unexpected eventType: " + eventType);
} }
@ -206,7 +206,7 @@ public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmp
builder.append(' '); builder.append(' ');
builder.append(parser.getAttributeName(i)); builder.append(parser.getAttributeName(i));
builder.append("=\""); builder.append("=\"");
builder.append(StringUtils.escapeForXML(parser.getAttributeValue(i))); builder.append(StringUtils.escapeForXml(parser.getAttributeValue(i)));
builder.append('"'); builder.append('"');
} }
} }

View File

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

View File

@ -525,13 +525,13 @@ public class VCard extends IQ {
private void updateFN() { private void updateFN() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (firstName != null) { if (firstName != null) {
sb.append(StringUtils.escapeForXML(firstName)).append(' '); sb.append(StringUtils.escapeForXml(firstName)).append(' ');
} }
if (middleName != null) { if (middleName != null) {
sb.append(StringUtils.escapeForXML(middleName)).append(' '); sb.append(StringUtils.escapeForXml(middleName)).append(' ');
} }
if (lastName != null) { if (lastName != null) {
sb.append(StringUtils.escapeForXML(lastName)); sb.append(StringUtils.escapeForXml(lastName));
} }
setField("FN", sb.toString()); setField("FN", sb.toString());
} }

View File

@ -58,7 +58,7 @@ public class DataLayoutTest {
+ "<text>SectionText - &amp; \u00E9 \u00E1 </text>" + "<text>SectionText - &amp; \u00E9 \u00E1 </text>"
+ "</section>" + "</section>"
+ "<text>PageText - &amp; \u00E9 \u00E1 </text>" + "<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/>" + "<reportedref/>"
+ "</section>" + "</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>" + "<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> </section>
<text>PageText - &amp; é á </text> <text>PageText - &amp; é á </text>
<section <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 /> <reportedref />
</section> </section>
<text><![CDATA[<html><font color='red'><em>DO NOT DELAY</em></font><br/>supply further information</html>]]></text> <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) { if (getPersonalMacroGroup() != null) {
// CHECKSTYLE:OFF // CHECKSTYLE:OFF
buf.append("<personalMacro>"); buf.append("<personalMacro>");
buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML())); buf.append(StringUtils.escapeForXmlText(getPersonalMacroGroup().toXML()));
buf.append("</personalMacro>"); buf.append("</personalMacro>");
// CHECKSTYLE:ON // CHECKSTYLE:ON
} }

View File

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