Properly escape Bookmarks and FormField XML

by using XmlStringBuilder. Fixes SMACK-577

Also extend LazyStringBuilder with a cache. And extend XmlStringBuilder
with some more convenience methods.

Move the ELEMENT and NAMESPACE definition from Form to DataForm, where
it belongs.
This commit is contained in:
Florian Schmaus 2014-06-19 11:48:01 +02:00
parent 2ce7656180
commit 26b5bc0212
9 changed files with 140 additions and 95 deletions

View File

@ -22,13 +22,20 @@ import java.util.List;
public class LazyStringBuilder implements Appendable, CharSequence {
private final List<CharSequence> list;
private String cache;
private void invalidateCache() {
cache = null;
}
public LazyStringBuilder() {
list = new ArrayList<CharSequence>(20);
}
public LazyStringBuilder append(LazyStringBuilder lsb) {
list.addAll(lsb.list);
invalidateCache();
return this;
}
@ -36,6 +43,7 @@ public class LazyStringBuilder implements Appendable, CharSequence {
public LazyStringBuilder append(CharSequence csq) {
assert csq != null;
list.add(csq);
invalidateCache();
return this;
}
@ -43,17 +51,22 @@ public class LazyStringBuilder implements Appendable, CharSequence {
public LazyStringBuilder append(CharSequence csq, int start, int end) {
CharSequence subsequence = csq.subSequence(start, end);
list.add(subsequence);
invalidateCache();
return this;
}
@Override
public LazyStringBuilder append(char c) {
list.add(Character.toString(c));
invalidateCache();
return this;
}
@Override
public int length() {
if (cache != null) {
return cache.length();
}
int length = 0;
for (CharSequence csq : list) {
length += csq.length();
@ -63,6 +76,9 @@ public class LazyStringBuilder implements Appendable, CharSequence {
@Override
public char charAt(int index) {
if (cache != null) {
return cache.charAt(index);
}
for (CharSequence csq : list) {
if (index < csq.length()) {
return csq.charAt(index);
@ -80,10 +96,13 @@ public class LazyStringBuilder implements Appendable, CharSequence {
@Override
public String toString() {
StringBuilder sb = new StringBuilder(length());
for (CharSequence csq : list) {
sb.append(csq);
if (cache == null) {
StringBuilder sb = new StringBuilder(length());
for (CharSequence csq : list) {
sb.append(csq);
}
cache = sb.toString();
}
return sb.toString();
return cache;
}
}

View File

@ -168,6 +168,25 @@ public class XmlStringBuilder implements Appendable, CharSequence {
return this;
}
public XmlStringBuilder emptyElement(String element) {
halfOpenElement(element);
return closeEmptyElement();
}
public XmlStringBuilder condEmptyElement(boolean condition, String element) {
if (condition) {
emptyElement(element);
}
return this;
}
public XmlStringBuilder condAttribute(boolean condition, String name, String value) {
if (condition) {
attribute(name, value);
}
return this;
}
@Override
public XmlStringBuilder append(CharSequence csq) {
assert csq != null;
@ -207,4 +226,18 @@ public class XmlStringBuilder implements Appendable, CharSequence {
public String toString() {
return sb.toString();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof XmlStringBuilder)) {
return false;
}
XmlStringBuilder otherXmlStringBuilder = (XmlStringBuilder) other;
return toString().equals(otherXmlStringBuilder.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
}

View File

@ -16,6 +16,7 @@
*/
package org.jivesoftware.smackx.bookmarks;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.iqprivate.packet.PrivateData;
import org.jivesoftware.smackx.iqprivate.provider.PrivateDataProvider;
import org.xmlpull.v1.XmlPullParser;
@ -59,6 +60,9 @@ import java.util.List;
*/
public class Bookmarks implements PrivateData {
public static final String NAMESPACE = "storage:bookmarks";
public static final String ELEMENT = "storage";
private List<BookmarkedURL> bookmarkedURLS;
private List<BookmarkedConference> bookmarkedConferences;
@ -145,7 +149,7 @@ public class Bookmarks implements PrivateData {
* @return the element name.
*/
public String getElementName() {
return "storage";
return ELEMENT;
}
/**
@ -154,28 +158,26 @@ public class Bookmarks implements PrivateData {
* @return the namespace.
*/
public String getNamespace() {
return "storage:bookmarks";
return NAMESPACE;
}
/**
* Returns the XML reppresentation of the PrivateData.
* Returns the XML representation of the PrivateData.
*
* @return the private data as XML.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<storage xmlns=\"storage:bookmarks\">");
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder buf = new XmlStringBuilder();
buf.openElement(ELEMENT).xmlnsAttribute(NAMESPACE);
for (BookmarkedURL urlStorage : getBookmarkedURLS()) {
if(urlStorage.isShared()) {
continue;
}
buf.append("<url name=\"").append(urlStorage.getName()).
append("\" url=\"").append(urlStorage.getURL()).append("\"");
if(urlStorage.isRss()) {
buf.append(" rss=\"").append(true).append("\"");
}
buf.append(" />");
buf.openElement("url").attribute("name", urlStorage.getName()).attribute("url", urlStorage.getURL());
buf.condAttribute(urlStorage.isRss(), "rss", "true");
buf.closeEmptyElement();
}
// Add Conference additions
@ -183,26 +185,20 @@ public class Bookmarks implements PrivateData {
if(conference.isShared()) {
continue;
}
buf.append("<conference ");
buf.append("name=\"").append(conference.getName()).append("\" ");
buf.append("autojoin=\"").append(conference.isAutoJoin()).append("\" ");
buf.append("jid=\"").append(conference.getJid()).append("\" ");
buf.append(">");
buf.openElement("conference");
buf.attribute("name", conference.getName());
buf.attribute("autojoin", Boolean.toString(conference.isAutoJoin()));
buf.attribute("jid", conference.getJid());
buf.rightAngelBracket();
if (conference.getNickname() != null) {
buf.append("<nick>").append(conference.getNickname()).append("</nick>");
}
buf.optElement("nick", conference.getNickname());
buf.optElement("password", conference.getPassword());
if (conference.getPassword() != null) {
buf.append("<password>").append(conference.getPassword()).append("</password>");
}
buf.append("</conference>");
buf.closeElement("conference");
}
buf.append("</storage>");
return buf.toString();
buf.closeElement(ELEMENT);
return buf;
}
/**

View File

@ -44,7 +44,6 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Feature;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity;
import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item;
import org.jivesoftware.smackx.xdata.Form;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -528,7 +527,7 @@ public class EntityCapsManager extends Manager {
protected static boolean verifyPacketExtensions(DiscoverInfo info) {
List<FormField> foundFormTypes = new LinkedList<FormField>();
for (PacketExtension pe : info.getExtensions()) {
if (pe.getNamespace().equals(Form.NAMESPACE)) {
if (pe.getNamespace().equals(DataForm.NAMESPACE)) {
DataForm df = (DataForm) pe;
for (FormField f : df.getFields()) {
if (f.getVariable().equals("FORM_TYPE")) {
@ -561,7 +560,7 @@ public class EntityCapsManager extends Manager {
if (md == null)
return null;
DataForm extendedInfo = (DataForm) discoverInfo.getExtension(Form.ELEMENT, Form.NAMESPACE);
DataForm extendedInfo = (DataForm) discoverInfo.getExtension(DataForm.ELEMENT, DataForm.NAMESPACE);
// 1. Initialize an empty string S ('sb' in this method).
StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't

View File

@ -45,5 +45,5 @@ public interface PrivateData {
*
* @return the private data as XML.
*/
public String toXML();
public CharSequence toXML();
}

View File

@ -49,9 +49,6 @@ public class Form {
public static final String TYPE_CANCEL = "cancel";
public static final String TYPE_RESULT = "result";
public static final String NAMESPACE = "jabber:x:data";
public static final String ELEMENT = "x";
private DataForm dataForm;
/**

View File

@ -17,7 +17,6 @@
package org.jivesoftware.smackx.xdata;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import java.util.ArrayList;
@ -33,6 +32,8 @@ import java.util.List;
*/
public class FormField {
public static final String ELEMENT = "field";
public static final String TYPE_BOOLEAN = "boolean";
public static final String TYPE_FIXED = "fixed";
public static final String TYPE_HIDDEN = "hidden";
@ -263,25 +264,17 @@ public class FormField {
}
}
public String toXML() {
public XmlStringBuilder toXML() {
XmlStringBuilder buf = new XmlStringBuilder();
buf.append("<field");
buf.halfOpenElement(ELEMENT);
// Add attributes
if (getLabel() != null) {
buf.append(" label=\"").append(getLabel()).append("\"");
}
buf.attribute("var", getVariable());
if (getType() != null) {
buf.append(" type=\"").append(getType()).append("\"");
}
buf.append(">");
buf.optAttribute("label", getLabel());
buf.optAttribute("var", getVariable());
buf.optAttribute("type", getType());
buf.rightAngelBracket();
// Add elements
if (getDescription() != null) {
buf.append("<desc>").append(getDescription()).append("</desc>");
}
if (isRequired()) {
buf.append("<required/>");
}
buf.optElement("desc", getDescription());
buf.condEmptyElement(isRequired(), "required");
// Loop through all the values and append them to the string buffer
for (String value : getValues()) {
buf.element("value", value);
@ -290,8 +283,8 @@ public class FormField {
for (Option option : getOptions()) {
buf.append(option.toXML());
}
buf.append("</field>");
return buf.toString();
buf.closeElement(ELEMENT);
return buf;
}
@Override
@ -320,8 +313,10 @@ public class FormField {
*/
public static class Option {
public static final String ELEMNT = "option";
private final String value;
private String label;
private String value;
public Option(String value) {
this.value = value;
@ -355,19 +350,18 @@ public class FormField {
return getLabel();
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<option");
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(ELEMNT);
// Add attribute
if (getLabel() != null) {
buf.append(" label=\"").append(getLabel()).append("\"");
}
buf.append(">");
// Add element
buf.append("<value>").append(StringUtils.escapeForXML(getValue())).append("</value>");
xml.optAttribute("label", getLabel());
xml.rightAngelBracket();
buf.append("</option>");
return buf.toString();
// Add element
xml.element("value", getValue());
xml.closeElement(ELEMENT);
return xml;
}
@Override

View File

@ -18,7 +18,7 @@
package org.jivesoftware.smackx.xdata.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.xdata.Form;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.xdata.FormField;
import java.util.ArrayList;
@ -33,6 +33,9 @@ import java.util.List;
*/
public class DataForm implements PacketExtension {
public static final String NAMESPACE = "jabber:x:data";
public static final String ELEMENT = "x";
private String type;
private String title;
private List<String> instructions = new ArrayList<String>();
@ -120,11 +123,11 @@ public class DataForm implements PacketExtension {
}
public String getElementName() {
return Form.ELEMENT;
return ELEMENT;
}
public String getNamespace() {
return Form.NAMESPACE;
return NAMESPACE;
}
/**
@ -207,15 +210,15 @@ public class DataForm implements PacketExtension {
return found;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\" type=\"" + getType() +"\">");
if (getTitle() != null) {
buf.append("<title>").append(getTitle()).append("</title>");
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder buf = new XmlStringBuilder(this);
buf.attribute("type", getType());
buf.rightAngelBracket();
buf.optElement("title", getTitle());
for (String instruction : getInstructions()) {
buf.append("<instructions>").append(instruction).append("</instructions>");
buf.element("instructions", instruction);
}
// Append the list of fields returned from a search
if (getReportedData() != null) {
@ -229,8 +232,8 @@ public class DataForm implements PacketExtension {
for (FormField field : getFields()) {
buf.append(field.toXML());
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
buf.closeElement(this);
return buf;
}
/**
@ -241,6 +244,8 @@ public class DataForm implements PacketExtension {
* @author Gaston Dombiak
*/
public static class ReportedData {
public static final String ELEMENT = "reported";
private List<FormField> fields = new ArrayList<FormField>();
public ReportedData(List<FormField> fields) {
@ -256,15 +261,15 @@ public class DataForm implements PacketExtension {
return Collections.unmodifiableList(new ArrayList<FormField>(fields));
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<reported>");
public CharSequence toXML() {
XmlStringBuilder buf = new XmlStringBuilder();
buf.openElement(ELEMENT);
// Loop through all the form items and append them to the string buffer
for (FormField field : getFields()) {
buf.append(field.toXML());
}
buf.append("</reported>");
return buf.toString();
buf.closeElement(ELEMENT);
return buf;
}
}
@ -275,6 +280,8 @@ public class DataForm implements PacketExtension {
* @author Gaston Dombiak
*/
public static class Item {
public static final String ELEMENT = "item";
private List<FormField> fields = new ArrayList<FormField>();
public Item(List<FormField> fields) {
@ -290,15 +297,15 @@ public class DataForm implements PacketExtension {
return Collections.unmodifiableList(new ArrayList<FormField>(fields));
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<item>");
public CharSequence toXML() {
XmlStringBuilder buf = new XmlStringBuilder();
buf.openElement(ELEMENT);
// Loop through all the form items and append them to the string buffer
for (FormField field : getFields()) {
buf.append(field.toXML());
}
buf.append("</item>");
return buf.toString();
buf.closeElement(ELEMENT);
return buf;
}
}
}

View File

@ -51,6 +51,6 @@ public class FileTransferNegotiatorTest {
fileNeg.negotiateOutgoingTransfer("me", "streamid", "file", 1024, null, 10);
Packet packet = connection.getSentPacket();
String xml = packet.toXML().toString();
assertTrue(xml.indexOf("var='stream-method' type=\"list-single\"") != -1);
assertTrue(xml.indexOf("var='stream-method' type='list-single'") != -1);
}
}