mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 12:02:05 +01:00
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:
parent
2ce7656180
commit
26b5bc0212
9 changed files with 140 additions and 95 deletions
|
@ -23,12 +23,19 @@ 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() {
|
||||
if (cache == null) {
|
||||
StringBuilder sb = new StringBuilder(length());
|
||||
for (CharSequence csq : list) {
|
||||
sb.append(csq);
|
||||
}
|
||||
return sb.toString();
|
||||
cache = sb.toString();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
buf.closeElement("conference");
|
||||
}
|
||||
|
||||
|
||||
if (conference.getPassword() != null) {
|
||||
buf.append("<password>").append(conference.getPassword()).append("</password>");
|
||||
}
|
||||
buf.append("</conference>");
|
||||
}
|
||||
|
||||
|
||||
buf.append("</storage>");
|
||||
return buf.toString();
|
||||
buf.closeElement(ELEMENT);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -45,5 +45,5 @@ public interface PrivateData {
|
|||
*
|
||||
* @return the private data as XML.
|
||||
*/
|
||||
public String toXML();
|
||||
public CharSequence toXML();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue