XmlReader.java

  1package eu.siacs.conversations.xml;
  2
  3import android.util.Log;
  4import android.util.Xml;
  5import eu.siacs.conversations.Config;
  6import im.conversations.android.xmpp.ExtensionFactory;
  7import im.conversations.android.xmpp.model.StreamElement;
  8import java.io.Closeable;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.io.InputStreamReader;
 12import org.xmlpull.v1.XmlPullParser;
 13import org.xmlpull.v1.XmlPullParserException;
 14
 15public class XmlReader implements Closeable {
 16
 17    private static final int XML_ELEMENT_MAX_DEPTH = 128;
 18
 19    private final XmlPullParser parser;
 20    private InputStream is;
 21
 22    public XmlReader() {
 23        this.parser = Xml.newPullParser();
 24        try {
 25            this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
 26        } catch (XmlPullParserException e) {
 27            Log.d(Config.LOGTAG, "error setting namespace feature on parser");
 28        }
 29    }
 30
 31    public void setInputStream(InputStream inputStream) throws IOException {
 32        if (inputStream == null) {
 33            throw new IOException();
 34        }
 35        this.is = inputStream;
 36        try {
 37            parser.setInput(new InputStreamReader(this.is));
 38        } catch (XmlPullParserException e) {
 39            throw new IOException("error resetting parser");
 40        }
 41    }
 42
 43    public void reset() throws IOException {
 44        if (this.is == null) {
 45            throw new IOException();
 46        }
 47        try {
 48            parser.setInput(new InputStreamReader(this.is));
 49        } catch (XmlPullParserException e) {
 50            throw new IOException("error resetting parser");
 51        }
 52    }
 53
 54    @Override
 55    public void close() {
 56        this.is = null;
 57    }
 58
 59    public Tag readTag() throws IOException {
 60        try {
 61            while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) {
 62                if (parser.getEventType() == XmlPullParser.START_TAG) {
 63                    Tag tag = Tag.start(parser.getName());
 64                    final String xmlns = parser.getNamespace();
 65                    for (int i = 0; i < parser.getAttributeCount(); ++i) {
 66                        final var prefix = parser.getAttributePrefix(i);
 67                        final var ns = parser.getAttributeNamespace(i);
 68                        String name;
 69                        if ("xml".equals(prefix)) {
 70                            name = "xml:" + parser.getAttributeName(i);
 71                        } else if (ns != null && !ns.isEmpty()) {
 72                            name = "{" + ns + "}" + parser.getAttributeName(i);
 73                        } else {
 74                            name = parser.getAttributeName(i);
 75                        }
 76                        tag.setAttribute(name,parser.getAttributeValue(i));
 77                    }
 78                    if (xmlns != null) {
 79                        tag.setAttribute("xmlns", xmlns);
 80                    }
 81                    return tag;
 82                } else if (parser.getEventType() == XmlPullParser.END_TAG) {
 83                    return Tag.end(parser.getName());
 84                } else if (parser.getEventType() == XmlPullParser.TEXT) {
 85                    return Tag.no(parser.getText());
 86                }
 87            }
 88
 89        } catch (Throwable throwable) {
 90            throw new IOException(
 91                    "xml parser mishandled "
 92                            + throwable.getClass().getSimpleName()
 93                            + "("
 94                            + throwable.getMessage()
 95                            + ")",
 96                    throwable);
 97        }
 98        return null;
 99    }
100
101    public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
102            throws IOException {
103        final Element element = readElement(current);
104        if (clazz.isInstance(element)) {
105            return clazz.cast(element);
106        }
107        throw new IOException(
108                String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
109    }
110
111    public Element readElement(final Tag currentTag) throws IOException {
112        return readElement(currentTag, 0);
113    }
114
115    private Element readElement(final Tag currentTag, final int depth) throws IOException {
116        if (depth >= XML_ELEMENT_MAX_DEPTH) {
117            throw new XmlMaxDepthReachedException();
118        }
119        final var namespace = currentTag.getAttributes().get("xmlns");
120        final var name = currentTag.getName();
121        final Element element = ExtensionFactory.create(name, namespace);
122        element.setAttributes(currentTag.getAttributes());
123        Tag nextTag = this.readTag();
124        if (nextTag == null) {
125            throw new IOException("interrupted mid tag");
126        }
127        while (!nextTag.isEnd(element.getName())) {
128            if (nextTag.isNo()) {
129                if (nextTag.getName() != null) element.addChild(new TextNode(nextTag.getName()));
130            } else {
131                final var child = this.readElement(nextTag, depth + 1);
132                element.addChild(child);
133            }
134            nextTag = this.readTag();
135        }
136        return element;
137    }
138
139    public static class XmlMaxDepthReachedException extends IOException {
140        public XmlMaxDepthReachedException() {
141            super("Reached maximum depth of XML stream");
142        }
143    }
144}